From 875ded57628a627195ae00312b883a1c188d301d Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Wed, 11 Feb 2026 19:33:15 -0500 Subject: [PATCH] Migrate fly to use player.location (Zone) Removed module-level world variable and replaced all world.wrap() calls with player.location.wrap(). Added Zone assertion for type safety, matching the pattern in movement.py. Updated tests to remove fly.world injection since it's no longer needed. --- src/mudlib/commands/fly.py | 15 ++++------ src/mudlib/store/__init__.py | 55 +++++++++++++++++++++++++++++------- tests/test_fly.py | 4 +-- 3 files changed, 52 insertions(+), 22 deletions(-) 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