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)
|
- Make IP throttle use Django-based cache system for optional persistence (PR by strikaco)
|
||||||
- Renamed Tutorial classes "Weapon" and "WeaponRack" to "TutorialWeapon" and
|
- Renamed Tutorial classes "Weapon" and "WeaponRack" to "TutorialWeapon" and
|
||||||
"TutorialWeaponRack" to prevent collisions with classes in mygame
|
"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
|
- `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False
|
||||||
- `py` command now reroutes stdout to output results in-game client. `py`
|
- `py` command now reroutes stdout to output results in-game client. `py`
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ URL_REMAPS = {
|
||||||
"Starting/Adding-Command-Tutorial": "Adding-Commands",
|
"Starting/Adding-Command-Tutorial": "Adding-Commands",
|
||||||
"Adding-Command-Tutorial": "Adding-Commands",
|
"Adding-Command-Tutorial": "Adding-Commands",
|
||||||
"CmdSet": "Command-Sets",
|
"CmdSet": "Command-Sets",
|
||||||
"Spawner": "Spawner-and-Prototypes",
|
"Spawner": "Prototypes",
|
||||||
"issue": "github:issue",
|
"issue": "github:issue",
|
||||||
"issues": "github:issue",
|
"issues": "github:issue",
|
||||||
"bug": "github:issue",
|
"bug": "github:issue",
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ than, the doc-strings of each component in the [API](../Evennia-API).
|
||||||
- [Attributes](./Attributes)
|
- [Attributes](./Attributes)
|
||||||
- [Nicks](./Nicks)
|
- [Nicks](./Nicks)
|
||||||
- [Tags](./Tags)
|
- [Tags](./Tags)
|
||||||
- [Spawner and prototypes](./Spawner-and-Prototypes)
|
- [Spawner and prototypes](./Prototypes)
|
||||||
- [Help entries](./Help-System)
|
- [Help entries](./Help-System)
|
||||||
|
|
||||||
## Commands
|
## 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
|
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
|
`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
|
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.
|
to use though - see next section.
|
||||||
|
|
||||||
## Properties and functions on Objects
|
## 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).
|
- `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.
|
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 -
|
- `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
|
### 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
|
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.
|
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
|
## 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)
|
- [A voice-operated elevator using events](./A-voice-operated-elevator-using-events)
|
||||||
- [Dialogues using events](./Dialogues-in-events)
|
- [Dialogues using events](./Dialogues-in-events)
|
||||||
|
|
||||||
## Maps
|
## Maps
|
||||||
|
|
||||||
|
Solutions for generating and displaying maps in-game.
|
||||||
|
|
||||||
- [Dynamic in-game map](./Dynamic-In-Game-Map)
|
- [Dynamic in-game map](./Dynamic-In-Game-Map)
|
||||||
- [Static in-game map](./Static-In-Game-Map)
|
- [Static in-game map](./Static-In-Game-Map)
|
||||||
|
|
||||||
## The tutorial-world
|
## 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)
|
- [The tutorial world introduction](../Howto/Starting/Part1/Tutorial-World-Introduction)
|
||||||
|
|
||||||
## Menu-builder
|
## 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)
|
- [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.gametime](api:evennia.utils.gametime) - server run- and game time ([docs](Components/Coding-Utils#gametime))
|
||||||
- [evennia.logger](api:evennia.utils.logger) - logging tools
|
- [evennia.logger](api:evennia.utils.logger) - logging tools
|
||||||
- [evennia.ansi](api:evennia.utils.ansi) - ansi coloring 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.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.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
|
- [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)).
|
- `locale/` - Language files ([i18n](../../../Concepts/Internationalization)).
|
||||||
- [`locks/`](../../../Components/Locks) - Lock system for restricting access to in-game entities.
|
- [`locks/`](../../../Components/Locks) - Lock system for restricting access to in-game entities.
|
||||||
- [`objects/`](../../../Components/Objects) - In-game entities (all types of items and Characters).
|
- [`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)
|
- [`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.
|
- [`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.
|
- [`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
|
- [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
|
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.
|
[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
|
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
|
tell that Two goblins, while both of the class 'Goblin' (so they follow the same code logic), should have different
|
||||||
equipment, stats and looks.
|
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
|
:maxdepth: 6
|
||||||
|
|
||||||
evennia.contrib.awsstorage
|
evennia.contrib.awsstorage
|
||||||
|
evennia.contrib.crafting
|
||||||
|
evennia.contrib.evscaperoom
|
||||||
evennia.contrib.ingame_python
|
evennia.contrib.ingame_python
|
||||||
evennia.contrib.security
|
evennia.contrib.security
|
||||||
evennia.contrib.turnbattle
|
evennia.contrib.turnbattle
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@
|
||||||
- [Components/Objects](Components/Objects)
|
- [Components/Objects](Components/Objects)
|
||||||
- [Components/Outputfuncs](Components/Outputfuncs)
|
- [Components/Outputfuncs](Components/Outputfuncs)
|
||||||
- [Components/Portal And Server](Components/Portal-And-Server)
|
- [Components/Portal And Server](Components/Portal-And-Server)
|
||||||
|
- [Components/Prototypes](Components/Prototypes)
|
||||||
- [Components/Scripts](Components/Scripts)
|
- [Components/Scripts](Components/Scripts)
|
||||||
- [Components/Server](Components/Server)
|
- [Components/Server](Components/Server)
|
||||||
- [Components/Server Conf](Components/Server-Conf)
|
- [Components/Server Conf](Components/Server-Conf)
|
||||||
- [Components/Sessions](Components/Sessions)
|
- [Components/Sessions](Components/Sessions)
|
||||||
- [Components/Signals](Components/Signals)
|
- [Components/Signals](Components/Signals)
|
||||||
- [Components/Spawner and Prototypes](Components/Spawner-and-Prototypes)
|
|
||||||
- [Components/Tags](Components/Tags)
|
- [Components/Tags](Components/Tags)
|
||||||
- [Components/TickerHandler](Components/TickerHandler)
|
- [Components/TickerHandler](Components/TickerHandler)
|
||||||
- [Components/Typeclasses](Components/Typeclasses)
|
- [Components/Typeclasses](Components/Typeclasses)
|
||||||
|
|
@ -70,6 +70,7 @@
|
||||||
- [Contribs/Arxcode installing help](Contribs/Arxcode-installing-help)
|
- [Contribs/Arxcode installing help](Contribs/Arxcode-installing-help)
|
||||||
- [Contribs/Building menus](Contribs/Building-menus)
|
- [Contribs/Building menus](Contribs/Building-menus)
|
||||||
- [Contribs/Contrib Overview](Contribs/Contrib-Overview)
|
- [Contribs/Contrib Overview](Contribs/Contrib-Overview)
|
||||||
|
- [Contribs/Crafting](Contribs/Crafting)
|
||||||
- [Contribs/Dialogues in events](Contribs/Dialogues-in-events)
|
- [Contribs/Dialogues in events](Contribs/Dialogues-in-events)
|
||||||
- [Contribs/Dynamic In Game Map](Contribs/Dynamic-In-Game-Map)
|
- [Contribs/Dynamic In Game Map](Contribs/Dynamic-In-Game-Map)
|
||||||
- [Contribs/Static In Game Map](Contribs/Static-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
|
Crafting - Griatch 2020
|
||||||
|
|
||||||
This is a general crafting engine. The basic functionality of crafting is to
|
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
|
combine any number of of items or tools in a 'recipe' to produce a new result.
|
||||||
useful not only for traditional crafting but also for puzzle-solving or
|
|
||||||
similar.
|
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
|
## 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
|
- Create a new module and add it to a new list in your settings file
|
||||||
(`server/conf/settings.py`) named `CRAFT_MODULE_RECIPES`.
|
(`server/conf/settings.py`) named `CRAFT_RECIPES_MODULES`, such as
|
||||||
- In the new module, create one or more classes, each a child of
|
`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`
|
`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
|
property. It also defines what inputs are required and what is created using
|
||||||
this recipe.
|
this recipe.
|
||||||
- Objects to use for crafting should (by default) be tagged with tags using the
|
- 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
|
tag-category `crafting_material` or `crafting_tool`. The name of the object
|
||||||
its tag.
|
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!).
|
|
||||||
|
|
||||||
## Usage
|
## Crafting in game
|
||||||
|
|
||||||
By default the crafter needs to specify which components
|
The default `craft` command handles all crafting needs.
|
||||||
should be used for the recipe:
|
::
|
||||||
|
|
||||||
craft spiked club from club, nails
|
> craft spiked club from club, nails
|
||||||
|
|
||||||
Here, `spiked club` specifies the recipe while `club` and `nails` are objects
|
Here, `spiked club` specifies the recipe while `club` and `nails` are objects
|
||||||
the crafter must have in their inventory. These will be consumed during
|
the crafter must have in their inventory. These will be consumed during
|
||||||
crafting (by default only if crafting was successful).
|
crafting (by default only if crafting was successful).
|
||||||
|
|
||||||
A recipe can also require _tools_. These must be either in inventory or in
|
A recipe can also require *tools* (like the `hammer` above). These must be
|
||||||
the current location. Tools are not consumed during the crafting.
|
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
|
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
|
specifies the name of the recipe to use and expects all suitable
|
||||||
ingredients/tools as arguments (consumables and tools should be added together,
|
ingredients/tools as arguments (consumables and tools should be added together,
|
||||||
tools will be identified before consumables).
|
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
|
from evennia.contrib.crafting import crafting
|
||||||
of any error in this case (this should be handle by the recipe itself).
|
|
||||||
|
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
|
## Recipes
|
||||||
|
|
||||||
A _recipe_ works like an input/output blackbox: you put consumables (and/or
|
A *recipe* is a class that works like an input/output blackbox: you initialize
|
||||||
tools) into it and if they match the recipe, a new result is spit out.
|
it with consumables (and/or tools) if they match the recipe, a new
|
||||||
Consumables are consumed in the process while tools are not.
|
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
|
This module contains a base class for making new ingredient types
|
||||||
(`CraftingRecipeBase`) and an implementation of the most common form of
|
(`CraftingRecipeBase`) and an implementation of the most common form of
|
||||||
crafting (`CraftingRecipe`) using objects and prototypes.
|
crafting (`CraftingRecipe`) using objects and prototypes.
|
||||||
|
|
||||||
Recipes are put in one or more modules added as a list to the
|
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
|
CRAFT_RECIPE_MODULES = ['world.recipes_weapons', 'world.recipes_potions']
|
||||||
details of which properties and methods are available to override - the craft
|
|
||||||
behavior can be modified substantially this way.
|
```
|
||||||
|
|
||||||
|
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
|
```python
|
||||||
|
|
||||||
|
|
@ -73,7 +92,7 @@ behavior can be modified substantially this way.
|
||||||
class PigIronRecipe(CraftingRecipe):
|
class PigIronRecipe(CraftingRecipe):
|
||||||
# Pig iron is a high-carbon result of melting iron in a blast furnace.
|
# 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"]
|
tool_tags = ["blast furnace"]
|
||||||
consumable_tags = ["iron ore", "coal", "coal"]
|
consumable_tags = ["iron ore", "coal", "coal"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
|
|
@ -82,18 +101,26 @@ behavior can be modified substantially this way.
|
||||||
"tags": [("pig iron", "crafting_material")]}
|
"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
|
If the above class was added to a module in `CRAFT_RECIPE_MODULES`, it could be
|
||||||
recipes.
|
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 copy import copy
|
||||||
from evennia.utils.utils import (
|
from evennia.utils.utils import iter_to_str, callables_from_module, inherits_from, make_iter
|
||||||
iter_to_str, callables_from_module, inherits_from, make_iter)
|
|
||||||
from evennia.commands.cmdset import CmdSet
|
from evennia.commands.cmdset import CmdSet
|
||||||
from evennia.commands.command import Command
|
from evennia.commands.command import Command
|
||||||
from evennia.prototypes.spawner import spawn
|
from evennia.prototypes.spawner import spawn
|
||||||
|
|
@ -109,6 +136,7 @@ def _load_recipes():
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
global _RECIPE_CLASSES
|
global _RECIPE_CLASSES
|
||||||
if not _RECIPE_CLASSES:
|
if not _RECIPE_CLASSES:
|
||||||
paths = ["evennia.contrib.crafting.example_recipes"]
|
paths = ["evennia.contrib.crafting.example_recipes"]
|
||||||
|
|
@ -126,12 +154,14 @@ class CraftingError(RuntimeError):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class CraftingValidationError(CraftingError):
|
class CraftingValidationError(CraftingError):
|
||||||
"""
|
"""
|
||||||
Error if crafting validation failed.
|
Error if crafting validation failed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class CraftingRecipeBase:
|
class CraftingRecipeBase:
|
||||||
"""
|
"""
|
||||||
The recipe handles all aspects of performing a 'craft' operation. This is
|
The recipe handles all aspects of performing a 'craft' operation. This is
|
||||||
|
|
@ -164,6 +194,7 @@ class CraftingRecipeBase:
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "recipe base"
|
name = "recipe base"
|
||||||
|
|
||||||
# if set, allow running `.craft` more than once on the same instance.
|
# if set, allow running `.craft` more than once on the same instance.
|
||||||
|
|
@ -436,6 +467,7 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
shown to the crafter automatically
|
shown to the crafter automatically
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "crafting recipe"
|
name = "crafting recipe"
|
||||||
|
|
||||||
# this define the overall category all material tags must have
|
# 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_tool_missing_message = "Could not craft {outputs} without {missing}."
|
||||||
# error to show if tool-order matters and it was wrong. Missing is the first
|
# error to show if tool-order matters and it was wrong. Missing is the first
|
||||||
# tool out of order
|
# tool out of order
|
||||||
error_tool_order_message = \
|
error_tool_order_message = (
|
||||||
"Could not craft {outputs} since {missing} was added in the wrong order."
|
"Could not craft {outputs} since {missing} was added in the wrong order."
|
||||||
|
)
|
||||||
# if .exact_tools is set and there are more than needed
|
# 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})."
|
"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
|
# 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.
|
# 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_consumable_missing_message = "Could not craft {outputs} without {missing}."
|
||||||
# error to show if consumable order matters and it was wrong. Missing is the first
|
# error to show if consumable order matters and it was wrong. Missing is the first
|
||||||
# consumable out of order
|
# consumable out of order
|
||||||
error_consumable_order_message = \
|
error_consumable_order_message = (
|
||||||
"Could not craft {outputs} since {missing} was added in the wrong order."
|
"Could not craft {outputs} since {missing} was added in the wrong order."
|
||||||
|
)
|
||||||
# if .exact_consumables is set and there are more than needed
|
# 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})."
|
"Could not craft {outputs} without the exact ingredients (extra {excess})."
|
||||||
|
)
|
||||||
|
|
||||||
# this is a list of one or more prototypes (prototype_keys to existing
|
# 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
|
# 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
|
# show after a successful craft
|
||||||
success_message = "You successfully craft {outputs}!"
|
success_message = "You successfully craft {outputs}!"
|
||||||
|
|
||||||
def __init__(self, crafter, *inputs, **kwargs):
|
def __init__(self, crafter, *inputs, **kwargs):
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
crafter (Object): The one doing the crafting.
|
crafter (Object): The one doing the crafting.
|
||||||
|
|
@ -528,32 +564,37 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
|
|
||||||
# validate class properties
|
# validate class properties
|
||||||
if self.consumable_names:
|
if self.consumable_names:
|
||||||
assert len(self.consumable_names) == len(self.consumable_tags), \
|
assert len(self.consumable_names) == len(self.consumable_tags), (
|
||||||
f"Crafting {self.__class__}.consumable_names list must " \
|
f"Crafting {self.__class__}.consumable_names list must "
|
||||||
"have the same length as .consumable_tags."
|
"have the same length as .consumable_tags."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.consumable_names = self.consumable_tags
|
self.consumable_names = self.consumable_tags
|
||||||
|
|
||||||
if self.tool_names:
|
if self.tool_names:
|
||||||
assert len(self.tool_names) == len(self.tool_tags), \
|
assert len(self.tool_names) == len(self.tool_tags), (
|
||||||
f"Crafting {self.__class__}.tool_names list must " \
|
f"Crafting {self.__class__}.tool_names list must "
|
||||||
"have the same length as .tool_tags."
|
"have the same length as .tool_tags."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.tool_names = self.tool_tags
|
self.tool_names = self.tool_tags
|
||||||
|
|
||||||
if self.output_names:
|
if self.output_names:
|
||||||
assert len(self.consumable_names) == len(self.consumable_tags), \
|
assert len(self.consumable_names) == len(self.consumable_tags), (
|
||||||
f"Crafting {self.__class__}.output_names list must " \
|
f"Crafting {self.__class__}.output_names list must "
|
||||||
"have the same length as .output_prototypes."
|
"have the same length as .output_prototypes."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.output_names = [
|
self.output_names = [
|
||||||
prot.get("key", prot.get("typeclass", "unnamed"))
|
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
|
for prot in self.output_prototypes
|
||||||
]
|
]
|
||||||
|
|
||||||
assert isinstance(self.output_prototypes, (list, tuple)), \
|
assert isinstance(
|
||||||
"Crafting {self.__class__}.output_prototypes must be a list or tuple."
|
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
|
# don't allow reuse if we have consumables. If only tools we can reuse
|
||||||
# over and over since nothing changes.
|
# over and over since nothing changes.
|
||||||
|
|
@ -568,14 +609,15 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
|
|
||||||
# build template context
|
# build template context
|
||||||
mapping = {"missing": missing, "excess": excess}
|
mapping = {"missing": missing, "excess": excess}
|
||||||
mapping.update({
|
mapping.update(
|
||||||
f"i{ind}": self.consumable_names[ind]
|
{
|
||||||
for ind, name in enumerate(self.consumable_names or self.consumable_tags)
|
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"o{ind}": self.output_names[ind] for ind, name in enumerate(self.output_names)}
|
||||||
|
)
|
||||||
mapping["tools"] = involved_tools
|
mapping["tools"] = involved_tools
|
||||||
mapping["consumables"] = involved_cons
|
mapping["consumables"] = involved_cons
|
||||||
|
|
||||||
|
|
@ -633,18 +675,17 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
create_object(
|
create_object(
|
||||||
key=tool_key or (cls.tool_names[itag] if cls.tool_names else tag.capitalize()),
|
key=tool_key or (cls.tool_names[itag] if cls.tool_names else tag.capitalize()),
|
||||||
tags=[(tag, cls.tool_tag_category), *tool_tags],
|
tags=[(tag, cls.tool_tag_category), *tool_tags],
|
||||||
**tool_kwargs
|
**tool_kwargs,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
consumables = []
|
consumables = []
|
||||||
for itag, tag in enumerate(cls.consumable_tags):
|
for itag, tag in enumerate(cls.consumable_tags):
|
||||||
consumables.append(
|
consumables.append(
|
||||||
create_object(
|
create_object(
|
||||||
key=cons_key or (cls.consumable_names[itag] if
|
key=cons_key
|
||||||
cls.consumable_names else
|
or (cls.consumable_names[itag] if cls.consumable_names else tag.capitalize()),
|
||||||
tag.capitalize()),
|
|
||||||
tags=[(tag, cls.consumable_tag_category), *cons_tags],
|
tags=[(tag, cls.consumable_tag_category), *cons_tags],
|
||||||
**consumable_kwargs
|
**consumable_kwargs,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return tools, consumables
|
return tools, consumables
|
||||||
|
|
@ -669,8 +710,15 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _check_completeness(
|
def _check_completeness(
|
||||||
tagmap, taglist, namelist, exact_match, exact_order,
|
tagmap,
|
||||||
error_missing_message, error_order_message, error_excess_message):
|
taglist,
|
||||||
|
namelist,
|
||||||
|
exact_match,
|
||||||
|
exact_order,
|
||||||
|
error_missing_message,
|
||||||
|
error_order_message,
|
||||||
|
error_excess_message,
|
||||||
|
):
|
||||||
"""Compare tagmap (inputs) to taglist (required)"""
|
"""Compare tagmap (inputs) to taglist (required)"""
|
||||||
valids = []
|
valids = []
|
||||||
for itag, tagkey in enumerate(taglist):
|
for itag, tagkey in enumerate(taglist):
|
||||||
|
|
@ -682,8 +730,8 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
if exact_order:
|
if exact_order:
|
||||||
# if we get here order is wrong
|
# if we get here order is wrong
|
||||||
err = self._format_message(
|
err = self._format_message(
|
||||||
error_order_message,
|
error_order_message, missing=obj.get_display_name(looker=self.crafter)
|
||||||
missing=obj.get_display_name(looker=self.crafter))
|
)
|
||||||
self.msg(err)
|
self.msg(err)
|
||||||
raise CraftingValidationError(err)
|
raise CraftingValidationError(err)
|
||||||
|
|
||||||
|
|
@ -694,7 +742,8 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
elif exact_match:
|
elif exact_match:
|
||||||
err = self._format_message(
|
err = self._format_message(
|
||||||
error_missing_message,
|
error_missing_message,
|
||||||
missing=namelist[itag] if namelist else tagkey.capitalize())
|
missing=namelist[itag] if namelist else tagkey.capitalize(),
|
||||||
|
)
|
||||||
self.msg(err)
|
self.msg(err)
|
||||||
raise CraftingValidationError(err)
|
raise CraftingValidationError(err)
|
||||||
|
|
||||||
|
|
@ -703,21 +752,30 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
# thus this is not an exact match
|
# thus this is not an exact match
|
||||||
err = self._format_message(
|
err = self._format_message(
|
||||||
error_excess_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)
|
self.msg(err)
|
||||||
raise CraftingValidationError(err)
|
raise CraftingValidationError(err)
|
||||||
|
|
||||||
return valids
|
return valids
|
||||||
|
|
||||||
# get tools and consumables from self.inputs
|
# get tools and consumables from self.inputs
|
||||||
tool_map = {obj: obj.tags.get(category=self.tool_tag_category, return_list=True)
|
tool_map = {
|
||||||
for obj in self.inputs if obj and hasattr(obj, "tags") and
|
obj: obj.tags.get(category=self.tool_tag_category, return_list=True)
|
||||||
inherits_from(obj, "evennia.objects.models.ObjectDB")}
|
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}
|
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)
|
consumable_map = {
|
||||||
for obj in self.inputs
|
obj: obj.tags.get(category=self.consumable_tag_category, return_list=True)
|
||||||
if obj and hasattr(obj, "tags") and obj not in tool_map and
|
for obj in self.inputs
|
||||||
inherits_from(obj, "evennia.objects.models.ObjectDB")}
|
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}
|
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,
|
# 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.
|
# all the recipe needs now.
|
||||||
if len(tools) != len(self.tool_tags):
|
if len(tools) != len(self.tool_tags):
|
||||||
raise CraftingValidationError(
|
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):
|
if len(consumables) != len(self.consumable_tags):
|
||||||
raise CraftingValidationError(
|
raise CraftingValidationError(
|
||||||
f"Consumables {consumables}'s tags do not match "
|
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_tools = tools
|
||||||
self.validated_consumables = consumables
|
self.validated_consumables = consumables
|
||||||
|
|
@ -816,25 +876,29 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
|
|
||||||
def craft(crafter, recipe_name, *inputs, raise_exception=False, **kwargs):
|
def craft(crafter, recipe_name, *inputs, raise_exception=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Craft a given recipe from a source recipe module. A recipe module is a
|
Access function. Craft a given recipe from a source recipe module. A
|
||||||
Python module containing recipe classes. Note that this requires
|
recipe module is a Python module containing recipe classes. Note that this
|
||||||
`settings.CRAFT_RECIPE_MODULES` to be added to a list of one or more
|
requires `settings.CRAFT_RECIPE_MODULES` to be added to a list of one or
|
||||||
python-paths to modules holding Recipe-classes.
|
more python-paths to modules holding Recipe-classes.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
crafter (Object): The one doing the crafting.
|
crafter (Object): The one doing the crafting.
|
||||||
recipe_name (str): The `CraftRecipe.name` to use.
|
recipe_name (str): The `CraftRecipe.name` to use. This uses fuzzy-matching
|
||||||
*inputs: Suitable ingredients (Objects) to use in the crafting.
|
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
|
raise_exception (bool, optional): If crafting failed for whatever
|
||||||
reason, raise `CraftingError`. The user will still be informed by the recipe.
|
reason, raise `CraftingError`. The user will still be informed by the
|
||||||
**kwargs: Optional kwargs to pass into the recipe (will passed into recipe.craft).
|
recipe.
|
||||||
|
**kwargs: Optional kwargs to pass into the recipe (will passed into
|
||||||
|
recipe.craft).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: Crafted objects, if any.
|
list: Crafted objects, if any.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
CraftingError: If `raise_exception` is True and crafting failed to produce an output.
|
CraftingError: If `raise_exception` is True and crafting failed to
|
||||||
KeyError: If `recipe_name` failed to find a matching recipe class.
|
produce an output. KeyError: If `recipe_name` failed to find a
|
||||||
|
matching recipe class (or the hit was not precise enough.)
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
If no recipe_module is given, will look for a list `settings.CRAFT_RECIPE_MODULES` and
|
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)
|
RecipeClass = _RECIPE_CLASSES.get(recipe_name, None)
|
||||||
if not RecipeClass:
|
if not RecipeClass:
|
||||||
raise KeyError("No recipe in settings.CRAFT_RECIPE_MODULES "
|
# try a startswith fuzzy match
|
||||||
f"has a name matching {recipe_name}")
|
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)
|
recipe = RecipeClass(crafter, *inputs, **kwargs)
|
||||||
return recipe.craft(raise_exception=raise_exception)
|
return recipe.craft(raise_exception=raise_exception)
|
||||||
|
|
||||||
|
|
||||||
# craft command/cmdset
|
# craft command/cmdset
|
||||||
|
|
||||||
|
|
||||||
class CraftingCmdSet(CmdSet):
|
class CraftingCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
Store crafting command.
|
Store crafting command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "Crafting cmdset"
|
key = "Crafting cmdset"
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
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):
|
def parse(self):
|
||||||
"""
|
"""
|
||||||
Handle parsing of
|
Handle parsing of:
|
||||||
::
|
::
|
||||||
|
|
||||||
<recipe> [FROM <ingredients>] [USING <tools>]
|
<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()
|
self.args = args = self.args.strip().lower()
|
||||||
recipe, ingredients, tools = "", "", ""
|
recipe, ingredients, tools = "", "", ""
|
||||||
|
|
||||||
if 'from' in args:
|
if "from" in args:
|
||||||
recipe, *rest = args.split(" from ", 1)
|
recipe, *rest = args.split(" from ", 1)
|
||||||
rest = rest[0] if rest else ""
|
rest = rest[0] if rest else ""
|
||||||
ingredients, *tools = rest.split(" using ", 1)
|
ingredients, *tools = rest.split(" using ", 1)
|
||||||
elif 'using' in args:
|
elif "using" in args:
|
||||||
recipe, *tools = args.split(" using ", 1)
|
recipe, *tools = args.split(" using ", 1)
|
||||||
tools = tools[0] if tools else ""
|
tools = tools[0] if tools else ""
|
||||||
|
|
||||||
|
|
@ -931,13 +1020,19 @@ class CmdCraft(Command):
|
||||||
# try to include characters or accounts etc.
|
# try to include characters or accounts etc.
|
||||||
if not obj:
|
if not obj:
|
||||||
return
|
return
|
||||||
if (not inherits_from(obj, "evennia.objects.models.ObjectDB")
|
if (
|
||||||
or obj.sessions.all() or not obj.access(caller, "craft", default=True)):
|
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
|
# We don't allow to include puppeted objects nor those with the
|
||||||
# 'negative' permission 'nocraft'.
|
# 'negative' permission 'nocraft'.
|
||||||
caller.msg(obj.attributes.get(
|
caller.msg(
|
||||||
"crafting_consumable_err_msg",
|
obj.attributes.get(
|
||||||
default=f"{obj.get_display_name(looker=caller)} can't be used for this."))
|
"crafting_consumable_err_msg",
|
||||||
|
default=f"{obj.get_display_name(looker=caller)} can't be used for this.",
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
ingredients.append(obj)
|
ingredients.append(obj)
|
||||||
|
|
||||||
|
|
@ -950,9 +1045,12 @@ class CmdCraft(Command):
|
||||||
if not obj:
|
if not obj:
|
||||||
return None
|
return None
|
||||||
if not obj.access(caller, "craft", default=True):
|
if not obj.access(caller, "craft", default=True):
|
||||||
caller.msg(obj.attributes.get(
|
caller.msg(
|
||||||
"crafting_tool_err_msg",
|
obj.attributes.get(
|
||||||
default=f"{obj.get_display_name(looker=caller)} can't be used for this."))
|
"crafting_tool_err_msg",
|
||||||
|
default=f"{obj.get_display_name(looker=caller)} can't be used for this.",
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
tools.append(obj)
|
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
|
See the `SwordSmithingBaseRecipe` in this module for an example of extendng the
|
||||||
mocked 'skill' system (just random chance in our case). The skill system used
|
recipe with a mocked 'skill' system (just random chance in our case). The skill
|
||||||
is game-specific but likely to be needed for most 'real' crafting systems.
|
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
|
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
|
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
|
reference to a blast furnace used, not suggesting the crafter is carrying it
|
||||||
around with them.
|
around with them.
|
||||||
|
|
||||||
::
|
## Sword crafting tree
|
||||||
|
|
||||||
Sword crafting tree
|
::
|
||||||
|
|
||||||
# base materials (consumables)
|
# base materials (consumables)
|
||||||
|
|
||||||
|
|
@ -40,6 +41,7 @@ around with them.
|
||||||
sword = sword blade + sword guard + sword pommel
|
sword = sword blade + sword guard + sword pommel
|
||||||
+ sword handle + leather + knife[T] + hammer[T] + furnace[T]
|
+ 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.
|
Pig iron is a high-carbon result of melting iron in a blast furnace.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "pig iron"
|
name = "pig iron"
|
||||||
tool_tags = ["blast furnace"]
|
tool_tags = ["blast furnace"]
|
||||||
consumable_tags = ["iron ore", "coal", "coal"]
|
consumable_tags = ["iron ore", "coal", "coal"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Pig Iron ingot",
|
{
|
||||||
"desc": "An ingot of crude pig iron.",
|
"key": "Pig Iron ingot",
|
||||||
"tags": [("pig iron", "crafting_material")]}
|
"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).
|
crucible produces a medieval level of steel (like damascus steel).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "crucible steel"
|
name = "crucible steel"
|
||||||
tool_tags = ["crucible"]
|
tool_tags = ["crucible"]
|
||||||
consumable_tags = ["pig iron", "ash", "sand", "coal", "coal"]
|
consumable_tags = ["pig iron", "ash", "sand", "coal", "coal"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Crucible steel ingot",
|
{
|
||||||
"desc": "An ingot of multi-colored crucible steel.",
|
"key": "Crucible steel ingot",
|
||||||
"tags": [("crucible steel", "crafting_material")]}
|
"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}!"
|
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. "
|
failed_message = (
|
||||||
"You need to start over.")
|
"You work and work but you are not happy with the result. You need to start over."
|
||||||
|
)
|
||||||
|
|
||||||
def do_craft(self, **kwargs):
|
def do_craft(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -130,28 +139,35 @@ class SwordBladeRecipe(_SwordSmithingBaseRecipe):
|
||||||
part of the sword you hold on to).
|
part of the sword you hold on to).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "sword blade"
|
name = "sword blade"
|
||||||
tool_tags = ["hammer", "anvil", "furnace"]
|
tool_tags = ["hammer", "anvil", "furnace"]
|
||||||
consumable_tags = ["crucible steel"]
|
consumable_tags = ["crucible steel"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Sword blade",
|
{
|
||||||
"desc": "A long blade that may one day become a sword.",
|
"key": "Sword blade",
|
||||||
"tags": [("sword blade", "crafting_material")]}
|
"desc": "A long blade that may one day become a sword.",
|
||||||
|
"tags": [("sword blade", "crafting_material")],
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class SwordPommelRecipe(_SwordSmithingBaseRecipe):
|
class SwordPommelRecipe(_SwordSmithingBaseRecipe):
|
||||||
"""
|
"""
|
||||||
The pommel is the 'button' or 'ball' etc the end of the sword hilt, holding
|
The pommel is the 'button' or 'ball' etc the end of the sword hilt, holding
|
||||||
it together.
|
it together.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "sword pommel"
|
name = "sword pommel"
|
||||||
tool_tags = ["hammer", "anvil", "furnace"]
|
tool_tags = ["hammer", "anvil", "furnace"]
|
||||||
consumable_tags = ["crucible steel"]
|
consumable_tags = ["crucible steel"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Sword pommel",
|
{
|
||||||
"desc": "The pommel for a future sword.",
|
"key": "Sword pommel",
|
||||||
"tags": [("sword pommel", "crafting_material")]}
|
"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.
|
sword's blade and also protects the hand when parrying.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "sword guard"
|
name = "sword guard"
|
||||||
tool_tags = ["hammer", "anvil", "furnace"]
|
tool_tags = ["hammer", "anvil", "furnace"]
|
||||||
consumable_tags = ["crucible steel"]
|
consumable_tags = ["crucible steel"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Sword guard",
|
{
|
||||||
"desc": "The cross-guard for a future sword.",
|
"key": "Sword guard",
|
||||||
"tags": [("sword guard", "crafting_material")]}
|
"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.
|
Rawhide is animal skin cleaned and stripped of hair.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "rawhide"
|
name = "rawhide"
|
||||||
tool_tags = ["knife"]
|
tool_tags = ["knife"]
|
||||||
consumable_tags = ["fur"]
|
consumable_tags = ["fur"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Rawhide",
|
{
|
||||||
"desc": "Animal skin, cleaned and with hair removed.",
|
"key": "Rawhide",
|
||||||
"tags": [("rawhide", "crafting_material")]}
|
"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.
|
This produces two outputs - the bark and the cleaned wood.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "oak bark"
|
name = "oak bark"
|
||||||
tool_tags = ["knife"]
|
tool_tags = ["knife"]
|
||||||
consumable_tags = ["oak wood"]
|
consumable_tags = ["oak wood"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Oak bark",
|
{
|
||||||
"desc": "Bark of oak, stripped from the core wood.",
|
"key": "Oak bark",
|
||||||
"tags": [("oak bark", "crafting_material")]},
|
"desc": "Bark of oak, stripped from the core wood.",
|
||||||
{"key": "Oak Wood (cleaned)",
|
"tags": [("oak bark", "crafting_material")],
|
||||||
"desc": "Oakwood core, stripped of bark.",
|
},
|
||||||
"tags": [("cleaned oak wood", "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 ...
|
'tanning rack' tool should be required too ...
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "leather"
|
name = "leather"
|
||||||
tool_tags = ["cauldron"]
|
tool_tags = ["cauldron"]
|
||||||
consumable_tags = ["rawhide", "oak bark", "water"]
|
consumable_tags = ["rawhide", "oak bark", "water"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Piece of Leather",
|
{
|
||||||
"desc": "A piece of leather.",
|
"key": "Piece of Leather",
|
||||||
"tags": [("leather", "crafting_material")]}
|
"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.
|
is wrapped in leather, but that will be added at the end.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "sword handle"
|
name = "sword handle"
|
||||||
tool_tags = ["knife"]
|
tool_tags = ["knife"]
|
||||||
consumable_tags = ["cleaned oak wood"]
|
consumable_tags = ["cleaned oak wood"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Sword handle",
|
{
|
||||||
"desc": "Two pieces of wood to be be fitted onto a sword's tang as its handle.",
|
"key": "Sword handle",
|
||||||
"tags": [("sword handle", "crafting_material")]}
|
"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.
|
This covers only a single 'sword' type.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "sword"
|
name = "sword"
|
||||||
tool_tags = ["hammer", "furnace", "knife"]
|
tool_tags = ["hammer", "furnace", "knife"]
|
||||||
consumable_tags = ["sword blade", "sword guard", "sword pommel", "sword handle",
|
consumable_tags = ["sword blade", "sword guard", "sword pommel", "sword handle", "leather"]
|
||||||
"leather"]
|
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Sword",
|
{
|
||||||
"desc": "A bladed weapon.",
|
"key": "Sword",
|
||||||
# setting the tag as well - who knows if one can make something from this too!
|
"desc": "A bladed weapon.",
|
||||||
"tags": [("sword", "crafting_material")]}
|
# setting the tag as well - who knows if one can make something from this too!
|
||||||
# obviously there would be other properties of a 'sword' added here
|
"tags": [("sword", "crafting_material")],
|
||||||
# too, depending on how combat works in the your game!
|
}
|
||||||
|
# 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
|
# this requires more precision
|
||||||
exact_consumable_order = True
|
exact_consumable_order = True
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ class TestCraftUtils(TestCase):
|
||||||
Test helper utils for crafting.
|
Test helper utils for crafting.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
maxDiff = None
|
maxDiff = None
|
||||||
|
|
||||||
@override_settings(CRAFT_RECIPE_MODULES=[])
|
@override_settings(CRAFT_RECIPE_MODULES=[])
|
||||||
|
|
@ -28,17 +29,17 @@ class TestCraftUtils(TestCase):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
crafting._RECIPE_CLASSES,
|
crafting._RECIPE_CLASSES,
|
||||||
{
|
{
|
||||||
'crucible steel': example_recipes.CrucibleSteelRecipe,
|
"crucible steel": example_recipes.CrucibleSteelRecipe,
|
||||||
'leather': example_recipes.LeatherRecipe,
|
"leather": example_recipes.LeatherRecipe,
|
||||||
'oak bark': example_recipes.OakBarkRecipe,
|
"oak bark": example_recipes.OakBarkRecipe,
|
||||||
'pig iron': example_recipes.PigIronRecipe,
|
"pig iron": example_recipes.PigIronRecipe,
|
||||||
'rawhide': example_recipes.RawhideRecipe,
|
"rawhide": example_recipes.RawhideRecipe,
|
||||||
'sword': example_recipes.SwordRecipe,
|
"sword": example_recipes.SwordRecipe,
|
||||||
'sword blade': example_recipes.SwordBladeRecipe,
|
"sword blade": example_recipes.SwordBladeRecipe,
|
||||||
'sword guard': example_recipes.SwordGuardRecipe,
|
"sword guard": example_recipes.SwordGuardRecipe,
|
||||||
'sword handle': example_recipes.SwordHandleRecipe,
|
"sword handle": example_recipes.SwordHandleRecipe,
|
||||||
'sword pommel': example_recipes.SwordPommelRecipe,
|
"sword pommel": example_recipes.SwordPommelRecipe,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -54,6 +55,7 @@ class TestCraftingRecipeBase(TestCase):
|
||||||
"""
|
"""
|
||||||
Test the parent recipe class.
|
Test the parent recipe class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.crafter = mock.MagicMock()
|
self.crafter = mock.MagicMock()
|
||||||
self.crafter.msg = mock.MagicMock()
|
self.crafter.msg = mock.MagicMock()
|
||||||
|
|
@ -65,7 +67,8 @@ class TestCraftingRecipeBase(TestCase):
|
||||||
self.kwargs = {"kw1": 1, "kw2": 2}
|
self.kwargs = {"kw1": 1, "kw2": 2}
|
||||||
|
|
||||||
self.recipe = crafting.CraftingRecipeBase(
|
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):
|
def test_msg(self):
|
||||||
"""Test messaging to crafter"""
|
"""Test messaging to crafter"""
|
||||||
|
|
@ -76,9 +79,7 @@ class TestCraftingRecipeBase(TestCase):
|
||||||
def test_pre_craft(self):
|
def test_pre_craft(self):
|
||||||
"""Test validating hook"""
|
"""Test validating hook"""
|
||||||
self.recipe.pre_craft()
|
self.recipe.pre_craft()
|
||||||
self.assertEqual(
|
self.assertEqual(self.recipe.validated_inputs, (self.inp1, self.inp2, self.inp3))
|
||||||
self.recipe.validated_inputs, (self.inp1, self.inp2, self.inp3)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_pre_craft_fail(self):
|
def test_pre_craft_fail(self):
|
||||||
"""Should rase error if validation fails"""
|
"""Should rase error if validation fails"""
|
||||||
|
|
@ -126,9 +127,11 @@ class _MockRecipe(crafting.CraftingRecipe):
|
||||||
tool_tags = ["tool1", "tool2"]
|
tool_tags = ["tool1", "tool2"]
|
||||||
consumable_tags = ["cons1", "cons2", "cons3"]
|
consumable_tags = ["cons1", "cons2", "cons3"]
|
||||||
output_prototypes = [
|
output_prototypes = [
|
||||||
{"key": "Result1",
|
{
|
||||||
"prototype_key": "resultprot",
|
"key": "Result1",
|
||||||
"tags": [("result1", "crafting_material")]}
|
"prototype_key": "resultprot",
|
||||||
|
"tags": [("result1", "crafting_material")],
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -137,6 +140,7 @@ class TestCraftingRecipe(TestCase):
|
||||||
"""
|
"""
|
||||||
Test the CraftingRecipe class with one recipe
|
Test the CraftingRecipe class with one recipe
|
||||||
"""
|
"""
|
||||||
|
|
||||||
maxDiff = None
|
maxDiff = None
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -162,19 +166,27 @@ class TestCraftingRecipe(TestCase):
|
||||||
def test_error_format(self):
|
def test_error_format(self):
|
||||||
"""Test the automatic error formatter """
|
"""Test the automatic error formatter """
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter,
|
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
||||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = ("{missing},{tools},{consumables},{inputs},{outputs}"
|
msg = "{missing},{tools},{consumables},{inputs},{outputs}" "{i0},{i1},{o0}"
|
||||||
"{i0},{i1},{o0}")
|
kwargs = {
|
||||||
kwargs = {"missing": "foo", "tools": ["bar", "bar2", "bar3"],
|
"missing": "foo",
|
||||||
"consumables": ["cons1", "cons2"]}
|
"tools": ["bar", "bar2", "bar3"],
|
||||||
|
"consumables": ["cons1", "cons2"],
|
||||||
|
}
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'missing': 'foo', 'i0': 'cons1', 'i1': 'cons2', 'i2': 'cons3', 'o0':
|
"missing": "foo",
|
||||||
'Result1', 'tools': 'bar, bar2 and bar3', 'consumables': 'cons1 and cons2',
|
"i0": "cons1",
|
||||||
'inputs': 'cons1, cons2 and cons3', 'outputs': 'Result1'}
|
"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)
|
result = recipe._format_message(msg, **kwargs)
|
||||||
self.assertEqual(result, msg.format(**expected))
|
self.assertEqual(result, msg.format(**expected))
|
||||||
|
|
@ -182,16 +194,16 @@ class TestCraftingRecipe(TestCase):
|
||||||
def test_craft__success(self):
|
def test_craft__success(self):
|
||||||
"""Test to create a result from the recipe"""
|
"""Test to create a result from the recipe"""
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter,
|
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
||||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
|
|
||||||
self.assertEqual(result[0].key, "Result1")
|
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(
|
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
|
# make sure consumables are gone
|
||||||
self.assertIsNone(self.cons1.pk)
|
self.assertIsNone(self.cons1.pk)
|
||||||
|
|
@ -208,17 +220,15 @@ class TestCraftingRecipe(TestCase):
|
||||||
tools, consumables = _MockRecipe.seed()
|
tools, consumables = _MockRecipe.seed()
|
||||||
|
|
||||||
# this should be a normal successful crafting
|
# this should be a normal successful crafting
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(self.crafter, *(tools + consumables))
|
||||||
self.crafter,
|
|
||||||
*(tools + consumables)
|
|
||||||
)
|
|
||||||
|
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
|
|
||||||
self.assertEqual(result[0].key, "Result1")
|
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(
|
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
|
# make sure consumables are gone
|
||||||
for cons in consumables:
|
for cons in consumables:
|
||||||
|
|
@ -229,15 +239,13 @@ class TestCraftingRecipe(TestCase):
|
||||||
|
|
||||||
def test_craft_missing_tool__fail(self):
|
def test_craft_missing_tool__fail(self):
|
||||||
"""Fail craft by missing tool2"""
|
"""Fail craft by missing tool2"""
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(self.crafter, self.tool1, self.cons1, self.cons2, self.cons3)
|
||||||
self.crafter,
|
|
||||||
self.tool1, self.cons1, self.cons2, self.cons3
|
|
||||||
)
|
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
self.crafter.msg.assert_called_with(
|
self.crafter.msg.assert_called_with(
|
||||||
recipe.error_tool_missing_message.format(outputs="Result1", missing='tool2'),
|
recipe.error_tool_missing_message.format(outputs="Result1", missing="tool2"),
|
||||||
{"type": "crafting"})
|
{"type": "crafting"},
|
||||||
|
)
|
||||||
|
|
||||||
# make sure consumables are still there
|
# make sure consumables are still there
|
||||||
self.assertIsNotNone(self.cons1.pk)
|
self.assertIsNotNone(self.cons1.pk)
|
||||||
|
|
@ -249,16 +257,13 @@ class TestCraftingRecipe(TestCase):
|
||||||
|
|
||||||
def test_craft_missing_cons__fail(self):
|
def test_craft_missing_cons__fail(self):
|
||||||
"""Fail craft by missing cons3"""
|
"""Fail craft by missing cons3"""
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(self.crafter, self.tool1, self.tool2, self.cons1, self.cons2)
|
||||||
self.crafter,
|
|
||||||
self.tool1, self.tool2, self.cons1, self.cons2
|
|
||||||
)
|
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
self.crafter.msg.assert_called_with(
|
self.crafter.msg.assert_called_with(
|
||||||
recipe.error_consumable_missing_message.format(
|
recipe.error_consumable_missing_message.format(outputs="Result1", missing="cons3"),
|
||||||
outputs="Result1", missing='cons3'),
|
{"type": "crafting"},
|
||||||
{"type": "crafting"})
|
)
|
||||||
|
|
||||||
# make sure consumables are still there
|
# make sure consumables are still there
|
||||||
self.assertIsNotNone(self.cons1.pk)
|
self.assertIsNotNone(self.cons1.pk)
|
||||||
|
|
@ -273,19 +278,16 @@ class TestCraftingRecipe(TestCase):
|
||||||
|
|
||||||
cons4 = create_object(key="cons4", tags=[("cons4", "crafting_material")], nohome=True)
|
cons4 = create_object(key="cons4", tags=[("cons4", "crafting_material")], nohome=True)
|
||||||
|
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, cons4)
|
||||||
self.crafter,
|
|
||||||
self.tool1, self.tool2, self.cons1, self.cons2, cons4
|
|
||||||
)
|
|
||||||
recipe.consume_on_fail = True
|
recipe.consume_on_fail = True
|
||||||
|
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
|
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
self.crafter.msg.assert_called_with(
|
self.crafter.msg.assert_called_with(
|
||||||
recipe.error_consumable_missing_message.format(
|
recipe.error_consumable_missing_message.format(outputs="Result1", missing="cons3"),
|
||||||
outputs="Result1", missing='cons3'),
|
{"type": "crafting"},
|
||||||
{"type": "crafting"})
|
)
|
||||||
|
|
||||||
# make sure consumables are deleted even though we failed
|
# make sure consumables are deleted even though we failed
|
||||||
self.assertIsNone(self.cons1.pk)
|
self.assertIsNone(self.cons1.pk)
|
||||||
|
|
@ -303,16 +305,15 @@ class TestCraftingRecipe(TestCase):
|
||||||
|
|
||||||
wrong = create_object(key="wrong", tags=[("wrongtool", "crafting_tool")], nohome=True)
|
wrong = create_object(key="wrong", tags=[("wrongtool", "crafting_tool")], nohome=True)
|
||||||
|
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, wrong)
|
||||||
self.crafter,
|
|
||||||
self.tool1, self.tool2, self.cons1, self.cons2, wrong
|
|
||||||
)
|
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
self.crafter.msg.assert_called_with(
|
self.crafter.msg.assert_called_with(
|
||||||
recipe.error_tool_excess_message.format(
|
recipe.error_tool_excess_message.format(
|
||||||
outputs="Result1", excess=wrong.get_display_name(looker=self.crafter)),
|
outputs="Result1", excess=wrong.get_display_name(looker=self.crafter)
|
||||||
{"type": "crafting"})
|
),
|
||||||
|
{"type": "crafting"},
|
||||||
|
)
|
||||||
# make sure consumables are still there
|
# make sure consumables are still there
|
||||||
self.assertIsNotNone(self.cons1.pk)
|
self.assertIsNotNone(self.cons1.pk)
|
||||||
self.assertIsNotNone(self.cons2.pk)
|
self.assertIsNotNone(self.cons2.pk)
|
||||||
|
|
@ -328,15 +329,16 @@ class TestCraftingRecipe(TestCase):
|
||||||
tool3 = create_object(key="tool3", tags=[("tool2", "crafting_tool")], nohome=True)
|
tool3 = create_object(key="tool3", tags=[("tool2", "crafting_tool")], nohome=True)
|
||||||
|
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter,
|
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, tool3
|
||||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, tool3
|
|
||||||
)
|
)
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
self.crafter.msg.assert_called_with(
|
self.crafter.msg.assert_called_with(
|
||||||
recipe.error_tool_excess_message.format(
|
recipe.error_tool_excess_message.format(
|
||||||
outputs="Result1", excess=tool3.get_display_name(looker=self.crafter)),
|
outputs="Result1", excess=tool3.get_display_name(looker=self.crafter)
|
||||||
{"type": "crafting"})
|
),
|
||||||
|
{"type": "crafting"},
|
||||||
|
)
|
||||||
|
|
||||||
# make sure consumables are still there
|
# make sure consumables are still there
|
||||||
self.assertIsNotNone(self.cons1.pk)
|
self.assertIsNotNone(self.cons1.pk)
|
||||||
|
|
@ -354,15 +356,16 @@ class TestCraftingRecipe(TestCase):
|
||||||
cons4 = create_object(key="cons4", tags=[("cons3", "crafting_material")], nohome=True)
|
cons4 = create_object(key="cons4", tags=[("cons3", "crafting_material")], nohome=True)
|
||||||
|
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter,
|
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, cons4
|
||||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, cons4
|
|
||||||
)
|
)
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
self.crafter.msg.assert_called_with(
|
self.crafter.msg.assert_called_with(
|
||||||
recipe.error_consumable_excess_message.format(
|
recipe.error_consumable_excess_message.format(
|
||||||
outputs="Result1", excess=cons4.get_display_name(looker=self.crafter)),
|
outputs="Result1", excess=cons4.get_display_name(looker=self.crafter)
|
||||||
{"type": "crafting"})
|
),
|
||||||
|
{"type": "crafting"},
|
||||||
|
)
|
||||||
|
|
||||||
# make sure consumables are still there
|
# make sure consumables are still there
|
||||||
self.assertIsNotNone(self.cons1.pk)
|
self.assertIsNotNone(self.cons1.pk)
|
||||||
|
|
@ -379,14 +382,14 @@ class TestCraftingRecipe(TestCase):
|
||||||
tool3 = create_object(key="tool3", tags=[("tool2", "crafting_tool")], nohome=True)
|
tool3 = create_object(key="tool3", tags=[("tool2", "crafting_tool")], nohome=True)
|
||||||
|
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter,
|
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, tool3
|
||||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, tool3
|
|
||||||
)
|
)
|
||||||
recipe.exact_tools = False
|
recipe.exact_tools = False
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
self.crafter.msg.assert_called_with(
|
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
|
# make sure consumables are gone
|
||||||
self.assertIsNone(self.cons1.pk)
|
self.assertIsNone(self.cons1.pk)
|
||||||
|
|
@ -402,14 +405,14 @@ class TestCraftingRecipe(TestCase):
|
||||||
cons4 = create_object(key="cons4", tags=[("cons3", "crafting_material")], nohome=True)
|
cons4 = create_object(key="cons4", tags=[("cons3", "crafting_material")], nohome=True)
|
||||||
|
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter,
|
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, cons4
|
||||||
self.tool1, self.tool2, self.cons1, self.cons2, self.cons3, cons4
|
|
||||||
)
|
)
|
||||||
recipe.exact_consumables = False
|
recipe.exact_consumables = False
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
self.crafter.msg.assert_called_with(
|
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
|
# make sure consumables are gone
|
||||||
self.assertIsNone(self.cons1.pk)
|
self.assertIsNone(self.cons1.pk)
|
||||||
|
|
@ -422,16 +425,17 @@ class TestCraftingRecipe(TestCase):
|
||||||
def test_craft_tool_order__fail(self):
|
def test_craft_tool_order__fail(self):
|
||||||
"""Strict tool-order recipe fail """
|
"""Strict tool-order recipe fail """
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter,
|
self.crafter, self.tool2, self.tool1, self.cons1, self.cons2, self.cons3
|
||||||
self.tool2, self.tool1, self.cons1, self.cons2, self.cons3
|
|
||||||
)
|
)
|
||||||
recipe.exact_tool_order = True
|
recipe.exact_tool_order = True
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
self.crafter.msg.assert_called_with(
|
self.crafter.msg.assert_called_with(
|
||||||
recipe.error_tool_order_message.format(
|
recipe.error_tool_order_message.format(
|
||||||
outputs="Result1", missing=self.tool2.get_display_name(looker=self.crafter)),
|
outputs="Result1", missing=self.tool2.get_display_name(looker=self.crafter)
|
||||||
{"type": "crafting"})
|
),
|
||||||
|
{"type": "crafting"},
|
||||||
|
)
|
||||||
|
|
||||||
# make sure consumables are still there
|
# make sure consumables are still there
|
||||||
self.assertIsNotNone(self.cons1.pk)
|
self.assertIsNotNone(self.cons1.pk)
|
||||||
|
|
@ -444,16 +448,17 @@ class TestCraftingRecipe(TestCase):
|
||||||
def test_craft_cons_order__fail(self):
|
def test_craft_cons_order__fail(self):
|
||||||
"""Strict tool-order recipe fail """
|
"""Strict tool-order recipe fail """
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter,
|
self.crafter, self.tool1, self.tool2, self.cons3, self.cons2, self.cons1
|
||||||
self.tool1, self.tool2, self.cons3, self.cons2, self.cons1
|
|
||||||
)
|
)
|
||||||
recipe.exact_consumable_order = True
|
recipe.exact_consumable_order = True
|
||||||
result = recipe.craft()
|
result = recipe.craft()
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
self.crafter.msg.assert_called_with(
|
self.crafter.msg.assert_called_with(
|
||||||
recipe.error_consumable_order_message.format(
|
recipe.error_consumable_order_message.format(
|
||||||
outputs="Result1", missing=self.cons3.get_display_name(looker=self.crafter)),
|
outputs="Result1", missing=self.cons3.get_display_name(looker=self.crafter)
|
||||||
{"type": "crafting"})
|
),
|
||||||
|
{"type": "crafting"},
|
||||||
|
)
|
||||||
|
|
||||||
# make sure consumables are still there
|
# make sure consumables are still there
|
||||||
self.assertIsNotNone(self.cons1.pk)
|
self.assertIsNotNone(self.cons1.pk)
|
||||||
|
|
@ -469,6 +474,7 @@ class TestCraftSword(TestCase):
|
||||||
Test the `craft` function by crafting the example sword.
|
Test the `craft` function by crafting the example sword.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.crafter = mock.MagicMock()
|
self.crafter = mock.MagicMock()
|
||||||
self.crafter.msg = mock.MagicMock()
|
self.crafter.msg = mock.MagicMock()
|
||||||
|
|
@ -578,8 +584,16 @@ class TestCraftSword(TestCase):
|
||||||
sword_handle = _craft("sword handle", *inputs)
|
sword_handle = _craft("sword handle", *inputs)
|
||||||
|
|
||||||
# sword (order matters)
|
# sword (order matters)
|
||||||
inputs = [sword_blade, sword_guard, sword_pommel, sword_handle,
|
inputs = [
|
||||||
leather, knife, hammer, furnace]
|
sword_blade,
|
||||||
|
sword_guard,
|
||||||
|
sword_pommel,
|
||||||
|
sword_handle,
|
||||||
|
leather,
|
||||||
|
knife,
|
||||||
|
hammer,
|
||||||
|
furnace,
|
||||||
|
]
|
||||||
sword = _craft("sword", *inputs)
|
sword = _craft("sword", *inputs)
|
||||||
|
|
||||||
self.assertEqual(sword.key, "Sword")
|
self.assertEqual(sword.key, "Sword")
|
||||||
|
|
@ -633,10 +647,8 @@ class TestCraftSword(TestCase):
|
||||||
self.assertIsNotNone(cauldron)
|
self.assertIsNotNone(cauldron)
|
||||||
|
|
||||||
|
|
||||||
@mock.patch("evennia.contrib.crafting.crafting._load_recipes",
|
@mock.patch("evennia.contrib.crafting.crafting._load_recipes", new=mock.MagicMock())
|
||||||
new=mock.MagicMock())
|
@mock.patch("evennia.contrib.crafting.crafting._RECIPE_CLASSES", new={"testrecipe": _MockRecipe})
|
||||||
@mock.patch("evennia.contrib.crafting.crafting._RECIPE_CLASSES",
|
|
||||||
new={"testrecipe": _MockRecipe})
|
|
||||||
@override_settings(CRAFT_RECIPE_MODULES=[], DEFAULT_HOME="#999999")
|
@override_settings(CRAFT_RECIPE_MODULES=[], DEFAULT_HOME="#999999")
|
||||||
class TestCraftCommand(CommandTest):
|
class TestCraftCommand(CommandTest):
|
||||||
"""Test the crafting command"""
|
"""Test the crafting command"""
|
||||||
|
|
@ -645,15 +657,15 @@ class TestCraftCommand(CommandTest):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
tools, consumables = _MockRecipe.seed(
|
tools, consumables = _MockRecipe.seed(
|
||||||
tool_kwargs={"location": self.char1},
|
tool_kwargs={"location": self.char1}, consumable_kwargs={"location": self.char1}
|
||||||
consumable_kwargs={"location": self.char1})
|
)
|
||||||
|
|
||||||
def test_craft__success(self):
|
def test_craft__success(self):
|
||||||
"Successfully craft using command"
|
"Successfully craft using command"
|
||||||
self.call(
|
self.call(
|
||||||
crafting.CmdCraft(),
|
crafting.CmdCraft(),
|
||||||
"testrecipe from cons1, cons2, cons3 using tool1, tool2",
|
"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):
|
def test_craft__notools__failure(self):
|
||||||
|
|
@ -661,12 +673,12 @@ class TestCraftCommand(CommandTest):
|
||||||
self.call(
|
self.call(
|
||||||
crafting.CmdCraft(),
|
crafting.CmdCraft(),
|
||||||
"testrecipe from cons1, cons2, cons3",
|
"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):
|
def test_craft__nocons__failure(self):
|
||||||
self.call(
|
self.call(
|
||||||
crafting.CmdCraft(),
|
crafting.CmdCraft(),
|
||||||
"testrecipe using tool1, tool2",
|
"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(), "", "What do you want to say?", cmdstring="")
|
||||||
self.call(commands.CmdSpeak(), "Hello!", "You say: Hello!", 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(), "", "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!", "You shout: HELLO!", cmdstring="shout")
|
||||||
|
|
||||||
self.call(commands.CmdSpeak(), "Hello to obj", "You say: Hello", cmdstring="say")
|
self.call(commands.CmdSpeak(), "Hello", "You say: Hello", cmdstring="say")
|
||||||
self.call(commands.CmdSpeak(), "Hello to obj", "You shout: Hello", cmdstring="shout")
|
self.call(commands.CmdSpeak(), "Hello", "You shout: HELLO", cmdstring="shout")
|
||||||
|
|
||||||
def test_emote(self):
|
def test_emote(self):
|
||||||
self.call(
|
self.call(
|
||||||
|
|
@ -272,7 +271,7 @@ class TestStates(EvenniaTest):
|
||||||
dirname = path.join(path.dirname(__file__), "states")
|
dirname = path.join(path.dirname(__file__), "states")
|
||||||
states = []
|
states = []
|
||||||
for imp, module, ispackage in pkgutil.walk_packages(
|
for imp, module, ispackage in pkgutil.walk_packages(
|
||||||
path=[dirname], prefix="evscaperoom.states."
|
path=[dirname], prefix="evennia.contrib.evscaperoom.states."
|
||||||
):
|
):
|
||||||
mod = mod_import(module)
|
mod = mod_import(module)
|
||||||
states.append(mod)
|
states.append(mod)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue