- Update _find_container to use targeting module (prefix + ordinal) - Update cmd_put to use find_in_inventory directly - Add 'get all from <container>' support with portable item filtering - Add comprehensive tests for all container grammar features
176 lines
5.7 KiB
Python
176 lines
5.7 KiB
Python
"""Get, drop, and inventory commands for items."""
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
Deprecated: Use find_in_inventory from mudlib.targeting instead.
|
|
"""
|
|
return find_in_inventory(name, player)
|
|
|
|
|
|
def _format_thing_name(thing: Thing) -> str:
|
|
"""Format a thing's name with container state if applicable."""
|
|
if not isinstance(thing, Container):
|
|
return thing.name
|
|
|
|
if thing.closed:
|
|
return f"{thing.name} (closed)"
|
|
|
|
# Container is open
|
|
contents = [obj for obj in thing.contents if isinstance(obj, Thing)]
|
|
if not contents:
|
|
return f"{thing.name} (open, empty)"
|
|
|
|
names = ", ".join(item.name for item in contents)
|
|
return f"{thing.name} (open, containing: {names})"
|
|
|
|
|
|
async def cmd_get(player: Player, args: str) -> None:
|
|
"""Pick up an item from the ground or take from a container."""
|
|
if not args.strip():
|
|
await player.send("Get what?\r\n")
|
|
return
|
|
|
|
# Check if this is "take/get X from Y" syntax
|
|
# Match " from " or "from " (at start) or " from" (at end)
|
|
args_lower = args.lower()
|
|
if (
|
|
" from " in args_lower
|
|
or args_lower.startswith("from ")
|
|
or args_lower.endswith(" from")
|
|
or args_lower == "from"
|
|
):
|
|
await _handle_take_from(player, args)
|
|
return
|
|
|
|
zone = player.location
|
|
if zone is None or not isinstance(zone, Zone):
|
|
await player.send("You are nowhere.\r\n")
|
|
return
|
|
|
|
thing = _find_thing_at(args.strip(), zone, player.x, player.y)
|
|
if thing is None:
|
|
await player.send(f"You don't see '{args.strip()}' here.\r\n")
|
|
return
|
|
|
|
if not player.can_accept(thing):
|
|
await player.send("You can't pick that up.\r\n")
|
|
return
|
|
|
|
thing.move_to(player)
|
|
await player.send(f"You pick up {thing.name}.\r\n")
|
|
|
|
|
|
async def _handle_take_from(player: Player, args: str) -> None:
|
|
"""Handle 'take/get X from Y' to remove items from containers."""
|
|
# Parse "thing from container"
|
|
parts = args.strip().split(" from ", 1)
|
|
thing_name = parts[0].strip() if len(parts) > 0 else ""
|
|
container_name = parts[1].strip() if len(parts) > 1 else ""
|
|
|
|
if not thing_name or not container_name:
|
|
await player.send("Take what from where? (take <item> from <container>)\r\n")
|
|
return
|
|
|
|
# Find container (on ground or in inventory)
|
|
from mudlib.commands.containers import _find_container
|
|
|
|
container_obj = _find_container(container_name, player)
|
|
if container_obj is None:
|
|
await player.send("You don't see that here.\r\n")
|
|
return
|
|
|
|
if not isinstance(container_obj, Container):
|
|
await player.send("That's not a container.\r\n")
|
|
return
|
|
|
|
if container_obj.closed:
|
|
await player.send(f"The {container_obj.name} is closed.\r\n")
|
|
return
|
|
|
|
# Handle "get all from container"
|
|
if thing_name.lower() == "all":
|
|
container_things = [
|
|
obj for obj in container_obj.contents if isinstance(obj, Thing)
|
|
]
|
|
portable_things = [t for t in container_things if player.can_accept(t)]
|
|
|
|
if not portable_things:
|
|
msg = f"There's nothing in the {container_obj.name} to take.\r\n"
|
|
await player.send(msg)
|
|
return
|
|
|
|
for thing in portable_things:
|
|
thing.move_to(player)
|
|
msg = f"You take the {thing.name} from the {container_obj.name}.\r\n"
|
|
await player.send(msg)
|
|
return
|
|
|
|
# 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")
|
|
return
|
|
|
|
thing.move_to(player)
|
|
await player.send(f"You take the {thing.name} from the {container_obj.name}.\r\n")
|
|
|
|
|
|
async def cmd_drop(player: Player, args: str) -> None:
|
|
"""Drop an item from inventory onto the ground."""
|
|
if not args.strip():
|
|
await player.send("Drop what?\r\n")
|
|
return
|
|
|
|
zone = player.location
|
|
if zone is None or not isinstance(zone, Zone):
|
|
await player.send("You can't drop things here.\r\n")
|
|
return
|
|
|
|
thing = _find_thing_in_inventory(args.strip(), player)
|
|
if thing is None:
|
|
await player.send(f"You're not carrying '{args.strip()}'.\r\n")
|
|
return
|
|
|
|
thing.move_to(zone, x=player.x, y=player.y)
|
|
await player.send(f"You drop {thing.name}.\r\n")
|
|
|
|
|
|
async def cmd_inventory(player: Player, args: str) -> None:
|
|
"""List items in the player's inventory."""
|
|
things = [obj for obj in player.contents if isinstance(obj, Thing)]
|
|
|
|
if not things:
|
|
await player.send("You aren't carrying anything.\r\n")
|
|
return
|
|
|
|
lines = ["You are carrying:\r\n"]
|
|
for thing in things:
|
|
lines.append(f" {_format_thing_name(thing)}\r\n")
|
|
|
|
await player.send("".join(lines))
|
|
|
|
|
|
register(CommandDefinition("get", cmd_get, aliases=["take"]))
|
|
register(CommandDefinition("drop", cmd_drop))
|
|
register(CommandDefinition("inventory", cmd_inventory, aliases=["i"]))
|