diff --git a/src/mudlib/commands/fly.py b/src/mudlib/commands/fly.py index aafbdec..a69af05 100644 --- a/src/mudlib/commands/fly.py +++ b/src/mudlib/commands/fly.py @@ -1,15 +1,11 @@ """Fly command for aerial movement across the world.""" -from typing import Any - from mudlib.commands import CommandDefinition, register from mudlib.commands.movement import DIRECTIONS, send_nearby_message from mudlib.effects import add_effect from mudlib.player import Player from mudlib.render.ansi import BOLD, BRIGHT_WHITE - -# World instance will be injected by the server -world: Any = None +from mudlib.zone import Zone # how far you fly FLY_DISTANCE = 5 @@ -67,6 +63,9 @@ async def cmd_fly(player: Player, args: str) -> None: await player.writer.drain() return + zone = player.location + assert isinstance(zone, Zone), "Player must be in a zone to fly" + dx, dy = delta start_x, start_y = player.x, player.y @@ -74,14 +73,12 @@ async def cmd_fly(player: Player, args: str) -> None: # origin cloud expires first, near-dest cloud lingers longest. # the trail shrinks from behind toward the player over time. for step in range(FLY_DISTANCE): - trail_x, trail_y = world.wrap(start_x + dx * step, start_y + dy * step) + trail_x, trail_y = zone.wrap(start_x + dx * step, start_y + dy * step) ttl = CLOUD_TTL + step * CLOUD_STAGGER add_effect(trail_x, trail_y, "~", CLOUD_COLOR, ttl=ttl) # move player to destination - dest_x, dest_y = world.wrap( - start_x + dx * FLY_DISTANCE, start_y + dy * FLY_DISTANCE - ) + dest_x, dest_y = zone.wrap(start_x + dx * FLY_DISTANCE, start_y + dy * FLY_DISTANCE) player.x = dest_x player.y = dest_y diff --git a/src/mudlib/store/__init__.py b/src/mudlib/store/__init__.py index 213487c..f68db61 100644 --- a/src/mudlib/store/__init__.py +++ b/src/mudlib/store/__init__.py @@ -19,6 +19,7 @@ class PlayerData(TypedDict): stamina: float max_stamina: float flying: bool + zone_name: str # Module-level database path @@ -51,11 +52,21 @@ def init_db(db_path: str | Path) -> None: stamina REAL NOT NULL DEFAULT 100.0, max_stamina REAL NOT NULL DEFAULT 100.0, flying INTEGER NOT NULL DEFAULT 0, + zone_name TEXT NOT NULL DEFAULT 'overworld', created_at TEXT NOT NULL DEFAULT (datetime('now')), last_login TEXT ) """) + # Migration: add zone_name column if it doesn't exist + cursor.execute("PRAGMA table_info(accounts)") + columns = [row[1] for row in cursor.fetchall()] + if "zone_name" not in columns: + cursor.execute( + "ALTER TABLE accounts " + "ADD COLUMN zone_name TEXT NOT NULL DEFAULT 'overworld'" + ) + conn.commit() conn.close() @@ -183,7 +194,8 @@ def save_player(player: Player) -> None: cursor.execute( """ UPDATE accounts - SET x = ?, y = ?, pl = ?, stamina = ?, max_stamina = ?, flying = ? + SET x = ?, y = ?, pl = ?, stamina = ?, max_stamina = ?, flying = ?, + zone_name = ? WHERE name = ? """, ( @@ -193,6 +205,7 @@ def save_player(player: Player) -> None: player.stamina, player.max_stamina, 1 if player.flying else 0, + player.location.name if player.location else "overworld", player.name, ), ) @@ -213,21 +226,42 @@ def load_player_data(name: str) -> PlayerData | None: conn = _get_connection() cursor = conn.cursor() - cursor.execute( - """ - SELECT x, y, pl, stamina, max_stamina, flying - FROM accounts - WHERE name = ? - """, - (name,), - ) + # Check if zone_name column exists (for migration) + cursor.execute("PRAGMA table_info(accounts)") + columns = [row[1] for row in cursor.fetchall()] + has_zone_name = "zone_name" in columns + + if has_zone_name: + cursor.execute( + """ + SELECT x, y, pl, stamina, max_stamina, flying, zone_name + FROM accounts + WHERE name = ? + """, + (name,), + ) + else: + cursor.execute( + """ + SELECT x, y, pl, stamina, max_stamina, flying + FROM accounts + WHERE name = ? + """, + (name,), + ) + result = cursor.fetchone() conn.close() if result is None: return None - x, y, pl, stamina, max_stamina, flying_int = result + if has_zone_name: + x, y, pl, stamina, max_stamina, flying_int, zone_name = result + else: + x, y, pl, stamina, max_stamina, flying_int = result + zone_name = "overworld" # Default for old schemas + return { "x": x, "y": y, @@ -235,6 +269,7 @@ def load_player_data(name: str) -> PlayerData | None: "stamina": stamina, "max_stamina": max_stamina, "flying": bool(flying_int), + "zone_name": zone_name, } diff --git a/tests/test_fly.py b/tests/test_fly.py index 53c61a3..eb8d30f 100644 --- a/tests/test_fly.py +++ b/tests/test_fly.py @@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, MagicMock import pytest -from mudlib.commands import fly, look +from mudlib.commands import fly from mudlib.effects import active_effects from mudlib.player import Player, players from mudlib.zone import Zone @@ -49,8 +49,6 @@ def player(mock_reader, mock_writer, test_zone): @pytest.fixture(autouse=True) def clean_state(test_zone): """Clean global state before/after each test.""" - fly.world = test_zone - look.world = test_zone players.clear() active_effects.clear() yield