Wire target resolution into look command
This commit is contained in:
parent
86797c3a82
commit
a98f340e5a
2 changed files with 253 additions and 4 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
"""Look command for viewing the world."""
|
"""Look command for viewing the world."""
|
||||||
|
|
||||||
from mudlib.commands import CommandDefinition, register
|
from mudlib.commands import CommandDefinition, register
|
||||||
from mudlib.commands.examine import cmd_examine
|
|
||||||
from mudlib.commands.things import _format_thing_name
|
from mudlib.commands.things import _format_thing_name
|
||||||
from mudlib.effects import get_effects_at
|
from mudlib.effects import get_effects_at
|
||||||
from mudlib.entity import Entity
|
from mudlib.entity import Entity
|
||||||
|
|
@ -27,11 +26,54 @@ async def cmd_look(player: Player, args: str) -> None:
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
player: The player executing the command
|
player: The player executing the command
|
||||||
args: Command arguments (if provided, route to examine)
|
args: Command arguments (if provided, use targeting to resolve)
|
||||||
"""
|
"""
|
||||||
# If args provided, route to examine
|
# If args provided, use targeting to resolve
|
||||||
if args.strip():
|
if args.strip():
|
||||||
await cmd_examine(player, args)
|
from mudlib.targeting import (
|
||||||
|
find_entity_on_tile,
|
||||||
|
find_in_inventory,
|
||||||
|
find_thing_on_tile,
|
||||||
|
)
|
||||||
|
|
||||||
|
target_name = args.strip()
|
||||||
|
|
||||||
|
# First try to find an entity on the tile
|
||||||
|
entity = find_entity_on_tile(target_name, player)
|
||||||
|
if entity:
|
||||||
|
# Show entity info (name and posture)
|
||||||
|
if hasattr(entity, "description") and entity.description:
|
||||||
|
await player.send(f"{entity.description}\r\n")
|
||||||
|
else:
|
||||||
|
await player.send(f"{entity.name} is {entity.posture}.\r\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Then try to find a thing on the ground
|
||||||
|
zone = player.location
|
||||||
|
if zone is not None and isinstance(zone, Zone):
|
||||||
|
thing = find_thing_on_tile(target_name, zone, player.x, player.y)
|
||||||
|
if thing:
|
||||||
|
# Show thing description
|
||||||
|
desc = getattr(thing, "description", "")
|
||||||
|
if desc:
|
||||||
|
await player.send(f"{desc}\r\n")
|
||||||
|
else:
|
||||||
|
await player.send("You see nothing special.\r\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Finally try inventory
|
||||||
|
thing = find_in_inventory(target_name, player)
|
||||||
|
if thing:
|
||||||
|
# Show thing description
|
||||||
|
desc = getattr(thing, "description", "")
|
||||||
|
if desc:
|
||||||
|
await player.send(f"{desc}\r\n")
|
||||||
|
else:
|
||||||
|
await player.send("You see nothing special.\r\n")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Nothing found
|
||||||
|
await player.send("You don't see that here.\r\n")
|
||||||
return
|
return
|
||||||
|
|
||||||
zone = player.location
|
zone = player.location
|
||||||
|
|
|
||||||
207
tests/test_look_targeting.py
Normal file
207
tests/test_look_targeting.py
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
"""Tests for look command target resolution."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from mudlib.commands.look import cmd_look
|
||||||
|
from mudlib.entity import Mob
|
||||||
|
from mudlib.player import Player
|
||||||
|
from mudlib.thing import Thing
|
||||||
|
from mudlib.zone import Zone
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def zone():
|
||||||
|
"""Create a test zone."""
|
||||||
|
terrain = [["." for _ in range(100)] for _ in range(100)]
|
||||||
|
return Zone(
|
||||||
|
name="test",
|
||||||
|
description="Test Zone",
|
||||||
|
width=100,
|
||||||
|
height=100,
|
||||||
|
toroidal=False,
|
||||||
|
terrain=terrain,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_writer():
|
||||||
|
"""Create a mock writer that captures output."""
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
class MockWriter:
|
||||||
|
def __init__(self):
|
||||||
|
self.output = []
|
||||||
|
# Mock telnet options
|
||||||
|
self.local_option = MagicMock()
|
||||||
|
self.remote_option = MagicMock()
|
||||||
|
self.local_option.enabled = MagicMock(return_value=False)
|
||||||
|
self.remote_option.enabled = MagicMock(return_value=False)
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
self.output.append(data)
|
||||||
|
|
||||||
|
async def drain(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_output(self):
|
||||||
|
return "".join(self.output)
|
||||||
|
|
||||||
|
return MockWriter()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def player(zone, mock_writer):
|
||||||
|
"""Create a test player."""
|
||||||
|
p = Player(
|
||||||
|
name="TestPlayer",
|
||||||
|
location=zone,
|
||||||
|
x=50,
|
||||||
|
y=50,
|
||||||
|
writer=mock_writer,
|
||||||
|
)
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_look_finds_mob_by_exact_name(player, zone, mock_writer):
|
||||||
|
"""look goblin finds mob with exact name."""
|
||||||
|
_mob = Mob(name="goblin", location=zone, x=50, y=50)
|
||||||
|
|
||||||
|
await cmd_look(player, "goblin")
|
||||||
|
|
||||||
|
output = mock_writer.get_output()
|
||||||
|
assert "goblin" in output.lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_look_finds_mob_by_prefix(player, zone, mock_writer):
|
||||||
|
"""look gob prefix matches goblin."""
|
||||||
|
_mob = Mob(name="goblin", location=zone, x=50, y=50)
|
||||||
|
|
||||||
|
await cmd_look(player, "gob")
|
||||||
|
|
||||||
|
output = mock_writer.get_output()
|
||||||
|
assert "goblin" in output.lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_look_finds_second_mob_with_ordinal(player, zone, mock_writer):
|
||||||
|
"""look 2.goblin finds second goblin."""
|
||||||
|
_mob1 = Mob(name="goblin", location=zone, x=50, y=50)
|
||||||
|
_mob2 = Mob(name="goblin", location=zone, x=50, y=50)
|
||||||
|
|
||||||
|
await cmd_look(player, "2.goblin")
|
||||||
|
|
||||||
|
output = mock_writer.get_output()
|
||||||
|
# Should find the second goblin
|
||||||
|
assert "goblin" in output.lower()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_look_finds_thing_on_ground(player, zone, mock_writer):
|
||||||
|
"""look sword finds thing on ground and shows description."""
|
||||||
|
_sword = Thing(
|
||||||
|
name="sword",
|
||||||
|
description="A sharp blade.",
|
||||||
|
location=zone,
|
||||||
|
x=50,
|
||||||
|
y=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
await cmd_look(player, "sword")
|
||||||
|
|
||||||
|
output = mock_writer.get_output()
|
||||||
|
assert "A sharp blade." in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_look_shows_error_for_nonexistent(player, zone, mock_writer):
|
||||||
|
"""look nonexistent shows 'You don't see that here.'"""
|
||||||
|
await cmd_look(player, "nonexistent")
|
||||||
|
|
||||||
|
output = mock_writer.get_output()
|
||||||
|
assert "You don't see that here." in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_look_prioritizes_entity_over_thing(player, zone, mock_writer):
|
||||||
|
"""look target finds entity before thing with same name."""
|
||||||
|
_mob = Mob(name="target", location=zone, x=50, y=50)
|
||||||
|
_thing = Thing(
|
||||||
|
name="target",
|
||||||
|
description="A thing.",
|
||||||
|
location=zone,
|
||||||
|
x=50,
|
||||||
|
y=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
await cmd_look(player, "target")
|
||||||
|
|
||||||
|
output = mock_writer.get_output()
|
||||||
|
# Should show entity info (name/posture), not thing description
|
||||||
|
assert "target" in output.lower()
|
||||||
|
# Should not show thing description
|
||||||
|
assert "A thing." not in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_look_skips_dead_mobs(player, zone, mock_writer):
|
||||||
|
"""look goblin skips dead mobs."""
|
||||||
|
_mob = Mob(name="goblin", location=zone, x=50, y=50)
|
||||||
|
_mob.alive = False
|
||||||
|
|
||||||
|
await cmd_look(player, "goblin")
|
||||||
|
|
||||||
|
output = mock_writer.get_output()
|
||||||
|
assert "You don't see that here." in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_look_skips_self(player, zone, mock_writer):
|
||||||
|
"""look TestPlayer doesn't target the player themselves."""
|
||||||
|
await cmd_look(player, "TestPlayer")
|
||||||
|
|
||||||
|
output = mock_writer.get_output()
|
||||||
|
assert "You don't see that here." in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_look_finds_thing_in_inventory(player, zone, mock_writer):
|
||||||
|
"""look sword finds thing in inventory when not on ground."""
|
||||||
|
_sword = Thing(
|
||||||
|
name="sword",
|
||||||
|
description="A sharp blade.",
|
||||||
|
location=player,
|
||||||
|
)
|
||||||
|
|
||||||
|
await cmd_look(player, "sword")
|
||||||
|
|
||||||
|
output = mock_writer.get_output()
|
||||||
|
assert "A sharp blade." in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_look_finds_second_thing_with_ordinal(player, zone, mock_writer):
|
||||||
|
"""look 2.sword finds second sword on ground."""
|
||||||
|
_sword1 = Thing(
|
||||||
|
name="sword",
|
||||||
|
description="A rusty blade.",
|
||||||
|
location=zone,
|
||||||
|
x=50,
|
||||||
|
y=50,
|
||||||
|
)
|
||||||
|
_sword2 = Thing(
|
||||||
|
name="sword",
|
||||||
|
description="A sharp blade.",
|
||||||
|
location=zone,
|
||||||
|
x=50,
|
||||||
|
y=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
await cmd_look(player, "2.sword")
|
||||||
|
|
||||||
|
output = mock_writer.get_output()
|
||||||
|
# Should show the second sword's description
|
||||||
|
assert "A sharp blade." in output
|
||||||
|
# Should not show the first sword's description
|
||||||
|
assert "A rusty blade." not in output
|
||||||
Loading…
Reference in a new issue