Create overworld Zone at startup, set player.location
This commit is contained in:
parent
6f58ae0501
commit
66c6e1ebd4
2 changed files with 47 additions and 14 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue