mud/tests/test_paint_mode.py
Jared Miller c3884e236b
Add per-zone mob spawn rules
Zones can now define spawn rules in TOML:
- [[spawns]] sections specify mob type, max count, and respawn timer
- SpawnRule dataclass stores configuration
- load_zone() parses spawn rules from TOML
- Added example spawn rules to treehouse zone (squirrel, crow)

This is configuration infrastructure only - actual spawning logic
will be handled by the game loop in a future phase.
2026-02-11 22:38:14 -05:00

223 lines
6 KiB
Python

"""Tests for paint mode - terrain editing admin tool."""
import pytest
from mudlib.player import Player
from mudlib.zone import Zone
@pytest.fixture
def player():
"""Create a test player with a mock writer."""
mock_writer = MockWriter()
player = Player(
name="TestPlayer",
x=5,
y=5,
writer=mock_writer,
)
# Create a simple zone for testing
zone = Zone(
"test_zone",
width=20,
height=20,
terrain=[["." for _ in range(20)] for _ in range(20)],
impassable={"^", "~", "#"}, # Add # as impassable
)
# Add a wall for passability testing
zone.terrain[5][6] = "#"
player.location = zone
return player
class MockWriter:
"""Mock writer that captures output."""
def __init__(self):
self.messages = []
def write(self, message: str):
self.messages.append(message)
async def drain(self):
pass
def get_output(self) -> str:
return "".join(self.messages)
def clear(self):
self.messages = []
@pytest.mark.asyncio
async def test_enter_paint_mode(player):
"""@paint command sets player.paint_mode = True and sends confirmation."""
from mudlib.commands.paint import cmd_paint
await cmd_paint(player, "")
assert player.paint_mode is True
output = player.writer.get_output()
assert "paint mode" in output.lower()
@pytest.mark.asyncio
async def test_exit_paint_mode(player):
"""@paint when already in paint mode exits it."""
from mudlib.commands.paint import cmd_paint
# Enter paint mode first
await cmd_paint(player, "")
player.writer.clear()
# Exit paint mode
await cmd_paint(player, "")
assert player.paint_mode is False
output = player.writer.get_output()
assert "exit" in output.lower() or "off" in output.lower()
@pytest.mark.asyncio
async def test_paint_mode_default_survey(player):
"""Entering paint mode starts in survey state (player.painting = False)."""
from mudlib.commands.paint import cmd_paint
await cmd_paint(player, "")
assert player.paint_mode is True
assert player.painting is False
@pytest.mark.asyncio
async def test_toggle_painting(player):
"""p command toggles player.painting between True and False."""
from mudlib.commands.paint import cmd_toggle_painting
# Must be in paint mode first
player.paint_mode = True
# Toggle to painting
await cmd_toggle_painting(player, "")
assert player.painting is True
player.writer.clear()
# Toggle back to survey
await cmd_toggle_painting(player, "")
assert player.painting is False
@pytest.mark.asyncio
async def test_toggle_painting_requires_paint_mode(player):
"""p command only works in paint mode."""
from mudlib.commands.paint import cmd_toggle_painting
player.paint_mode = False
await cmd_toggle_painting(player, "")
output = player.writer.get_output()
assert "paint mode" in output.lower()
@pytest.mark.asyncio
async def test_set_brush(player):
"""Typing a single character while in paint mode sets player.paint_brush."""
from mudlib.commands.paint import cmd_set_brush
player.paint_mode = True
await cmd_set_brush(player, "#")
assert player.paint_brush == "#"
await cmd_set_brush(player, "~")
assert player.paint_brush == "~"
@pytest.mark.asyncio
async def test_set_brush_requires_paint_mode(player):
"""Brush command only works in paint mode."""
from mudlib.commands.paint import cmd_set_brush
player.paint_mode = False
await cmd_set_brush(player, "#")
output = player.writer.get_output()
assert "paint mode" in output.lower()
@pytest.mark.asyncio
async def test_set_brush_requires_single_char(player):
"""Brush must be a single character."""
from mudlib.commands.paint import cmd_set_brush
player.paint_mode = True
await cmd_set_brush(player, "##")
output = player.writer.get_output()
assert "single character" in output.lower()
@pytest.mark.asyncio
async def test_paint_mode_movement_ignores_passability(player):
"""Movement in paint mode doesn't check passability."""
from mudlib.commands.movement import move_player
# There's a wall at (6, 5) - normally can't move there
player.paint_mode = True
# Should be able to move into the wall
await move_player(player, 1, 0, "east")
assert player.x == 6
assert player.y == 5
@pytest.mark.asyncio
async def test_painting_places_tile(player):
"""Moving while painting sets terrain tile at old position to brush char."""
from mudlib.commands.movement import move_player
player.paint_mode = True
player.painting = True
player.paint_brush = "~"
# Move from (5,5) to (6,5)
old_x, old_y = player.x, player.y
await move_player(player, 1, 0, "east")
# Check that the old position now has the brush character
zone = player.location
assert isinstance(zone, Zone)
assert zone.terrain[old_y][old_x] == "~"
@pytest.mark.asyncio
async def test_painting_only_in_paint_mode(player):
"""Painting flag has no effect outside paint mode."""
from mudlib.commands.movement import move_player
player.paint_mode = False
player.painting = True
player.paint_brush = "~"
# Try to move into wall at (6, 5)
await move_player(player, 1, 0, "east")
# Should have failed - player still at (5, 5)
assert player.x == 5
assert player.y == 5
@pytest.mark.asyncio
async def test_survey_mode_does_not_paint(player):
"""Survey mode (painting=False) allows movement but doesn't paint."""
from mudlib.commands.movement import move_player
player.paint_mode = True
player.painting = False
player.paint_brush = "~"
old_x, old_y = player.x, player.y
await move_player(player, 1, 0, "east")
# Movement should have happened
assert player.x == 6
assert player.y == 5
# But no painting should have occurred
zone = player.location
assert isinstance(zone, Zone)
assert zone.terrain[old_y][old_x] == "." # Still the original tile