diff --git a/src/mudlib/server.py b/src/mudlib/server.py index dff8e8b..5693c23 100644 --- a/src/mudlib/server.py +++ b/src/mudlib/server.py @@ -43,6 +43,7 @@ from mudlib.store import ( update_last_login, ) from mudlib.world.terrain import World +from mudlib.zone import Zone log = logging.getLogger(__name__) @@ -54,6 +55,7 @@ AUTOSAVE_INTERVAL = 60.0 # seconds between auto-saves # Module-level world instance, generated once at startup _world: World | None = None +_overworld: Zone | None = None def load_world_config(world_name: str = "earth") -> dict: @@ -92,11 +94,11 @@ async def game_loop() -> None: await asyncio.sleep(sleep_time) -def find_passable_start(world: World, start_x: int, start_y: int) -> tuple[int, int]: +def find_passable_start(zone: Zone, start_x: int, start_y: int) -> tuple[int, int]: """Find a passable tile starting from (start_x, start_y) and searching outward. Args: - world: The world to search in + zone: The zone to search in start_x: Starting X coordinate start_y: Starting Y coordinate @@ -104,7 +106,7 @@ def find_passable_start(world: World, start_x: int, start_y: int) -> tuple[int, Tuple of (x, y) for the first passable tile found """ # Try the starting position first - if world.is_passable(start_x, start_y): + if zone.is_passable(start_x, start_y): return start_x, start_y # Spiral outward from the starting position (wrapping) @@ -115,9 +117,9 @@ def find_passable_start(world: World, start_x: int, start_y: int) -> tuple[int, if abs(dx) != radius and abs(dy) != radius: continue - x, y = world.wrap(start_x + dx, start_y + dy) + x, y = zone.wrap(start_x + dx, start_y + dy) - if world.is_passable(x, y): + if zone.is_passable(x, y): return x, y # Fallback to starting position if nothing found @@ -210,6 +212,9 @@ async def shell( _writer = cast(telnetlib3.TelnetWriterUnicode, writer) assert _world is not None, "World must be initialized before accepting connections" + assert _overworld is not None, ( + "Overworld zone must be initialized before accepting connections" + ) log.debug("new connection from %s", _writer.get_extra_info("peername")) @@ -248,9 +253,9 @@ async def shell( player_data: PlayerData | None = login_result["player_data"] if player_data is None: # New player - find a passable starting position - center_x = _world.width // 2 - center_y = _world.height // 2 - start_x, start_y = find_passable_start(_world, center_x, center_y) + center_x = _overworld.width // 2 + center_y = _overworld.height // 2 + start_x, start_y = find_passable_start(_overworld, center_x, center_y) player_data = { "x": start_x, "y": start_y, @@ -261,10 +266,10 @@ async def shell( } else: # Existing player - verify spawn position is still passable - if not _world.is_passable(player_data["x"], player_data["y"]): + if not _overworld.is_passable(player_data["x"], player_data["y"]): # Saved position is no longer passable, find a new one start_x, start_y = find_passable_start( - _world, player_data["x"], player_data["y"] + _overworld, player_data["x"], player_data["y"] ) player_data["x"] = start_x player_data["y"] = start_y @@ -272,6 +277,7 @@ async def shell( # Create player instance player = Player( name=player_name, + location=_overworld, x=player_data["x"], y=player_data["y"], pl=player_data["pl"], @@ -381,7 +387,7 @@ async def shell( async def run_server() -> None: """Start the MUD telnet server.""" - global _world + global _world, _overworld # Initialize database data_dir = pathlib.Path(__file__).resolve().parents[2] / "data" @@ -412,6 +418,16 @@ async def run_server() -> None: else: log.info("world generated in %.2fs (cached for next run)", elapsed) + # Create overworld zone from generated terrain + _overworld = Zone( + name="overworld", + width=_world.width, + height=_world.height, + terrain=_world.terrain, + toroidal=True, + ) + log.info("created overworld zone (%dx%d, toroidal)", _world.width, _world.height) + # Inject world into command modules mudlib.commands.fly.world = _world mudlib.commands.look.world = _world diff --git a/tests/test_server.py b/tests/test_server.py index 851f769..29aa9b0 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -11,6 +11,7 @@ import pytest from mudlib import server from mudlib.store import init_db from mudlib.world.terrain import World +from mudlib.zone import Zone @pytest.fixture @@ -44,16 +45,23 @@ def test_run_server_exists(): def test_find_passable_start(): world = World(seed=42, width=100, height=100) - x, y = server.find_passable_start(world, 50, 50) + zone = Zone( + name="test", width=100, height=100, terrain=world.terrain, toroidal=True + ) + x, y = server.find_passable_start(zone, 50, 50) assert isinstance(x, int) assert isinstance(y, int) - assert world.is_passable(x, y) + assert zone.is_passable(x, y) @pytest.mark.asyncio async def test_shell_greets_and_accepts_commands(temp_db): world = World(seed=42, width=100, height=100) + zone = Zone( + name="overworld", width=100, height=100, terrain=world.terrain, toroidal=True + ) server._world = world + server._overworld = zone reader = AsyncMock() writer = MagicMock() @@ -91,7 +99,12 @@ async def test_shell_greets_and_accepts_commands(temp_db): @pytest.mark.asyncio async def test_shell_handles_eof(): - server._world = World(seed=42, width=100, height=100) + world = World(seed=42, width=100, height=100) + zone = Zone( + name="overworld", width=100, height=100, terrain=world.terrain, toroidal=True + ) + server._world = world + server._overworld = zone reader = AsyncMock() writer = MagicMock() @@ -110,7 +123,11 @@ async def test_shell_handles_eof(): @pytest.mark.asyncio async def test_shell_handles_quit(temp_db): world = World(seed=42, width=100, height=100) + zone = Zone( + name="overworld", width=100, height=100, terrain=world.terrain, toroidal=True + ) server._world = world + server._overworld = zone reader = AsyncMock() writer = MagicMock()