(unless: (passage:)'s tags contains "noheader")[{<div class="header"><div class="title">The Harlowe cookbook</div>
<div class="menubar">
(link-repeat: "Home")[(gotourl: "index.html")] | (link-repeat: "Twine")[(gotourl: "twine_guide.html")] | [[Cookbook->start]] | (link-repeat: "Examples")[(gotourl: "index.html?p=Examples")] | (link-repeat: "Links")[(gotourl: "index.html?p=useful")]
</div>
<div class="options">
{(set: $url to "Cookbook_test.html?p=" + (passage:)'s name)
(print: window.set_adressbar($url))
(if: window.history_api_available)[<script>$("tw-sidebar").css("display", "none");</script>]
(set: $source_list to (array: "View sourcecode", "Hide sourcecode"))
(set: $source_index to 1)
(link-repeat: "[(print: $source_list's $source_index)]<source_link|")[(set: $source_index to it + 1)(if: $source_index > $source_list's length)[(set: $source_index to 1)](replace: ?source_link)[(print: $source_list's $source_index)](if: $source_index is 1)[(replace: ?src)[]](else:)[(replace: ?src)[(print: window.displaySource())]]]}
</div>
</div>
}
[]<src|]
###Cookbook
[[Notes about this guide->notes]]
[[One liners->basics]]
Basic ideas which can be implemented with a single line of code.
[[Styling->css]]
Using CSS to change your story's appearance.
[[Multimedia->media]]
Adding pictures, sound or videos.
[[Systems->systems]]
In depth systems which are commonly used in games.
[[Miscelaneous->misc]]
Everything else.
[[Index->index]]
(set: $up to "start")(display: "cookbook_nav")
###Index
{(set: $keywords to (a: "combat", "darkness", "inventory", "links", "random", "cycling links", "conversation", "`(link:)`", "`(link-repeat:)`", "`(goto:)`", "`(display:)`", "javascript", "`(print:)`", "`(set:)`", "CSS", "sidebar", "picture", "multimedia", "setter links"))
(set: $index to makeIndex($keywords))}
(print: displayIndex($index))
(display: "cookbook_nav")
(set: $startPassage to window.getStartPassage())
(if: $startPassage is not "")[(goto: $startPassage)](else:)[(goto: "start")]
<div class="navigation">(if: $up is not "")[(link-goto: "Up", $up)] | [[Contents->start]] | [[Index->index]]</div>
(set: $up to "start")(display: "cookbook_nav")
#One liners
Different ways to combine the various Harlowe macros to produce certain effects.
[[Fun with links->links]]
Different things you can do using the `(link:)` family of macros.
[[Arrays->arrays]]
[[Datamaps->datamaps]]
[[Saving and loading the player's progress->saving]]
(display: "cookbook_nav")
(set: $up to "start")(display: "cookbook_nav")
###Styling
Here are some ways to change the look of your story using CSS
[[Styling a passage based on its tags->tagstyle]]
[[Adding a sidebar->sidebar_doc]]
[[Adding a picture in a box->picturebox]]
[[Hiding the startup passage->startuphide]]
(display: "cookbook_nav")
(set: $up to "start")(display: "cookbook_nav")
###Multimedia
Harlowe doesn't include any macros for handling multimedia elements, but it's possible to add them using HTML elements.
[[Linking to multimedia->link_media]]
[[Embedding multimedia->embed_media]]
(display: "cookbook_nav")
(set: $up to "start")(display: "cookbook_nav")
Systems which are commonly used in games:
[[Inventory->inventory]]
Picking up and carrying stuff
[[Darkness->darkness]]
Grues optional
[[Combat->combat]]
Violence based problem solving
[[Conversation->conversation]]
If only you could talk to the monsters
(display: "cookbook_nav")
(set: $up to "start")(display: "cookbook_nav")
###Miscelaneous
[[Cycling links->cycle]]
[[Countdowns->countdown]]
(display: "cookbook_nav")
(set: $up to "systems")(display: "cookbook_nav")
[[Simple inventory system->simple_inv]]
[[Complicated inventory system->complex_inv]]
(display: "cookbook_nav")
(set: $up to "systems")(display: "cookbook_nav")
###Combat systems
One of the selling points of Twine is the ability to make games which play like choose your own adventure books. Two popular CYOA series were the Fighting Fantasy and the Lone Wolf books. One thing that set these apart from other CYOA books was the fact they included combat.
[[Simple combat->simple_combat]]
A system based on the one used in the Fighting Fantasy books.
[[Complex combat->complex_combat]]
A system which integrates with the players inventory.
(set: $up to "systems")(display: "cookbook_nav")
###Conversation systems
####A conversation about conversations
One thing to be aware of is that making interesting, believable conversation systems is a complicated subject. There is a lot of work still going on in creating conversation systems which flow naturally.
The things we need to think about are how to store the comments and responses, and how the dialogue options should affect both the flow of the conversation and the game as a whole.
[[A conversation tree->con_tree]]
[[Knowledge based conversation->con_know]]
(display: "cookbook_nav")
(set: $up to "systems")(display: "cookbook_nav")
###Darkness
A common trope in interarctive fiction is dark rooms which can't be navigated unless a light source has been discovered.
[[Simple darknes system->simple_dark]]
A simple system which uses a flag to indicate the presence of a light source.
[[Inventory based darkness system->complex_dark]]
A more advanced system which ties in with the [[inventory system->complex_inv]].
[[Day/night systems->daynight]]
Adding night time to a game.
(display: "cookbook_nav")
(set: $up to "inventory")(display: "cookbook_nav")
###A Basic Inventory system
####Storing the inventory
The easiest way to store the player's inventory is to use an array. In the startup passage:
`(set: $inv to (a:))`
####Picking an item up
It's possible to add items to an array by adding another array.
E.G.
`You can see a (link: "rusty key")[(set $inv to $inv + (a: "rusty key"))]`
####Dropping an item
This is done by subtracting an array containing the item's name
`(set: $inv to $inv - (a: "rusty key"))`
####Testing to see if the player is carrying an item
The contains operator can be used to test if the item is being carried:
`(if: $inv contains "rusty key")[You can unlock the door]`
####Only allowing the player to carry a certain number of items
If the player is only allowed to carry 5 items (which is stored in a variable, `$inv_limit`)
`You can see a (link: "rusty key")[(if: $inv's length < $inv_limit)[(set $inv to $inv + (a: "rusty key"))](else:)[You can't carry any more]]`
####Display what the player is carrying
If there's a lot of items, then you'll probably want the player to be able to see what they're carrying. The obvious way to do this would be to just print the array:
`(print: $inv)`
However that just prints the inventory as a comma seperated list, with no way to customise the output.
A better way would be to use javascript to convert the array into a string. for example using the join command allows you to specify a seperator. so to print every item on a seperate line:
`(print: $inv.join("\n"))`
`\n` represents a new line.
(print: window.gotoURL("Play example game", "recipes/recipe simple inventory.html"))
(display: "cookbook_nav")
(set: $up to "inventory")(display: "cookbook_nav")
### Advanced inventory system
The inventory system described before is fine as it goes, but it's possible to do something a bit more advanced. Which creates links to automatically pick up or drop an item, and to give each item properties such as light, allowing the implementation of a system for dark rooms.
<div class="warning">This system requires some poking at Harlowe's internals. Which means that changes to Harlowe may break it.</div>
There's more setup involved. In your set up passage write the following:
<div class="code">`(set: $itemlist to (datamap: "torch", (datamap: "name", "A torch", "description", "The torch emits a powerful light")))
(set: $inv to (a:))
(set: $map to (datamap: "hall", (a:), "diningroom", (a:), "kitchen", (a:)))
(set: $offstage to (a: "torch"))`</div>
This creates a datamap of items, mapping each item's ID to it's property. In this example the properties are just the name (what to print in item lists) and it's description (which will appear as a tool tip when the player hovers over the item).
Next it creats an inventory array as usual.
`$map` is a datamap of all the passages that can hold items, mapping them to an array of the items that they contain at the start of the story. Note the `$map` should contain not just the passages which hold items at the start of the story but also any passages where the player can drop an item.
Lastly `$offstage` is an array holding any items which aren't available at the start of the story.
Note the arrays for `$inv`, `$map` and `$offstage` should all hold the IDs of the items.
When displaying the inventory, or the contents of the room, we don't just want to list the items, we want them to be hyperlinks. So we need to add the following javascript
<div class="code">`window.make_link = function (element) {
var items = State.variables.itemlist;
var passage = this;
var name = items.get(element).get("name");
var desc = items.get(element).get("description");`
`var link_text = "(link`:` \"" + name + "\"\)[(set`:` $__item to \"" + element + "\"\)(goto: \"" + passage + "\"\)]";`
`if(desc) {
link_text = ("<span title=\"" + desc + "\">" + link_text + "</span>");
}
return link_text;
};`</div>
While we're editting the javascript, also add the following. It'll make moving items about easier.
<div class="code">`window.move_object = function (object, source, dest) {
var index = source.indexOf(object);
if(index != -1){
source.splice(index, 1);
dest.push(object);
} else {
return "The object '" + object + "' is not in the '" + source + "'.";
}
return "";
};`</div>
It will be neccessary to check if a passage exists, so the last bit of javascript to add is:
<div class="code">`State.variables.passages = Passages`</div>
This creates assigns a map of all the passages in the story to the variable `$passages`.
To display the inventory and room contents we need the following:
<div class="code">`
You see:
{(set: $list to "nothing")
(if: $map contains $loc)[
(set: $items to $map's $loc)
(if: $items.length > 0)[
<!--converts an array of objects into an array of links and then joins them with newlines between each item-->
(set: $list to $items.map(window.make_link, "take").join("\n"))
] ]
$list
}
You are carrying:
{(if: $inv.length > 0)[
<!--converts an array of objects into an array of links and then joins them with newlines between each item-->
(print: $inv.map(window.make_link, "drop").join("\n"))]
(else:)[nothing]}
`</div>
Note that when the player clicks on an item in their inventory it goes to a passage called "drop" and when they click on a visible item it goes to a passage called "take" with the ID of the clicked on item in the variable `$__item`
So we need to write these passages.
take:
<div class="code">`{
<!--if a passage called "take-<item>" exists then call that instead-->
(set: $__passage to "take-" + $__item)
(if: ($passages.get($__passage) != undefined))[(display: $__passage)]
(else:)[
You take the (print: $itemlist's $__item's "name").
(set: $loc to (history:)'s last)
(print: window.move_object($__item, $map's $loc, $inv))
(live: 1s)[(goto: $loc)] ]}`</div>
drop:
<div class="code">`{
<!--if a passage called "drop-<item>" exists then call that instead-->
(set: $__passage to "drop-" + $__item)
(if: ($passages.get($__passage) != undefined))[(display: $__passage)]
(else:)[
<!--Check that the objects can be dropped in this passage-->
(set: $loc to (history:)'s last)
(if: $map contains $loc)[
You drop the (print: $itemlist's $__item's "name")
(print: window.move_object($__item, $inv, $map's $loc))]
(else:)[You can't drop the (print: $itemlist's $__item's "name") here.]
(live: 1s)[(goto: $loc)] ]}
`</div>
note these allow the default behaviour to be overridden by creating a passage called `take-<<Item ID>>` or `drop-<<Item ID>>`. So if there's an anvil that's too heavy to be picked up, write a passage called take-anvil which explains why the player can't pick it up.
Drop checks that the player is on a valid passage and displays an error otherwise. After giving the player a second to read the messae they then go back to the previous passage.
There's one last bit of set up. A lot of these functions need to know the name of the current passage. so add the following in a header:
<div class="code">`(set: $loc to (passage:)'s name)`</div>
That's a lot of work, but it's a complicated system which can easily be expanded to handle thinkgs like [[darkness]] or [[combat]].
(print: window.gotoURL("Play example game", "recipes/recipe advanced inventory.html"))
(display: "cookbook_nav")
(set: $up to "darkness")(display: "cookbook_nav")
###Adding darkness to a game.
If in a story there are certain passages which are in darkness, so the description is only displayed if the player has a light source.
The following method allows each dark passage to have a different method when there is no light source.
In order for a passage to be dark firstly give it the tag "dark", then split its text into two named hooks. One for when there is a light source and one for when there isn't.
<div class="code">`[The room is a plain basement. Not much to see]<lightroom|
[The room is dark. You might be eaten by a Grue.]<darkroom|`</div>
The advantage of this method is that if there's more than one room that's dark, they can have different desciprions. Now we need to determine which hook to display.
####The light flag
At the start of the story a flag `$light` is set to false. Then when the player finds a torch or a lantern it gets set to true.
create a ''footer'' passage with the following code:
<div class="code">`(if (passage:)'s tags contains "dark")[(if: $light)[(replace: ?darkroom)[]](else:)[(replace: ?lightroom)[]]]`</div>
if the passage has been tagged as dark it checks the `$light` flag. It then replaces the relevant hook with an empty hook, preventing it from displaying.
(print: window.gotoURL("Play example game", "recipes/recipe darkness.html"))
(display: "cookbook_nav")
(set: $up to "darkness")(display: "cookbook_nav")
###Inventory based darkness
####Setup
As with the [[simpler version->simple_dark]] this uses two hooks to specify the light and dark text in a passage tagged 'dark'.
<div class="code">`[The room is a plain basement. Not much to see]<lightroom|
[The room is dark. You might be eaten by a Grue.]<darkroom|`</div>
####Adding the light sources
Using the inventory system from the [[complex inventory recipe->complex_inv]], then you can modify it to include light sources.
Firstly modify each item in the item list so it has a light property.
<div class="code">`(set: $itemlist to (datamap: "torch", (datamap: "name", "A torch", "description", "The torch emits a powerful light", "light", true)))
`</div>
Now we want to display the `?lightroom` hook if either the player is holding a lightsource or there's one present in the passage. To make this easier, add the following function to the story's javascript.
<div class="code">`window.lightsource = function (container) {
var itemlist = State.variables.itemlist;
var result = container.filter( function(element) {return itemlist.get(element).get("light");});
return (result.length > 0);
};`</div>
This is called with an array of items. It checks if any of them have the light property set to true.
Now put the following in a ''header'' (naming it so that it runs before any other headers)
<div class="code">`(set: $tags to (passage:)'s tags)\
(set: $__isdark to false)\
(if: $tags contains "dark")[
(if: not Window.lightsource($inv, $itemlist) and not Window.lightsource($map's $loc, $itemlist))[(set: $__isdark to true) ]
`</div>
And add the following into a ''footer'':
<div class="code">`(if: $tags contains "dark")[
(if: $__isdark)[(replace: ?lightroom)[]](else:)[(replace: ?darkroom)[]] ]
`</div>
The reason why it is done in two parts is so we can stop the room's contents from being displayed when in darkness. Change the code to display the room's contents to:
<div class="code">`{(set: $list to "nothing")
(if: $map contains $loc)[
(set: $items to $map's $loc)
(if: $items.length > 0 and not $__isdark)[
<!--converts an array of objects into an array of links and then joins them with newlines between each item-->
(set: $list to $items.map(window.make_link, "take").join("\n"))
] ]
$list
}`</div>
Notice that when deciding whether to show the item list it both checks that the room contains anything, and that it isn't dark.
(print: window.gotoURL("Play example game", "recipes/recipe inventory and darkness.html"))
(display: "cookbook_nav")
(set: $up to "darkness")(display: "cookbook_nav")
###Day/night cycles
The previous recipes are fine for dank caves or creepy basements, but what about if you also want to simulate night time? There are a number of points that need to be considered.
####What time is it?
In order to know if it's day or night there needs to be some way of checking the time. The simplest way is to have a variable that tracks the number of minutes that have passed. After every action this would be increased by a certain amount.
In a startup passage, put:
`(set: $minutes to 0)`
Then in a footer passage put:
`(set: $minutes to it + 10)
(if: $minutes >= 1440)[(set: $minutes to it - 1440)]`
Note this uses the classic pattern of a turn taking ten minutes.
It's possible to use a different value depending on the story. At a rate of ten minutes a turn, it'd have to be a very long story for the day/night cycle to have an affect.
<div class="idea">A possible enhancement is to use different amounts of time for different actions. Picking something up should take less time than walking a mile.</div>
It also checks to make sure that the value hasn't gone over the number of minutes in a day and reduces it accordingly.
####Is it dark yet?
Now that we know how much time has passed we need to figure out at what point it gets dark. Since the story will most likely be harder at night time, it'd be best to nighttime be shorter than daytime.
####What areas are affected?
One way to handle night time is to make every passage act as though it's dark. A more nuianced approache would be to say that indoor locations have a lightsource, unless theyre explicitly tagged as being dark. In which case only passages which have been tagged as being outdoors would be affected during night time.
####How dark sould night be?
When someone enters a dark room without a light source it's reasonable to only give them the option of leaving again.
What about if someone is in the middle of nowhere without a light when night falls? There are two possible approaches:
1: If the game features a base location, give them the option to fast travel to it
2: Make it so the player can't see items or details, but they can see well enough to still navigate.
####Putting it together
So you would end up with a footer passage containing:
(set: $minutes to it + 10)
(if: $minutes >= 1440)[(set: $minutes to it - 1440)]
(if: (passage)'s tags contains "outdoors")[
(set: $litarea to true)
(if: $minutes >= 1080)[(set: $litarea to $lightsource)]
(if: $litarea)[(replace: ?dark)[] ](else:)[(replace: ?light)[] ]]
This assumes that outdoor passages have a `?light` and `?dark` hook, similar to dark rooms.
For outdoors passages the `$litarea` flags defaults to true. If it's night time then `$litarea` gets set to the `$lightsource` flag, which contains whether the player has a lightsource available.
The value of 1080 was chosen so that three quarters of a day would be daytime, and one quarter would be night time.
####An enhancement
There's a problem with the above code. Imagine the player walking alone a lonely road. They're going to be able to see more during daytime than they would at night using a lightsource.
That means we need three hooks. One for daytime, one for nighttime with a light and one for nighttime without. We'll call these `?day`, `?light` and `?dark`.
`(set: $minutes to it + 10)
(if: $minutes >= 1440)[(set: $minutes to it - 1440)]
(if: (passage:)'s tags contains "outdoors")[
(if: $minutes < 1080)[(replace: ?light)[](replace: ?dark)[] ](else:)[(if: $lightsource)[(replace: ?day)[](replace: ?dark)[] ](else:)[(replace: ?light)[](replace: ?day)[] ]]`
<div class="idea">The examples use a flag to specify the presence of light. However as with dark rooms, you can tie it in with the inventory system.</div>
(display: "cookbook_nav")
(set: $up to "combat")(display: "cookbook_nav")
### Simple combat system
This is a simple system which is based on the one used in the Fighting Fantasy books.
First we need to generate the player's stats:
<div class="code">`(set: $pc to (datamap:))
(set: $pc's skill to (random: 1, 6) + 6)
(set: $pc's health to (random: 1, 6) + (random: 1, 6) + 12)`
</div>
When the player meets a monster we need to set up the enemy:
<div class="code">`(set: $enemy to (datamap:))
(set: $enemy's skill to 6)
(set: $enemy's health to 4)`
</div>
Now combat can begin. In Fighting Fantasy games each combatant rolls 2 dice and adds it to their skill, whoever has higher hits the other, deducting 2 health.
You then need to check if either the player or their enemy has been killed. If so go to the correct passage. Otherwise give the player the option to keep fighting or run away.
<div class="code">`(set: $playerscore to $pc's skill + (random: 1, 6) + (random: 1, 6))
(set: $enemyscore to $enemyscore + (random: 1, 6) + (random: 1, 6))
(if: $playerscore > $enemyscore)[You swing and hit your oponent. (set: $enemy's health to it - 2)](elseif: $enemyscore > $playerscore)[Your oponent deals you a cutting blow.(set: $pc's health to it - 2)]
(if: $enemy's health <= 0)[(display: "fight won")](else if $pc's health <= 0)[(display: "death")](else:)[[[continue fighting->fight] or [[run away->escape]]`</div>
One addition you can make to this system is to allow magical weapons. Normally these give a bonus to the player's chance to hit and the damage they do. First add another value to the player's stats.
<div class="code">`(set: $pc to (datamap:))
(set: $pc's skill to (random: 1, 6) + 6)
(set: $pc's health to (random: 1, 6) + (random: 1, 6) + 12)`
`(set: $pc's attack_bonus to 0)`
</div>
Then if they find a sword +1:
<div class="code">`(set: $pc's attack_bonus to 1)`
</div>
Then when in combat
<div class="code">`(set: $playerscore to $pc's skill + (random: 1, 6) + (random: 1, 6) + $pc's attack_bonus)
(set: $enemyscore to $enemyscore + (random: 1, 6) + (random: 1, 6))
(if: $playerscore > $enemyscore)[You swing and hit your oponent. (set: $enemy's health to it - 2 - $pc's attack_bonus)](elseif: $enemyscore > $playerscore)[Your oponent deals you a cutting blow.(set: $pc's health to it - 2)]
(if: $enemy's health <= 0)[(display: "fight won")](else if $pc's health <= 0)[(display: "death")](else:)[[[continue fighting->fight] or [[run away->escape]]`</div>
(display: "cookbook_nav")
(set: $up to "combat")(display: "cookbook_nav")
### Advanced Combat
The previous system works well, but by tying our inventory and combat systems together, it's possible to do more interesting things. For instance it's possible to have different weapons which have different damage values. It also allows the player to wear armour
This builds on the [[inventory system->complex_inv]] that was introduced earlier.
#### The pc
The player starts with a health of fifty and a skill of fifteen. The skill value represents their chance of hitting an unarmoured oponent. The more armour the openent has the lower their chance of hitting.
<div class="code">`(set: $pc to (datamap:))`
`(set: $pc's health to 50)`
`(set: $pc's skill to 15)`</div>
#### The armoury
When setting up the list of items we need to add three more stats. Hit bonus, damage and armour score.
<div class="code">`(set: $itemlist to (datamap: "torch", (datamap: "name", "A torch", "description", "The torch emits a powerful light", "attack", 0, "damage", 0, "armour", 0), "shield", (datamap: "name", "A shield", "description", "A sturdy metal shield", "attack", 0, "damage", 0, "armour", 1), "breastplate", (datamap: "name", "A metal breastplate", "description", "Perfect for warding off sword blows", "attack", 0, "damage", 0, "armour", 4), "metal bikini", (datamap: "name", "A bikini but metal", "description", "Combining discomfort with a total lack of protection", "attack", 0, "damage", 0, "armour", -1), "dagger", (datamap: "name", "A dagger", "description", "small and sharp, perfect for when you're feeling all stabby", "attack", 0, "damage", 4, "armour", 0), "sword", (datamap: "name", "A sword", "description", "A finely honed blade. A weapon from a more civilised age.", "attack", 0, "damage", 8, "armour", 0), "magic sword", (datamap: "name", "A magical sword", "description", "This powerful blade glows with an inner light", "attack", 2, "damage", 10, "armour", 0)))`</div>
#### Preparing to fight
Before combat can begin we need to determine both opponents armour score and determine which weapons they're using.
To calculate the armour score we need to go through the character's inventory and add up the armour score's of everything they're carrying.
This requires a bit of javascript:
<div class="code">`window.calc_armour = function(inventory, itemlist) {
return inventory.reduce(function(total, item) {return total + itemlist[item]["armour"]}, 0);
}`</div>
Before starting combat do the following:
<div class="code">`(set: $player_armour to calc_armour($inventory, $itemlist))`
`(set: $enemy_armour to calc_armour($enemy's inventory, $itemlist))`</div>
For this example we will just use the weapon which has the highest damage value:
<div class="code">`window.calc_weapon = function(inventory, itemlist) {
var items = inventory.map(function(item) {[item, itemlist[item]["damage"]});
var weapon = items.sort(function(item1, item2) {return item1[1] - item2[1];})[0][0];
return weapon;
}`
</div>
This creates an array of arrays containing the name and damage value of every item in the inventory. It then sorts that array by the damage value (highest to lowest) and takes the name of the first element.
<div class="code">`(set: $player_weapon to calc_weapon($inventory, $itemlist))`
`(set: $enemy_weapon to calc_weapon($enemy's inventory, $itemlist))`</div>
#### The fight
With all the preperation out the way we can get down to hitting each other.
<div class="code">`(set: $player_attack to (random: 1,20) - $itemlist's $player_weapon's attack)`
`(if: $player_attack < ($pc's skill - $enemy_armour))[You swing and hit.(set: $enemy's health to it - $itemlist's $player_weapon's damage)](else:)[You swing wildly and miss]`
`(set: $enemy_attack to (random: 1,20) - $itemlist's $enemy_weapon's attack)`
`(if: $enemy_attack < ($enemy's skill - $player_armour))[Your opponent catches you with a crushing blow.(set: $pc's health to it - $itemlist's $enemy_weapon's damage)](else:)[Your opponent swings wildly and misses.]`
`(if: $enemy's health <= 0)[You stand victorious!](elseif: $pc's health <= 0)[Is there a doctor in the house?](else:)[The fight continues]`</div>
<div class="idea">There are a number of improvements that can be made to this system
0. If the player has two breastplates or two helmets they'll gain the benefit of both of them, which doesn't make sense.
0. This system chooses the most damaging weapon, but the player might want to use a different one.
0. This system also fails if the player doesn't have a weapon. In that case it should default to good, old fashioned fisticuffs.
0. This system could be extended to battles with more than two adversaries.</div>
(display: "cookbook_nav")
Double-click this passage to edit it.
(set: $up to "misc")(display: "cookbook_nav")
### Cycling links
Sometimes you want to have a link cycle through a set of options.
<div class="code">`(set: $cycle_list to (array: "Good", "Nuetral", "Evil"))
(set: $cycle_index to 1)
Your alignment is (link-repeat: "[(print: $cycle_list's $cycle_index)]<cycle_link|")[(set: $cycle_index to it + 1)(if: $cycle_index > $cycle_list's length)[(set: $cycle_index to 1)](replace: ?cycle_link)[(print: $cycle_list's $cycle_index)]]`</div>
<div class="output">(set: $cycle_list to (array: "Good", "Nuetral", "Evil"))
(set: $cycle_index to 1)
Your alignment is (link-repeat: "[(print: $cycle_list's $cycle_index)]<cycle_link|")[(set: $cycle_index to it + 1)(if: $cycle_index > $cycle_list's length)[(set: $cycle_index to 1)](replace: ?cycle_link)[(print: $cycle_list's $cycle_index)]]</div>
<div class="changes">The `(link-repeat:)` macro was only introduced with version 1.2 of Harlowe. Before that it was necessary to split the cycling link code into two passages.</div>
<div class="warning">It has been pointed out that putting a hook into the text of the `(link-repeat:)` macro is an undocumented feature, which means it could be removed in a future version of Harlowe.</div>
(display: "cookbook_nav")
(set: $up to "misc")(display: "cookbook_nav")
###Adding a countdown
The player has been locked in a room with a bomb and they need to find a way out before the bomb explodes. Harlowe 1.1 added the special header and footer passages, which make it easy to create situations where the player has a limited number of turns to complete their objective.
In the ''startup'' passage
<div class="code">`(set: $countdown to false)
(set: $count to 0)`</div>
When they player gets to the passage where the countdown starts:
<div class="code">`(set: $countdown to true)
(set: $count to 5)`</div>
Which would give them 5 turns.
Then in either a ''header'' or ''footer'' passage, add the following:
<div class="code">`(if: $countdown)[(set: $count to $count - 1)(if: $count is 0)[(set: $countdown to false)(goto: "countdown_failed")]]`</div>
If the player has entered the countdown section the code checks if the timer has run out, and then decreases it by one. The failure code sets `$countdown` to false, since otherwise when it went to the failure passage, the check would be rerun and it would go into an infinite loop.
Then, when the objective has been completed (E.G. the player escapes the room) set `$countdown` to false.
What if there are some actions that can be done so quickly that they won't decrease the timer? This can be done using passage tags. Give all the passages that will decrease the timer a tag such as 'timer' Then put the following in the ''header''/''footer'':
<div class="code">`(if: $countdown)[(if: (passage:)'s tags contains "timer")[(set: $count to $count - 1)(if: $count is 0)[(set: $countdown to false)(goto: "countdown_failed")]]]`</div>
Now any passage that doesn't have that tag won't decrease the timer, though it will check that the timer hasn't run out.
(print: window.gotoURL("Play example game", "recipes/recipe countdown.html"))
(display: "cookbook_nav")
(set: $up to "css")(display: "cookbook_nav")
###Assigning a passages css class based on its tags
The Sugarcube story format has a feature where any tags applied to a passage will be assigned to its css class, allowing you to format each passage based on its tags.
For instance if a group of passages are within a forest and you want them to have different formatting, then just tag those passages forest and apply the formatting to the forest class.
Harlowe doesn't have this feature, but it's possible to emulate it using javascript.
Place the following in the story's javascript.
<div class="code">`window.setBodyClass = function(classlist) {
var classStr = classlist.join(" ");
document.body.className = classStr;
return "";
}`</div>
This sets the class of the body element based on an array that is passed in. The function is created as an element of the window object so it can be accessed from within the story. It returns an empty string to it can be used from within a print macro.
Then in either a header or footer passage write.
<div class="code">`(print: window.setBodyClass((passage:)'s tags))`</div>
Then you just need to add the css for each tag that you want to reformat.
E.G.
<div class="code">`body.forest {background-color: green;}`</div>
Now tag any passage within the forest with the forest tag.
Alternative if you want to give the forest passages a certain background picture you can use the following css:
<div class="code">`body.forest {background-image: forest-url}`</div>
where forest url would be the location of the image you want to use.
<div class="warningblock">There are some limiations to this approach. Not all css properties can be changed. For instance it's not possible to change the text color using the color property.</div>
(print: window.gotoURL("Play example game", "recipes/recipe passage classes.html"))
(display: "cookbook_nav")
(set: $up to "css")(display: "cookbook_nav")(set: $show_sidebar to true)
###Sidebars
In order to put add a sidebar with links and information.
####The sidebar content
Create a ''footer'' passage called sidebar. This will contain the information that you want to show in the sidebar.
Note: You could also use a ''header'' passage, but that isn't recommended since any changes to variable won't be reflected in the sidebar.
####The CSS
In the story's stylesheet add the following:
<div class="code">`tw-includes[title="sidebar"] {
text-overflow: clip;
position: absolute;
left: 100%;
width: 20%;
top: 20%;
}`</div>
This will position any text a passage called sidebar over to the right of the display area. You can adjust the values for left, width and top to get the position right. The left value should be higher than 100% in order to avoid overwriting the main text.
####Moving the sidebar to the left
You can get the sidebar to appear on the left by changing the left value to less then 0%.
<div class="code">`tw-includes[title="sidebar"] {
text-overflow: clip;
position: absolute;
left: -20%;
width: 20%;
top: 20%;
}`</div>
The total of the left and width values should be 0 or less in order to avoid overwriting the passage text.
(display: "cookbook_nav")
(set: $up to "css")(display: "cookbook_nav")
###Displaying an image in a seperate box
Suppose you want each passage to have a image, which displays in a box to the right of the passage. This can be done using footers.
In a footer passage add
<div class="code">`
<div class="picture">
(if: $passageimg is not "")[(set: $url to "<img src=\"" + $passageimg + "\">")
(print: $url)]
</div>`</div>
Then in any passage that has an image set `$passageimg` to the url of the img. Alternately if you're importing any images into the story as base64, then replace the contents of the if block with `(display: $passageimg)` and `$passageimg` should be the name of the passage containging the image data.
Now we just need to format the passage so that the image is placed in it's own box. Now add a css selector for the `.picture` class.
E.G. to place the picture in a box to the side, use the following.
<div class="code">`.picture {float: right;
position: fixed;
top: 20%;
width: 10%;
left: 60%;
padding: 20px;
border: 1px solid black;
}
`</div>
(print: window.gotoURL("Play example game", "recipes/recipe picture box.html"))
(display: "cookbook_nav")
(set: $up to "css")(display: "cookbook_nav")
###Hiding the startup passage
Normally any ''startup'' passages are displayed at the top of the first passage. This can be prevented using CSS.
<div class="code">`tw-include[type="startup"] {
display: none
}`</div>
####Also hiding header passages
''header'' passages have a type of header, while ''footer'' passages have a type of footer, so it's possible to hide those as well.
<div class="code">`tw-include[type="header"] {
display: none;
}`</div>
It's also possible to only hide certain passages, depending on the passage name.
<div class="code">`tw-include[type="header"][title="passage name"] {
display: none;
}`</div>
Note: This page is based on code posted to the twine forum by greyelf.
(display: "cookbook_nav")
(if: $show_sidebar)[This text will appear in the sidebar.
You can add any information that you want.
Such as a link to the player's inventory,
or statistics such as health
or what time it is, or how many turns are lef.
(set: $show_sidebar to false)]
(set: $show_sidebar to false)
(set: $up to "basics")(display: "cookbook_nav")
###Fun with links
If you want something more advanced than just a plain link to a passage then you need to use the one of the link macros. Here are some of the ways it can be used:
####Link to a random passage.
A malfunctioning teleporter might take the player to one of three random passages. You can randomise the destination using the `(either:)` macro. The full line would be
`(link-goto: "enter teleporter", (either: "jungle", "desert", "temple"))`
####Setter links
A common practice is to set a variable at the same time as jumping to a passage.
For instance, if a set of options only have slightly different results then you might have them all go to same passage, and use a variable to store which option they selected.
`(link: "option A")[(set: $selected to "A")(goto: "Next question")]`
####Link to an external page
If you need to link to a different web page, then you need th `(gotourl:)` macro.
`(link-repeat: "Twine main page")[(gotourl: "http://twinery.org")]`
Note this used `(link-repeat:)` in order to stop the link from disappearing.
####Link to an external page, opening in a new tab/window
If you want to open the external page in a new tab or window (so as to now take the player away from the story) then you need the `(openurl:)` macro.
`(link-repeat: "Twine main page")[(openurl: "http://twinery.org")]`
<div class="warning">a lot of browsers are set up to block webpages from opening up new tabs, so they'll block this macro from working.</div>
<div class="changes">The `(openurl:)` macro requires at least version 1.2 of Harlowe.</div>
(display: "cookbook_nav")
(set: $up to "media")(display: "cookbook_nav")
###External multimedia
HTML provides tags for including pictures, sounds and videos.
####Pictures
Pictures can be included using the img tag.
`<img src="http://twine.analytical-engine.co.uk/img/pic.jpg">`
<img src="http://twine.analytical-engine.co.uk/img/pic.jpg">
####Audio
<div class="code">`<audio src="media/drums.mp3" controls>
<p>Your browser does not support the audio element.</p>
</audio>`</div>
This will create an audio controller widget.
<audio src="media/drums.mp3" controls>
<p>Your browser does not support the audio element.</p>
</audio>
You're more likely to want to play a sound as soon as the player visits a passage. In which case replace `controls` with `autoplay`.
<div class="code">`<audio src="media/drums.mp3" autoplay>
<p>Your browser does not support the audio element.</p>
</audio>`</div>
[[passage with auto playing sound->audiopassage]]
Adding `loop` will make the sound repeat.
####Video
<div class="code">`<video src="http://twine.analytical-engine.co.uk/media/video.ogv" controls>
Your browser does not support the video element.
</video>`</div>
<video src="http://twine.analytical-engine.co.uk/media/video.ogv" controls>
Your browser does not support the video element.
</video>
Again, replacing controls with autoplay will produce an autoplaying video.
<div class="warningblock">Different browsers support different audio and video formats. Which means if you do include them the story might not work properly in some browsers.</div>
####Hosting
The problem with this method is you need somewhere online to store any multimedia elements. If you are uploading the story to your own webspace then you can upload any other files to the same place.
If you're uploading to a story hosting site like philomen.ia then you need a place to host the other files. Media hosting sites exist, but make sure they allow hot linking. That is, the link they provide should point directly to the media and not to a web page containing it.
(display: "cookbook_nav")
(set: $up to "media")(display: "cookbook_nav")
###Embedding media in the story
There is a way to include media in a story so it doesn't require an external file. The method is to turn the media into a text encoding called base64. This text can then be included in a regular passage.
There are websites that will do the encoding for you. Such as (link-repeat: "https://www.base64-image.de")[(gotourl: "https://www.base64-image.de")].
After encoding the media you will get a piece of text that starts with data:image/png;base64, and then contains a series of letters and numbers. This text can be inserted into the src of an img, audio, or video tag.
It's a good idea to put the media tags each into a seperate passage and then use the `(display:)` macro to insert it into the story.
<div class="warning">This will increase the size of your published story file. If it grows too large (Too many high resolution images, or videos) then it might make the game unplayable for people with slow connections.</div>
(display: "cookbook_nav")
(set: $up to "conversation")(display: "cookbook_nav")
###Conversation tree
This is one of the simplest way to present a conversation. The user is given a list of options. Each option will bring a response and a new set of options.
####Storing the conversation
Each element of conversation (that is a comment and a response) is stored in a datamap. The datamap holds the following information.
* comment - The players comment
* response - The NPC's response
* effect - Any effects that this has
* requirements - Any requirements for the option
* next - The next list of elements.
requirements and effect are both optional fields.
If effect is set then it is used to set any flags. So it's possible to check if the player has asked a certain question.
If requirements is set then then the option will only be shown if the requirment is fulfilled. This can be used if the player should only be able to ask a question if they've performed some action. It can also be combined with the effect field so that choosing one option makes another disappear.
If the next field is an array of options then those will be shown if this option is selected.
If it's an empty array then the current set of options will be reshown.
If it's 0 then the last set of options will be shown, unless this is the top level in which case the conversation will end.
So the first round of conversation could be:
<div class="code">`(set: $conv to (a: (datamap: "comment", "Where are you from?", "response", "I'm from the North. A rough, dangerous area", "effect", "", "next", (a:)), (datamap: "comment", "What do you do around here?", "response", "I'm the mechanic. I keep everything running.", "effect", "", "next", (a:)), (datamap: "comment", "Who's in charge here?", "response", "That's Jacob. You'll find him in the big house on Gable street.", "effect", "(set: $ask_boss to true)", "next", (a:)), (datamap: "comment", "Goodbye", "response", "Talk to you soon", "effect", "", "next", 0)))`</div>
This creates a conversation with just three things the player can ask. Only one has an effect outside the conversation, setting a flag to note that the player has asked about the boss.
In an actual game each comment would lead to a new set of possibilities. If you ask about where the NPC is from there'd be options to ask about the north.
####Displaying the options to the player
Now that the conversation has been modelled, the options need to be presented to the player. Which is where we hit a hurdle. Since Harlowe doesn't have any looping macros, it's necessary to use some javascript.
<div class="code">`window.conversation = {};
window.conversation.list_options = function(conversation) {
var links = conversation.map(function(entry, index) {
var result = '(link: "' + entry.get("comment") + '")[(set: $__option to ' + (index + 1) + ')(goto: "conversation")]';
if (entry.get("requirement"))
{
result = "(if:" + entry.get("requirement") + ")[" + result + "]";
};
return result;
});
return links;
}`</div>
The map function runs a bit of code against each entry in an array, creating a new array.
First we turn the comment into a link. This would turn the first option when talking to the workman into:
<div class="output">`(link: "Where are you from?")[(set: $__option to 1)(goto: "conversation")]`</div>
Note that when writing the code to set the `$__option` value it uses `index + 1`. This is because in javascript the first element of an array has an index of 0, while in Harlowe it has an index of 1.
The code then checks if there are any requirements for displaying this option. If there is a requirement that you can only ask about the castle if you've seen it (i.e. the requirement field is `"$seencastle is true"`) then the result would be
<div class="output">`(if: $seencastle is true)[(link: "Tell me about the castle)[(set: $__option to 1)(goto: "conversation")]]`</div>
Now we have an array of links we need to present them to the player.
There's a bit of setup that needs to be carried out before the conversation can start.
<div class="code">`{(set: $__option to -1)(set: $__endpassage to "endworker")(set: $__conv to $worker)
(display: "conversation")}`</div>
This sets some variables which are needed by the conversation system.
`$__option` holds the number of the last dialogue option chosen by the player. Since the conversation hasn't started it's set to -1.
`$__endpassage` holds the name of the passage to display when the player ends the conversation
`$__conv` holds the characters conversation tree.
Now onto the display passage. This involves quite a lot of code. Firstly if this is start of the conversation we do some set up
<div class="code">`{(if: $__option is -1)[
(set: $__oldconv to (a:))
(set: $__end to false)]`</div>
If `$__option` is -1 we set `$__oldconv` to an empty array. This will hold the players path through the conversation tree. So when they want to talk about something else, it can display the options for the previous topic. `$__end` is a flag that stores whether the conversation has ended.
Now we have the code for if the player has already selected a piece of dialogue:
<div class="code">`(else:)[
(set: $__dialogue to $__conv's $__option)
(print: "'" + $__dialogue's response + "'")
(if: $__dialogue contains "effect")[(print: $__dialogue's effect)]
(set: $__next to $__dialogue's $__option's next)`</div>
This sets `$__dialogue` to the correct entry from the array.
This displays the other persons response and runs any side effects if there are any. It then reads the next piece of the conversation tree. What happens next depends on if there are any further conversation options.
If the player has chosen to talk about something else (the next value is 0) Then it gets the last visited branch of the tree from the `$__oldconv` array.
If that array is empty then the conversation is over. The `$__end` flag is set to true and the `$__endpassage` is displayed.
<div class="code">`(if: $__next is 0)[
(if: $__oldconv is (a:))[
(set: $__end to true)
(print: "\n")
(display: $__endpassage)](else:)[
(set: $__conv to $__oldconv's last)
(set: $__oldconv to it - (a: $__conv))]]`</div>
Otherwise, if there is another set of dialogue options then `$__conv` is set to store those, while `$__oldconv` is updated to keep track of the previous set of options.
If there are no deeper dialogue options then `$__conv` and `$__oldconv` are unchanged.
<div class="code">`(else:)[
(unless: $__next is (a:))[
(set: $__oldconv to it + (a:$__conv))
(set: $__conv to $__next)]]]`</div>
The whole code is:
<div class="code">`{(if: $__option is -1)[
(set: $__oldconv to (a:))
(set: $__end to false)](else:)[
(set: $__dialogue to $__conv's $__option)
(print: "'" + $__dialogue's response + "'")
(if: (datanames: $__dialogue) contains "effect")[(print: $__dialogue's effect)]
(set: $__next to $__dialogue's $__option's next)
(if: $__next is 0)[
(if: $__oldconv is (a:))[
(set: $__end to true)
(print: "\n")
(display: $__endpassage)](else:)[
(set: $__conv to $__oldconv's last)
(set: $__oldconv to it - (a: $__conv))]](else:)[
(unless: $__next is (a:))[
(set: $__oldconv to it + (a:$__conv))
(set: $__conv to $__next)]]]}`</div>
Now we have a list of dialogue options, we need to present them to the player.
<div class="code">`(unless: $__end)[(print: conversation.list_options($__conv).join("\n"))]`</div>
As long as the conversation hasn't ended, we create the list of links and then use the join function to turn them into a string with a newline between each one.
(print: gotoURL("Play example game", "recipes/recipe talk the talk.html"))
(display: "cookbook_nav")
(set: $up to "conversation")(display: "cookbook_nav")
###Knowledge based conversations
This is a conversation system where at the start of the game the character has knowledge of certain subjects. When they talk to a character they can ask about any subject they have knowledge of. In doing so they'll learn about knew subjects which they can talk to people about.
####The model
The topics that the character has knowledge of can be represented by an array
<div class="code">`(set: $topics to (a: "castle", "smithing", "soldiers"))`</div>
Each character's responses can be stored as a datamap, which maps the topic to the response.
The actual response will be an array which stores
* The characters response
* Any new topics which are made available
* Any gameplay effects
<div class="code">`(set: $bartender to (datamap: "castle", (a: "Ah, lord Appleby rules there", (a: "lord appleby"), ""), "smithing", (a: "My brother is the best smith in the town", (a: "brother"), ""), "soldiers", (a: "They keep the bandits away", (a: "bandits"), ""), "brother", (a: "The best blacksmith in town", (a:), ""), "lord appleby", (a: "He does a fairly good job of ruling", (a:), ""), "bandits", (a: "Used to cause all kinds of trouble for the traders, but the soldiers keep them in check now.", (a: "soldiers", "traders"), ""), "traders", (a: "They come through here every week peddling their wares.", (a:), ""), "end", (a: "Be off with you now. I have a business to run", (a:), ""), "unknown", (a: "I don't know much about that", "You'll have to ask someone else", "Who knows?")))`</div>
There are two special options at the end.
end: This holds the other persons response when the player ends the conversation
unknown: This is different from the other entries since it just contains an array of strings. One of them will be displayed at random if the player asks about a topic the other person doesn't know anything about.
Note: This isn't needed if you decide to only list the topics that the other person understands.
####Presentation
Now we need to present the options to the player. Again this will require some javascript.
<div class="code">`window.conversation = {}
window.conversation.list_options = function(topics) {
return topics.map(function(topic){ return '(link: "' + topic + '")[(set: $__option to "' + topic + '")(goto: "conversation")]'})
}`</div>
The map function runs a bit of code on every element of an array, creating a new array. In this case it takes an array of topic names and turns it into an array of links. "castle" will become
<div class="output">`(link: "castle")[(set: $__option to "castle")(goto: "conversation")]`</div>
Now we need to do a bit of setup before displaying the conversation passage. In the passage where the conversation is innitiated we need to following code:
<div class="code">`(set: $__conv to $bartender)(set: $__option to "")(set: $__endpassage to "leave_bar")(display: "conversation")`</div>
`$__conv` is the datamap for the person being talked to
`$__option` holds the option the player selected. Since the conversation hasn't started yet we set it to an empty string.
`$__endpassage` holds the passage to show when the conversation ends
Now we finally get to the conversation passage. In this passage we need to do the following:
0. Check the value of `$__option`
0. If it's not an empty string we check if `$__conv` contains an entry for `$__option`
0. If it does we display the response text and the effect text
0. We then need to add any new topics
0. If `$__option` isn't present in `$__conv` then we display a random line from the `unknown` entry of `$__conv`
<div class="code">`{(unless: $__option is "")[
(if: $__conv contains $__option)[
(set: $__response to $__conv's $__option)
(print: $__response's 1st)
(print: $__response's 3rd)
(set: $topics to $topics - $__response's 2nd)
(set: $topics to $__response's 2nd + $topics)](else:)[
(print: (either: ... $__conv's unknown))]]}`</div>
One complication arises when adding any new topics. Namely what to do if it is already present in the list of known topics. Since the PC will have just been reminded of the topic it makes sense to move it to the start of the list. This is accomplished by deleting the topic from the list and then adding it back again at the start.
<div class="code">`(set: $topics to $topics - $__response's 2nd)
(set: $topics to $__response's 2nd + $topics)`</div>
The first line has no effect if the topic isn't present in the list.
With that all done we give the player the list of conversation topics.
<div class="code">`You can ask about the following:
(print: conversation.list_options($topics).join(", "))`</div>
The list of links returned by list_options is turned into a string using the join function.
Lastly we need to give the player a way to get out of the conversation.
<div class="code">`Or you can (link-repeat: "end the conversation")[
(print: $__conv's end's 1st)
(print: $__conv's end's 3rd)
(display: $__endpassage)]`</div>
If the player clicks the end conversation link, it prints the characters farewell text and runs any side effects. It then displays the end of conversation passage.
####Just listing topics the person knows about
As the game goes on the list of topics the PC knows could get quite long. The interface can be made simpler by just displaying the options that the person they're talking to has an answer for.
In order to do that we replace the list_options javascript function with
<div class="code">`window.conversation.list_options = function(topics, conversation) {
return topics.map(function(topic){ if(conversation.get(topic)) {return '(link: "' + topic + '")[(set: $__option to "' + topic + '")(goto: "conversation")]'} else {return "UNKNOWN"};})
}`</div>
This now takes two parameters. The list of topics known by the PC and the topic list for the person being talked to. When this is mapping the list of topics it checks if the person being talked to understands it. If they don't rather than turning the topic into a link, it becomes the word UNKNOWN.
This simplifies the code to print the other characters response. Since there's now no need to check if they understand the option.
<div class="code">`{(unless: $__option is "")[
(set: $__response to $__conv's $__option)
(print: $__response's 1st)
(print: $__response's 3rd)
(set: $topics to $topics - $__response's 2nd)
(set: $topics to $__response's 2nd + $topics)]}`</div>
When giving the player the list of options we need to remove any entries which say 'UNKNOWN' which is done with the following:
<div class="code">`(set: $options to conversation.list_options($topics, $__conv) - (a: "UNKNOWN"))
(print: $options.join(", "))`</div>
Then the option to end the conversation is the same.
<div class="idea">One way to make conversations less repetetive is to allow more than response for each topic. Then either print one at random when the player chooses that topic, or cycle through them.</div>
(print: gotoURL("Play example game", "recipes/recipe local knowledge.html"))
(display: "cookbook_nav")
(set: $up to "basics")(display: "cookbook_nav")
###Saving and loading
A lot of Twine games are short enough that they can be completed in one sitting. However if you're making something a bit longer then you'll need to give the player a chance to save their progress.
####Adding a save game link
Saving the state of a game can be achieved using the `(savegame:)` macro
`(link-repeat: "Save game")[(savegame: "saveslot")]`
####Letting the player specify a save location
You might want to let the player have more than one save. This requires using the `(prompt:)` macro to ask the player to enter a name.
`(link-repeat: "Save game")[(set: $name to (prompt: "Where to save the file?"))(if: $name is "")[(set: $name to "saveslot")](savegame: $name)]`
####Loading a save file
The reverse of the `(savegame:)` macro is `(loadgame:)`
`(link-repeat: "Load game")[(loadgame: "saveslot")]`
####Loading when the player specified the name
`(link-repeat: "Load game")(set: $name to (prompt: "Where to load the file from?"))(if: $name is "")[(set: $name to "saveslot")](loadgame: $name)]`
<div class="idea">You can make these links be always available by putting them in a ''header'' or ''footer'' passage</div>
####Giving the player the option to continue from a previous save at startup
The `(saved-games:)` macro returns a datamap containing all the save game files.
`(if: (saved-games:) contains "saveslot")[(link: "Continue from last save?")[(loadgame: "saveslot")]]`
(display: "cookbook_nav")
(set: $up to "start")(display: "cookbook_nav")
###Notes about this guide
Some notes on the conventions used in this guide.
####Special passages
Some of the guides may refer to adding text to a ''startup'', ''header'', and ''footer'' passages. This means to create a passage which has been tagged as either startup, header, or footer.
####Colour scheme
<div class="code">This shows code to be input</div>
<div class="output">This shows the output</div>
<div class="idea">Ideas for enhancements</div>
<div class="warning">Things to watch out for</div>
<div class="changes">Things that have changed in the latest version of Harlowe</div>
(set: $up to "basics")(display: "cookbook_nav")
###Arrays
####Checking if an array contains all the items in a second array
The `contains` operator will tell you if an array contains a single value. It doesn't work if you want to know if it contains all the items of a second array.
There is a way to achieve this effect though. Subtracting one array from another, `$array1 - $array2`, will remove all entries in `$array2` from `$array1`. So if `$array1` contains every element within `$array2` then `$array2 - $array1` will return an empty array.
`(if: $array2 - $array1 is (a:))[yes](else:)[no]`
####Checking if an array contains multiple values
If you want to check if an array contains multiple values, but don't want to writie out lots of contains tests, then you can use the previous check. You just need to turn the values being tested against into an array first.
`(if: (a: $value1, $value2, $value3, etc) - $array is (a:))[true]`
####Finding any values which are in two arrays.
You have two arrays and want to get an array containing all the values present in both. Again this is a job for Harlowe's set arithmatic. `($array1 - $array2)` will return all the elements which are in `$array1` but not `$array2`. So `$array1 - ($array1 - $array2)` will remove from all the elements from `$array1` which are just in `$array1`. I.e. it will return all the elements which are in both arrays.
(display: "cookbook_nav")
(set: $up to "basics")(display: "cookbook_nav")
###Datamaps
####Checking that a datamap contains a key before accessing it
Trying to extract a value for a key which doesn't exist will cause an error. In order to avoid that you need to test if the datamap contains the key. This can be done using the `contains` operator.
`(if: $datamap contains $key)[$datamap's $key](else:)[default value]`
(display: "cookbook_nav")
<audio src="media/drums.mp3" autoplay>
<p>Your browser does not support the audio element.</p>
</audio>
[[return to tutorial->multimedia]]
Double-click this passage to edit it.
(color: red)[ [[test]]]
Double-click this passage to edit it.