Add describe command for home zone descriptions
Allows players to set custom descriptions for their home zones. Only works in the player's own home zone. Saves to TOML file.
This commit is contained in:
parent
5d14011684
commit
5b6c808050
3 changed files with 204 additions and 0 deletions
50
src/mudlib/commands/describe.py
Normal file
50
src/mudlib/commands/describe.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
"""Describe command — set home zone description."""
|
||||||
|
|
||||||
|
from mudlib.commands import CommandDefinition, register
|
||||||
|
from mudlib.housing import save_home_zone
|
||||||
|
from mudlib.player import Player
|
||||||
|
from mudlib.zone import Zone
|
||||||
|
|
||||||
|
|
||||||
|
async def cmd_describe(player: Player, args: str) -> None:
|
||||||
|
"""Set the description for your home zone.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
describe <text> — set your home zone description
|
||||||
|
describe — show current description
|
||||||
|
"""
|
||||||
|
zone = player.location
|
||||||
|
|
||||||
|
# Must be in a zone
|
||||||
|
if not isinstance(zone, Zone):
|
||||||
|
await player.send("You aren't anywhere.\r\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Must be in own home zone
|
||||||
|
if zone.name != player.home_zone:
|
||||||
|
await player.send("You can only describe your own home zone.\r\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
# No args — show current description
|
||||||
|
if not args.strip():
|
||||||
|
await player.send(f"Current description: {zone.description}\r\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set new description
|
||||||
|
description = args.strip()
|
||||||
|
if len(description) > 500:
|
||||||
|
await player.send("Description too long (max 500 characters).\r\n")
|
||||||
|
return
|
||||||
|
zone.description = description
|
||||||
|
save_home_zone(player.name, zone)
|
||||||
|
|
||||||
|
await player.send(f"Home zone description set to: {zone.description}\r\n")
|
||||||
|
|
||||||
|
|
||||||
|
register(
|
||||||
|
CommandDefinition(
|
||||||
|
"describe",
|
||||||
|
cmd_describe,
|
||||||
|
help="Set your home zone description.",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
@ -16,6 +16,8 @@ from telnetlib3.server_shell import readline2
|
||||||
import mudlib.combat.commands
|
import mudlib.combat.commands
|
||||||
import mudlib.commands
|
import mudlib.commands
|
||||||
import mudlib.commands.containers
|
import mudlib.commands.containers
|
||||||
|
import mudlib.commands.crafting
|
||||||
|
import mudlib.commands.describe
|
||||||
import mudlib.commands.edit
|
import mudlib.commands.edit
|
||||||
import mudlib.commands.examine
|
import mudlib.commands.examine
|
||||||
import mudlib.commands.fly
|
import mudlib.commands.fly
|
||||||
|
|
@ -31,6 +33,7 @@ import mudlib.commands.reload
|
||||||
import mudlib.commands.snapneck
|
import mudlib.commands.snapneck
|
||||||
import mudlib.commands.spawn
|
import mudlib.commands.spawn
|
||||||
import mudlib.commands.talk
|
import mudlib.commands.talk
|
||||||
|
import mudlib.commands.terrain
|
||||||
import mudlib.commands.things
|
import mudlib.commands.things
|
||||||
import mudlib.commands.use
|
import mudlib.commands.use
|
||||||
from mudlib.caps import parse_mtts
|
from mudlib.caps import parse_mtts
|
||||||
|
|
|
||||||
151
tests/test_command_describe.py
Normal file
151
tests/test_command_describe.py
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
"""Tests for the describe command."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from mudlib.commands.describe import cmd_describe
|
||||||
|
from mudlib.housing import init_housing
|
||||||
|
from mudlib.player import Player
|
||||||
|
from mudlib.zone import Zone
|
||||||
|
from mudlib.zones import register_zone, zone_registry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _clean_registries():
|
||||||
|
saved = dict(zone_registry)
|
||||||
|
zone_registry.clear()
|
||||||
|
yield
|
||||||
|
zone_registry.clear()
|
||||||
|
zone_registry.update(saved)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def _init_housing(tmp_path):
|
||||||
|
from mudlib.store import create_account, init_db
|
||||||
|
|
||||||
|
init_housing(tmp_path / "player_zones")
|
||||||
|
init_db(tmp_path / "test.db")
|
||||||
|
# Create accounts for test players
|
||||||
|
for name in ["alice", "bob", "charlie"]:
|
||||||
|
create_account(name, "testpass")
|
||||||
|
|
||||||
|
|
||||||
|
def _make_home_zone(player_name="alice"):
|
||||||
|
name = f"home:{player_name}"
|
||||||
|
terrain = []
|
||||||
|
for y in range(9):
|
||||||
|
row = []
|
||||||
|
for x in range(9):
|
||||||
|
if x == 0 or x == 8 or y == 0 or y == 8:
|
||||||
|
row.append("#")
|
||||||
|
else:
|
||||||
|
row.append(".")
|
||||||
|
terrain.append(row)
|
||||||
|
zone = Zone(
|
||||||
|
name=name,
|
||||||
|
description=f"{player_name}'s home",
|
||||||
|
width=9,
|
||||||
|
height=9,
|
||||||
|
terrain=terrain,
|
||||||
|
toroidal=False,
|
||||||
|
impassable={"#", "^", "~"},
|
||||||
|
spawn_x=4,
|
||||||
|
spawn_y=4,
|
||||||
|
safe=True,
|
||||||
|
)
|
||||||
|
register_zone(name, zone)
|
||||||
|
return zone
|
||||||
|
|
||||||
|
|
||||||
|
def _make_player(name="alice", zone=None, x=4, y=4):
|
||||||
|
mock_writer = MagicMock()
|
||||||
|
mock_writer.write = MagicMock()
|
||||||
|
mock_writer.drain = AsyncMock()
|
||||||
|
p = Player(name=name, location=zone, x=x, y=y, writer=mock_writer)
|
||||||
|
p.home_zone = f"home:{name}"
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_describe_sets_description(_init_housing):
|
||||||
|
"""describe <text> sets zone.description."""
|
||||||
|
home = _make_home_zone("alice")
|
||||||
|
player = _make_player("alice", zone=home)
|
||||||
|
|
||||||
|
await cmd_describe(player, "a cozy cottage by the sea")
|
||||||
|
|
||||||
|
assert home.description == "a cozy cottage by the sea"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_describe_not_in_home_zone(_init_housing):
|
||||||
|
"""describe fails if not in player's own home zone."""
|
||||||
|
_make_home_zone("alice")
|
||||||
|
other_zone = Zone(
|
||||||
|
name="overworld",
|
||||||
|
description="The world",
|
||||||
|
width=20,
|
||||||
|
height=20,
|
||||||
|
terrain=[["." for _ in range(20)] for _ in range(20)],
|
||||||
|
toroidal=True,
|
||||||
|
)
|
||||||
|
register_zone("overworld", other_zone)
|
||||||
|
player = _make_player("alice", zone=other_zone)
|
||||||
|
|
||||||
|
await cmd_describe(player, "trying to describe the overworld")
|
||||||
|
|
||||||
|
# Should show error message
|
||||||
|
assert player.writer.write.called
|
||||||
|
output = "".join(c[0][0] for c in player.writer.write.call_args_list)
|
||||||
|
assert "only" in output.lower() and "home" in output.lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_describe_no_args_shows_current(_init_housing):
|
||||||
|
"""describe with no args shows current description."""
|
||||||
|
home = _make_home_zone("alice")
|
||||||
|
home.description = "a warm and welcoming place"
|
||||||
|
player = _make_player("alice", zone=home)
|
||||||
|
|
||||||
|
await cmd_describe(player, "")
|
||||||
|
|
||||||
|
assert player.writer.write.called
|
||||||
|
output = "".join(c[0][0] for c in player.writer.write.call_args_list)
|
||||||
|
assert "a warm and welcoming place" in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_describe_saves_zone(_init_housing):
|
||||||
|
"""describe saves the zone to disk."""
|
||||||
|
import tomllib
|
||||||
|
|
||||||
|
from mudlib.housing import _zone_path
|
||||||
|
|
||||||
|
home = _make_home_zone("alice")
|
||||||
|
player = _make_player("alice", zone=home)
|
||||||
|
|
||||||
|
await cmd_describe(player, "a newly described home")
|
||||||
|
|
||||||
|
# Check TOML file was saved
|
||||||
|
zone_path = _zone_path("alice")
|
||||||
|
assert zone_path.exists()
|
||||||
|
|
||||||
|
with open(zone_path, "rb") as f:
|
||||||
|
data = tomllib.load(f)
|
||||||
|
|
||||||
|
assert data["description"] == "a newly described home"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_describe_multiword(_init_housing):
|
||||||
|
"""describe handles multiword descriptions."""
|
||||||
|
home = _make_home_zone("bob")
|
||||||
|
player = _make_player("bob", zone=home)
|
||||||
|
|
||||||
|
await cmd_describe(player, "a cozy cottage with warm lighting")
|
||||||
|
|
||||||
|
assert home.description == "a cozy cottage with warm lighting"
|
||||||
|
assert player.writer.write.called
|
||||||
|
output = "".join(c[0][0] for c in player.writer.write.call_args_list)
|
||||||
|
assert "description" in output.lower() or "set" in output.lower()
|
||||||
Loading…
Reference in a new issue