Migrate movement to use player.location (Zone)
Movement commands now access the zone through player.location instead of a module-level world variable. send_nearby_message uses zone.contents_near() to find nearby entities, eliminating the need for the global players dict and manual distance calculations. Tests updated to create zones and add entities via location assignment.
This commit is contained in:
parent
66c6e1ebd4
commit
404a1cdf0c
9 changed files with 232 additions and 137 deletions
|
|
@ -1,13 +1,9 @@
|
||||||
"""Movement commands for navigating the world."""
|
"""Movement commands for navigating the world."""
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from mudlib.commands import CommandDefinition, register
|
from mudlib.commands import CommandDefinition, register
|
||||||
from mudlib.entity import Entity
|
from mudlib.entity import Entity
|
||||||
from mudlib.player import Player, players
|
from mudlib.player import Player
|
||||||
|
from mudlib.zone import Zone
|
||||||
# World instance will be injected by the server
|
|
||||||
world: Any = None
|
|
||||||
|
|
||||||
# Direction mappings: command -> (dx, dy)
|
# Direction mappings: command -> (dx, dy)
|
||||||
DIRECTIONS: dict[str, tuple[int, int]] = {
|
DIRECTIONS: dict[str, tuple[int, int]] = {
|
||||||
|
|
@ -66,10 +62,12 @@ async def move_player(player: Player, dx: int, dy: int, direction_name: str) ->
|
||||||
dy: Y delta
|
dy: Y delta
|
||||||
direction_name: Full name of the direction for messages
|
direction_name: Full name of the direction for messages
|
||||||
"""
|
"""
|
||||||
target_x, target_y = world.wrap(player.x + dx, player.y + dy)
|
zone = player.location
|
||||||
|
assert isinstance(zone, Zone), "Player must be in a zone to move"
|
||||||
|
target_x, target_y = zone.wrap(player.x + dx, player.y + dy)
|
||||||
|
|
||||||
# Check if the target is passable
|
# Check if the target is passable
|
||||||
if not world.is_passable(target_x, target_y):
|
if not zone.is_passable(target_x, target_y):
|
||||||
await player.send("You can't go that way.\r\n")
|
await player.send("You can't go that way.\r\n")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -106,17 +104,11 @@ async def send_nearby_message(entity: Entity, x: int, y: int, message: str) -> N
|
||||||
# For now, use a simple viewport range (could be configurable)
|
# For now, use a simple viewport range (could be configurable)
|
||||||
viewport_range = 10
|
viewport_range = 10
|
||||||
|
|
||||||
for other in players.values():
|
zone = entity.location
|
||||||
if other.name == entity.name:
|
assert isinstance(zone, Zone), "Entity must be in a zone to send nearby messages"
|
||||||
continue
|
for obj in zone.contents_near(x, y, viewport_range):
|
||||||
|
if obj is not entity and isinstance(obj, Entity):
|
||||||
# Check if other player is within viewport range (wrapping)
|
await obj.send(message)
|
||||||
dx_dist = abs(other.x - x)
|
|
||||||
dy_dist = abs(other.y - y)
|
|
||||||
dx_dist = min(dx_dist, world.width - dx_dist)
|
|
||||||
dy_dist = min(dy_dist, world.height - dy_dist)
|
|
||||||
if dx_dist <= viewport_range and dy_dist <= viewport_range:
|
|
||||||
await other.send(message)
|
|
||||||
|
|
||||||
|
|
||||||
# Define individual movement command handlers
|
# Define individual movement command handlers
|
||||||
|
|
|
||||||
|
|
@ -431,7 +431,6 @@ async def run_server() -> None:
|
||||||
# Inject world into command modules
|
# Inject world into command modules
|
||||||
mudlib.commands.fly.world = _world
|
mudlib.commands.fly.world = _world
|
||||||
mudlib.commands.look.world = _world
|
mudlib.commands.look.world = _world
|
||||||
mudlib.commands.movement.world = _world
|
|
||||||
mudlib.combat.commands.world = _world
|
mudlib.combat.commands.world = _world
|
||||||
|
|
||||||
# Load content-defined commands from TOML files
|
# Load content-defined commands from TOML files
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import mudlib.commands.movement as movement_mod
|
|
||||||
from mudlib.combat import commands as combat_commands
|
from mudlib.combat import commands as combat_commands
|
||||||
from mudlib.combat.engine import active_encounters, get_encounter
|
from mudlib.combat.engine import active_encounters, get_encounter
|
||||||
from mudlib.combat.moves import load_moves
|
from mudlib.combat.moves import load_moves
|
||||||
from mudlib.player import Player, players
|
from mudlib.player import Player, players
|
||||||
|
from mudlib.zone import Zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
|
@ -23,16 +23,19 @@ def clear_state():
|
||||||
players.clear()
|
players.clear()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture
|
||||||
def mock_world():
|
def test_zone():
|
||||||
"""Inject a mock world for send_nearby_message."""
|
"""Create a test zone for players."""
|
||||||
fake_world = MagicMock()
|
terrain = [["." for _ in range(256)] for _ in range(256)]
|
||||||
fake_world.width = 256
|
zone = Zone(
|
||||||
fake_world.height = 256
|
name="testzone",
|
||||||
old = movement_mod.world
|
width=256,
|
||||||
movement_mod.world = fake_world
|
height=256,
|
||||||
yield fake_world
|
toroidal=True,
|
||||||
movement_mod.world = old
|
terrain=terrain,
|
||||||
|
impassable=set(),
|
||||||
|
)
|
||||||
|
return zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -49,15 +52,19 @@ def mock_reader():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def player(mock_reader, mock_writer):
|
def player(mock_reader, mock_writer, test_zone):
|
||||||
p = Player(name="Goku", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
p = Player(name="Goku", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
||||||
|
p.location = test_zone
|
||||||
|
test_zone._contents.append(p)
|
||||||
players[p.name] = p
|
players[p.name] = p
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def target(mock_reader, mock_writer):
|
def target(mock_reader, mock_writer, test_zone):
|
||||||
t = Player(name="Vegeta", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
t = Player(name="Vegeta", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
||||||
|
t.location = test_zone
|
||||||
|
test_zone._contents.append(t)
|
||||||
players[t.name] = t
|
players[t.name] = t
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from mudlib.commands import CommandDefinition, look, movement
|
||||||
from mudlib.effects import active_effects, add_effect
|
from mudlib.effects import active_effects, add_effect
|
||||||
from mudlib.player import Player
|
from mudlib.player import Player
|
||||||
from mudlib.render.ansi import RESET
|
from mudlib.render.ansi import RESET
|
||||||
|
from mudlib.zone import Zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -25,21 +26,26 @@ def mock_reader():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_world():
|
def test_zone():
|
||||||
world = MagicMock()
|
# Create a 100x100 zone filled with passable terrain
|
||||||
world.width = 100
|
terrain = [["." for _ in range(100)] for _ in range(100)]
|
||||||
world.height = 100
|
zone = Zone(
|
||||||
world.is_passable = MagicMock(return_value=True)
|
name="testzone",
|
||||||
world.wrap = MagicMock(side_effect=lambda x, y: (x % 100, y % 100))
|
width=100,
|
||||||
# Create a 21x11 viewport filled with "."
|
height=100,
|
||||||
viewport = [["." for _ in range(21)] for _ in range(11)]
|
toroidal=True,
|
||||||
world.get_viewport = MagicMock(return_value=viewport)
|
terrain=terrain,
|
||||||
return world
|
impassable=set(), # All terrain is passable for tests
|
||||||
|
)
|
||||||
|
return zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def player(mock_reader, mock_writer):
|
def player(mock_reader, mock_writer, test_zone):
|
||||||
return Player(name="TestPlayer", x=5, y=5, reader=mock_reader, writer=mock_writer)
|
p = Player(name="TestPlayer", x=5, y=5, reader=mock_reader, writer=mock_writer)
|
||||||
|
p.location = test_zone
|
||||||
|
test_zone._contents.append(p)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
# Test command registration
|
# Test command registration
|
||||||
|
|
@ -132,11 +138,10 @@ def test_direction_deltas(direction, expected_delta):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_movement_updates_position(player, mock_world):
|
async def test_movement_updates_position(player, test_zone):
|
||||||
"""Test that movement updates player position when passable."""
|
"""Test that movement updates player position when passable."""
|
||||||
# Inject mock world into both movement and look modules
|
# Inject test_zone into look command (still uses module-level world)
|
||||||
movement.world = mock_world
|
look.world = test_zone
|
||||||
look.world = mock_world
|
|
||||||
|
|
||||||
# Clear players registry to avoid test pollution
|
# Clear players registry to avoid test pollution
|
||||||
from mudlib.player import players
|
from mudlib.player import players
|
||||||
|
|
@ -148,14 +153,15 @@ async def test_movement_updates_position(player, mock_world):
|
||||||
|
|
||||||
assert player.x == original_x
|
assert player.x == original_x
|
||||||
assert player.y == original_y - 1
|
assert player.y == original_y - 1
|
||||||
assert mock_world.is_passable.called
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_movement_blocked_by_impassable_terrain(player, mock_world, mock_writer):
|
async def test_movement_blocked_by_impassable_terrain(player, test_zone, mock_writer):
|
||||||
"""Test that movement is blocked by impassable terrain."""
|
"""Test that movement is blocked by impassable terrain."""
|
||||||
mock_world.is_passable.return_value = False
|
# Make the target position impassable
|
||||||
movement.world = mock_world
|
target_y = player.y - 1
|
||||||
|
test_zone.terrain[target_y][player.x] = "^" # mountain
|
||||||
|
test_zone.impassable = {"^"}
|
||||||
|
|
||||||
original_x, original_y = player.x, player.y
|
original_x, original_y = player.x, player.y
|
||||||
await movement.move_north(player, "")
|
await movement.move_north(player, "")
|
||||||
|
|
@ -171,10 +177,9 @@ async def test_movement_blocked_by_impassable_terrain(player, mock_world, mock_w
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_movement_sends_departure_message(player, mock_world):
|
async def test_movement_sends_departure_message(player, test_zone):
|
||||||
"""Test that movement sends departure message to nearby players."""
|
"""Test that movement sends departure message to nearby players."""
|
||||||
movement.world = mock_world
|
look.world = test_zone
|
||||||
look.world = mock_world
|
|
||||||
|
|
||||||
# Create another player in the area
|
# Create another player in the area
|
||||||
other_writer = MagicMock()
|
other_writer = MagicMock()
|
||||||
|
|
@ -183,6 +188,8 @@ async def test_movement_sends_departure_message(player, mock_world):
|
||||||
other_player = Player(
|
other_player = Player(
|
||||||
name="OtherPlayer", x=5, y=4, reader=MagicMock(), writer=other_writer
|
name="OtherPlayer", x=5, y=4, reader=MagicMock(), writer=other_writer
|
||||||
)
|
)
|
||||||
|
other_player.location = test_zone
|
||||||
|
test_zone._contents.append(other_player)
|
||||||
|
|
||||||
# Register both players
|
# Register both players
|
||||||
from mudlib.player import players
|
from mudlib.player import players
|
||||||
|
|
@ -199,10 +206,9 @@ async def test_movement_sends_departure_message(player, mock_world):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_arrival_message_uses_opposite_direction(player, mock_world):
|
async def test_arrival_message_uses_opposite_direction(player, test_zone):
|
||||||
"""Test that arrival messages use the opposite direction."""
|
"""Test that arrival messages use the opposite direction."""
|
||||||
movement.world = mock_world
|
look.world = test_zone
|
||||||
look.world = mock_world
|
|
||||||
|
|
||||||
# Create another player at the destination
|
# Create another player at the destination
|
||||||
other_writer = MagicMock()
|
other_writer = MagicMock()
|
||||||
|
|
@ -211,6 +217,8 @@ async def test_arrival_message_uses_opposite_direction(player, mock_world):
|
||||||
other_player = Player(
|
other_player = Player(
|
||||||
name="OtherPlayer", x=5, y=3, reader=MagicMock(), writer=other_writer
|
name="OtherPlayer", x=5, y=3, reader=MagicMock(), writer=other_writer
|
||||||
)
|
)
|
||||||
|
other_player.location = test_zone
|
||||||
|
test_zone._contents.append(other_player)
|
||||||
|
|
||||||
from mudlib.player import players
|
from mudlib.player import players
|
||||||
|
|
||||||
|
|
@ -228,20 +236,20 @@ async def test_arrival_message_uses_opposite_direction(player, mock_world):
|
||||||
|
|
||||||
# Test look command
|
# Test look command
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_look_command_sends_viewport(player, mock_world):
|
async def test_look_command_sends_viewport(player, test_zone):
|
||||||
"""Test that look command sends the viewport to the player."""
|
"""Test that look command sends the viewport to the player."""
|
||||||
look.world = mock_world
|
# look.py still uses module-level world, so inject test_zone
|
||||||
|
look.world = test_zone
|
||||||
|
|
||||||
await look.cmd_look(player, "")
|
await look.cmd_look(player, "")
|
||||||
|
|
||||||
assert mock_world.get_viewport.called
|
|
||||||
assert player.writer.write.called
|
assert player.writer.write.called
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_look_command_shows_player_at_center(player, mock_world):
|
async def test_look_command_shows_player_at_center(player, test_zone):
|
||||||
"""Test that look command shows player @ at center."""
|
"""Test that look command shows player @ at center."""
|
||||||
look.world = mock_world
|
look.world = test_zone
|
||||||
|
|
||||||
await look.cmd_look(player, "")
|
await look.cmd_look(player, "")
|
||||||
|
|
||||||
|
|
@ -251,9 +259,9 @@ async def test_look_command_shows_player_at_center(player, mock_world):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_look_command_shows_other_players(player, mock_world):
|
async def test_look_command_shows_other_players(player, test_zone):
|
||||||
"""Test that look command shows other players as *."""
|
"""Test that look command shows other players as *."""
|
||||||
look.world = mock_world
|
look.world = test_zone
|
||||||
|
|
||||||
# Create another player in the viewport
|
# Create another player in the viewport
|
||||||
other_player = Player(
|
other_player = Player(
|
||||||
|
|
@ -263,6 +271,8 @@ async def test_look_command_shows_other_players(player, mock_world):
|
||||||
reader=MagicMock(),
|
reader=MagicMock(),
|
||||||
writer=MagicMock(),
|
writer=MagicMock(),
|
||||||
)
|
)
|
||||||
|
other_player.location = test_zone
|
||||||
|
test_zone._contents.append(other_player)
|
||||||
|
|
||||||
from mudlib.player import players
|
from mudlib.player import players
|
||||||
|
|
||||||
|
|
@ -278,9 +288,9 @@ async def test_look_command_shows_other_players(player, mock_world):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_look_shows_effects_on_viewport(player, mock_world):
|
async def test_look_shows_effects_on_viewport(player, test_zone):
|
||||||
"""Test that active effects overlay on the viewport."""
|
"""Test that active effects overlay on the viewport."""
|
||||||
look.world = mock_world
|
look.world = test_zone
|
||||||
|
|
||||||
from mudlib.player import players
|
from mudlib.player import players
|
||||||
|
|
||||||
|
|
@ -303,9 +313,9 @@ async def test_look_shows_effects_on_viewport(player, mock_world):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_effects_dont_override_player_marker(player, mock_world):
|
async def test_effects_dont_override_player_marker(player, test_zone):
|
||||||
"""Effects at the player's position should not hide the @ marker."""
|
"""Effects at the player's position should not hide the @ marker."""
|
||||||
look.world = mock_world
|
look.world = test_zone
|
||||||
|
|
||||||
from mudlib.player import players
|
from mudlib.player import players
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@ from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from mudlib.commands import fly, look, movement
|
from mudlib.commands import fly, look
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -24,27 +25,32 @@ def mock_reader():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_world():
|
def test_zone():
|
||||||
world = MagicMock()
|
terrain = [["." for _ in range(100)] for _ in range(100)]
|
||||||
world.width = 100
|
zone = Zone(
|
||||||
world.height = 100
|
name="testzone",
|
||||||
world.wrap = MagicMock(side_effect=lambda x, y: (x % 100, y % 100))
|
width=100,
|
||||||
viewport = [["." for _ in range(21)] for _ in range(11)]
|
height=100,
|
||||||
world.get_viewport = MagicMock(return_value=viewport)
|
toroidal=True,
|
||||||
return world
|
terrain=terrain,
|
||||||
|
impassable=set(),
|
||||||
|
)
|
||||||
|
return zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def player(mock_reader, mock_writer):
|
def player(mock_reader, mock_writer, test_zone):
|
||||||
return Player(name="shmup", x=50, y=50, reader=mock_reader, writer=mock_writer)
|
p = Player(name="shmup", x=50, y=50, reader=mock_reader, writer=mock_writer)
|
||||||
|
p.location = test_zone
|
||||||
|
test_zone._contents.append(p)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def clean_state(mock_world):
|
def clean_state(test_zone):
|
||||||
"""Clean global state before/after each test."""
|
"""Clean global state before/after each test."""
|
||||||
fly.world = mock_world
|
fly.world = test_zone
|
||||||
look.world = mock_world
|
look.world = test_zone
|
||||||
movement.world = mock_world
|
|
||||||
players.clear()
|
players.clear()
|
||||||
active_effects.clear()
|
active_effects.clear()
|
||||||
yield
|
yield
|
||||||
|
|
@ -82,7 +88,7 @@ async def test_fly_toggles_off(player, mock_writer):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_fly_toggle_on_notifies_nearby(player):
|
async def test_fly_toggle_on_notifies_nearby(player, test_zone):
|
||||||
"""Others see liftoff message."""
|
"""Others see liftoff message."""
|
||||||
players[player.name] = player
|
players[player.name] = player
|
||||||
|
|
||||||
|
|
@ -96,6 +102,8 @@ async def test_fly_toggle_on_notifies_nearby(player):
|
||||||
reader=MagicMock(),
|
reader=MagicMock(),
|
||||||
writer=other_writer,
|
writer=other_writer,
|
||||||
)
|
)
|
||||||
|
other.location = test_zone
|
||||||
|
test_zone._contents.append(other)
|
||||||
players[other.name] = other
|
players[other.name] = other
|
||||||
|
|
||||||
await fly.cmd_fly(player, "")
|
await fly.cmd_fly(player, "")
|
||||||
|
|
@ -105,7 +113,7 @@ async def test_fly_toggle_on_notifies_nearby(player):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_fly_toggle_off_notifies_nearby(player):
|
async def test_fly_toggle_off_notifies_nearby(player, test_zone):
|
||||||
"""Others see landing message."""
|
"""Others see landing message."""
|
||||||
players[player.name] = player
|
players[player.name] = player
|
||||||
player.flying = True
|
player.flying = True
|
||||||
|
|
@ -120,6 +128,8 @@ async def test_fly_toggle_off_notifies_nearby(player):
|
||||||
reader=MagicMock(),
|
reader=MagicMock(),
|
||||||
writer=other_writer,
|
writer=other_writer,
|
||||||
)
|
)
|
||||||
|
other.location = test_zone
|
||||||
|
test_zone._contents.append(other)
|
||||||
players[other.name] = other
|
players[other.name] = other
|
||||||
|
|
||||||
await fly.cmd_fly(player, "")
|
await fly.cmd_fly(player, "")
|
||||||
|
|
@ -279,13 +289,14 @@ async def test_fly_bad_direction_gives_error(player, mock_writer):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_fly_triggers_look(player, mock_world):
|
async def test_fly_triggers_look(player, test_zone):
|
||||||
"""Flying should auto-look at the destination."""
|
"""Flying should auto-look at the destination."""
|
||||||
players[player.name] = player
|
players[player.name] = player
|
||||||
player.flying = True
|
player.flying = True
|
||||||
await fly.cmd_fly(player, "east")
|
await fly.cmd_fly(player, "east")
|
||||||
|
|
||||||
assert mock_world.get_viewport.called
|
# look was called (check that writer was written to)
|
||||||
|
assert player.writer.write.called
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import mudlib.commands.movement as movement_mod
|
|
||||||
from mudlib.combat import commands as combat_commands
|
from mudlib.combat import commands as combat_commands
|
||||||
from mudlib.combat.encounter import CombatState
|
from mudlib.combat.encounter import CombatState
|
||||||
from mudlib.combat.engine import (
|
from mudlib.combat.engine import (
|
||||||
|
|
@ -22,6 +21,7 @@ from mudlib.mobs import (
|
||||||
spawn_mob,
|
spawn_mob,
|
||||||
)
|
)
|
||||||
from mudlib.player import Player, players
|
from mudlib.player import Player, players
|
||||||
|
from mudlib.zone import Zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
|
@ -36,21 +36,49 @@ def clear_state():
|
||||||
players.clear()
|
players.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_zone():
|
||||||
|
"""Create a test zone for entities."""
|
||||||
|
terrain = [["." for _ in range(256)] for _ in range(256)]
|
||||||
|
zone = Zone(
|
||||||
|
name="testzone",
|
||||||
|
width=256,
|
||||||
|
height=256,
|
||||||
|
toroidal=True,
|
||||||
|
terrain=terrain,
|
||||||
|
impassable=set(),
|
||||||
|
)
|
||||||
|
return zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_world():
|
def inject_world_for_combat(test_zone):
|
||||||
"""Inject a mock world for movement and combat commands."""
|
"""Inject test_zone into combat commands (still uses module-level world)."""
|
||||||
fake_world = MagicMock()
|
|
||||||
fake_world.width = 256
|
|
||||||
fake_world.height = 256
|
|
||||||
old_movement = movement_mod.world
|
|
||||||
old_combat = combat_commands.world
|
old_combat = combat_commands.world
|
||||||
movement_mod.world = fake_world
|
combat_commands.world = test_zone
|
||||||
combat_commands.world = fake_world
|
yield test_zone
|
||||||
yield fake_world
|
|
||||||
movement_mod.world = old_movement
|
|
||||||
combat_commands.world = old_combat
|
combat_commands.world = old_combat
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def auto_zone_mobs(test_zone, monkeypatch):
|
||||||
|
"""Monkeypatch spawn_mob to automatically add mobs to test_zone."""
|
||||||
|
original_spawn = spawn_mob
|
||||||
|
|
||||||
|
def spawn_and_zone(template, x, y):
|
||||||
|
mob = original_spawn(template, x, y)
|
||||||
|
mob.location = test_zone
|
||||||
|
test_zone._contents.append(mob)
|
||||||
|
return mob
|
||||||
|
|
||||||
|
monkeypatch.setattr("mudlib.mobs.spawn_mob", spawn_and_zone)
|
||||||
|
# Also patch it in the test module's import
|
||||||
|
import mudlib.mob_ai as mob_ai_mod
|
||||||
|
|
||||||
|
if hasattr(mob_ai_mod, "spawn_mob"):
|
||||||
|
monkeypatch.setattr(mob_ai_mod, "spawn_mob", spawn_and_zone)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_writer():
|
def mock_writer():
|
||||||
writer = MagicMock()
|
writer = MagicMock()
|
||||||
|
|
@ -65,8 +93,10 @@ def mock_reader():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def player(mock_reader, mock_writer):
|
def player(mock_reader, mock_writer, test_zone):
|
||||||
p = Player(name="Goku", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
p = Player(name="Goku", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
||||||
|
p.location = test_zone
|
||||||
|
test_zone._contents.append(p)
|
||||||
players[p.name] = p
|
players[p.name] = p
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import mudlib.commands.movement as movement_mod
|
|
||||||
from mudlib.combat import commands as combat_commands
|
from mudlib.combat import commands as combat_commands
|
||||||
from mudlib.combat.encounter import CombatState
|
from mudlib.combat.encounter import CombatState
|
||||||
from mudlib.combat.engine import active_encounters, get_encounter
|
from mudlib.combat.engine import active_encounters, get_encounter
|
||||||
|
|
@ -20,6 +19,7 @@ from mudlib.mobs import (
|
||||||
spawn_mob,
|
spawn_mob,
|
||||||
)
|
)
|
||||||
from mudlib.player import Player, players
|
from mudlib.player import Player, players
|
||||||
|
from mudlib.zone import Zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
|
@ -34,21 +34,44 @@ def clear_state():
|
||||||
players.clear()
|
players.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_zone():
|
||||||
|
"""Create a test zone for entities."""
|
||||||
|
terrain = [["." for _ in range(256)] for _ in range(256)]
|
||||||
|
zone = Zone(
|
||||||
|
name="testzone",
|
||||||
|
width=256,
|
||||||
|
height=256,
|
||||||
|
toroidal=True,
|
||||||
|
terrain=terrain,
|
||||||
|
impassable=set(),
|
||||||
|
)
|
||||||
|
return zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_world():
|
def inject_world_for_combat(test_zone):
|
||||||
"""Inject a mock world for movement and combat commands."""
|
"""Inject test_zone into combat commands (still uses module-level world)."""
|
||||||
fake_world = MagicMock()
|
|
||||||
fake_world.width = 256
|
|
||||||
fake_world.height = 256
|
|
||||||
old_movement = movement_mod.world
|
|
||||||
old_combat = combat_commands.world
|
old_combat = combat_commands.world
|
||||||
movement_mod.world = fake_world
|
combat_commands.world = test_zone
|
||||||
combat_commands.world = fake_world
|
yield test_zone
|
||||||
yield fake_world
|
|
||||||
movement_mod.world = old_movement
|
|
||||||
combat_commands.world = old_combat
|
combat_commands.world = old_combat
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def auto_zone_mobs(test_zone, monkeypatch):
|
||||||
|
"""Monkeypatch spawn_mob to automatically add mobs to test_zone."""
|
||||||
|
original_spawn = spawn_mob
|
||||||
|
|
||||||
|
def spawn_and_zone(template, x, y):
|
||||||
|
mob = original_spawn(template, x, y)
|
||||||
|
mob.location = test_zone
|
||||||
|
test_zone._contents.append(mob)
|
||||||
|
return mob
|
||||||
|
|
||||||
|
monkeypatch.setattr("mudlib.mobs.spawn_mob", spawn_and_zone)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def goblin_toml(tmp_path):
|
def goblin_toml(tmp_path):
|
||||||
"""Create a goblin TOML file."""
|
"""Create a goblin TOML file."""
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import mudlib.commands.movement as movement_mod
|
|
||||||
from mudlib.commands.rest import cmd_rest
|
from mudlib.commands.rest import cmd_rest
|
||||||
from mudlib.player import Player, players
|
from mudlib.player import Player, players
|
||||||
from mudlib.resting import process_resting
|
from mudlib.resting import process_resting
|
||||||
|
from mudlib.zone import Zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
|
@ -18,16 +18,19 @@ def clear_state():
|
||||||
players.clear()
|
players.clear()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture
|
||||||
def mock_world():
|
def test_zone():
|
||||||
"""Inject a mock world for send_nearby_message."""
|
"""Create a test zone for players."""
|
||||||
fake_world = MagicMock()
|
terrain = [["." for _ in range(256)] for _ in range(256)]
|
||||||
fake_world.width = 256
|
zone = Zone(
|
||||||
fake_world.height = 256
|
name="testzone",
|
||||||
old = movement_mod.world
|
width=256,
|
||||||
movement_mod.world = fake_world
|
height=256,
|
||||||
yield fake_world
|
toroidal=True,
|
||||||
movement_mod.world = old
|
terrain=terrain,
|
||||||
|
impassable=set(),
|
||||||
|
)
|
||||||
|
return zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -44,15 +47,19 @@ def mock_reader():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def player(mock_reader, mock_writer):
|
def player(mock_reader, mock_writer, test_zone):
|
||||||
p = Player(name="Goku", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
p = Player(name="Goku", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
||||||
|
p.location = test_zone
|
||||||
|
test_zone._contents.append(p)
|
||||||
players[p.name] = p
|
players[p.name] = p
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def nearby_player(mock_reader, mock_writer):
|
def nearby_player(mock_reader, mock_writer, test_zone):
|
||||||
p = Player(name="Vegeta", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
p = Player(name="Vegeta", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
||||||
|
p.location = test_zone
|
||||||
|
test_zone._contents.append(p)
|
||||||
players[p.name] = p
|
players[p.name] = p
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import mudlib.commands.movement as movement_mod
|
|
||||||
from mudlib.combat import commands as combat_commands
|
from mudlib.combat import commands as combat_commands
|
||||||
from mudlib.combat.engine import active_encounters
|
from mudlib.combat.engine import active_encounters
|
||||||
from mudlib.combat.moves import load_moves
|
from mudlib.combat.moves import load_moves
|
||||||
from mudlib.player import Player, players
|
from mudlib.player import Player, players
|
||||||
|
from mudlib.zone import Zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
|
@ -22,16 +22,28 @@ def clear_state():
|
||||||
players.clear()
|
players.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def test_zone():
|
||||||
|
"""Create a test zone for players."""
|
||||||
|
terrain = [["." for _ in range(256)] for _ in range(256)]
|
||||||
|
zone = Zone(
|
||||||
|
name="testzone",
|
||||||
|
width=256,
|
||||||
|
height=256,
|
||||||
|
toroidal=True,
|
||||||
|
terrain=terrain,
|
||||||
|
impassable=set(),
|
||||||
|
)
|
||||||
|
return zone
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_world():
|
def inject_world_for_combat(test_zone):
|
||||||
"""Inject a mock world for send_nearby_message."""
|
"""Inject test_zone into combat commands (still uses module-level world)."""
|
||||||
fake_world = MagicMock()
|
old_combat = combat_commands.world
|
||||||
fake_world.width = 256
|
combat_commands.world = test_zone
|
||||||
fake_world.height = 256
|
yield test_zone
|
||||||
old = movement_mod.world
|
combat_commands.world = old_combat
|
||||||
movement_mod.world = fake_world
|
|
||||||
yield fake_world
|
|
||||||
movement_mod.world = old
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -48,15 +60,19 @@ def mock_reader():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def player(mock_reader, mock_writer):
|
def player(mock_reader, mock_writer, test_zone):
|
||||||
p = Player(name="Goku", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
p = Player(name="Goku", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
||||||
|
p.location = test_zone
|
||||||
|
test_zone._contents.append(p)
|
||||||
players[p.name] = p
|
players[p.name] = p
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def target(mock_reader, mock_writer):
|
def target(mock_reader, mock_writer, test_zone):
|
||||||
t = Player(name="Vegeta", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
t = Player(name="Vegeta", x=0, y=0, reader=mock_reader, writer=mock_writer)
|
||||||
|
t.location = test_zone
|
||||||
|
test_zone._contents.append(t)
|
||||||
players[t.name] = t
|
players[t.name] = t
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue