"""Zone — a spatial area with a terrain grid.""" from __future__ import annotations from dataclasses import dataclass, field from mudlib.object import Object @dataclass class Zone(Object): """A spatial area with a grid of terrain tiles. Zones are top-level containers (location=None). Everything in the world lives inside a zone — players, mobs, items, fixtures. The overworld is a zone. A tavern interior is a zone. A pocket dimension is a zone. """ width: int = 0 height: int = 0 toroidal: bool = True terrain: list[list[str]] = field(default_factory=list) impassable: set[str] = field(default_factory=lambda: {"^", "~"}) def can_accept(self, obj: Object) -> bool: """Zones accept everything.""" return True def wrap(self, x: int, y: int) -> tuple[int, int]: """Wrap or clamp coordinates depending on zone type.""" if self.toroidal: return x % self.width, y % self.height else: return ( max(0, min(x, self.width - 1)), max(0, min(y, self.height - 1)), ) def get_tile(self, x: int, y: int) -> str: """Return terrain character at position (wrapping/clamping).""" wx, wy = self.wrap(x, y) return self.terrain[wy][wx] def is_passable(self, x: int, y: int) -> bool: """Check if position is passable (wrapping/clamping).""" return self.get_tile(x, y) not in self.impassable def get_viewport( self, cx: int, cy: int, width: int, height: int ) -> list[list[str]]: """Return 2D slice of terrain centered on (cx, cy).""" viewport = [] half_w = width // 2 half_h = height // 2 start_x = cx - half_w start_y = cy - half_h for dy in range(height): row = [] for dx in range(width): wx, wy = self.wrap(start_x + dx, start_y + dy) row.append(self.terrain[wy][wx]) viewport.append(row) return viewport def contents_at(self, x: int, y: int) -> list[Object]: """Return all contents at the given coordinates.""" return [obj for obj in self._contents if obj.x == x and obj.y == y] def contents_near(self, x: int, y: int, range_: int) -> list[Object]: """Return all contents within range_ tiles of (x, y). Uses wrapping-aware Manhattan distance for toroidal zones. Includes objects at exactly (x, y). Args: x: X coordinate of center point y: Y coordinate of center point range_: Maximum Manhattan distance (inclusive) Returns: List of all objects within range """ nearby = [] for obj in self._contents: # Skip objects without coordinates if obj.x is None or obj.y is None: continue dx_dist = abs(obj.x - x) dy_dist = abs(obj.y - y) if self.toroidal: # Use wrapping-aware distance dx_dist = min(dx_dist, self.width - dx_dist) dy_dist = min(dy_dist, self.height - dy_dist) manhattan_dist = dx_dist + dy_dist if manhattan_dist <= range_: nearby.append(obj) return nearby