Update: this behaviour is now enabled in Twine 1.4, so this script code is no longer necessary.
The following code, when placed in a script passage, changes <<display>>
so that you can use variables and operators in it, such as <<display $passage>>
.
version.extensions.displayMacro={major:2,minor:0,revision:0};macros.display={handler:function(place,macroName,params,parser){ try{var output=eval(parser.fullArgs()); new Wikifier(place,tale.get(output.toString()).text);} catch(e){throwError(place,"bad expression: "+e.message);}}};
A quick and untidy alternative to using this code is to just use <<print tale.get($passage).text>>
in place of <<display $passage>>
Feel free to report any bugs to @webbedspace.
Since everyone has read my summary of Twine's built-in macros, you'll notice that many other JavaScript operators can be used in the <<set>>, <<remember>>, <<if>>
and <<print>>
macros. Here is an incomplete list of a few that you may find useful. Many of these are not as "tidy" as the documented operators, and some of them are counterintuitive in their meaning. You can still use them, though, if you so wish.
Note: No greater-than signs
Before we begin, a quick reminder: the greater-than sign can't be used inside a valid macro tag - the macro will not be recognised. That's why the "gt" and "gte" operators are provided to you instead.
Setting multiple variables in one <<set>>
You can set multiple variables in a <<set>> macro if you separate their equations with a semicolon:
<<set $green = "Rice" ; $gold = "Cheese" ; $ammo = 14; $score = 0 >>
Note: you should not do this using <<remember>>
: the macro only remembers the first $variable used in it.
Modifying a number variable: +=, -=, *=, /=
A commonly known programming shorthand for arithmetic operations is available. Instead of writing <<set $strength = $strength - 1>>
you can just write
<<set $strength -= 1>>
This sets $strength to itself, minus 1. The other operators +=, *=, /=
also work for their respective arithmetic. These can only be used in situations where the = sign would normally be used.
(Do not accidentally write <<set $strength =- 1>>
, because that will set $strength to -1. The arithmetic operator goes before the equals sign.)
The ternary operator ( ? : )
The ternary operator can be used as a condensed, specific version of <<if>> <<else>> <<endif>>
. Whenever a statement calls for a value, you could instead put ( [an if-macro condition] ? [a value if the condition is true] : [a value if the condition is false] ).
<<print ($ammo gt 4 ? "Ammo OK" : "Ammo low" )>>
The above line prints "Ammo OK" if $ammo is greater than 4, and "Ammo low" otherwise. If you want to make things much less readable and comprehensible, you can nest ternaries in each other:
<<print ($ammo gt 4 ? ($ammo gt 16 ? "Ammo oversupplied" : "Ammo OK") : "Ammo low" )>>
If $ammo exceeds 4, then the value of the inner ternary is printed. You can also rewrite it like this, which is slightly more readable:
<<print ($ammo gt 16? : "Ammo oversupplied" : ($ammo gt 4 ? "Ammo OK" : "Ammo low" )>>
Show all the game's variables for debug purposes
All the story variables are accessible in the JavaScript object "state.history[0].variables".
This line of code will print the variables that have currently been set:
<<print JSON.stringify(state.history[0].variables)>>
This will print something like the following:
{"meal":"Rice","snack":"Cheese","ammo":14,"score":0}
As you can see, it features pairs of values - the variable name, followed by a colon, followed by the value of the variable.
Internal links using string variables
Pointed out by one HarmlessTrouble: the <<print>>
macro, if given a string containing wiki markup, will render the markup when printing it. This means that you can generate an internal link that uses string variables for its data, just by doing something like this:
<<print "[[" + $passage + "]]">> <<print "[[" + $label + "|" + $passage + "]]">>
Remarkable! Of course, due to the No Greater-Than Signs rule, you can't do this with the <<choice>>
macro, the <<display>>
macro, or any macro at all.
Creating an internal link inside HTML blocks
You can do this like so:
<a href='javascript:void(0)' onclick='state.display("PassageName")' class='internalLink'>Link text</a>
PassageName is the name of the passage. You need to give it the class "internalLink" if you want the standard CSS to be applied to the link.
Alternative to the <<display>> macro that can use string variables
Since <<display>>
doesn't interpret its parameter as code, you can't, for instance, specify a passage name inside a variable. Hopefully this will be fixed at some point, but until then, here's an alternative you can use right now:
<<print tale.get($variable).text>>
$variable can be any statement or valid series of operations, as long as it equals the name of one of your passages:
<<print tale.get($hp eq 0 ? "DeathEnd" : $race + "Ending").text>>
Of course, it'd be much better if you could just fix <<display>>
so that you can use code in it.
Accessing information about the current passage
This could be useful if, for instance, you use <<display>>
a lot, and want the displayed passage to know something about whatever's displaying it.
The JavaScript object corresponding to the current passage is accessible in state.history[0].passage. If you want to, for instance, get the title of the current passage, try:
<<set $title = state.history[0].passage.title >>
All of the tags are in the array state.history[0].passage.tags. You can use the array syntax to access them (but I do not recommend modifying them). To see if the current passage has a tag, try:
<<set $hasTheTag = (state.history[0].passage.tags.indexOf("tag") gte 0) >>
Putting non-printed space between macros
You can use the TiddlyWiki comment markers /% and %/ to prevent whatever is inside them from being printed or run. You can use this to give large blocks of macros some breathing room without having to use a <<silently>>
macro.
<<set $red = 1>>/% %/<<if $green gt 4>>/% %/<<set $blue += 2>>/% %/<<remember $white = 2>>/% %/<<endif>>/% %/Some actual passage text...
Having investigated the current Twine codebase, here's a written-up a list of built-in macros. Most are well-known, but some have different limitations than what the documentation says. Others are little-known, and often for good reason.
This is out of date! Please use the Twine wiki instead.
Having investigated the Twine engine code, I've found that the official passage syntax documentation(*) is incomplete. Here, then, is what I believe to be a complete list of Twine markup syntax. Almost all of it is derived from TiddlyWiki, and as such, some parts are not necessarily relevant to the typical Twine author. Nevertheless, they remain available.
http://www.glorioustrainwrecks.com/files/Twine1.3.5.MarkupSyntax_0.html
Update: the Javascript on this page is now installed in Twine 1.4, so this script code is no longer necessary.
Twine's engine is largely based on TiddlyWiki. The standard formatting codes are all TiddlyWiki syntax. Though, having a closer look at the engine source, I discovered a few other TiddlyWiki formatting codes are present in Jonah and Sugarcane - most interesting being Inline CSS, which ostensibly lets you apply inline styles around passage text. All you have to do is type "@@", then list CSS style attributes separated and terminated with semicolons, then put the passage text (including any other formatting and macros) ending with another "@@".
However, in the stable Twine versions of Jonah and Sugarcane, it is bugged - it will not work unless you include this code in a script passage:
String.prototype.unDash = function() { var s = this.split("-"); if(s.length > 1) for(var t=1; t < s.length; t++) s[t] = s[t].substr(0,1).toUpperCase() + s[t].substr(1); return s.join(""); };
If you include this code, you can use it. Here are some usage examples:
@@background-color:hsl(30,50%,50%);This text has an umber background.@@
@@color:gray;text-decoration:overline;This text is gray and has a vertical line over it.@@
@@letter-spacing: -2px;This text <<timedreplace 6>>and this macro<<endtimedreplace>> will be compressed.@@
@@text-shadow: 0.1em 0.1em #333;This text will have a gray shadow.@@
@@opacity:0.5;This text and this image [img[image.png]] will be translucent.@@
You may notice that this is all functionally equivalent to simply writing raw HTML: <html><span style="background-color:hsl(30,50%,50%);">This text has an umber background.</span></html>
. Except, being in HTML mode prevents you from using wiki syntax, internal links and such, so this method is both briefer and more consistent with Twine markup.
If you want to easily apply CSS to an entire group of passages without needing to copy-paste CSS code, then you can use this method and script code to do so.
Here's version 1.1.1 of some Twine macros to play sound files with HTML5 audio.
http://www.glorioustrainwrecks.com/files/TwineMacros-SoundMacros-1.1.2.txt
These macros accept either strings or string variables as their first argument. I recommend you set the filenames to specific variables and then use those as arguments to the macros.
<<playsound "carolofthebells.mp3" >>
plays the file "carolofthebells.mp3" from the start.
<<loopsound $heartbeat >>
starts playing $heartbeat, over and over. Note: currently browsers are not that good at looping audio seamlessly - brief silences between loops may occur.
<<fadeinsound $heartbeat >>
is identical to loopsound, but fades in the sound over 2 seconds.
<<unloopsound $heartbeat >>
makes $heartbeat no longer repeat when it finishes.
<<stopsound "birds.ogg" >>
stops playing "birds.ogg". When <<playsound "birds.ogg" >> is used again, it will start from the beginning.
<<fadeoutsound "birds.ogg" >>
is identical to stopsound, but fades out the sound over 2 seconds.
<<pausesound "trees.ogg" >>
pauses "trees.ogg" at its current location. Use <<playsound "trees.ogg" >> to resume it.
<<stopallsound>>
stops all the sounds.
Important instructions:
MP3 | O | O | O | O | O |
OGG | O | O | O | X | X |
There's a lot that can be done to expand on this. Something I might add is a way to bind looping sounds to a specific passage tag. So, you could perhaps write <<tagsound "trees" "Howling_Monkeys.mp3">> and then give a passage a tag of "trees", and it will keep looping "Howling_Monkeys.mp3" if you're at such a passage, and stop it when you leave. Also on the list of possibilities: support for base64-encoded sounds (which no one except me will ever use).
License:
Regard this script as public domain using CC0. I don't really care for attribution - if you're using Twine, you're already running code I wrote. It's welcome if you want, but entirely optional.
Version history:
The Rewind menu in the Sugarcane story format only displays previously visited passages that have been given the tag "bookmark".
Fig. 1: Anna Anthropy's "Town" after certain passages were given the 'bookmark' tag.
Passages in the menu are displayed as a truncated snippet of their text, in ascending order in which you visited (earliest at the top, latest at the bottom). As you can see, since I visited the plaza twice already, it appears twice in the list. (Also, if you're currently at a "bookmark" passage, it will appear at the bottom, letting you redundantly "rewind" to the present.)
Of course, since the player can already step back through their entire game history using the browser's "back" button, the utility of the Rewind menu is somewhat questionable. And, of course, games that use the <<display>> macro heavily (such as Town itself) will not benefit well from it (unless, perhaps, you use <<addtag>> in the displayed passage to add the bookmark tag to everything that displays it???).
Bug: a bug in the current version of Sugarcane means that the Rewind menu will stop working if one of the bookmarked passages has less than 7 words in it. Just so you know.
I have updated the code snippets for applying CSS to passages with specific tags to include being able to select the body element of the HTML document. I have also updated these tag-related macros to match.
An array is essentially an ordered list of data values, such as strings or number expressions. You can declare a Twine variable to be an array using just the set macro.
*To create an empty array called $inv: <<set $inv = [] >>
*To create an array called $inv containing the string "Dagger": <<set $inv = ["Dagger"] >>
*To create an array called $inv containing "Dagger", "Shield" and "Potion" in that order: <<set $inv = ["Dagger", "Shield", "Potion"] >>
To examine or change the values inside an array, a number of methods are available that can be used in the set, if and print macros. Here are some examples.
Examining arrays and their contents
*To print the entire contents of an array in order (usually for debug purposes): <<print $inv >>
*To see if the value "Magic Knife" is inside an array: <<if $inv.indexOf("Magic Knife") gte 0>>
*To get the number of values inside an array: <<set $size = $inv.length >>
*To get the first value inside an array: <<set $first = $inv[0] >>
*To get the last value inside an array: <<set $first = $inv[$inv.length - 1] >>
Changing values in arrays
*To add the value "Magic Knife" to the end of an array: <<set $inv.push("Magic Knife")>>
*To add the values "Blunt Axe", "Key" and $item to the end of an array: <<set $inv.push("Blunt Axe", "Key", $item)>>
(push() can insert many values at once.)
*To add the value "Weird Pear" to the start of an array: <<set $inv.unshift("Weird Pear")>>
*To add the values "Blunt Axe", $item, 2, and $weapon to the start of an array: <<set $inv.unshift("Blunt Axe", $item, 2, $weapon)>>
(unshift() can also insert many values at once.)
*To remove the value "Weird Pear" from an array: <<set $inv.splice($inv.indexOf("Weird Pear"),1)>>
*To sort an array alphabetically: <<set $inv.sort()>>
*To reverse an array: <<set $inv.reverse()>>
*To remove the first value from an array: <<set $inv.shift()>>
*To remove the last value from an array: <<set $inv.pop()>>
*To remove the first value from an array and set $item to it: <<set $item = $inv.shift()>>
*To remove the last value from an array and set $item to it: <<set $item = $inv.pop()>>
Similar to my <<replace>> macro, this code causes the passage text in between the <<timedreplace>>
and <<becomes>>
tags to be replaced with what is between those and the <<endtimedreplace>>
tag, after a certain amount of time has elapsed.
Variants
* <<timedinsert>>
causes its text to be inserted into the page. It doesn't need a <<becomes>>
tag to function.
* <<timedremove>>
works in a matching fashion, removing its contained text after the time elapses.
* <<timedcontinue>>
is similar to <<timedinsert>>
but does not require a final <<endtimedcontinue>>
- instead, it causes all subsequent text in the passage to appear.
Gains
You can also substitute the <<becomes>>
tag with <<gains>>
to cause the next run of text to appear at the end of the previous run, without replacing it.
Chains
If you put multiple <<becomes>>
or <<gains>>
tags in, the macro will make them appear as well after the same amount of time passes.
Install my <<Replace>> Macro Set to use these macros.
This takes CSS time values, which are decimal numbers ending in "s" (for seconds) or "ms" (for milliseconds).
Usage examples:
* <<timedreplace 2s >>You see nothing. <<becomes>>After 2 seconds, you still see nothing.<<endtimedreplace>>
* <<timedreplace 2s >>You see <<gains>>a dog, <<gains>>a walrus, <<gains>>and a civet.<<endtimedreplace>>
* You search. <<timedinsert 1s>>You find nothing.<<endtimedinsert>>
.
* <<timedremove 2.5s >>Something is disappearing... <<endtimedremove>>
* Look <<timedcontinue 3s>>A bird just few by...
Implementation details:
* If inserted text appears and descends below the bottom of the screen, the page should automatically scroll down to make it visible.
* Note: due to the way the browser and Twine interact, any changes made by code inside a <<timedreplace>> tag will be forgotten if you use the Back or Forward browser buttons, unless you use this script.
Version history:
* 16/06/2013 - Updated regarding Combined Replace Macro Set.
* 26/04/2013 - Added timedcontinue.
* 05/04/2013 - Added timedinsert and timedremove, as well as downward scrolling.
* 07/03/2013 - Made the transition CSS-based.
* 01/02/2013 - Initial.
Feel free to report any bugs to @webbedspace.