evennia.contrib.crafting.crafting

Crafting - Griatch 2020

This is a general crafting engine. The basic functionality of crafting is to combine any number of of items or tools in a ‘recipe’ to produce a new result.

item + item + item + tool + tool -> recipe -> new result

This is useful not only for traditional crafting but the engine is flexible enough to also be useful for puzzles or similar.

Installation

  • Add the CmdCraft Command from this module to your default cmdset. This allows for crafting from in-game using a simple syntax.

  • Create a new module and add it to a new list in your settings file (server/conf/settings.py) named CRAFT_RECIPES_MODULES, such as CRAFT_RECIPE_MODULES = [“world.recipes_weapons”].

  • In the new module(s), create one or more classes, each a child of CraftingRecipe from this module. Each such class must have a unique .name property. It also defines what inputs are required and what is created using this recipe.

  • Objects to use for crafting should (by default) be tagged with tags using the tag-category crafting_material or crafting_tool. The name of the object doesn’t matter, only its tag.

Crafting in game

The default craft command handles all crafting needs.

> craft spiked club from club, nails

Here, spiked club specifies the recipe while club and nails are objects the crafter must have in their inventory. These will be consumed during crafting (by default only if crafting was successful).

A recipe can also require tools (like the hammer above). These must be either in inventory or be in the current location. Tools are not consumed during the crafting process.

> craft wooden doll from wood with knife

Crafting in code

In code, you should use the helper function craft from this module. This specifies the name of the recipe to use and expects all suitable ingredients/tools as arguments (consumables and tools should be added together, tools will be identified before consumables).

from evennia.contrib.crafting import crafting

spiked_club = crafting.craft(crafter, "spiked club", club, nails)

The result is always a list with zero or more objects. A fail leads to an empty list. The crafter should already have been notified of any error in this case (this should be handle by the recipe itself).

Recipes

A recipe is a class that works like an input/output blackbox: you initialize it with consumables (and/or tools) if they match the recipe, a new result is spit out. Consumables are consumed in the process while tools are not.

This module contains a base class for making new ingredient types (CraftingRecipeBase) and an implementation of the most common form of crafting (CraftingRecipe) using objects and prototypes.

Recipes are put in one or more modules added as a list to the CRAFT_RECIPE_MODULES setting, for example:

CRAFT_RECIPE_MODULES = ['world.recipes_weapons', 'world.recipes_potions']

Below is an example of a crafting recipe and how craft calls it under the hood. See the CraftingRecipe class for details of which properties and methods are available to override - the craft behavior can be modified substantially this way.

from evennia.contrib.crafting.crafting import CraftingRecipe

class PigIronRecipe(CraftingRecipe):
    # Pig iron is a high-carbon result of melting iron in a blast furnace.

    name = "pig iron"  # this is what crafting.craft and CmdCraft uses
    tool_tags = ["blast furnace"]
    consumable_tags = ["iron ore", "coal", "coal"]
    output_prototypes = [
        {"key": "Pig Iron ingot",
         "desc": "An ingot of crude pig iron.",
         "tags": [("pig iron", "crafting_material")]}
    ]

# for testing, conveniently spawn all we need based on the tags on the class
tools, consumables = PigIronRecipe.seed()

recipe = PigIronRecipe(caller, *(tools + consumables))
result = recipe.craft()

If the above class was added to a module in CRAFT_RECIPE_MODULES, it could be called using its .name property, as “pig iron”.

The [example_recipies](api:evennia.contrib.crafting.example_recipes) module has a full example of the components for creating a sword from base components.


exception evennia.contrib.crafting.crafting.CraftingError[source]

Bases: RuntimeError

Crafting error.

exception evennia.contrib.crafting.crafting.CraftingValidationError[source]

Bases: evennia.contrib.crafting.crafting.CraftingError

Error if crafting validation failed.

class evennia.contrib.crafting.crafting.CraftingRecipeBase(crafter, *inputs, **kwargs)[source]

Bases: object

The recipe handles all aspects of performing a ‘craft’ operation. This is the base of the crafting system, intended to be replace if you want to adapt it for very different functionality - see the CraftingRecipe child class for an implementation of the most common type of crafting using objects.

Example of usage:

recipe = CraftRecipe(crafter, obj1, obj2, obj3)
result = recipe.craft()

Note that the most common crafting operation is that the inputs are consumed - so in that case the recipe cannot be used a second time (doing so will raise a CraftingError)

Process:

  1. .craft(**kwargs) - this starts the process on the initialized recipe. The kwargs are optional but will be passed into all of the following hooks.

  2. .pre_craft(**kwargs) - this normally validates inputs and stores them in .validated_inputs.. Raises CraftingValidationError otherwise.

  1. .do_craft(**kwargs) - should return the crafted item(s) or the empty list. Any crafting errors should be immediately reported to user.

  2. .post_craft(crafted_result, **kwargs)- always called, even if pre_craft raised a CraftingError or CraftingValidationError. Should return crafted_result (modified or not).

name = 'recipe base'
allow_reuse = False
__init__(crafter, *inputs, **kwargs)[source]

Initialize the recipe.

Parameters
  • crafter (Object) – The one doing the crafting.

  • *inputs (any) – The ingredients of the recipe to use.

  • **kwargs (any) – Any other parameters that are relevant for this recipe.

msg(message, **kwargs)[source]

Send message to crafter. This is a central point to override if wanting to change crafting return style in some way.

Parameters
  • message (str) – The message to send.

  • **kwargs – Any optional properties relevant to this send.

pre_craft(**kwargs)[source]

Hook to override.

This is called just before crafting operation and is normally responsible for validating the inputs, storing data on self.validated_inputs.

Parameters
  • **kwargs – Optional extra flags passed during initialization or

  • **.craft

Raises

CraftingValidationError – If validation fails.

do_craft(**kwargs)[source]

Hook to override.

This performs the actual crafting. At this point the inputs are expected to have been verified already. If needed, the validated inputs are available on this recipe instance.

Parameters

**kwargs – Any extra flags passed at initialization.

Returns

any – The result of crafting.

post_craft(crafting_result, **kwargs)[source]

Hook to override.

This is called just after crafting has finished. A common use of this method is to delete the inputs.

Parameters
  • crafting_result (any) – The outcome of crafting, as returned by do_craft.

  • **kwargs – Any extra flags passed at initialization.

Returns

any – The final crafting result.

craft(raise_exception=False, **kwargs)[source]

Main crafting call method. Call this to produce a result and make sure all hooks run correctly.

Parameters
  • raise_exception (bool) – If crafting would return None, raise exception instead.

  • **kwargs (any) – Any other parameters that is relevant for this particular craft operation. This will temporarily override same-named kwargs given at the creation of this recipe and be passed into all of the crafting hooks.

Returns

any – The result of the craft, or None if crafting failed.

Raises
  • CraftingValidationError – If recipe validation failed and raise_exception is True.

  • CraftingError – On If trying to rerun a no-rerun recipe, or if crafting would return None and raise_exception** is set.

class evennia.contrib.crafting.crafting.CraftingRecipe(crafter, *inputs, **kwargs)[source]

Bases: evennia.contrib.crafting.crafting.CraftingRecipeBase

The CraftRecipe implements the most common form of crafting: Combining (and consuming) inputs to produce a new result. This type of recipe only works with typeclassed entities as inputs and outputs, since it’s based on Tags and Prototypes.

There are two types of crafting ingredients: ‘tools’ and ‘consumables’. The difference between them is that the former is not consumed in the crafting process. So if you need a hammer and anvil to craft a sword, they are ‘tools’ whereas the materials of the sword are ‘consumables’.

Examples:

class FlourRecipe(CraftRecipe):
    name = "flour"
    tool_tags = ['windmill']
    consumable_tags = ["wheat"]
    output_prototypes = [
        {"key": "Bag of flour",
         "typeclass": "typeclasses.food.Flour",
         "desc": "A small bag of flour."
         "tags": [("flour", "crafting_material"),
        }

class BreadRecipe(CraftRecipe):
    name = "bread"
    tool_tags = ["roller", "owen"]
    consumable_tags = ["flour", "egg", "egg", "salt", "water", "yeast"]
    output_prototypes = [
        {"key": "bread",
         "desc": "A tasty bread."
        }
  • name (str): The name of this recipe. This should be globally unique.

  • tool_tag_category (str): What tag-category tools must use. Default is ‘crafting_tool’.

  • tool_tags (list): Object-tags to use for tooling. If more than one instace of a tool is needed, add multiple entries here.

  • tool_names (list): Human-readable names for tools. These are used for informative messages/errors. If not given, the tags will be used. If given, this list should match the length of tool_tags.:

  • exact_tools (bool, default True): Must have exactly the right tools, any extra leads to failure.

  • exact_tool_order (bool, default False): Tools must be added in exactly the right order for crafting to pass.

  • consumable_tag_category (str): What tag-category consumables must use. Default is ‘crafting_material’.

  • consumable_tags (list): Tags for objects that will be consumed as part of running the recipe.

  • consumable_names (list): Human-readable names for consumables. Same as for tools.

  • exact_consumables (bool, default True): Normally, adding more consumables than needed leads to a a crafting error. If this is False, the craft will still succeed (only the needed ingredients will be consumed).

  • exact_consumable_order (bool, default False): Normally, the order in which ingredients are added does not matter. With this set, trying to add consumables in another order than given will lead to failing crafting.

  • consume_on_fail (bool, default False): Normally, consumables remain if crafting fails. With this flag, a failed crafting will still consume consumables. Note that this will also consume any ‘extra’ consumables added not part of the recipe!

  • output_prototypes (list): One or more prototypes (prototype_keys or full dicts) describing how to create the result(s) of this recipe.

  • output_names (list): Human-readable names for (prospective) prototypes. This is used in error messages. If not given, this is extracted from the prototypes’ key if possible.

custom messages all have custom formatting markers. Many are empty strings when not applicable.

{missing}: Comma-separated list of tool/consumable missing for missing/out of order errors.
{excess}: Comma-separated list of tool/consumable added in excess of recipe
{inputs}: Comma-separated list of any inputs (tools + consumables) involved in error.
{tools}: Comma-sepatated list of tools involved in error.
{consumables}: Comma-separated list of consumables involved in error.
{outputs}: Comma-separated list of (expected) outputs
{t0}..{tN-1}: Individual tools, same order as **.tool_names**.
{c0}..{cN-1}: Individual consumables, same order as **.consumable_names**.
{o0}..{oN-1}: Individual outputs, same order as **.output_names**.
  • error_tool_missing_message: “Could not craft {outputs} without {missing}.”

  • error_tool_order_message: “Could not craft {outputs} since {missing} was added in the wrong order.”

  • error_tool_excess_message: “Could not craft {outputs} (extra {excess}).”

  • error_consumable_missing_message: “Could not craft {outputs} without {missing}.”

  • error_consumable_order_message: “Could not craft {outputs} since {missing} was added in the wrong order.”

  • error_consumable_excess_message: “Could not craft {outputs} (excess {excess}).”

  • success_message: “You successfuly craft {outputs}!”

  • failure_message: “” (this is handled by the other error messages by default)

  1. Crafting starts by calling .craft(**kwargs) on the parent class. The **kwargs are optional, extends any **kwargs passed to the class constructor and will be passed into all the following hooks.

  1. .pre_craft(**kwargs) should handle validation of inputs. Results should be stored in validated_consumables/tools respectively. Raises CraftingValidationError otherwise.

  2. .do_craft(**kwargs) will not be called if validation failed. Should return a list of the things crafted.

  3. .post_craft(crafting_result, **kwargs) is always called, also if validation failed (crafting_result will then be falsy). It does any cleanup. By default this deletes consumables.

Use .msg to conveniently send messages to the crafter. Raise evennia.contrib.crafting.crafting.CraftingError exception to abort crafting at any time in the sequence. If raising with a text, this will be shown to the crafter automatically

name = 'crafting recipe'
consumable_tag_category = 'crafting_material'
tool_tag_category = 'crafting_tool'
tool_tags = []
exact_tools = True
exact_tool_order = False
error_tool_missing_message = 'Could not craft {outputs} without {missing}.'
error_tool_order_message = 'Could not craft {outputs} since {missing} was added in the wrong order.'
error_tool_excess_message = 'Could not craft {outputs} without the exact tools (extra {excess}).'
consumable_tags = []
consume_on_fail = False
exact_consumables = True
exact_consumable_order = False
error_consumable_missing_message = 'Could not craft {outputs} without {missing}.'
error_consumable_order_message = 'Could not craft {outputs} since {missing} was added in the wrong order.'
error_consumable_excess_message = 'Could not craft {outputs} without the exact ingredients (extra {excess}).'
output_prototypes = []
failure_message = ''
success_message = 'You successfully craft {outputs}!'
__init__(crafter, *inputs, **kwargs)[source]
Parameters
  • crafter (Object) – The one doing the crafting.

  • *inputs (Object) – The ingredients (+tools) of the recipe to use. The The recipe will itself figure out (from tags) which is a tool and which is a consumable.

  • **kwargs (any) – Any other parameters that are relevant for this recipe. These will be passed into the crafting hooks.

Notes

Internally, this class stores validated data in .validated_consumables and .validated_tools respectively. The .validated_inputs property (from parent) holds a list of everything types in the order inserted to the class constructor.

consumable_names = []
tool_names = []
output_names = []
classmethod seed(tool_kwargs=None, consumable_kwargs=None)[source]

This is a helper class-method for easy testing and application of this recipe. When called, it will create simple dummy ingredients with names and tags needed by this recipe.

Parameters
  • consumable_kwargs (dict, optional) – This will be passed as **consumable_kwargs into the create_object call for each consumable. If not given, matching consumable_name or consumable_tag will be used for key.

  • tool_kwargs (dict, optional) – Will be passed as **tool_kwargs into the create_object call for each tool. If not given, the matching tool_name or tool_tag will be used for key.

Returns

tuple – A tuple (tools, consumables) with newly created dummy objects matching the recipe ingredient list.

Example:

tools, consumables = SwordRecipe.seed()
recipe = SwordRecipe(caller, *(tools + consumables))
result = recipe.craft()

Notes

If key is given in consumable/tool_kwargs then _every_ created item of each type will have the same key.

pre_craft(**kwargs)[source]

Do pre-craft checks, including input validation.

Check so the given inputs are what is needed. This operates on self.inputs which is set to the inputs added to the class constructor. Validated data is stored as lists on .validated_tools and .validated_consumables respectively.

Parameters

**kwargs – Any optional extra kwargs passed during initialization of the recipe class.

Raises

CraftingValidationError – If validation fails. At this point the crafter is expected to have been informed of the problem already.

do_craft(**kwargs)[source]

Hook to override. This will not be called if validation in pre_craft fails.

This performs the actual crafting. At this point the inputs are expected to have been verified already.

Returns

list

A list of spawned objects created from the inputs, or None

on a failure.

Notes

This method should use self.msg to inform the user about the specific reason of failure immediately. We may want to analyze the tools in some way here to affect the crafting process.

post_craft(craft_result, **kwargs)[source]

Hook to override. This is called just after crafting has finished. A common use of this method is to delete the inputs.

Parameters
  • craft_result (list) – The crafted result, provided by self.do_craft.

  • **kwargs (any) – Passed from self.craft.

Returns

list – The return(s) of the craft, possibly modified in this method.

Notes

This is _always_ called, also if validation in pre_craft fails (craft_result will then be None).

evennia.contrib.crafting.crafting.craft(crafter, recipe_name, *inputs, raise_exception=False, **kwargs)[source]

Access function. Craft a given recipe from a source recipe module. A recipe module is a Python module containing recipe classes. Note that this requires settings.CRAFT_RECIPE_MODULES to be added to a list of one or more python-paths to modules holding Recipe-classes.

Parameters
  • crafter (Object) – The one doing the crafting.

  • recipe_name (str) – The CraftRecipe.name to use. This uses fuzzy-matching if the result is unique.

  • *inputs – Suitable ingredients and/or tools (Objects) to use in the crafting.

  • raise_exception (bool, optional) – If crafting failed for whatever reason, raise CraftingError. The user will still be informed by the recipe.

  • **kwargs – Optional kwargs to pass into the recipe (will passed into recipe.craft).

Returns

list – Crafted objects, if any.

Raises
  • CraftingError – If raise_exception is True and crafting failed to

  • produce an output. KeyError – If recipe_name failed to find a

  • matching recipe class (or the hit was not precise enough.)

Notes

If no recipe_module is given, will look for a list settings.CRAFT_RECIPE_MODULES and lastly fall back to the example module “evennia.contrib.”

class evennia.contrib.crafting.crafting.CraftingCmdSet(cmdsetobj=None, key=None)[source]

Bases: evennia.commands.cmdset.CmdSet

Store crafting command.

key = 'Crafting cmdset'
at_cmdset_creation()[source]

Hook method - this should be overloaded in the inheriting class, and should take care of populating the cmdset by use of self.add().

path = 'evennia.contrib.crafting.crafting.CraftingCmdSet'
class evennia.contrib.crafting.crafting.CmdCraft(**kwargs)[source]

Bases: evennia.commands.command.Command

Craft an item using ingredients and tools

Usage:

craft <recipe> [from <ingredient>,…] [using <tool>, …]

Examples

craft snowball from snow craft puppet from piece of wood using knife craft bread from flour, butter, water, yeast using owen, bowl, roller craft fireball using wand, spellbook

Notes

Ingredients must be in the crafter’s inventory. Tools can also be things in the current location, like a furnace, windmill or anvil.

key = 'craft'
locks = 'cmd:all()'
help_category = 'general'
aliases = []
arg_regex = re.compile('\\s|$', re.IGNORECASE)
lock_storage = 'cmd:all()'
search_index_entry = {'aliases': '', 'category': 'general', 'key': 'craft', 'tags': '', 'text': "\n Craft an item using ingredients and tools\n\n Usage:\n craft <recipe> [from <ingredient>,...] [using <tool>, ...]\n\n Examples:\n craft snowball from snow\n craft puppet from piece of wood using knife\n craft bread from flour, butter, water, yeast using owen, bowl, roller\n craft fireball using wand, spellbook\n\n Notes:\n Ingredients must be in the crafter's inventory. Tools can also be\n things in the current location, like a furnace, windmill or anvil.\n\n "}
parse()[source]

Handle parsing of:

<recipe> [FROM <ingredients>] [USING <tools>]

Examples:

craft snowball from snow
craft puppet from piece of wood using knife
craft bread from flour, butter, water, yeast using owen, bowl, roller
craft fireball using wand, spellbook
func()[source]

Perform crafting.

Will check the craft locktype. If a consumable/ingredient does not pass this check, we will check for the ‘crafting_consumable_err_msg’ Attribute, otherwise will use a default. If failing on a tool, will use the crafting_tool_err_msg if available.