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