Add furnish and unfurnish commands
This commit is contained in:
parent
5b6c808050
commit
7342a70ba2
3 changed files with 356 additions and 0 deletions
92
src/mudlib/commands/furnish.py
Normal file
92
src/mudlib/commands/furnish.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"""Furnish and unfurnish commands — place/remove furniture in home zones."""
|
||||
|
||||
from mudlib.commands import CommandDefinition, register
|
||||
from mudlib.housing import save_home_zone
|
||||
from mudlib.player import Player
|
||||
from mudlib.targeting import find_in_inventory, find_thing_on_tile
|
||||
from mudlib.zone import Zone
|
||||
|
||||
|
||||
async def cmd_furnish(player: Player, args: str) -> None:
|
||||
"""Place an item from inventory as furniture in your home zone.
|
||||
|
||||
Usage:
|
||||
furnish <item>
|
||||
"""
|
||||
# Validate arguments
|
||||
if not args.strip():
|
||||
await player.send("Usage: furnish <item>\r\n")
|
||||
return
|
||||
|
||||
# Check that player is in their home zone
|
||||
zone = player.location
|
||||
if not isinstance(zone, Zone) or zone.name != player.home_zone:
|
||||
await player.send("You can only furnish items in your home zone.\r\n")
|
||||
return
|
||||
|
||||
# Find item in inventory
|
||||
item_name = args.strip()
|
||||
thing = find_in_inventory(item_name, player)
|
||||
|
||||
if thing is None:
|
||||
await player.send(f"You don't have '{item_name}'.\r\n")
|
||||
return
|
||||
|
||||
# Place item at player's position
|
||||
thing.move_to(zone, x=player.x, y=player.y)
|
||||
|
||||
# Save the zone
|
||||
save_home_zone(player.name, zone)
|
||||
|
||||
await player.send(f"You place the {thing.name} here.\r\n")
|
||||
|
||||
|
||||
async def cmd_unfurnish(player: Player, args: str) -> None:
|
||||
"""Pick up furniture from your home zone into inventory.
|
||||
|
||||
Usage:
|
||||
unfurnish <item>
|
||||
"""
|
||||
# Validate arguments
|
||||
if not args.strip():
|
||||
await player.send("Usage: unfurnish <item>\r\n")
|
||||
return
|
||||
|
||||
# Check that player is in their home zone
|
||||
zone = player.location
|
||||
if not isinstance(zone, Zone) or zone.name != player.home_zone:
|
||||
await player.send("You can only unfurnish items in your home zone.\r\n")
|
||||
return
|
||||
|
||||
# Find furniture at player's position
|
||||
item_name = args.strip()
|
||||
thing = find_thing_on_tile(item_name, zone, player.x, player.y)
|
||||
|
||||
if thing is None:
|
||||
await player.send(f"You don't see '{item_name}' here.\r\n")
|
||||
return
|
||||
|
||||
# Pick up the item
|
||||
thing.move_to(player)
|
||||
|
||||
# Save the zone
|
||||
save_home_zone(player.name, zone)
|
||||
|
||||
await player.send(f"You pick up the {thing.name}.\r\n")
|
||||
|
||||
|
||||
register(
|
||||
CommandDefinition(
|
||||
"furnish",
|
||||
cmd_furnish,
|
||||
help="Place an item from inventory as furniture in your home zone.",
|
||||
)
|
||||
)
|
||||
|
||||
register(
|
||||
CommandDefinition(
|
||||
"unfurnish",
|
||||
cmd_unfurnish,
|
||||
help="Pick up furniture from your home zone into inventory.",
|
||||
)
|
||||
)
|
||||
|
|
@ -21,6 +21,7 @@ import mudlib.commands.describe
|
|||
import mudlib.commands.edit
|
||||
import mudlib.commands.examine
|
||||
import mudlib.commands.fly
|
||||
import mudlib.commands.furnish
|
||||
import mudlib.commands.help
|
||||
import mudlib.commands.home
|
||||
import mudlib.commands.look
|
||||
|
|
|
|||
263
tests/test_furnish.py
Normal file
263
tests/test_furnish.py
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
"""Tests for furnish and unfurnish commands."""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from mudlib.housing import init_housing
|
||||
from mudlib.player import Player
|
||||
from mudlib.thing import Thing
|
||||
from mudlib.zone import Zone
|
||||
from mudlib.zones import register_zone, zone_registry
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clean_registries():
|
||||
"""Clear zone registry between tests."""
|
||||
saved = dict(zone_registry)
|
||||
zone_registry.clear()
|
||||
yield
|
||||
zone_registry.clear()
|
||||
zone_registry.update(saved)
|
||||
|
||||
|
||||
def _make_zone(name="overworld", width=20, height=20):
|
||||
"""Create a zone for testing."""
|
||||
terrain = [["." for _ in range(width)] for _ in range(height)]
|
||||
zone = Zone(
|
||||
name=name,
|
||||
description=name,
|
||||
width=width,
|
||||
height=height,
|
||||
terrain=terrain,
|
||||
toroidal=True,
|
||||
)
|
||||
register_zone(name, zone)
|
||||
return zone
|
||||
|
||||
|
||||
def _make_player(name="tester", zone=None, x=5, y=5):
|
||||
"""Create a player with mock writer."""
|
||||
mock_writer = MagicMock()
|
||||
mock_writer.write = MagicMock()
|
||||
mock_writer.drain = AsyncMock()
|
||||
return Player(name=name, location=zone, x=x, y=y, writer=mock_writer)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_furnish_places_item(tmp_path):
|
||||
"""furnish moves an item from inventory to the zone at player position."""
|
||||
from mudlib.commands.furnish import cmd_furnish
|
||||
|
||||
init_housing(tmp_path)
|
||||
|
||||
# Create home zone and player
|
||||
home_zone = _make_zone("home:alice", width=9, height=9)
|
||||
player = _make_player("alice", zone=home_zone, x=4, y=4)
|
||||
player.home_zone = "home:alice"
|
||||
|
||||
# Give player a chair
|
||||
chair = Thing(name="chair", description="A wooden chair")
|
||||
chair.move_to(player)
|
||||
|
||||
# Furnish the chair
|
||||
await cmd_furnish(player, "chair")
|
||||
|
||||
# Chair should be in the zone at player position
|
||||
assert chair.location is home_zone
|
||||
assert chair.x == 4
|
||||
assert chair.y == 4
|
||||
assert chair not in player.contents
|
||||
|
||||
# Player should get feedback
|
||||
player.writer.write.assert_called()
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "chair" in output.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_furnish_not_in_home_zone():
|
||||
"""furnish fails if player is not in their home zone."""
|
||||
from mudlib.commands.furnish import cmd_furnish
|
||||
|
||||
overworld = _make_zone("overworld")
|
||||
player = _make_player("alice", zone=overworld)
|
||||
player.home_zone = "home:alice"
|
||||
|
||||
chair = Thing(name="chair")
|
||||
chair.move_to(player)
|
||||
|
||||
await cmd_furnish(player, "chair")
|
||||
|
||||
# Chair should still be in inventory
|
||||
assert chair.location is player
|
||||
assert chair in player.contents
|
||||
|
||||
# Player should get error message
|
||||
player.writer.write.assert_called()
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "home zone" in output.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_furnish_item_not_in_inventory():
|
||||
"""furnish fails if item is not in player inventory."""
|
||||
from mudlib.commands.furnish import cmd_furnish
|
||||
|
||||
home_zone = _make_zone("home:alice", width=9, height=9)
|
||||
player = _make_player("alice", zone=home_zone)
|
||||
player.home_zone = "home:alice"
|
||||
|
||||
await cmd_furnish(player, "chair")
|
||||
|
||||
# Player should get error message
|
||||
player.writer.write.assert_called()
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "don't have" in output.lower() or "not carrying" in output.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_furnish_no_args():
|
||||
"""furnish fails with usage message if no args provided."""
|
||||
from mudlib.commands.furnish import cmd_furnish
|
||||
|
||||
home_zone = _make_zone("home:alice", width=9, height=9)
|
||||
player = _make_player("alice", zone=home_zone)
|
||||
player.home_zone = "home:alice"
|
||||
|
||||
await cmd_furnish(player, "")
|
||||
|
||||
# Player should get usage message
|
||||
player.writer.write.assert_called()
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "usage" in output.lower() or "furnish" in output.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unfurnish_picks_up_item(tmp_path):
|
||||
"""unfurnish moves furniture from zone to player inventory."""
|
||||
from mudlib.commands.furnish import cmd_unfurnish
|
||||
|
||||
init_housing(tmp_path)
|
||||
|
||||
# Create home zone with furniture
|
||||
home_zone = _make_zone("home:bob", width=9, height=9)
|
||||
player = _make_player("bob", zone=home_zone, x=4, y=4)
|
||||
player.home_zone = "home:bob"
|
||||
|
||||
# Place a table at player position
|
||||
table = Thing(name="table", description="A wooden table")
|
||||
table.move_to(home_zone, x=4, y=4)
|
||||
|
||||
# Unfurnish the table
|
||||
await cmd_unfurnish(player, "table")
|
||||
|
||||
# Table should be in inventory
|
||||
assert table.location is player
|
||||
assert table in player.contents
|
||||
assert table not in home_zone._contents
|
||||
|
||||
# Player should get feedback
|
||||
player.writer.write.assert_called()
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "table" in output.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unfurnish_not_in_home_zone():
|
||||
"""unfurnish fails if player is not in their home zone."""
|
||||
from mudlib.commands.furnish import cmd_unfurnish
|
||||
|
||||
overworld = _make_zone("overworld")
|
||||
player = _make_player("bob", zone=overworld)
|
||||
player.home_zone = "home:bob"
|
||||
|
||||
# Place a table
|
||||
table = Thing(name="table")
|
||||
table.move_to(overworld, x=5, y=5)
|
||||
|
||||
await cmd_unfurnish(player, "table")
|
||||
|
||||
# Table should still be on ground
|
||||
assert table.location is overworld
|
||||
assert table not in player.contents
|
||||
|
||||
# Player should get error message
|
||||
player.writer.write.assert_called()
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "home zone" in output.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unfurnish_nothing_at_position():
|
||||
"""unfurnish fails if no matching furniture at player position."""
|
||||
from mudlib.commands.furnish import cmd_unfurnish
|
||||
|
||||
home_zone = _make_zone("home:bob", width=9, height=9)
|
||||
player = _make_player("bob", zone=home_zone, x=4, y=4)
|
||||
player.home_zone = "home:bob"
|
||||
|
||||
# No furniture at position
|
||||
await cmd_unfurnish(player, "table")
|
||||
|
||||
# Player should get error message
|
||||
player.writer.write.assert_called()
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert (
|
||||
"no" in output.lower()
|
||||
or "don't see" in output.lower()
|
||||
or "can't find" in output.lower()
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unfurnish_no_args():
|
||||
"""unfurnish fails with usage message if no args provided."""
|
||||
from mudlib.commands.furnish import cmd_unfurnish
|
||||
|
||||
home_zone = _make_zone("home:bob", width=9, height=9)
|
||||
player = _make_player("bob", zone=home_zone)
|
||||
player.home_zone = "home:bob"
|
||||
|
||||
await cmd_unfurnish(player, "")
|
||||
|
||||
# Player should get usage message
|
||||
player.writer.write.assert_called()
|
||||
output = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "usage" in output.lower() or "unfurnish" in output.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_furnish_saves_zone(tmp_path):
|
||||
"""furnish persists furniture to the zone TOML file."""
|
||||
import tomllib
|
||||
|
||||
from mudlib.commands.furnish import cmd_furnish
|
||||
|
||||
init_housing(tmp_path)
|
||||
|
||||
# Create home zone and player
|
||||
home_zone = _make_zone("home:charlie", width=9, height=9)
|
||||
player = _make_player("charlie", zone=home_zone, x=4, y=4)
|
||||
player.home_zone = "home:charlie"
|
||||
|
||||
# Give player a lamp
|
||||
lamp = Thing(name="lamp", description="A brass lamp")
|
||||
lamp.move_to(player)
|
||||
|
||||
# Furnish it
|
||||
await cmd_furnish(player, "lamp")
|
||||
|
||||
# Check TOML file
|
||||
zone_file = tmp_path / "charlie.toml"
|
||||
assert zone_file.exists()
|
||||
|
||||
with open(zone_file, "rb") as f:
|
||||
data = tomllib.load(f)
|
||||
|
||||
# Should have furniture entry
|
||||
furniture = data.get("furniture", [])
|
||||
assert len(furniture) == 1
|
||||
assert furniture[0]["template"] == "lamp"
|
||||
assert furniture[0]["x"] == 4
|
||||
assert furniture[0]["y"] == 4
|
||||
Loading…
Reference in a new issue