mud/src/mudlib/zone.py

103 lines
3.3 KiB
Python

"""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