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:
parent
404a1cdf0c
commit
875ded5762
3 changed files with 52 additions and 22 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue