mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Clean up contrib docs, autogeneration
This commit is contained in:
parent
b922cf9b3c
commit
e96bbb4b86
94 changed files with 4126 additions and 2536 deletions
|
|
@ -1,25 +1,46 @@
|
|||
# Crafting system
|
||||
|
||||
Contrib - Griatch 2020
|
||||
Contribution by Griatch 2020
|
||||
|
||||
This implements a full crafting system. The principle is that of a 'recipe':
|
||||
This implements a full crafting system. The principle is that of a 'recipe',
|
||||
where you combine items (tagged as ingredients) create something new. The recipe can also
|
||||
require certain (non-consumed) tools. An example would be to use the 'bread recipe' to
|
||||
combine 'flour', 'water' and 'yeast' with an 'oven' to bake a 'loaf of bread'.
|
||||
|
||||
ingredient1 + ingredient2 + ... + tool1 + tool2 + ... + craft_recipe -> objectA, objectB, ...
|
||||
The recipe process can be understood like this:
|
||||
|
||||
ingredient(s) + tool(s) + recipe -> object(s)
|
||||
|
||||
Here, 'ingredients' are consumed by the crafting process, whereas 'tools' are
|
||||
necessary for the process by will not be destroyed by it.
|
||||
necessary for the process but will not be destroyed by it.
|
||||
|
||||
An example would be to use the tools 'bowl' and 'oven' to use the ingredients
|
||||
'flour', 'salt', 'yeast' and 'water' to create 'bread' using the 'bread recipe'.
|
||||
The included `craft` command works like this:
|
||||
|
||||
A recipe does not have to use tools, like 'snow' + 'snowball-recipe' becomes
|
||||
'snowball'. Conversely one could also imagine using tools without consumables,
|
||||
like using 'spell book' and 'wand' to produce 'fireball' by having the recipe
|
||||
check some magic skill on the character.
|
||||
craft <recipe> [from <ingredient>,...] [using <tool>, ...]
|
||||
|
||||
The system is generic enough to be used also for adventure-like puzzles, like
|
||||
combining 'stick', 'string' and 'hook' to get a 'makeshift fishing rod' that
|
||||
you can use with 'storm drain' (treated as a tool) to get 'key' ...
|
||||
## Examples
|
||||
|
||||
Using the `craft` command:
|
||||
|
||||
craft toy car from plank, wooden wheels, nails using saw, hammer
|
||||
|
||||
A recipe does not have to use tools or even multiple ingredients:
|
||||
|
||||
snow + snowball_recipe -> snowball
|
||||
|
||||
Conversely one could also imagine using tools without consumables, like
|
||||
|
||||
spell_book + wand + fireball_recipe -> fireball
|
||||
|
||||
The system is generic enough to be used also for adventure-like puzzles (but
|
||||
one would need to change the command and determine the recipe on based on what
|
||||
is being combined instead):
|
||||
|
||||
stick + string + hook -> makeshift_fishing_rod
|
||||
makeshift_fishing_rod + storm_drain -> key
|
||||
|
||||
See the [sword example](evennia.contrib.game_systems.crafting.example_recipes) for an example
|
||||
of how to design a recipe tree for crafting a sword from base elements.
|
||||
|
||||
## Intallation and Usage
|
||||
|
||||
|
|
@ -29,18 +50,30 @@ available to you:
|
|||
|
||||
craft <recipe> [from <ingredient>,...] [using <tool>, ...]
|
||||
|
||||
For example
|
||||
In code, you can craft using the
|
||||
`evennia.contrib.game_systems.crafting.craft` function:
|
||||
|
||||
craft toy car from plank, wooden wheels, nails using saw, hammer
|
||||
```python
|
||||
from evennia.contrib.game_systems.crafting import craft
|
||||
|
||||
To use crafting you need recipes. Add a new variable to `mygame/server/conf/settings.py`:
|
||||
result = craft(caller, "recipename", *inputs)
|
||||
|
||||
```
|
||||
Here, `caller` is the one doing the crafting and `*inputs` is any combination of
|
||||
consumables and/or tool Objects. The system will identify which is which by the
|
||||
[Tags](../Components/Tags.md) on them (see below) The `result` is always a list.
|
||||
|
||||
To use crafting you need recipes. Add a new variable to
|
||||
`mygame/server/conf/settings.py`:
|
||||
|
||||
CRAFT_RECIPE_MODULES = ['world.recipes']
|
||||
|
||||
All top-level classes in these modules (whose name does not start with `_`)
|
||||
will be parsed by Evennia as recipes to make available to the crafting system.
|
||||
Using the above example, create `mygame/world/recipes.py` and add your recipies
|
||||
in there:
|
||||
All top-level classes in these modules (whose name does not start with `_`) will
|
||||
be parsed by Evennia as recipes to make available to the crafting system. Using
|
||||
the above example, create `mygame/world/recipes.py` and add your recipies in
|
||||
there:
|
||||
|
||||
A quick example (read on for more details):
|
||||
|
||||
```python
|
||||
|
||||
|
|
@ -69,37 +102,202 @@ class RecipeBread(CraftingRecipe):
|
|||
def pre_craft(self, **kwargs):
|
||||
# validates inputs etc. Raise `CraftingValidationError` if fails
|
||||
|
||||
def craft(self, **kwargs):
|
||||
# performs the craft - but it can still fail (check skills etc here)
|
||||
def do_craft(self, **kwargs):
|
||||
# performs the craft - report errors directly to user and return None (if
|
||||
# failed) and the created object(s) if successful.
|
||||
|
||||
def craft(self, result, **kwargs):
|
||||
# any post-crafting effects. Always called, even if crafting failed (be
|
||||
def post_craft(self, result, **kwargs):
|
||||
# any post-crafting effects. Always called, even if do_craft failed (the
|
||||
# result would be None then)
|
||||
|
||||
```
|
||||
|
||||
## Technical
|
||||
## Adding new recipes
|
||||
|
||||
The Recipe is a class that specifies the consumables, tools and output along
|
||||
with various methods (that you can override) to do the the validation of inputs
|
||||
and perform the crafting itself.
|
||||
A *recipe* is a class inheriting from
|
||||
`evennia.contrib.crafting.crafting.CraftingRecipe`. This class implements the
|
||||
most common form of crafting - that using in-game objects. Each recipe is a
|
||||
separate class which gets initialized with the consumables/tools you provide.
|
||||
|
||||
By default the input is a list of object-tags (using the "crafting_material"
|
||||
and "crafting_tool" tag-categories respectively). Providing a set of objects
|
||||
matching these tags are required for the crafting to be done. The use of tags
|
||||
means that multiple different objects could all work for the same recipe, as
|
||||
long as they have the right tag. This can be very useful for allowing players
|
||||
to experiment and explore alternative ways to create things!
|
||||
For the `craft` command to find your custom recipes, you need to tell Evennia
|
||||
where they are. Add a new line to your `mygame/server/conf/settings.py` file,
|
||||
with a list to any new modules with recipe classes.
|
||||
|
||||
The output is given by a set of prototype-dicts. If the input is correct and
|
||||
other checks are passed (such as crafting skill, for example), these prototypes
|
||||
will be used to generate the new object(s) being crafted.
|
||||
```python
|
||||
CRAFT_RECIPE_MODULES = ["world.myrecipes"]
|
||||
```
|
||||
|
||||
(You need to reload after adding this). All global-level classes in these
|
||||
modules (whose names don't start with underscore) are considered by the system
|
||||
as viable recipes.
|
||||
|
||||
Here we assume you created `mygame/world/myrecipes.py` to match the above
|
||||
example setting:
|
||||
|
||||
```python
|
||||
# in mygame/world/myrecipes.py
|
||||
|
||||
from evennia.contrib.crafting.crafting import CraftingRecipe
|
||||
|
||||
class WoodenPuppetRecipe(CraftingRecipe):
|
||||
"""A puppet""""
|
||||
name = "wooden puppet" # name to refer to this recipe as
|
||||
tool_tags = ["knife"]
|
||||
consumable_tags = ["wood"]
|
||||
output_prototypes = [
|
||||
{"key": "A carved wooden doll",
|
||||
"typeclass": "typeclasses.objects.decorations.Toys",
|
||||
"desc": "A small carved doll"}
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
This specifies which tags to look for in the inputs. It defines a
|
||||
[Prototype](../Components/Prototypes.md) for the recipe to use to spawn the
|
||||
result on the fly (a recipe could spawn more than one result if needed).
|
||||
Instead of specifying the full prototype-dict, you could also just provide a
|
||||
list of `prototype_key`s to existing prototypes you have.
|
||||
|
||||
After reloading the server, this recipe would now be available to use. To try it
|
||||
we should create materials and tools to insert into the recipe.
|
||||
|
||||
|
||||
The recipe analyzes inputs, looking for [Tags](../Components/Tags.md) with
|
||||
specific tag-categories. The tag-category used can be set per-recipe using the
|
||||
(`.consumable_tag_category` and `.tool_tag_category` respectively). The defaults
|
||||
are `crafting_material` and `crafting_tool`. For
|
||||
the puppet we need one object with the `wood` tag and another with the `knife`
|
||||
tag:
|
||||
|
||||
```python
|
||||
from evennia import create_object
|
||||
|
||||
knife = create_object(key="Hobby knife", tags=[("knife", "crafting_tool")])
|
||||
wood = create_object(key="Piece of wood", tags[("wood", "crafting_material")])
|
||||
```
|
||||
|
||||
Note that the objects can have any name, all that matters is the
|
||||
tag/tag-category. This means if a "bayonet" also had the "knife" crafting tag,
|
||||
it could also be used to carve a puppet. This is also potentially interesting
|
||||
for use in puzzles and to allow users to experiment and find alternatives to
|
||||
know ingredients.
|
||||
|
||||
By the way, there is also a simple shortcut for doing this:
|
||||
|
||||
```
|
||||
tools, consumables = WoodenPuppetRecipe.seed()
|
||||
```
|
||||
|
||||
The `seed` class-method will create simple dummy objects that fulfills the
|
||||
recipe's requirements. This is great for testing.
|
||||
|
||||
Assuming these objects were put in our inventory, we could now craft using the
|
||||
in-game command:
|
||||
|
||||
```bash
|
||||
> craft wooden puppet from wood using hobby knife
|
||||
```
|
||||
In code we would do
|
||||
|
||||
```python
|
||||
from evennia.contrub.crafting.crafting import craft
|
||||
puppet = craft(crafter, "wooden puppet", knife, wood)
|
||||
|
||||
```
|
||||
In the call to `craft`, the order of `knife` and `wood` doesn't matter - the
|
||||
recipe will sort out which is which based on their tags.
|
||||
|
||||
## Deeper customization of recipes
|
||||
|
||||
For customizing recipes further, it helps to understand how to use the
|
||||
recipe-class directly:
|
||||
|
||||
```python
|
||||
class MyRecipe(CraftingRecipe):
|
||||
# ...
|
||||
|
||||
tools, consumables = MyRecipe.seed()
|
||||
recipe = MyRecipe(crafter, *(tools + consumables))
|
||||
result = recipe.craft()
|
||||
|
||||
```
|
||||
This is useful for testing and allows you to use the class directly without
|
||||
adding it to a module in `settings.CRAFTING_RECIPE_MODULES`.
|
||||
|
||||
Even without modifying more than the class properties, there are a lot of
|
||||
options to set on the `CraftingRecipe` class. Easiest is to refer to the
|
||||
[CraftingRecipe api
|
||||
documentation](evennia.contrib.game_systems.crafting.crafting.CraftingRecipe). For example,
|
||||
you can customize the validation-error messages, decide if the ingredients have
|
||||
to be exactly right, if a failure still consumes the ingredients or not, and
|
||||
much more.
|
||||
|
||||
For even more control you can override hooks in your own class:
|
||||
|
||||
- `pre_craft` - this should handle input validation and store its data in `.validated_consumables` and
|
||||
`validated_tools` respectively. On error, this reports the error to the crafter and raises the
|
||||
`CraftingValidationError`.
|
||||
- `craft` - this will only be called if `pre_craft` finished without an exception. This should
|
||||
return the result of the crafting, by spawnging the prototypes. Or the empty list if crafting
|
||||
fails for some reason. This is the place to add skill-checks or random chance if you need it
|
||||
for your game.
|
||||
- `post_craft` - this receives the result from `craft` and handles error messages and also deletes
|
||||
any consumables as needed. It may also modify the result before returning it.
|
||||
- `msg` - this is a wrapper for `self.crafter.msg` and should be used to send messages to the
|
||||
crafter. Centralizing this means you can also easily modify the sending style in one place later.
|
||||
|
||||
The class constructor (and the `craft` access function) takes optional `**kwargs`. These are passed
|
||||
into each crafting hook. These are unused by default but could be used to customize things per-call.
|
||||
|
||||
### Skilled crafters
|
||||
|
||||
What the crafting system does not have out of the box is a 'skill' system - the
|
||||
notion of being able to fail the craft if you are not skilled enough. Just how
|
||||
skills work is game-dependent, so to add this you need to make your own recipe
|
||||
parent class and have your recipes inherit from this.
|
||||
|
||||
|
||||
```python
|
||||
from random import randint
|
||||
from evennia.contrib.crafting.crafting import CraftingRecipe
|
||||
|
||||
class SkillRecipe(CraftingRecipe):
|
||||
"""A recipe that considers skill"""
|
||||
|
||||
difficulty = 20
|
||||
|
||||
def craft(self, **kwargs):
|
||||
"""The input is ok. Determine if crafting succeeds"""
|
||||
|
||||
# this is set at initialization
|
||||
crafter = self.crafte
|
||||
|
||||
# let's assume the skill is stored directly on the crafter
|
||||
# - the skill is 0..100.
|
||||
crafting_skill = crafter.db.skill_crafting
|
||||
# roll for success:
|
||||
if randint(1, 100) <= (crafting_skill - self.difficulty):
|
||||
# all is good, craft away
|
||||
return super().craft()
|
||||
else:
|
||||
self.msg("You are not good enough to craft this. Better luck next time!")
|
||||
return []
|
||||
```
|
||||
In this example we introduce a `.difficulty` for the recipe and makes a 'dice roll' to see
|
||||
if we succed. We would of course make this a lot more immersive and detailed in a full game. In
|
||||
principle you could customize each recipe just the way you want it, but you could also inherit from
|
||||
a central parent like this to cut down on work.
|
||||
|
||||
The [sword recipe example module](evennia.contrib.game_systems.crafting.example_recipes) also shows an example
|
||||
of a random skill-check being implemented in a parent and then inherited for multiple use.
|
||||
|
||||
## Even more customization
|
||||
|
||||
If you want to build something even more custom (maybe using different input types of validation logic)
|
||||
you could also look at the `CraftingRecipe` parent class `CraftingRecipeBase`.
|
||||
It implements just the minimum needed to be a recipe and for big changes you may be better off starting
|
||||
from this rather than the more opinionated `CraftingRecipe`.
|
||||
|
||||
Each recipe is a stand-alone entity which allows for very advanced
|
||||
customization for every recipe - for example one could have a recipe that
|
||||
checks other properties of the inputs (like quality, color etc) and have that
|
||||
affect the result. Your recipes could also (and likely would) tie into your
|
||||
game's skill system to determine the success or outcome of the crafting.
|
||||
|
||||
|
||||
----
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue