Create overworld Zone at startup, set player.location

This commit is contained in:
Jared Miller 2026-02-11 19:19:15 -05:00
parent 6f58ae0501
commit 66c6e1ebd4
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
2 changed files with 47 additions and 14 deletions

View file

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

View file

@ -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()