twine

Twine 1.3.5 Markup Syntax Examples

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

Twine: apply inline CSS to passage text

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.

Twine: HTML5 sound macros

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:

  1. This macro does not work if you have ROT13 obfuscation enabled in StorySettings - sorry.
  2. Included with the macros is a script that will preload all of the music files as soon as you begin the game. It does this by searching through all of your passages for anything that resembles a music filename - a string in either single or double quotes that ends with ".ogg", ".mp3", ".wav" or ".webm". Then, it will attempt to load that as a file. I consider this to be simpler and more desired behaviour than explicitly requiring you to put preloader macros at the start of the story, but bear this in mind when you write passages.
  3. For legal and free software advocacy reasons, the Apple and Microsoft browsers (Safari, Internet Explorer) do not support OGG format, whereas the cross-platform browsers (Opera, Firefox) do not support MP3. (Google Chrome supports both.) So, you may have to include your music file in both formats. Give each of these files the same name ("ghost.mp3", "ghost.ogg") and store them in the same directory. You only need to <<playsound>> one of these files - if it can't be played, the other one will be used instead. If you only have the music in one format, consider opening the file in Audacity and saving it using "Export as..." in the File menu.

    MP3OOOOO
    OGGOOOXX

  4. For short sound effects, it would be safe to use "wav" format for them, but be warned - Wavs don't work in Internet Explorer. So, you may need them in MP3 format too. Again, provided both are available, you only need to use one of them in your <<playsound>> macros.
  5. Implementation-wise, this creates an object, "macros.playsound.soundtracks", which stores Audio objects for every found sound file. I'm not sure if this is the most usable method just yet. This may change in future versions.

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:

  1. 29-9-14 - Added CC0 "license".
  2. 29-9-13 - Fixed bug where <<stopallsound>> would crash if one of your sounds wasn't loaded correctly (+ maybe other situations).
  3. 4-3-13 - Added <<fadeinsound>> and <<fadeoutsound>>. 1.1.1 edit: No longer force-restarts or force-starts music that's already looping/stopped.
  4. 15-2-13 - Now play and loop no longer explicitly reset the position to the beginning, so playing something that's already playing won't cause it to reset. (Use stopsound to do this on purpose). This also fixes a bug with the back and forward browser buttons.
  5. 15-2-13 - Initial.
Feel free to report any bugs to @webbedspace.

Twine: how the Rewind menu in Sugarcane works

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.

Twine: apply CSS to the <body> element during specific tagged passages

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.

Twine: Bar Charts.

Howdy. The name's Kit. I've been trying to work out how much you can make Twine do that isn't just display the characters you typed in different ways. I'm working on a battle simulator, and I realised that to make it look computer-gamey, it would need health bars. How does one make a bar chart in Twine, anyhow? Well, after a few hours of poking, I can suggest that this is one way - well, three ways. It's best to read them in order, but it's ok, they get simpler. They are all built around the idea of recurring a passage using an if condition nested inside itself, as suggested here.

---

Method 1

30 OOOOOO

Each bar has three variables - health, which is what the bar is intended to measure, increment, which is the number represented by each unit of the bar, and symbol, which is what each unit of the bar is displayed as. Outer 1 prints the value of health for each bar, divides health by increment, and saves this value as block. It uses a ceiling function (Math.ceil, everyone loves a little javascript) so that if block is not an integer, it is always rounded up. If block is greater than 0, Inner 1 is fired off, which prints the symbol, subtracts 1 from block, and fires itself off if block is still greater than 0. By the time this stops calling itself, it has printed the symbol block times.

Include where you want bar (set the values for health, increment and symbol that you wish - symbol must be in ""s)
<<set $health = 30>><<set $increment = 5>><<set $symbol = "O">><<display 'Outer 1'>>

Outer 1:
<<print $health>> <<set $blocks = Math.ceil($health / $increment)>><<if $blocks gt 0>><<display 'Inner 1'>><<endif>>

Inner 1:
<<print $symbol>><<set $blocks = $blocks - 1>><<if $blocks gt 0>><<display 'Inner 1'>><<endif>>

---

Method 2

30 ||||||

These bars run using the passages Outer 2 and Inner 2. The bars work exactly the same as in Method 1, except the symbol is not specified. It is "|", hard-coded into Inner 2. If you always intend to use the same symbol, the code should run slightly faster if you use this method rather than that shown in Method 1.

Include where you want bar (set the values for health and increment)
<<set $health = 30>><<set $increment = 5>><<display 'Outer 2'>>

Outer 2:
<<print $health>> <<set $blocks = Math.ceil($health / $increment)>><<if $blocks gt 0>><<display 'Inner 2'>><<endif>>

Inner 2:
|<<set $blocks = $blocks - 1>><<if $blocks gt 0>><<display 'Inner 2'>><<endif>>

---

Method 3

10 ||||||||||

This is the simplest version of my bars to use. The only variable that has to be specified is health, which will be the length of the bar. Outer 3 has been simplified because of this.

Include where you want bar, specifying health:
<<set $health = 10>><<display 'Outer 3'>>

Outer 3:
<<print $health>> <<if $health gt 0>><<display 'Inner 3'>><<endif>>

Inner 3:
|<<set $health = $health - 1>><<if $health gt 0>><<display 'Inner 3'>><<endif>>

---

There is one bug I have found - if I try to display a bar of length zero, not only will it not print a bar (which is what I'd like), it also won't print the number 0. I have no idea why this is, but if anyone does, any help is appreciated! If you want to see these running, go to the barchartdemo listed below.

Twine macro: << timedreplace >>, << timedinsert >>, << timedremove >>, << timedcontinue >>

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...

Example program.

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.

Twine macro: << hoverlink >>

This simple macro produces a usable internal link whose text changes whenever you mouseover it.

version.extensions.hoverlinkMacro={major:1,minor:1,revision:0};macros.hoverlink={handler:function(a,b,c){var d,l=Wikifier.createInternalLink(a,c[0]);
l.className+=" hoverLink";insertElement(l,"span",null,null,c[1]||c[0]);if(c[2]){d=insertElement(l,"span",null,null,c[2]);
d.style.display="none";}l.onmouseover=function(){if(this.childNodes.length>1){this.childNodes[0].style.display="none";
this.childNodes[1].style.display="inline";}};l.onmouseout=function(){if(this.childNodes.length>1){this.childNodes[1].style.display="none";
this.childNodes[0].style.display="inline";}};}};


This uses the << choice >> macro's parameter order - the destination passage name comes first. Then comes the visible label, then the hidden label.

Usage example: <<hoverlink "CryPassage" "Stand and fight, guns blazing" "Sit down and have a cry">>

For obvious reasons, this won't work on iPads!

Implementation details:

* The link has a class of "hoverLink" in addition to the standard internalLink/brokenLink classes.
* Each text label is a <span> inside the <a>, and the mouseover and mouseout events make one or the other visible.

Version history:

  1. 29-1-13 - Shortened name to "hoverlink"
  2. 12-5-13 - Initial.

Feel free to report any bugs to @webbedspace.

Twine macro: << addtag >>, << removetag >>, << toggletag >>

This extends my code for applying Twine tags to passage divs, and extends on it as a mechanism for CSS selection. What these macros do, in order, is add a tag to the current passage's tag attribute, remove a tag, and toggle its presence (remove it if it's there, add it if it isn't). This means that conditional application of CSS, controlled by << if >> statements, is possible.
Usage examples: <<addtag "dream">> <<toggletag "lightworld">> <<removetag "darkworld">>

These are guaranteed to work both inside passages, inside << replace >> macros, and, tantalisingly enough, inside << display >> macros. Interesting, very interesting.

If you're using the latest Twine 1.3.6 Alpha (28-1-13) , use this code:

version.extensions["toggletagMacros"]={major:1,minor:1,revision:0};macros["toggletag"]={handler:function(a,b,c){var p=e(a);var d=document.body;if(p){var t=p.getAttribute("data-tags");var i=t.indexOf(c[0]);if(b!="addtag"&&i>=0){var r=t.replace(c[0],"");p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}else{if(b!="removetag"&&i<0){var r=t+" "+c[0];p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}}}function e(f){while(f.parentNode&&!f.classList.contains("passage")){f=f.parentNode;
}if(f!=document&&f.getAttribute("data-tags")){return f;}return null;}}};macros["addtag"]=macros["toggletag"];macros["removetag"]=macros["toggletag"];


If you're using a standard version of Twine, use this code instead:

version.extensions["toggletagMacros"]={major:1,minor:1,revision:0};macros["toggletag"]={handler:function(a,b,c){var p=e(a);var d=document.body;if(p){var t=p.getAttribute("data-tags");var i=t.indexOf(c[0]);if(b!="addtag"&&i>=0){var r=t.replace(c[0],"");p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}else{if(b!="removetag"&&i<0){var r=t+" "+c[0];p.setAttribute("data-tags",r);d.setAttribute("data-tags",r);}}}else{var t=state.history[0].passage.tags;var i=t.indexOf(c[0]);if(b!="addtag"&&i>=0){t.splice(i,1);
}else{if(b!="removetag"&&i<0){t.push(c[0]);}}d.setAttribute("data-tags",t.join(" "));}function e(f){while(f.parentNode&&!f.classList.contains("passage")){f=f.parentNode;}if(f!=document&&f.getAttribute("data-tags")){return f;}return null;}}};macros["addtag"]=macros["toggletag"];macros["removetag"]=macros["toggletag"];

Version history:

  1. 1.1.0 - Now adjusts tags on the body element as well.
  2. 1.0.1 - Fixed not working in Jonah.
  3. 1.0.0 - Initial.
Feel free to report any bugs to @webbedspace.

Twine macro: << cyclinglink >>

This simply produces a link whose text cycles between a number of values whenever you click on it. It otherwise leads nowhere. You can use it as a silly clicky trinket, a cheap alternative to the <<replace>> macro, or (as detailed below) as an input interface element.

Script code

version.extensions.cyclinglinkMacro={major:3,minor:3,revision:0};
macros.cyclinglink={handler:function(a,b,c){var rl="cyclingLink";
function toggleText(w){w.classList.remove("cyclingLinkInit");
w.classList.toggle(rl+"Enabled");w.classList.toggle(rl+"Disabled");
w.style.display=((w.style.display=="none")?"inline":"none")}switch(c[c.length-1]){case"end":var end=true;
c.pop();break;case"out":var out=true;c.pop();break}var v="";if(c.length&&c[0][0]=="$"){v=c[0].slice(1);
c.shift()}var h=state.history[0].variables;if(out&&h[v]===""){return
}var l=Wikifier.createInternalLink(a,null);l.className="internalLink cyclingLink";
l.setAttribute("data-cycle",0);for(var i=0;i<c.length;i++){var on=(i==Math.max(c.indexOf(h[v]),0));
var d=insertElement(null,"span",null,"cyclingLinkInit cyclingLink"+((on)?"En":"Dis")+"abled");
if(on){h[v]=c[i];l.setAttribute("data-cycle",i)}else{d.style.display="none"
}insertText(d,c[i]);if(on&&end&&i==c.length-1){l.parentNode.replaceChild(d,l)
}else{l.appendChild(d)}}l.onclick=function(){var t=this.childNodes;
var u=this.getAttribute("data-cycle")-0;var m=t.length;toggleText(t[u]);
u=(u+1);if(!(out&&u==m)){u%=m;if(v){h[v]=c[u]}}else{h[v]=""}if((end||out)&&u==m-(end?1:0)){if(end){var n=this.removeChild(t[u]);
n.className=rl+"End";n.style.display="inline";this.parentNode.replaceChild(n,this)
}else{this.parentNode.removeChild(this);return}return}toggleText(t[u]);
this.setAttribute("data-cycle",u)}}};

Features

If the first parameter begins with the "$" sigil, then it will interpret it as a variable to be altered by clicking the link. When the player visits the page or clicks the link, the variable will be changed to match the text of the link. If the variable already has a string value when you load the passage, then the link text that matches the variable will be selected, instead of the first one.

If the last parameter is the word "end", then it will terminate the cycling link at the parameter before last. The containing that text will replace the link element altogether. Similarly, if the last parameter is the word "out", then it will disappear altogether after the final link text is clicked.

Usage examples:
* You look around, but only see <<cyclinglink "grass" "a flower" "a cloud" "the road">>. - This is a purely cosmetic, endlessly cycling link.
* You see a bowl containing <<cyclinglink "3 cookies" "2 cookies" "1 cookie" "a few crumbs" end>>. - This link changes to the words "a few crumbs" when you get to the end.
* You see a dial: <<cyclinglink $heat "off" "low" "high" "fearsome">>. - The $heat variable will be changed to "off" when the page loads (unless it was already set to "high" or "fearsome"), and then sets it to "low", "high" and "fearsome" as the player clicks the link.
* You see a fuel gauge: <<cyclinglink $fuel "100%" "50%" "10%" "0%" end>>.
* You see an air meter: <<cyclinglink $air "********" "******" "****" "**" out>>.

Example program 1 (cosmetic).
Example program 2 (mechanical).
Example program 3 (mechanical with CSS)..

New: Advanced CSS effects

I've gone to the trouble of writing a number of advanced CSS effects for use with this macro. You can see them all and obtain the CSS code for them by clicking here. These use some very recent CSS animation features in unusual combinations, but they've been tested in both Chrome and Firefox.

Implementation details:
* For CSS users: the <a> tag has the class names "cyclingLink" and "internalLink".
* Each option is a <span> inside the <a> tag. Clicking it causes the next one down to have the class "cyclingLinkEnabled", and the others to have the class "cyclingLinkDisabled". New: on passage load, each option also has the class "cyclingLinkInit", which is removed as each is clicked.
* When you click, the "data-cycle" attribute of the <a> tag increases by 1 (wrapping around to 0 at the end). You could select this with CSS, I guess, using [data-cycle=1] or somesuch.

Version history:

  1. 9-8-13 - Added 'cyclingLinkInit' class support.
  2. 27-3-13 - Bugfixes, added "out" parameter.
  3. 4-3-13 - Bugfix.
  4. 27-2-13 - Added CSS hooks and the "end" parameter.
  5. 26-2-13 - Added new variable-setting ability.
  6. 16-2-13 - Fixed typo preventing it from working.
  7. 29-1-13 - Initial.

Feel free to report any bugs to @webbedspace.

Syndicate content