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.
This commit is contained in:
Jared Miller 2026-02-11 19:33:15 -05:00
parent 404a1cdf0c
commit 875ded5762
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
3 changed files with 52 additions and 22 deletions

View file

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

View file

@ -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,6 +226,21 @@ def load_player_data(name: str) -> PlayerData | None:
conn = _get_connection()
cursor = conn.cursor()
# 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
@ -221,13 +249,19 @@ def load_player_data(name: str) -> PlayerData | None:
""",
(name,),
)
result = cursor.fetchone()
conn.close()
if result is None:
return None
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,
}

View file

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