Here’s a TADS3 module for (hopefully easily) implementing crafting systems: craftingSystem github repo.
It’s built on the ruleEngine and stateMachine modules I’ve discussed previously.
I’ll just dive into a simple example: making toast. First, as always, we need to declare a RuleEngine
instance (if we don’t, or if we comment it out, then none of the rule-based stuff will be enabled, which can be useful for testing):
// Declare a RuleEngine instance.
RuleEngine;
Then we declare a CraftingSystem
instance. It can be anonymous, but you probably want to add a label so you can refer to it elsewhere in your code:
// Declare the crafting system.
cookingSystem: CraftingSystem;
Recipes are declared by adding them to the CraftingSystem
instance using the normal TADS3 +[object declaration]
syntax. Here’s just the barebones nuts and bolts of our “make toast” recipe (we’ll fill it out later):
+Recipe 'toast' @Toast ->toaster;
++Ingredient @Bread;
++RecipeAction @toaster ->TurnOnAction;
Line by line, that’s:
- A
Recipe
declaration. It creates aRecipe
with the ID “toast” (the single-quoted string). The recipe will produce an instance ofToast
(the class name after the@
). The created object will end up in thetoaster
object (the object after the->
). Note that theToast
class andtoaster
object need to be declared elsewhere. - An
Ingredient
declaration. This says that the recipe wants aBread
object (the class after the@
).Ingredient
declarations can include locations, but this one doesn’t. So by default the recipe will expect the ingredients to go in the same place the recipe results will go, in this case thetoaster
. - A
RecipeAction
declaration. This is an action trigger (under the hood aTrigger
like used in theruleEngine
module). It fires when thetoaster
(object after the@
) receives theTurnOnAction
(action name after the->
). That is, the trigger will fire on the>TURN TOASTER ON
command.
All together, this recipe will be matched by the command sequence:
>PUT BREAD IN TOASTER
>TURN TOASTER ON
…assuming the player has bread to put into the toaster, the toaster is in the room, and so on.
When the sequence is completed, the bread will be whisked away (moved into nil
) and a new Toast
instance will be placed in the toaster.
This all works, but note that there are no informational messages or other feedback supplied in the recipe as written above. So let’s go through it again to make it more friendly:
+Recipe 'toast' @Toast ->toaster
"The toaster produces a slice of toast. ";
++RecipeNoAction @toaster ->TurnOnAction
"The toaster won't start without bread. ";
++IngredientList
"{You/He} put{s} the bread in the toaster. ";
+++Ingredient @Bread;
++RecipeAction @toaster ->TurnOnAction
"{You/he} start{s} the toaster. ";
The first thing to notice is all the double-quoted strings. These will be displayed when the corresponding part of the recipe is completed. For example, the Recipe
declaration:
+Recipe 'toast' @Toast ->toaster
"The toaster produces a slice of toast. ";
…means that "The toaster produces a slice of toast. " will be output when the recipe is completed.
The double-quoted strings are an optional part of the template for most of the recipe parts, but you can alternately add a recipeAction()
method to the declaration:
+Recipe 'toast' @Toast ->toaster
recipeAction() {
"The toaster produces a slice of toast. ";
}
;
In this case it would work exactly the same as the more concise version, but this allows arbitrary code to be executed during the appropriate point in the recipe.
Additionally, the expanded version of the recipe includes a RecipeNoAction
declaration:
++RecipeNoAction @toaster ->TurnOnAction
"The toaster won't start without bread. ";
This works like a RecipeAction
, but doesn’t change the recipe state. In this case it’s just for an informational message if the toaster is turned on before bread is added.
The next thing to notice in the expanded version of the recipe is an explicit IngredientList
declaration. In the simpler version of the recipe the Ingredient
was added directly to the Recipe
.
All Ingredient
declarations are added to the “nearest” IngredientList
above them. If no IngredientList
exists in the recipe, one is created automagically.
For recipes with only one “combine a bunch of ingredients” step you don’t have to declare an explicit IngredientList
, but if you have multiple steps combining ingredients you will.
Giving an explicit IngredientList
also allows you to attach an informational message and/or a full recipeAction()
method, which you cannot do with a Ingredient
declaration.
Here’s a complete example that will compile with the craftingSystem
module:
#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>
#include "craftingSystem.h"
versionInfo: GameID;
gameMain: GameMainDef initialPlayerChar = me;
class Slice: Thing, CraftingIngredient
desc = "It's <<aName>>. "
isEquivalent = true
;
class Bread: Slice '(slice) bread' 'slice of bread';
class Toast: Slice '(slice) toast' 'slice of toast';
startRoom: Room 'Void' "This is a featureless void.";
+toaster: Container, CraftingGear '(silver) (metal) toaster slot' 'toaster'
"A silver toaster with a single slot on the top. "
dobjFor(TurnOn) { verify() {} }
iobjFor(PutIn) {
verify() {
if(contents.length != 0)
illogicalNow('The toaster can only hold one
thing at a time. ');
}
}
canFitObjThruOpening(obj) { return(obj.ofKind(Slice)); }
;
+me: Person;
++Bread;
++Bread;
RuleEngine;
CraftingSystem;
+Recipe 'toast' @Toast ->toaster "The toaster produces a slice of toast. ";
++RecipeNoAction @toaster ->TurnOnAction
"The toaster won't start without bread. ";
++IngredientList "{You/He} put{s} the bread in the toaster. ";
+++Ingredient @Bread;
++RecipeAction @toaster ->TurnOnAction "{You/he} start{s} the toaster. ";
Thrilling transcript:
Void
This is a featureless void.
You see a toaster here.
>turn toaster on
The toaster won't start without bread.
>put bread in toaster
You put the bread in the toaster.
>put bread in toaster
The toaster can only hold one thing at a time.
>turn toaster on
You start the toaster. The toaster produces a slice of toast.
>turn toaster on
The toaster won't start without bread.
>l
Void
This is a featureless void.
You see a toaster (which contains a slice of toast) here.
>take toast
Taken.
>turn toaster on
The toaster won't start without bread.
>put bread in toaster
You put the bread in the toaster.
>turn toaster on
You start the toaster. The toaster produces a slice of toast.