Wire target resolution into thing commands
Replace local exact-match helpers with targeting module calls for prefix matching and ordinal disambiguation. Works in get, drop, and container extraction (get X from Y).
This commit is contained in:
parent
5a0c1b2151
commit
aca9864881
2 changed files with 141 additions and 32 deletions
|
|
@ -3,34 +3,25 @@
|
|||
from mudlib.commands import CommandDefinition, register
|
||||
from mudlib.container import Container
|
||||
from mudlib.player import Player
|
||||
from mudlib.targeting import find_in_inventory, find_thing_on_tile
|
||||
from mudlib.thing import Thing
|
||||
from mudlib.zone import Zone
|
||||
|
||||
|
||||
def _find_thing_at(name: str, zone: Zone, x: int, y: int) -> Thing | None:
|
||||
"""Find a thing on the ground matching name or alias."""
|
||||
name_lower = name.lower()
|
||||
for obj in zone.contents_at(x, y):
|
||||
if not isinstance(obj, Thing):
|
||||
continue
|
||||
if obj.name.lower() == name_lower:
|
||||
return obj
|
||||
if name_lower in (a.lower() for a in obj.aliases):
|
||||
return obj
|
||||
return None
|
||||
"""Find a thing on the ground matching name or alias.
|
||||
|
||||
Deprecated: Use find_thing_on_tile from mudlib.targeting instead.
|
||||
"""
|
||||
return find_thing_on_tile(name, zone, x, y)
|
||||
|
||||
|
||||
def _find_thing_in_inventory(name: str, player: Player) -> Thing | None:
|
||||
"""Find a thing in the player's inventory matching name or alias."""
|
||||
name_lower = name.lower()
|
||||
for obj in player.contents:
|
||||
if not isinstance(obj, Thing):
|
||||
continue
|
||||
if obj.name.lower() == name_lower:
|
||||
return obj
|
||||
if name_lower in (a.lower() for a in obj.aliases):
|
||||
return obj
|
||||
return None
|
||||
"""Find a thing in the player's inventory matching name or alias.
|
||||
|
||||
Deprecated: Use find_in_inventory from mudlib.targeting instead.
|
||||
"""
|
||||
return find_in_inventory(name, player)
|
||||
|
||||
|
||||
def _format_thing_name(thing: Thing) -> str:
|
||||
|
|
@ -113,18 +104,11 @@ async def _handle_take_from(player: Player, args: str) -> None:
|
|||
await player.send(f"The {container_obj.name} is closed.\r\n")
|
||||
return
|
||||
|
||||
# Find thing in container
|
||||
thing = None
|
||||
thing_name_lower = thing_name.lower()
|
||||
for obj in container_obj.contents:
|
||||
if not isinstance(obj, Thing):
|
||||
continue
|
||||
if obj.name.lower() == thing_name_lower:
|
||||
thing = obj
|
||||
break
|
||||
if thing_name_lower in (a.lower() for a in obj.aliases):
|
||||
thing = obj
|
||||
break
|
||||
# Find thing in container using targeting (supports prefix and ordinals)
|
||||
from mudlib.targeting import resolve_target
|
||||
|
||||
container_things = [obj for obj in container_obj.contents if isinstance(obj, Thing)]
|
||||
thing = resolve_target(thing_name, container_things)
|
||||
|
||||
if thing is None:
|
||||
await player.send(f"The {container_obj.name} doesn't contain that.\r\n")
|
||||
|
|
|
|||
125
tests/test_things_targeting.py
Normal file
125
tests/test_things_targeting.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
"""Tests for targeting integration in thing commands."""
|
||||
|
||||
import pytest
|
||||
|
||||
from mudlib.commands.things import cmd_drop, cmd_get
|
||||
from mudlib.container import Container
|
||||
from mudlib.thing import Thing
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_prefix_match(player, test_zone):
|
||||
"""Test that 'get gob' prefix matches 'goblin figurine' on ground."""
|
||||
figurine = Thing(name="goblin figurine", x=0, y=0)
|
||||
figurine.location = test_zone
|
||||
test_zone._contents.append(figurine)
|
||||
|
||||
await cmd_get(player, "gob")
|
||||
|
||||
# Check thing moved to player inventory
|
||||
assert figurine in player.contents
|
||||
assert figurine not in test_zone._contents
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_ordinal_from_ground(player, test_zone):
|
||||
"""Test that 'get 2.sword' gets second sword from ground."""
|
||||
sword1 = Thing(name="sword", x=0, y=0)
|
||||
sword1.location = test_zone
|
||||
test_zone._contents.append(sword1)
|
||||
|
||||
sword2 = Thing(name="sword", x=0, y=0)
|
||||
sword2.location = test_zone
|
||||
test_zone._contents.append(sword2)
|
||||
|
||||
await cmd_get(player, "2.sword")
|
||||
|
||||
# Check second sword moved to player inventory
|
||||
assert sword2 in player.contents
|
||||
assert sword1 not in player.contents
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_drop_prefix_match(player, test_zone):
|
||||
"""Test that 'drop sw' prefix matches sword in inventory."""
|
||||
sword = Thing(name="sword", x=0, y=0)
|
||||
sword.move_to(player)
|
||||
|
||||
await cmd_drop(player, "sw")
|
||||
|
||||
# Check sword moved to ground
|
||||
assert sword not in player.contents
|
||||
assert sword in test_zone._contents
|
||||
assert sword.x == 0
|
||||
assert sword.y == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_ordinal_from_container(player, test_zone):
|
||||
"""Test that 'get 2.sword from chest' gets second sword from container."""
|
||||
chest = Container(name="chest", x=0, y=0, capacity=100)
|
||||
chest.location = test_zone
|
||||
chest.closed = False
|
||||
test_zone._contents.append(chest)
|
||||
|
||||
sword1 = Thing(name="sword", x=0, y=0)
|
||||
sword1.move_to(chest)
|
||||
|
||||
sword2 = Thing(name="sword", x=0, y=0)
|
||||
sword2.move_to(chest)
|
||||
|
||||
await cmd_get(player, "2.sword from chest")
|
||||
|
||||
# Check second sword moved to player inventory
|
||||
assert sword2 in player.contents
|
||||
assert sword1 not in player.contents
|
||||
assert sword1 in chest.contents
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_no_match(player, test_zone):
|
||||
"""Test that get fails gracefully when no match is found."""
|
||||
await cmd_get(player, "nonexistent")
|
||||
|
||||
# Check for error message
|
||||
written = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "don't see" in written.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_drop_no_match(player, test_zone):
|
||||
"""Test that drop fails gracefully when no match is found."""
|
||||
await cmd_drop(player, "nonexistent")
|
||||
|
||||
# Check for error message
|
||||
written = "".join(call.args[0] for call in player.writer.write.call_args_list)
|
||||
assert "not carrying" in written.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_alias_match(player, test_zone):
|
||||
"""Test that aliases work with targeting."""
|
||||
figurine = Thing(name="goblin figurine", x=0, y=0, aliases=["fig", "goblin"])
|
||||
figurine.location = test_zone
|
||||
test_zone._contents.append(figurine)
|
||||
|
||||
await cmd_get(player, "fig")
|
||||
|
||||
assert figurine in player.contents
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_drop_ordinal(player, test_zone):
|
||||
"""Test that drop works with ordinal disambiguation."""
|
||||
sword1 = Thing(name="sword", x=0, y=0)
|
||||
sword1.move_to(player)
|
||||
|
||||
sword2 = Thing(name="sword", x=0, y=0)
|
||||
sword2.move_to(player)
|
||||
|
||||
await cmd_drop(player, "2.sword")
|
||||
|
||||
# Check second sword moved to ground
|
||||
assert sword2 not in player.contents
|
||||
assert sword1 in player.contents
|
||||
assert sword2 in test_zone._contents
|
||||
Loading…
Reference in a new issue