mud/src/mudlib/commands/crafting.py
Jared Miller 11636e073a
Add craft and recipes commands
Implements craft command to create items from recipes by consuming
ingredients from player inventory. Recipes command lists available
recipes or shows details for a specific recipe. Registers commands
and loads recipes at server startup.
2026-02-14 17:58:59 -05:00

130 lines
3.9 KiB
Python

"""Crafting commands."""
from collections import Counter
from mudlib.commands import CommandDefinition, register
from mudlib.crafting import recipes
from mudlib.player import Player
from mudlib.things import spawn_thing, thing_templates
async def cmd_craft(player: Player, args: str) -> None:
"""Craft an item from a recipe.
Args:
player: The player crafting
args: Recipe name
"""
if not args:
await player.send("Usage: craft <recipe>\r\n")
return
recipe_name = args.strip().lower()
# Find recipe by name (case-insensitive, prefix match)
matching_recipes = [
name for name in recipes if name.lower().startswith(recipe_name)
]
if not matching_recipes:
await player.send(f"Unknown recipe: {args}\r\n")
return
if len(matching_recipes) > 1:
await player.send(
f"Ambiguous recipe name. Matches: {', '.join(matching_recipes)}\r\n"
)
return
recipe = recipes[matching_recipes[0]]
# Count required ingredients
required = Counter(ingredient.lower() for ingredient in recipe.ingredients)
# Count available ingredients in inventory
inventory = player.contents
available = Counter(obj.name.lower() for obj in inventory)
# Check if player has all ingredients
missing = []
for ingredient, count in required.items():
if available[ingredient] < count:
needed = count - available[ingredient]
missing.append(f"{ingredient} (need {needed} more)")
if missing:
await player.send(f"Missing ingredients: {', '.join(missing)}\r\n")
return
# Check if result template exists
if recipe.result not in thing_templates:
await player.send(
f"Error: Recipe result '{recipe.result}' template not found.\r\n"
)
return
# Consume ingredients
consumed = Counter()
for obj in list(inventory):
obj_name = obj.name.lower()
if obj_name in required and consumed[obj_name] < required[obj_name]:
obj.move_to(None) # Remove from world
consumed[obj_name] += 1
# Create result item
result_template = thing_templates[recipe.result]
spawn_thing(result_template, player)
await player.send(f"You craft a {result_template.name}.\r\n")
async def cmd_recipes(player: Player, args: str) -> None:
"""List available recipes.
Args:
player: The player viewing recipes
args: Optional recipe name for details
"""
if not recipes:
await player.send("No recipes available.\r\n")
return
if args:
# Show details for specific recipe
recipe_name = args.strip().lower()
matching = [name for name in recipes if name.lower().startswith(recipe_name)]
if not matching:
await player.send(f"Unknown recipe: {args}\r\n")
return
if len(matching) > 1:
await player.send(
f"Ambiguous recipe name. Matches: {', '.join(matching)}\r\n"
)
return
recipe = recipes[matching[0]]
ingredient_counts = Counter(recipe.ingredients)
ingredient_list = ", ".join(
f"{count}x {name}" if count > 1 else name
for name, count in sorted(ingredient_counts.items())
)
await player.send(
f"Recipe: {recipe.name}\r\n"
f"{recipe.description}\r\n"
f"Ingredients: {ingredient_list}\r\n"
f"Result: {recipe.result}\r\n"
)
else:
# List all recipes
await player.send("Available recipes:\r\n")
for name, recipe in sorted(recipes.items()):
await player.send(f" {name}: {recipe.description}\r\n")
register(CommandDefinition("craft", cmd_craft, help="Craft items from recipes."))
register(
CommandDefinition("recipes", cmd_recipes, help="List available crafting recipes.")
)