mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Add documentation for new crafting contrib
This commit is contained in:
parent
e890bd9040
commit
87c43ccce0
32 changed files with 765 additions and 257 deletions
|
|
@ -24,10 +24,12 @@
|
|||
- Make IP throttle use Django-based cache system for optional persistence (PR by strikaco)
|
||||
- Renamed Tutorial classes "Weapon" and "WeaponRack" to "TutorialWeapon" and
|
||||
"TutorialWeaponRack" to prevent collisions with classes in mygame
|
||||
- New `crafting` contrib, adding a full crafting subsystem (Griatch 2020)
|
||||
|
||||
### Evennia 0.9.5
|
||||
### Evennia 0.9.5 (2019-2020)
|
||||
|
||||
A transitional release, including new doc system
|
||||
Released 2020-11-14.
|
||||
A transitional release, including new doc system.
|
||||
|
||||
- `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False
|
||||
- `py` command now reroutes stdout to output results in-game client. `py`
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ URL_REMAPS = {
|
|||
"Starting/Adding-Command-Tutorial": "Adding-Commands",
|
||||
"Adding-Command-Tutorial": "Adding-Commands",
|
||||
"CmdSet": "Command-Sets",
|
||||
"Spawner": "Spawner-and-Prototypes",
|
||||
"Spawner": "Prototypes",
|
||||
"issue": "github:issue",
|
||||
"issues": "github:issue",
|
||||
"bug": "github:issue",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ than, the doc-strings of each component in the [API](../Evennia-API).
|
|||
- [Attributes](./Attributes)
|
||||
- [Nicks](./Nicks)
|
||||
- [Tags](./Tags)
|
||||
- [Spawner and prototypes](./Spawner-and-Prototypes)
|
||||
- [Spawner and prototypes](./Prototypes)
|
||||
- [Help entries](./Help-System)
|
||||
|
||||
## Commands
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ like [Scripts](./Scripts)).
|
|||
This particular Rose class doesn't really do much, all it does it make sure the attribute
|
||||
`desc`(which is what the `look` command looks for) is pre-set, which is pretty pointless since you
|
||||
will usually want to change this at build time (using the `@desc` command or using the
|
||||
[Spawner](./Spawner-and-Prototypes)). The `Object` typeclass offers many more hooks that is available
|
||||
[Spawner](./Prototypes)). The `Object` typeclass offers many more hooks that is available
|
||||
to use though - see next section.
|
||||
|
||||
## Properties and functions on Objects
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ Deprecated as of Evennia 0.8:
|
|||
- `ndb_<name>` - sets the value of a non-persistent attribute (`"ndb_"` is stripped from the name).
|
||||
This is simply not useful in a prototype and is deprecated.
|
||||
- `exec` - This accepts a code snippet or a list of code snippets to run. This should not be used -
|
||||
use callables or [$protfuncs](./Spawner-and-Prototypes#protfuncs) instead (see below).
|
||||
use callables or [$protfuncs](./Prototypes#protfuncs) instead (see below).
|
||||
|
||||
### Prototype values
|
||||
|
||||
|
|
@ -3,20 +3,52 @@
|
|||
The [evennia/contrib/](api:evennia.contrib) folder holds Game-specific tools, systems and utilities created by the community. This gathers
|
||||
longer-form documentation associated with particular contribs.
|
||||
|
||||
## Crafting
|
||||
A full, extendable crafting system.
|
||||
|
||||
- [Crafting overview](./Crafting)
|
||||
- [Crafting API documentation](api:evennia.contrib.crafting.crafting)
|
||||
- [Example of a sword crafting tree](api:evennia.contrib.crafting.example_recipes)
|
||||
|
||||
## In-Game-Python
|
||||
|
||||
Allow Builders to add Python-scripted events to their objects (OBS-not for untrusted users!)
|
||||
|
||||
- [A voice-operated elevator using events](./A-voice-operated-elevator-using-events)
|
||||
- [Dialogues using events](./Dialogues-in-events)
|
||||
|
||||
## Maps
|
||||
|
||||
Solutions for generating and displaying maps in-game.
|
||||
|
||||
- [Dynamic in-game map](./Dynamic-In-Game-Map)
|
||||
- [Static in-game map](./Static-In-Game-Map)
|
||||
|
||||
## The tutorial-world
|
||||
|
||||
The Evennia single-player sole quest. Made to be analyzed to learn.
|
||||
|
||||
- [The tutorial world introduction](../Howto/Starting/Part1/Tutorial-World-Introduction)
|
||||
|
||||
## Menu-builder
|
||||
|
||||
A tool for building using an in-game menu instead of the normal build commands. Meant to
|
||||
be expanded for the needs of your game.
|
||||
|
||||
- [Building Menus](./Building-menus)
|
||||
|
||||
|
||||
```toctree::
|
||||
:hidden:
|
||||
|
||||
./Crafting
|
||||
../api/evennia.contrib.crafting.crafting
|
||||
../api/evennia.contrib.crafting.example_recipes
|
||||
./A-voice-operated-elevator-using-events
|
||||
./Dialogues-in-events
|
||||
./Dynamic-In-Game-Map
|
||||
./Static-In-Game-Map
|
||||
../Howto/Starting/Part1/Tutorial-World-Introduction
|
||||
./Building-menus
|
||||
|
||||
```
|
||||
214
docs/source/Contribs/Crafting.md
Normal file
214
docs/source/Contribs/Crafting.md
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
# Crafting system contrib
|
||||
|
||||
_Contrib by Griatch 2020_
|
||||
```versionadded:: 1.0
|
||||
```
|
||||
|
||||
This contrib implements a full Crafting system that can be expanded and modified to fit your game.
|
||||
|
||||
- See the [evennia/contrib/crafting/crafting.py API](api:evennia.contrib.crafting.crafting) for installation
|
||||
instructrions.
|
||||
- See the [sword example](api:evennia.contrib.crafting.example_recipes) for an example of how to design
|
||||
a crafting tree for crafting a sword from base elements.
|
||||
|
||||
From in-game it uses the new `craft` command:
|
||||
|
||||
```bash
|
||||
> craft bread from flour, eggs, salt, water, yeast using oven, roller
|
||||
> craft bandage from cloth using scissors
|
||||
```
|
||||
|
||||
The syntax is `craft <recipe> [from <ingredient>,...][ using <tool>,...]`.
|
||||
|
||||
The above example uses the `bread` *recipe* and requires `flour`, `eggs`, `salt`, `water` and `yeast` objects
|
||||
to be in your inventory. These will be consumed as part of crafting (baking) the bread.
|
||||
|
||||
The `oven` and `roller` are "tools" that can be either in your inventory or in your current location (you are not carrying an oven
|
||||
around with you after all). Tools are *not* consumed in the crafting. If the added ingredients/tools matches
|
||||
the requirements of the recipe, a new `bread` object will appear in the crafter's inventory.
|
||||
|
||||
If you wanted, you could also picture recipes without any consumables:
|
||||
|
||||
```
|
||||
> craft fireball using wand, spellbook
|
||||
```
|
||||
|
||||
With a little creativity, the 'recipe' concept could be adopted to all sorts of things, like puzzles or
|
||||
magic systems.
|
||||
|
||||
In code, you can craft using the `evennia.contrib.crafting.crafting.craft` function:
|
||||
|
||||
```python
|
||||
from evennia.contrib.crafting.crafting import craft
|
||||
|
||||
result = craft(caller, *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) on them (see below)
|
||||
The `result` is always a list.
|
||||
|
||||
## Adding new recipes
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
```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 what tags to look for in the inputs and a [Prototype](../Components/Prototypes) 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 the recipe understands.
|
||||
|
||||
The recipe looks only for the [Tag](../Components/Tags) of the ingredients. 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.
|
||||
|
||||
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
|
||||
|
||||
To understand how to customize recipes further, it helps to understand how they are used directly:
|
||||
|
||||
```python
|
||||
class MyRecipe(CraftingRecipe):
|
||||
...
|
||||
|
||||
# convenient helper to get dummy objects with the right tags
|
||||
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`. The `seed` class method is useful e.g. for making unit tests.
|
||||
|
||||
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.crafting.crafting.html#evennia.contrib.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`.
|
||||
- `do_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 `do_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 do_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().do_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](api:evennia.contrib.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
|
||||
|
||||
The base class `evennia.contrib.crafting.crafting.CraftingRecipeBase` implements just the minimum
|
||||
needed to be a recipe. It doesn't know about Objects or tags. If you want to adopt the crafting system
|
||||
for something entirely different (maybe using different input or validation logic), starting from this
|
||||
may be cleaner than overriding things in the more opinionated `CraftingRecipe`.
|
||||
|
|
@ -70,7 +70,7 @@ The flat API is defined in `__init__.py` [viewable here](github:evennia/__init__
|
|||
- [evennia.gametime](api:evennia.utils.gametime) - server run- and game time ([docs](Components/Coding-Utils#gametime))
|
||||
- [evennia.logger](api:evennia.utils.logger) - logging tools
|
||||
- [evennia.ansi](api:evennia.utils.ansi) - ansi coloring tools
|
||||
- [evennia.spawn](api:evennia.prototypes.spawner#evennia.prototypes.spawner.Spawn) - spawn/prototype system ([docs](Components/Spawner-and-Prototypes))
|
||||
- [evennia.spawn](api:evennia.prototypes.spawner#evennia.prototypes.spawner.Spawn) - spawn/prototype system ([docs](Components/Prototypes))
|
||||
- [evennia.lockfuncs](api:evennia.locks.lockfuncs) - default lock functions for access control ([docs](Components/Locks))
|
||||
- [evennia.EvMenu](api:evennia.utils.evmenu#evennia.utils.evmenu.EvMenu) - menu system ([docs](Components/EvMenu))
|
||||
- [evennia.EvTable](api:evennia.utils.evtable#evennia.utils.evtable.EvTable) - text table creater
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ from here to `mygame/server/settings.py` file.
|
|||
- `locale/` - Language files ([i18n](../../../Concepts/Internationalization)).
|
||||
- [`locks/`](../../../Components/Locks) - Lock system for restricting access to in-game entities.
|
||||
- [`objects/`](../../../Components/Objects) - In-game entities (all types of items and Characters).
|
||||
- [`prototypes/`](../../../Components/Spawner-and-Prototypes) - Object Prototype/spawning system and OLC menu
|
||||
- [`prototypes/`](../../../Components/Prototypes) - Object Prototype/spawning system and OLC menu
|
||||
- [`accounts/`](../../../Components/Accounts) - Out-of-game Session-controlled entities (accounts, bots etc)
|
||||
- [`scripts/`](../../../Components/Scripts) - Out-of-game entities equivalence to Objects, also with timer support.
|
||||
- [`server/`](../../../Components/Portal-And-Server) - Core server code and Session handling.
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ people change and re-structure this in various ways to better fit their ideas.
|
|||
- [batch_cmds.ev](github:evennia/game_template/world/batch_cmds.ev) - This is an `.ev` file, which is essentially
|
||||
just a list of Evennia commands to execute in sequence. This one is empty and ready to expand on. The
|
||||
[Tutorial World](./Tutorial-World-Introduction) was built with such a batch-file.
|
||||
- [prototypes.py](github:evennia/game_template/world/prototypes.py) - A [prototype](../../../Components/Spawner-and-Prototypes) is a way
|
||||
- [prototypes.py](github:evennia/game_template/world/prototypes.py) - A [prototype](../../../Components/Prototypes) is a way
|
||||
to easily vary objects without changing their base typeclass. For example, one could use prototypes to
|
||||
tell that Two goblins, while both of the class 'Goblin' (so they follow the same code logic), should have different
|
||||
equipment, stats and looks.
|
||||
|
|
|
|||
7
docs/source/api/evennia.contrib.crafting.crafting.rst
Normal file
7
docs/source/api/evennia.contrib.crafting.crafting.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.crafting.crafting
|
||||
========================================
|
||||
|
||||
.. automodule:: evennia.contrib.crafting.crafting
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.crafting.example\_recipes
|
||||
================================================
|
||||
|
||||
.. automodule:: evennia.contrib.crafting.example_recipes
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
16
docs/source/api/evennia.contrib.crafting.rst
Normal file
16
docs/source/api/evennia.contrib.crafting.rst
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
evennia.contrib.crafting
|
||||
================================
|
||||
|
||||
.. automodule:: evennia.contrib.crafting
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 6
|
||||
|
||||
evennia.contrib.crafting.crafting
|
||||
evennia.contrib.crafting.example_recipes
|
||||
evennia.contrib.crafting.tests
|
||||
7
docs/source/api/evennia.contrib.crafting.tests.rst
Normal file
7
docs/source/api/evennia.contrib.crafting.tests.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.crafting.tests
|
||||
=====================================
|
||||
|
||||
.. automodule:: evennia.contrib.crafting.tests
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
docs/source/api/evennia.contrib.evscaperoom.commands.rst
Normal file
7
docs/source/api/evennia.contrib.evscaperoom.commands.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.evscaperoom.commands
|
||||
===========================================
|
||||
|
||||
.. automodule:: evennia.contrib.evscaperoom.commands
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
docs/source/api/evennia.contrib.evscaperoom.menu.rst
Normal file
7
docs/source/api/evennia.contrib.evscaperoom.menu.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.evscaperoom.menu
|
||||
=======================================
|
||||
|
||||
.. automodule:: evennia.contrib.evscaperoom.menu
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
docs/source/api/evennia.contrib.evscaperoom.objects.rst
Normal file
7
docs/source/api/evennia.contrib.evscaperoom.objects.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.evscaperoom.objects
|
||||
==========================================
|
||||
|
||||
.. automodule:: evennia.contrib.evscaperoom.objects
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
docs/source/api/evennia.contrib.evscaperoom.room.rst
Normal file
7
docs/source/api/evennia.contrib.evscaperoom.room.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.evscaperoom.room
|
||||
=======================================
|
||||
|
||||
.. automodule:: evennia.contrib.evscaperoom.room
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
21
docs/source/api/evennia.contrib.evscaperoom.rst
Normal file
21
docs/source/api/evennia.contrib.evscaperoom.rst
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
evennia.contrib.evscaperoom
|
||||
===================================
|
||||
|
||||
.. automodule:: evennia.contrib.evscaperoom
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 6
|
||||
|
||||
evennia.contrib.evscaperoom.commands
|
||||
evennia.contrib.evscaperoom.menu
|
||||
evennia.contrib.evscaperoom.objects
|
||||
evennia.contrib.evscaperoom.room
|
||||
evennia.contrib.evscaperoom.scripts
|
||||
evennia.contrib.evscaperoom.state
|
||||
evennia.contrib.evscaperoom.tests
|
||||
evennia.contrib.evscaperoom.utils
|
||||
7
docs/source/api/evennia.contrib.evscaperoom.scripts.rst
Normal file
7
docs/source/api/evennia.contrib.evscaperoom.scripts.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.evscaperoom.scripts
|
||||
==========================================
|
||||
|
||||
.. automodule:: evennia.contrib.evscaperoom.scripts
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
docs/source/api/evennia.contrib.evscaperoom.state.rst
Normal file
7
docs/source/api/evennia.contrib.evscaperoom.state.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.evscaperoom.state
|
||||
========================================
|
||||
|
||||
.. automodule:: evennia.contrib.evscaperoom.state
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
docs/source/api/evennia.contrib.evscaperoom.tests.rst
Normal file
7
docs/source/api/evennia.contrib.evscaperoom.tests.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.evscaperoom.tests
|
||||
========================================
|
||||
|
||||
.. automodule:: evennia.contrib.evscaperoom.tests
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
7
docs/source/api/evennia.contrib.evscaperoom.utils.rst
Normal file
7
docs/source/api/evennia.contrib.evscaperoom.utils.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
evennia.contrib.evscaperoom.utils
|
||||
========================================
|
||||
|
||||
.. automodule:: evennia.contrib.evscaperoom.utils
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
@ -45,6 +45,8 @@ evennia.contrib
|
|||
:maxdepth: 6
|
||||
|
||||
evennia.contrib.awsstorage
|
||||
evennia.contrib.crafting
|
||||
evennia.contrib.evscaperoom
|
||||
evennia.contrib.ingame_python
|
||||
evennia.contrib.security
|
||||
evennia.contrib.turnbattle
|
||||
|
|
|
|||
|
|
@ -37,12 +37,12 @@
|
|||
- [Components/Objects](Components/Objects)
|
||||
- [Components/Outputfuncs](Components/Outputfuncs)
|
||||
- [Components/Portal And Server](Components/Portal-And-Server)
|
||||
- [Components/Prototypes](Components/Prototypes)
|
||||
- [Components/Scripts](Components/Scripts)
|
||||
- [Components/Server](Components/Server)
|
||||
- [Components/Server Conf](Components/Server-Conf)
|
||||
- [Components/Sessions](Components/Sessions)
|
||||
- [Components/Signals](Components/Signals)
|
||||
- [Components/Spawner and Prototypes](Components/Spawner-and-Prototypes)
|
||||
- [Components/Tags](Components/Tags)
|
||||
- [Components/TickerHandler](Components/TickerHandler)
|
||||
- [Components/Typeclasses](Components/Typeclasses)
|
||||
|
|
@ -70,6 +70,7 @@
|
|||
- [Contribs/Arxcode installing help](Contribs/Arxcode-installing-help)
|
||||
- [Contribs/Building menus](Contribs/Building-menus)
|
||||
- [Contribs/Contrib Overview](Contribs/Contrib-Overview)
|
||||
- [Contribs/Crafting](Contribs/Crafting)
|
||||
- [Contribs/Dialogues in events](Contribs/Dialogues-in-events)
|
||||
- [Contribs/Dynamic In Game Map](Contribs/Dynamic-In-Game-Map)
|
||||
- [Contribs/Static In Game Map](Contribs/Static-In-Game-Map)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0.9.0
|
||||
1.0-dev
|
||||
|
|
|
|||
0
evennia/contrib/crafting/__init__.py
Normal file
0
evennia/contrib/crafting/__init__.py
Normal file
|
|
@ -2,69 +2,88 @@
|
|||
Crafting - Griatch 2020
|
||||
|
||||
This is a general crafting engine. The basic functionality of crafting is to
|
||||
combine any number of of items in a 'recipe' to produce a new result. This is
|
||||
useful not only for traditional crafting but also for puzzle-solving or
|
||||
similar.
|
||||
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_MODULE_RECIPES`.
|
||||
- In the new module, create one or more classes, each a child of
|
||||
(`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`. The name of the object doesn't matter, only
|
||||
its tag.
|
||||
- Add the `CmdCraft` command from this module to your default cmdset. This is a
|
||||
very simple example-command (your real command will most likely need to do
|
||||
skill-checks etc!).
|
||||
tag-category `crafting_material` or `crafting_tool`. The name of the object
|
||||
doesn't matter, only its tag.
|
||||
|
||||
## Usage
|
||||
## Crafting in game
|
||||
|
||||
By default the crafter needs to specify which components
|
||||
should be used for the recipe:
|
||||
The default `craft` command handles all crafting needs.
|
||||
::
|
||||
|
||||
craft spiked club from club, nails
|
||||
> 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_. These must be either in inventory or in
|
||||
the current location. Tools are not consumed during the crafting.
|
||||
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
|
||||
> 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).
|
||||
|
||||
spiked_club = craft(crafter, "spiked club", club, nails)
|
||||
```python
|
||||
|
||||
A fail leads to an empty return. The crafter should already have been notified
|
||||
of any error in this case (this should be handle by the recipe itself).
|
||||
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_ works like an input/output blackbox: you put consumables (and/or
|
||||
tools) into it and if they match the recipe, a new result is spit out.
|
||||
Consumables are consumed in the process while tools are not.
|
||||
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_MODULE_RECIPES` setting, for example:
|
||||
`CRAFT_RECIPE_MODULES` setting, for example:
|
||||
|
||||
CRAFT_MODULE_RECIPES = ['world.recipes_weapons', 'world.recipes_potions']
|
||||
```python
|
||||
|
||||
Below is an example of a crafting recipe. See the `CraftingRecipe` class for
|
||||
details of which properties and methods are available to override - the craft
|
||||
behavior can be modified substantially this way.
|
||||
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.
|
||||
|
||||
```python
|
||||
|
||||
|
|
@ -73,7 +92,7 @@ behavior can be modified substantially this way.
|
|||
class PigIronRecipe(CraftingRecipe):
|
||||
# Pig iron is a high-carbon result of melting iron in a blast furnace.
|
||||
|
||||
name = "pig iron"
|
||||
name = "pig iron" # this is what crafting.craft and CmdCraft uses
|
||||
tool_tags = ["blast furnace"]
|
||||
consumable_tags = ["iron ore", "coal", "coal"]
|
||||
output_prototypes = [
|
||||
|
|
@ -82,18 +101,26 @@ behavior can be modified substantially this way.
|
|||
"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()
|
||||
|
||||
```
|
||||
|
||||
The `evennia/contrib/crafting/example_recipes.py` module has more examples of
|
||||
recipes.
|
||||
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.
|
||||
|
||||
----
|
||||
|
||||
"""
|
||||
|
||||
from copy import copy
|
||||
from evennia.utils.utils import (
|
||||
iter_to_str, callables_from_module, inherits_from, make_iter)
|
||||
from evennia.utils.utils import iter_to_str, callables_from_module, inherits_from, make_iter
|
||||
from evennia.commands.cmdset import CmdSet
|
||||
from evennia.commands.command import Command
|
||||
from evennia.prototypes.spawner import spawn
|
||||
|
|
@ -109,6 +136,7 @@ def _load_recipes():
|
|||
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
global _RECIPE_CLASSES
|
||||
if not _RECIPE_CLASSES:
|
||||
paths = ["evennia.contrib.crafting.example_recipes"]
|
||||
|
|
@ -126,12 +154,14 @@ class CraftingError(RuntimeError):
|
|||
|
||||
"""
|
||||
|
||||
|
||||
class CraftingValidationError(CraftingError):
|
||||
"""
|
||||
Error if crafting validation failed.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class CraftingRecipeBase:
|
||||
"""
|
||||
The recipe handles all aspects of performing a 'craft' operation. This is
|
||||
|
|
@ -164,6 +194,7 @@ class CraftingRecipeBase:
|
|||
|
||||
|
||||
"""
|
||||
|
||||
name = "recipe base"
|
||||
|
||||
# if set, allow running `.craft` more than once on the same instance.
|
||||
|
|
@ -436,6 +467,7 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
shown to the crafter automatically
|
||||
|
||||
"""
|
||||
|
||||
name = "crafting recipe"
|
||||
|
||||
# this define the overall category all material tags must have
|
||||
|
|
@ -458,11 +490,13 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
error_tool_missing_message = "Could not craft {outputs} without {missing}."
|
||||
# error to show if tool-order matters and it was wrong. Missing is the first
|
||||
# tool out of order
|
||||
error_tool_order_message = \
|
||||
error_tool_order_message = (
|
||||
"Could not craft {outputs} since {missing} was added in the wrong order."
|
||||
)
|
||||
# if .exact_tools is set and there are more than needed
|
||||
error_tool_excess_message = \
|
||||
error_tool_excess_message = (
|
||||
"Could not craft {outputs} without the exact tools (extra {excess})."
|
||||
)
|
||||
|
||||
# a list of tag-keys (of the `tag_category`). If more than one of each type
|
||||
# is needed, there should be multiple same-named entries in this list.
|
||||
|
|
@ -483,11 +517,13 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
error_consumable_missing_message = "Could not craft {outputs} without {missing}."
|
||||
# error to show if consumable order matters and it was wrong. Missing is the first
|
||||
# consumable out of order
|
||||
error_consumable_order_message = \
|
||||
error_consumable_order_message = (
|
||||
"Could not craft {outputs} since {missing} was added in the wrong order."
|
||||
)
|
||||
# if .exact_consumables is set and there are more than needed
|
||||
error_consumable_excess_message = \
|
||||
error_consumable_excess_message = (
|
||||
"Could not craft {outputs} without the exact ingredients (extra {excess})."
|
||||
)
|
||||
|
||||
# this is a list of one or more prototypes (prototype_keys to existing
|
||||
# prototypes or full prototype-dicts) to use to build the result. All of
|
||||
|
|
@ -503,7 +539,7 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
# show after a successful craft
|
||||
success_message = "You successfully craft {outputs}!"
|
||||
|
||||
def __init__(self, crafter, *inputs, **kwargs):
|
||||
def __init__(self, crafter, *inputs, **kwargs):
|
||||
"""
|
||||
Args:
|
||||
crafter (Object): The one doing the crafting.
|
||||
|
|
@ -528,32 +564,37 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
|
||||
# validate class properties
|
||||
if self.consumable_names:
|
||||
assert len(self.consumable_names) == len(self.consumable_tags), \
|
||||
f"Crafting {self.__class__}.consumable_names list must " \
|
||||
assert len(self.consumable_names) == len(self.consumable_tags), (
|
||||
f"Crafting {self.__class__}.consumable_names list must "
|
||||
"have the same length as .consumable_tags."
|
||||
)
|
||||
else:
|
||||
self.consumable_names = self.consumable_tags
|
||||
|
||||
if self.tool_names:
|
||||
assert len(self.tool_names) == len(self.tool_tags), \
|
||||
f"Crafting {self.__class__}.tool_names list must " \
|
||||
assert len(self.tool_names) == len(self.tool_tags), (
|
||||
f"Crafting {self.__class__}.tool_names list must "
|
||||
"have the same length as .tool_tags."
|
||||
)
|
||||
else:
|
||||
self.tool_names = self.tool_tags
|
||||
|
||||
if self.output_names:
|
||||
assert len(self.consumable_names) == len(self.consumable_tags), \
|
||||
f"Crafting {self.__class__}.output_names list must " \
|
||||
assert len(self.consumable_names) == len(self.consumable_tags), (
|
||||
f"Crafting {self.__class__}.output_names list must "
|
||||
"have the same length as .output_prototypes."
|
||||
)
|
||||
else:
|
||||
self.output_names = [
|
||||
prot.get("key", prot.get("typeclass", "unnamed"))
|
||||
if isinstance(prot, dict) else str(prot)
|
||||
if isinstance(prot, dict)
|
||||
else str(prot)
|
||||
for prot in self.output_prototypes
|
||||
]
|
||||
|
||||
assert isinstance(self.output_prototypes, (list, tuple)), \
|
||||
"Crafting {self.__class__}.output_prototypes must be a list or tuple."
|
||||
assert isinstance(
|
||||
self.output_prototypes, (list, tuple)
|
||||
), "Crafting {self.__class__}.output_prototypes must be a list or tuple."
|
||||
|
||||
# don't allow reuse if we have consumables. If only tools we can reuse
|
||||
# over and over since nothing changes.
|
||||
|
|
@ -568,14 +609,15 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
|
||||
# build template context
|
||||
mapping = {"missing": missing, "excess": excess}
|
||||
mapping.update({
|
||||
f"i{ind}": self.consumable_names[ind]
|
||||
for ind, name in enumerate(self.consumable_names or self.consumable_tags)
|
||||
})
|
||||
mapping.update({
|
||||
f"o{ind}": self.output_names[ind]
|
||||
for ind, name in enumerate(self.output_names)
|
||||
})
|
||||
mapping.update(
|
||||
{
|
||||
f"i{ind}": self.consumable_names[ind]
|
||||
for ind, name in enumerate(self.consumable_names or self.consumable_tags)
|
||||
}
|
||||
)
|
||||
mapping.update(
|
||||
{f"o{ind}": self.output_names[ind] for ind, name in enumerate(self.output_names)}
|
||||
)
|
||||
mapping["tools"] = involved_tools
|
||||
mapping["consumables"] = involved_cons
|
||||
|
||||
|
|
@ -633,18 +675,17 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
create_object(
|
||||
key=tool_key or (cls.tool_names[itag] if cls.tool_names else tag.capitalize()),
|
||||
tags=[(tag, cls.tool_tag_category), *tool_tags],
|
||||
**tool_kwargs
|
||||
**tool_kwargs,
|
||||
)
|
||||
)
|
||||
consumables = []
|
||||
for itag, tag in enumerate(cls.consumable_tags):
|
||||
consumables.append(
|
||||
create_object(
|
||||
key=cons_key or (cls.consumable_names[itag] if
|
||||
cls.consumable_names else
|
||||
tag.capitalize()),
|
||||
key=cons_key
|
||||
or (cls.consumable_names[itag] if cls.consumable_names else tag.capitalize()),
|
||||
tags=[(tag, cls.consumable_tag_category), *cons_tags],
|
||||
**consumable_kwargs
|
||||
**consumable_kwargs,
|
||||
)
|
||||
)
|
||||
return tools, consumables
|
||||
|
|
@ -669,8 +710,15 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
"""
|
||||
|
||||
def _check_completeness(
|
||||
tagmap, taglist, namelist, exact_match, exact_order,
|
||||
error_missing_message, error_order_message, error_excess_message):
|
||||
tagmap,
|
||||
taglist,
|
||||
namelist,
|
||||
exact_match,
|
||||
exact_order,
|
||||
error_missing_message,
|
||||
error_order_message,
|
||||
error_excess_message,
|
||||
):
|
||||
"""Compare tagmap (inputs) to taglist (required)"""
|
||||
valids = []
|
||||
for itag, tagkey in enumerate(taglist):
|
||||
|
|
@ -682,8 +730,8 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
if exact_order:
|
||||
# if we get here order is wrong
|
||||
err = self._format_message(
|
||||
error_order_message,
|
||||
missing=obj.get_display_name(looker=self.crafter))
|
||||
error_order_message, missing=obj.get_display_name(looker=self.crafter)
|
||||
)
|
||||
self.msg(err)
|
||||
raise CraftingValidationError(err)
|
||||
|
||||
|
|
@ -694,7 +742,8 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
elif exact_match:
|
||||
err = self._format_message(
|
||||
error_missing_message,
|
||||
missing=namelist[itag] if namelist else tagkey.capitalize())
|
||||
missing=namelist[itag] if namelist else tagkey.capitalize(),
|
||||
)
|
||||
self.msg(err)
|
||||
raise CraftingValidationError(err)
|
||||
|
||||
|
|
@ -703,21 +752,30 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
# thus this is not an exact match
|
||||
err = self._format_message(
|
||||
error_excess_message,
|
||||
excess=[obj.get_display_name(looker=self.crafter) for obj in tagmap])
|
||||
excess=[obj.get_display_name(looker=self.crafter) for obj in tagmap],
|
||||
)
|
||||
self.msg(err)
|
||||
raise CraftingValidationError(err)
|
||||
|
||||
return valids
|
||||
|
||||
# get tools and consumables from self.inputs
|
||||
tool_map = {obj: obj.tags.get(category=self.tool_tag_category, return_list=True)
|
||||
for obj in self.inputs if obj and hasattr(obj, "tags") and
|
||||
inherits_from(obj, "evennia.objects.models.ObjectDB")}
|
||||
tool_map = {
|
||||
obj: obj.tags.get(category=self.tool_tag_category, return_list=True)
|
||||
for obj in self.inputs
|
||||
if obj
|
||||
and hasattr(obj, "tags")
|
||||
and inherits_from(obj, "evennia.objects.models.ObjectDB")
|
||||
}
|
||||
tool_map = {obj: tags for obj, tags in tool_map.items() if tags}
|
||||
consumable_map = {obj: obj.tags.get(category=self.consumable_tag_category, return_list=True)
|
||||
for obj in self.inputs
|
||||
if obj and hasattr(obj, "tags") and obj not in tool_map and
|
||||
inherits_from(obj, "evennia.objects.models.ObjectDB")}
|
||||
consumable_map = {
|
||||
obj: obj.tags.get(category=self.consumable_tag_category, return_list=True)
|
||||
for obj in self.inputs
|
||||
if obj
|
||||
and hasattr(obj, "tags")
|
||||
and obj not in tool_map
|
||||
and inherits_from(obj, "evennia.objects.models.ObjectDB")
|
||||
}
|
||||
consumable_map = {obj: tags for obj, tags in consumable_map.items() if tags}
|
||||
|
||||
# we set these so they are available for error management at all times,
|
||||
|
|
@ -750,11 +808,13 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
# all the recipe needs now.
|
||||
if len(tools) != len(self.tool_tags):
|
||||
raise CraftingValidationError(
|
||||
f"Tools {tools}'s tags do not match expected tags {self.tool_tags}")
|
||||
f"Tools {tools}'s tags do not match expected tags {self.tool_tags}"
|
||||
)
|
||||
if len(consumables) != len(self.consumable_tags):
|
||||
raise CraftingValidationError(
|
||||
f"Consumables {consumables}'s tags do not match "
|
||||
f"expected tags {self.consumable_tags}")
|
||||
f"expected tags {self.consumable_tags}"
|
||||
)
|
||||
|
||||
self.validated_tools = tools
|
||||
self.validated_consumables = consumables
|
||||
|
|
@ -816,25 +876,29 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
|
||||
def craft(crafter, recipe_name, *inputs, raise_exception=False, **kwargs):
|
||||
"""
|
||||
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.
|
||||
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.
|
||||
|
||||
Args:
|
||||
crafter (Object): The one doing the crafting.
|
||||
recipe_name (str): The `CraftRecipe.name` to use.
|
||||
*inputs: Suitable ingredients (Objects) to use in 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).
|
||||
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.
|
||||
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
|
||||
|
|
@ -846,18 +910,30 @@ def craft(crafter, recipe_name, *inputs, raise_exception=False, **kwargs):
|
|||
|
||||
RecipeClass = _RECIPE_CLASSES.get(recipe_name, None)
|
||||
if not RecipeClass:
|
||||
raise KeyError("No recipe in settings.CRAFT_RECIPE_MODULES "
|
||||
f"has a name matching {recipe_name}")
|
||||
# try a startswith fuzzy match
|
||||
matches = [key for key in _RECIPE_CLASSES if key.startswith(recipe_name)]
|
||||
if not matches:
|
||||
# try in-match
|
||||
matches = [key for key in _RECIPE_CLASSES if recipe_name in key]
|
||||
if len(matches) == 1:
|
||||
RecipeClass = matches[0]
|
||||
|
||||
if not RecipeClass:
|
||||
raise KeyError(
|
||||
f"No recipe in settings.CRAFT_RECIPE_MODULES has a name matching {recipe_name}"
|
||||
)
|
||||
recipe = RecipeClass(crafter, *inputs, **kwargs)
|
||||
return recipe.craft(raise_exception=raise_exception)
|
||||
|
||||
|
||||
# craft command/cmdset
|
||||
|
||||
|
||||
class CraftingCmdSet(CmdSet):
|
||||
"""
|
||||
Store crafting command.
|
||||
"""
|
||||
|
||||
key = "Crafting cmdset"
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
|
|
@ -883,22 +959,35 @@ class CmdCraft(Command):
|
|||
|
||||
"""
|
||||
|
||||
key = "craft"
|
||||
locks = "cmd:all()"
|
||||
help_category = "General"
|
||||
arg_regex = r"\s|$"
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Handle parsing of
|
||||
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
|
||||
|
||||
"""
|
||||
self.args = args = self.args.strip().lower()
|
||||
recipe, ingredients, tools = "", "", ""
|
||||
|
||||
if 'from' in args:
|
||||
if "from" in args:
|
||||
recipe, *rest = args.split(" from ", 1)
|
||||
rest = rest[0] if rest else ""
|
||||
ingredients, *tools = rest.split(" using ", 1)
|
||||
elif 'using' in args:
|
||||
elif "using" in args:
|
||||
recipe, *tools = args.split(" using ", 1)
|
||||
tools = tools[0] if tools else ""
|
||||
|
||||
|
|
@ -931,13 +1020,19 @@ class CmdCraft(Command):
|
|||
# try to include characters or accounts etc.
|
||||
if not obj:
|
||||
return
|
||||
if (not inherits_from(obj, "evennia.objects.models.ObjectDB")
|
||||
or obj.sessions.all() or not obj.access(caller, "craft", default=True)):
|
||||
if (
|
||||
not inherits_from(obj, "evennia.objects.models.ObjectDB")
|
||||
or obj.sessions.all()
|
||||
or not obj.access(caller, "craft", default=True)
|
||||
):
|
||||
# We don't allow to include puppeted objects nor those with the
|
||||
# 'negative' permission 'nocraft'.
|
||||
caller.msg(obj.attributes.get(
|
||||
"crafting_consumable_err_msg",
|
||||
default=f"{obj.get_display_name(looker=caller)} can't be used for this."))
|
||||
caller.msg(
|
||||
obj.attributes.get(
|
||||
"crafting_consumable_err_msg",
|
||||
default=f"{obj.get_display_name(looker=caller)} can't be used for this.",
|
||||
)
|
||||
)
|
||||
return
|
||||
ingredients.append(obj)
|
||||
|
||||
|
|
@ -950,9 +1045,12 @@ class CmdCraft(Command):
|
|||
if not obj:
|
||||
return None
|
||||
if not obj.access(caller, "craft", default=True):
|
||||
caller.msg(obj.attributes.get(
|
||||
"crafting_tool_err_msg",
|
||||
default=f"{obj.get_display_name(looker=caller)} can't be used for this."))
|
||||
caller.msg(
|
||||
obj.attributes.get(
|
||||
"crafting_tool_err_msg",
|
||||
default=f"{obj.get_display_name(looker=caller)} can't be used for this.",
|
||||
)
|
||||
)
|
||||
return
|
||||
tools.append(obj)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
"""
|
||||
Example recipes for the crafting system - how to make a sword.
|
||||
How to make a sword - example crafting tree for the crafting system.
|
||||
|
||||
See the _SwordSmithingBaseRecipe for an example of extendng the recipe with a
|
||||
mocked 'skill' system (just random chance in our case). The skill system used
|
||||
is game-specific but likely to be needed for most 'real' crafting systems.
|
||||
See the `SwordSmithingBaseRecipe` in this module for an example of extendng the
|
||||
recipe with a mocked 'skill' system (just random chance in our case). The skill
|
||||
system used is game-specific but likely to be needed for most 'real' crafting
|
||||
systems.
|
||||
|
||||
Note that 'tools' are references to the tools used - they don't need to be in
|
||||
the inventory of the crafter. So when 'blast furnace' is given below, it is a
|
||||
reference to a blast furnace used, not suggesting the crafter is carrying it
|
||||
around with them.
|
||||
|
||||
::
|
||||
## Sword crafting tree
|
||||
|
||||
Sword crafting tree
|
||||
::
|
||||
|
||||
# base materials (consumables)
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ around with them.
|
|||
sword = sword blade + sword guard + sword pommel
|
||||
+ sword handle + leather + knife[T] + hammer[T] + furnace[T]
|
||||
|
||||
----
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -52,13 +54,16 @@ class PigIronRecipe(CraftingRecipe):
|
|||
Pig iron is a high-carbon result of melting iron in a blast furnace.
|
||||
|
||||
"""
|
||||
|
||||
name = "pig iron"
|
||||
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")]}
|
||||
{
|
||||
"key": "Pig Iron ingot",
|
||||
"desc": "An ingot of crude pig iron.",
|
||||
"tags": [("pig iron", "crafting_material")],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -68,13 +73,16 @@ class CrucibleSteelRecipe(CraftingRecipe):
|
|||
crucible produces a medieval level of steel (like damascus steel).
|
||||
|
||||
"""
|
||||
|
||||
name = "crucible steel"
|
||||
tool_tags = ["crucible"]
|
||||
consumable_tags = ["pig iron", "ash", "sand", "coal", "coal"]
|
||||
output_prototypes = [
|
||||
{"key": "Crucible steel ingot",
|
||||
"desc": "An ingot of multi-colored crucible steel.",
|
||||
"tags": [("crucible steel", "crafting_material")]}
|
||||
{
|
||||
"key": "Crucible steel ingot",
|
||||
"desc": "An ingot of multi-colored crucible steel.",
|
||||
"tags": [("crucible steel", "crafting_material")],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -87,8 +95,9 @@ class _SwordSmithingBaseRecipe(CraftingRecipe):
|
|||
"""
|
||||
|
||||
success_message = "Your smithing work bears fruit and you craft {outputs}!"
|
||||
failed_message = ("You work and work but you are not happy with the result. "
|
||||
"You need to start over.")
|
||||
failed_message = (
|
||||
"You work and work but you are not happy with the result. You need to start over."
|
||||
)
|
||||
|
||||
def do_craft(self, **kwargs):
|
||||
"""
|
||||
|
|
@ -130,28 +139,35 @@ class SwordBladeRecipe(_SwordSmithingBaseRecipe):
|
|||
part of the sword you hold on to).
|
||||
|
||||
"""
|
||||
|
||||
name = "sword blade"
|
||||
tool_tags = ["hammer", "anvil", "furnace"]
|
||||
consumable_tags = ["crucible steel"]
|
||||
output_prototypes = [
|
||||
{"key": "Sword blade",
|
||||
"desc": "A long blade that may one day become a sword.",
|
||||
"tags": [("sword blade", "crafting_material")]}
|
||||
{
|
||||
"key": "Sword blade",
|
||||
"desc": "A long blade that may one day become a sword.",
|
||||
"tags": [("sword blade", "crafting_material")],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
class SwordPommelRecipe(_SwordSmithingBaseRecipe):
|
||||
"""
|
||||
The pommel is the 'button' or 'ball' etc the end of the sword hilt, holding
|
||||
it together.
|
||||
|
||||
"""
|
||||
|
||||
name = "sword pommel"
|
||||
tool_tags = ["hammer", "anvil", "furnace"]
|
||||
consumable_tags = ["crucible steel"]
|
||||
output_prototypes = [
|
||||
{"key": "Sword pommel",
|
||||
"desc": "The pommel for a future sword.",
|
||||
"tags": [("sword pommel", "crafting_material")]}
|
||||
{
|
||||
"key": "Sword pommel",
|
||||
"desc": "The pommel for a future sword.",
|
||||
"tags": [("sword pommel", "crafting_material")],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -161,13 +177,16 @@ class SwordGuardRecipe(_SwordSmithingBaseRecipe):
|
|||
sword's blade and also protects the hand when parrying.
|
||||
|
||||
"""
|
||||
|
||||
name = "sword guard"
|
||||
tool_tags = ["hammer", "anvil", "furnace"]
|
||||
consumable_tags = ["crucible steel"]
|
||||
output_prototypes = [
|
||||
{"key": "Sword guard",
|
||||
"desc": "The cross-guard for a future sword.",
|
||||
"tags": [("sword guard", "crafting_material")]}
|
||||
{
|
||||
"key": "Sword guard",
|
||||
"desc": "The cross-guard for a future sword.",
|
||||
"tags": [("sword guard", "crafting_material")],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -176,13 +195,16 @@ class RawhideRecipe(CraftingRecipe):
|
|||
Rawhide is animal skin cleaned and stripped of hair.
|
||||
|
||||
"""
|
||||
|
||||
name = "rawhide"
|
||||
tool_tags = ["knife"]
|
||||
consumable_tags = ["fur"]
|
||||
output_prototypes = [
|
||||
{"key": "Rawhide",
|
||||
"desc": "Animal skin, cleaned and with hair removed.",
|
||||
"tags": [("rawhide", "crafting_material")]}
|
||||
{
|
||||
"key": "Rawhide",
|
||||
"desc": "Animal skin, cleaned and with hair removed.",
|
||||
"tags": [("rawhide", "crafting_material")],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -193,17 +215,21 @@ class OakBarkRecipe(CraftingRecipe):
|
|||
|
||||
This produces two outputs - the bark and the cleaned wood.
|
||||
"""
|
||||
|
||||
name = "oak bark"
|
||||
tool_tags = ["knife"]
|
||||
consumable_tags = ["oak wood"]
|
||||
output_prototypes = [
|
||||
{"key": "Oak bark",
|
||||
"desc": "Bark of oak, stripped from the core wood.",
|
||||
"tags": [("oak bark", "crafting_material")]},
|
||||
{"key": "Oak Wood (cleaned)",
|
||||
"desc": "Oakwood core, stripped of bark.",
|
||||
"tags": [("cleaned oak wood", "crafting_material")]},
|
||||
|
||||
{
|
||||
"key": "Oak bark",
|
||||
"desc": "Bark of oak, stripped from the core wood.",
|
||||
"tags": [("oak bark", "crafting_material")],
|
||||
},
|
||||
{
|
||||
"key": "Oak Wood (cleaned)",
|
||||
"desc": "Oakwood core, stripped of bark.",
|
||||
"tags": [("cleaned oak wood", "crafting_material")],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -214,13 +240,16 @@ class LeatherRecipe(CraftingRecipe):
|
|||
'tanning rack' tool should be required too ...
|
||||
|
||||
"""
|
||||
|
||||
name = "leather"
|
||||
tool_tags = ["cauldron"]
|
||||
consumable_tags = ["rawhide", "oak bark", "water"]
|
||||
output_prototypes = [
|
||||
{"key": "Piece of Leather",
|
||||
"desc": "A piece of leather.",
|
||||
"tags": [("leather", "crafting_material")]}
|
||||
{
|
||||
"key": "Piece of Leather",
|
||||
"desc": "A piece of leather.",
|
||||
"tags": [("leather", "crafting_material")],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -231,13 +260,16 @@ class SwordHandleRecipe(CraftingRecipe):
|
|||
is wrapped in leather, but that will be added at the end.
|
||||
|
||||
"""
|
||||
|
||||
name = "sword handle"
|
||||
tool_tags = ["knife"]
|
||||
consumable_tags = ["cleaned oak wood"]
|
||||
output_prototypes = [
|
||||
{"key": "Sword handle",
|
||||
"desc": "Two pieces of wood to be be fitted onto a sword's tang as its handle.",
|
||||
"tags": [("sword handle", "crafting_material")]}
|
||||
{
|
||||
"key": "Sword handle",
|
||||
"desc": "Two pieces of wood to be be fitted onto a sword's tang as its handle.",
|
||||
"tags": [("sword handle", "crafting_material")],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -252,17 +284,19 @@ class SwordRecipe(_SwordSmithingBaseRecipe):
|
|||
This covers only a single 'sword' type.
|
||||
|
||||
"""
|
||||
|
||||
name = "sword"
|
||||
tool_tags = ["hammer", "furnace", "knife"]
|
||||
consumable_tags = ["sword blade", "sword guard", "sword pommel", "sword handle",
|
||||
"leather"]
|
||||
consumable_tags = ["sword blade", "sword guard", "sword pommel", "sword handle", "leather"]
|
||||
output_prototypes = [
|
||||
{"key": "Sword",
|
||||
"desc": "A bladed weapon.",
|
||||
# setting the tag as well - who knows if one can make something from this too!
|
||||
"tags": [("sword", "crafting_material")]}
|
||||
# obviously there would be other properties of a 'sword' added here
|
||||
# too, depending on how combat works in the your game!
|
||||
{
|
||||
"key": "Sword",
|
||||
"desc": "A bladed weapon.",
|
||||
# setting the tag as well - who knows if one can make something from this too!
|
||||
"tags": [("sword", "crafting_material")],
|
||||
}
|
||||
# obviously there would be other properties of a 'sword' added here
|
||||
# too, depending on how combat works in the your game!
|
||||
]
|
||||
# this requires more precision
|
||||
exact_consumable_order = True
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class TestCraftUtils(TestCase):
|
|||
Test helper utils for crafting.
|
||||
|
||||
"""
|
||||
|
||||
maxDiff = None
|
||||
|
||||
@override_settings(CRAFT_RECIPE_MODULES=[])
|
||||
|
|
@ -28,17 +29,17 @@ class TestCraftUtils(TestCase):
|
|||
self.assertEqual(
|
||||
crafting._RECIPE_CLASSES,
|
||||
{
|
||||
'crucible steel': example_recipes.CrucibleSteelRecipe,
|
||||
'leather': example_recipes.LeatherRecipe,
|
||||
'oak bark': example_recipes.OakBarkRecipe,
|
||||
'pig iron': example_recipes.PigIronRecipe,
|
||||
'rawhide': example_recipes.RawhideRecipe,
|
||||
'sword': example_recipes.SwordRecipe,
|
||||
'sword blade': example_recipes.SwordBladeRecipe,
|
||||
'sword guard': example_recipes.SwordGuardRecipe,
|
||||
'sword handle': example_recipes.SwordHandleRecipe,
|
||||
'sword pommel': example_recipes.SwordPommelRecipe,
|
||||
}
|
||||
"crucible steel": example_recipes.CrucibleSteelRecipe,
|
||||
"leather": example_recipes.LeatherRecipe,
|
||||
"oak bark": example_recipes.OakBarkRecipe,
|
||||
"pig iron": example_recipes.PigIronRecipe,
|
||||
"rawhide": example_recipes.RawhideRecipe,
|
||||
"sword": example_recipes.SwordRecipe,
|
||||
"sword blade": example_recipes.SwordBladeRecipe,
|
||||
"sword guard": example_recipes.SwordGuardRecipe,
|
||||
"sword handle": example_recipes.SwordHandleRecipe,
|
||||
"sword pommel": example_recipes.SwordPommelRecipe,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -54,6 +55,7 @@ class TestCraftingRecipeBase(TestCase):
|
|||
"""
|
||||
Test the parent recipe class.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.crafter = mock.MagicMock()
|
||||
self.crafter.msg = mock.MagicMock()
|
||||
|
|
@ -65,7 +67,8 @@ class TestCraftingRecipeBase(TestCase):
|
|||
self.kwargs = {"kw1": 1, "kw2": 2}
|
||||
|
||||
self.recipe = crafting.CraftingRecipeBase(
|
||||
self.crafter, self.inp1, self.inp2, self.inp3, **self.kwargs)
|
||||
self.crafter, self.inp1, self.inp2, self.inp3, **self.kwargs
|
||||
)
|
||||
|
||||
def test_msg(self):
|
||||
"""Test messaging to crafter"""
|
||||
|
|
@ -76,9 +79,7 @@ class TestCraftingRecipeBase(TestCase):
|
|||
def test_pre_craft(self):
|
||||
"""Test validating hook"""
|
||||
self.recipe.pre_craft()
|
||||
self.assertEqual(
|
||||
self.recipe.validated_inputs, (self.inp1, self.inp2, self.inp3)
|
||||
)
|
||||
self.assertEqual(self.recipe.validated_inputs, (self.inp1, self.inp2, self.inp3))
|
||||
|
||||
def test_pre_craft_fail(self):
|
||||
"""Should rase error if validation fails"""
|
||||
|
|
@ -126,9 +127,11 @@ class _MockRecipe(crafting.CraftingRecipe):
|
|||
tool_tags = ["tool1", "tool2"]
|
||||
consumable_tags = ["cons1", "cons2", "cons3"]
|
||||
output_prototypes = [
|
||||
{"key": "Result1",
|
||||
"prototype_key": "resultprot",
|
||||
"tags": [("result1", "crafting_material")]}
|
||||
{
|
||||
"key": "Result1",
|
||||
"prototype_key": "resultprot",
|
||||
"tags": [("result1", "crafting_material")],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -137,6 +140,7 @@ class TestCraftingRecipe(TestCase):
|
|||
"""
|
||||
Test the CraftingRecipe class with one recipe
|
||||
"""
|
||||
|
||||
maxDiff = None
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -162,19 +166,27 @@ class TestCraftingRecipe(TestCase):
|
|||
def test_error_format(self):
|
||||
"""Test the automatic error formatter """
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
||||
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
||||
)
|
||||
|
||||
msg = ("{missing},{tools},{consumables},{inputs},{outputs}"
|
||||
"{i0},{i1},{o0}")
|
||||
kwargs = {"missing": "foo", "tools": ["bar", "bar2", "bar3"],
|
||||
"consumables": ["cons1", "cons2"]}
|
||||
msg = "{missing},{tools},{consumables},{inputs},{outputs}" "{i0},{i1},{o0}"
|
||||
kwargs = {
|
||||
"missing": "foo",
|
||||
"tools": ["bar", "bar2", "bar3"],
|
||||
"consumables": ["cons1", "cons2"],
|
||||
}
|
||||
|
||||
expected = {
|
||||
'missing': 'foo', 'i0': 'cons1', 'i1': 'cons2', 'i2': 'cons3', 'o0':
|
||||
'Result1', 'tools': 'bar, bar2 and bar3', 'consumables': 'cons1 and cons2',
|
||||
'inputs': 'cons1, cons2 and cons3', 'outputs': 'Result1'}
|
||||
"missing": "foo",
|
||||
"i0": "cons1",
|
||||
"i1": "cons2",
|
||||
"i2": "cons3",
|
||||
"o0": "Result1",
|
||||
"tools": "bar, bar2 and bar3",
|
||||
"consumables": "cons1 and cons2",
|
||||
"inputs": "cons1, cons2 and cons3",
|
||||
"outputs": "Result1",
|
||||
}
|
||||
|
||||
result = recipe._format_message(msg, **kwargs)
|
||||
self.assertEqual(result, msg.format(**expected))
|
||||
|
|
@ -182,16 +194,16 @@ class TestCraftingRecipe(TestCase):
|
|||
def test_craft__success(self):
|
||||
"""Test to create a result from the recipe"""
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
||||
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
||||
)
|
||||
|
||||
result = recipe.craft()
|
||||
|
||||
self.assertEqual(result[0].key, "Result1")
|
||||
self.assertEqual(result[0].tags.all(), ['result1', 'resultprot'])
|
||||
self.assertEqual(result[0].tags.all(), ["result1", "resultprot"])
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"})
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"}
|
||||
)
|
||||
|
||||
# make sure consumables are gone
|
||||
self.assertIsNone(self.cons1.pk)
|
||||
|
|
@ -208,17 +220,15 @@ class TestCraftingRecipe(TestCase):
|
|||
tools, consumables = _MockRecipe.seed()
|
||||
|
||||
# this should be a normal successful crafting
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
*(tools + consumables)
|
||||
)
|
||||
recipe = _MockRecipe(self.crafter, *(tools + consumables))
|
||||
|
||||
result = recipe.craft()
|
||||
|
||||
self.assertEqual(result[0].key, "Result1")
|
||||
self.assertEqual(result[0].tags.all(), ['result1', 'resultprot'])
|
||||
self.assertEqual(result[0].tags.all(), ["result1", "resultprot"])
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"})
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"}
|
||||
)
|
||||
|
||||
# make sure consumables are gone
|
||||
for cons in consumables:
|
||||
|
|
@ -229,15 +239,13 @@ class TestCraftingRecipe(TestCase):
|
|||
|
||||
def test_craft_missing_tool__fail(self):
|
||||
"""Fail craft by missing tool2"""
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.cons1, self.cons2, self.cons3
|
||||
)
|
||||
recipe = _MockRecipe(self.crafter, self.tool1, self.cons1, self.cons2, self.cons3)
|
||||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_tool_missing_message.format(outputs="Result1", missing='tool2'),
|
||||
{"type": "crafting"})
|
||||
recipe.error_tool_missing_message.format(outputs="Result1", missing="tool2"),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
self.assertIsNotNone(self.cons1.pk)
|
||||
|
|
@ -249,16 +257,13 @@ class TestCraftingRecipe(TestCase):
|
|||
|
||||
def test_craft_missing_cons__fail(self):
|
||||
"""Fail craft by missing cons3"""
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.tool2, self.cons1, self.cons2
|
||||
)
|
||||
recipe = _MockRecipe(self.crafter, self.tool1, self.tool2, self.cons1, self.cons2)
|
||||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_consumable_missing_message.format(
|
||||
outputs="Result1", missing='cons3'),
|
||||
{"type": "crafting"})
|
||||
recipe.error_consumable_missing_message.format(outputs="Result1", missing="cons3"),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
self.assertIsNotNone(self.cons1.pk)
|
||||
|
|
@ -273,19 +278,16 @@ class TestCraftingRecipe(TestCase):
|
|||
|
||||
cons4 = create_object(key="cons4", tags=[("cons4", "crafting_material")], nohome=True)
|
||||
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.tool2, self.cons1, self.cons2, cons4
|
||||
)
|
||||
recipe = _MockRecipe(self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, cons4)
|
||||
recipe.consume_on_fail = True
|
||||
|
||||
result = recipe.craft()
|
||||
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_consumable_missing_message.format(
|
||||
outputs="Result1", missing='cons3'),
|
||||
{"type": "crafting"})
|
||||
recipe.error_consumable_missing_message.format(outputs="Result1", missing="cons3"),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
|
||||
# make sure consumables are deleted even though we failed
|
||||
self.assertIsNone(self.cons1.pk)
|
||||
|
|
@ -303,16 +305,15 @@ class TestCraftingRecipe(TestCase):
|
|||
|
||||
wrong = create_object(key="wrong", tags=[("wrongtool", "crafting_tool")], nohome=True)
|
||||
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.tool2, self.cons1, self.cons2, wrong
|
||||
)
|
||||
recipe = _MockRecipe(self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, wrong)
|
||||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_tool_excess_message.format(
|
||||
outputs="Result1", excess=wrong.get_display_name(looker=self.crafter)),
|
||||
{"type": "crafting"})
|
||||
outputs="Result1", excess=wrong.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
# make sure consumables are still there
|
||||
self.assertIsNotNone(self.cons1.pk)
|
||||
self.assertIsNotNone(self.cons2.pk)
|
||||
|
|
@ -328,15 +329,16 @@ class TestCraftingRecipe(TestCase):
|
|||
tool3 = create_object(key="tool3", tags=[("tool2", "crafting_tool")], nohome=True)
|
||||
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, tool3
|
||||
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, tool3
|
||||
)
|
||||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_tool_excess_message.format(
|
||||
outputs="Result1", excess=tool3.get_display_name(looker=self.crafter)),
|
||||
{"type": "crafting"})
|
||||
outputs="Result1", excess=tool3.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
self.assertIsNotNone(self.cons1.pk)
|
||||
|
|
@ -354,15 +356,16 @@ class TestCraftingRecipe(TestCase):
|
|||
cons4 = create_object(key="cons4", tags=[("cons3", "crafting_material")], nohome=True)
|
||||
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, cons4
|
||||
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, cons4
|
||||
)
|
||||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_consumable_excess_message.format(
|
||||
outputs="Result1", excess=cons4.get_display_name(looker=self.crafter)),
|
||||
{"type": "crafting"})
|
||||
outputs="Result1", excess=cons4.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
self.assertIsNotNone(self.cons1.pk)
|
||||
|
|
@ -379,14 +382,14 @@ class TestCraftingRecipe(TestCase):
|
|||
tool3 = create_object(key="tool3", tags=[("tool2", "crafting_tool")], nohome=True)
|
||||
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, tool3
|
||||
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, tool3
|
||||
)
|
||||
recipe.exact_tools = False
|
||||
result = recipe.craft()
|
||||
self.assertTrue(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"})
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"}
|
||||
)
|
||||
|
||||
# make sure consumables are gone
|
||||
self.assertIsNone(self.cons1.pk)
|
||||
|
|
@ -402,14 +405,14 @@ class TestCraftingRecipe(TestCase):
|
|||
cons4 = create_object(key="cons4", tags=[("cons3", "crafting_material")], nohome=True)
|
||||
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, cons4
|
||||
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, cons4
|
||||
)
|
||||
recipe.exact_consumables = False
|
||||
result = recipe.craft()
|
||||
self.assertTrue(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"})
|
||||
recipe.success_message.format(outputs="Result1"), {"type": "crafting"}
|
||||
)
|
||||
|
||||
# make sure consumables are gone
|
||||
self.assertIsNone(self.cons1.pk)
|
||||
|
|
@ -422,16 +425,17 @@ class TestCraftingRecipe(TestCase):
|
|||
def test_craft_tool_order__fail(self):
|
||||
"""Strict tool-order recipe fail """
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool2, self.tool1, self.cons1, self.cons2, self.cons3
|
||||
self.crafter, self.tool2, self.tool1, self.cons1, self.cons2, self.cons3
|
||||
)
|
||||
recipe.exact_tool_order = True
|
||||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_tool_order_message.format(
|
||||
outputs="Result1", missing=self.tool2.get_display_name(looker=self.crafter)),
|
||||
{"type": "crafting"})
|
||||
outputs="Result1", missing=self.tool2.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
self.assertIsNotNone(self.cons1.pk)
|
||||
|
|
@ -444,16 +448,17 @@ class TestCraftingRecipe(TestCase):
|
|||
def test_craft_cons_order__fail(self):
|
||||
"""Strict tool-order recipe fail """
|
||||
recipe = _MockRecipe(
|
||||
self.crafter,
|
||||
self.tool1, self.tool2, self.cons3, self.cons2, self.cons1
|
||||
self.crafter, self.tool1, self.tool2, self.cons3, self.cons2, self.cons1
|
||||
)
|
||||
recipe.exact_consumable_order = True
|
||||
result = recipe.craft()
|
||||
self.assertFalse(result)
|
||||
self.crafter.msg.assert_called_with(
|
||||
recipe.error_consumable_order_message.format(
|
||||
outputs="Result1", missing=self.cons3.get_display_name(looker=self.crafter)),
|
||||
{"type": "crafting"})
|
||||
outputs="Result1", missing=self.cons3.get_display_name(looker=self.crafter)
|
||||
),
|
||||
{"type": "crafting"},
|
||||
)
|
||||
|
||||
# make sure consumables are still there
|
||||
self.assertIsNotNone(self.cons1.pk)
|
||||
|
|
@ -469,6 +474,7 @@ class TestCraftSword(TestCase):
|
|||
Test the `craft` function by crafting the example sword.
|
||||
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.crafter = mock.MagicMock()
|
||||
self.crafter.msg = mock.MagicMock()
|
||||
|
|
@ -578,8 +584,16 @@ class TestCraftSword(TestCase):
|
|||
sword_handle = _craft("sword handle", *inputs)
|
||||
|
||||
# sword (order matters)
|
||||
inputs = [sword_blade, sword_guard, sword_pommel, sword_handle,
|
||||
leather, knife, hammer, furnace]
|
||||
inputs = [
|
||||
sword_blade,
|
||||
sword_guard,
|
||||
sword_pommel,
|
||||
sword_handle,
|
||||
leather,
|
||||
knife,
|
||||
hammer,
|
||||
furnace,
|
||||
]
|
||||
sword = _craft("sword", *inputs)
|
||||
|
||||
self.assertEqual(sword.key, "Sword")
|
||||
|
|
@ -633,10 +647,8 @@ class TestCraftSword(TestCase):
|
|||
self.assertIsNotNone(cauldron)
|
||||
|
||||
|
||||
@mock.patch("evennia.contrib.crafting.crafting._load_recipes",
|
||||
new=mock.MagicMock())
|
||||
@mock.patch("evennia.contrib.crafting.crafting._RECIPE_CLASSES",
|
||||
new={"testrecipe": _MockRecipe})
|
||||
@mock.patch("evennia.contrib.crafting.crafting._load_recipes", new=mock.MagicMock())
|
||||
@mock.patch("evennia.contrib.crafting.crafting._RECIPE_CLASSES", new={"testrecipe": _MockRecipe})
|
||||
@override_settings(CRAFT_RECIPE_MODULES=[], DEFAULT_HOME="#999999")
|
||||
class TestCraftCommand(CommandTest):
|
||||
"""Test the crafting command"""
|
||||
|
|
@ -645,15 +657,15 @@ class TestCraftCommand(CommandTest):
|
|||
super().setUp()
|
||||
|
||||
tools, consumables = _MockRecipe.seed(
|
||||
tool_kwargs={"location": self.char1},
|
||||
consumable_kwargs={"location": self.char1})
|
||||
tool_kwargs={"location": self.char1}, consumable_kwargs={"location": self.char1}
|
||||
)
|
||||
|
||||
def test_craft__success(self):
|
||||
"Successfully craft using command"
|
||||
self.call(
|
||||
crafting.CmdCraft(),
|
||||
"testrecipe from cons1, cons2, cons3 using tool1, tool2",
|
||||
_MockRecipe.success_message.format(outputs="Result1")
|
||||
_MockRecipe.success_message.format(outputs="Result1"),
|
||||
)
|
||||
|
||||
def test_craft__notools__failure(self):
|
||||
|
|
@ -661,12 +673,12 @@ class TestCraftCommand(CommandTest):
|
|||
self.call(
|
||||
crafting.CmdCraft(),
|
||||
"testrecipe from cons1, cons2, cons3",
|
||||
_MockRecipe.error_tool_missing_message.format(outputs="Result1", missing="tool1")
|
||||
_MockRecipe.error_tool_missing_message.format(outputs="Result1", missing="tool1"),
|
||||
)
|
||||
|
||||
def test_craft__nocons__failure(self):
|
||||
self.call(
|
||||
crafting.CmdCraft(),
|
||||
"testrecipe using tool1, tool2",
|
||||
_MockRecipe.error_consumable_missing_message.format(outputs="Result1", missing="cons1")
|
||||
_MockRecipe.error_consumable_missing_message.format(outputs="Result1", missing="cons1"),
|
||||
)
|
||||
|
|
|
|||
0
evennia/contrib/evscaperoom/__init__.py
Normal file
0
evennia/contrib/evscaperoom/__init__.py
Normal file
|
|
@ -174,12 +174,11 @@ class TestEvscaperoomCommands(CommandTest):
|
|||
self.call(commands.CmdSpeak(), "", "What do you want to say?", cmdstring="")
|
||||
self.call(commands.CmdSpeak(), "Hello!", "You say: Hello!", cmdstring="")
|
||||
self.call(commands.CmdSpeak(), "", "What do you want to whisper?", cmdstring="whisper")
|
||||
self.call(commands.CmdSpeak(), "Hi.", "You whisper: Hi.", cmdstring="whisper")
|
||||
self.call(commands.CmdSpeak(), "Hi.", "You whisper: Hi.", cmdstring="whisper")
|
||||
self.call(commands.CmdSpeak(), "Hi.", "You whisper: (Hi.)", cmdstring="whisper")
|
||||
self.call(commands.CmdSpeak(), "HELLO!", "You shout: HELLO!", cmdstring="shout")
|
||||
|
||||
self.call(commands.CmdSpeak(), "Hello to obj", "You say: Hello", cmdstring="say")
|
||||
self.call(commands.CmdSpeak(), "Hello to obj", "You shout: Hello", cmdstring="shout")
|
||||
self.call(commands.CmdSpeak(), "Hello", "You say: Hello", cmdstring="say")
|
||||
self.call(commands.CmdSpeak(), "Hello", "You shout: HELLO", cmdstring="shout")
|
||||
|
||||
def test_emote(self):
|
||||
self.call(
|
||||
|
|
@ -272,7 +271,7 @@ class TestStates(EvenniaTest):
|
|||
dirname = path.join(path.dirname(__file__), "states")
|
||||
states = []
|
||||
for imp, module, ispackage in pkgutil.walk_packages(
|
||||
path=[dirname], prefix="evscaperoom.states."
|
||||
path=[dirname], prefix="evennia.contrib.evscaperoom.states."
|
||||
):
|
||||
mod = mod_import(module)
|
||||
states.append(mod)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue