L's blog

Twine: A script to convert parser IF transcripts into linear Twines

I've devised a way to convert game transcripts exported from Gargoyle (using the "script" command) directly into basic Twine games, programmatically. Here is a two-step process:

0) Open a new Twine 1 story.

1) Include this script:

var currentPassage=new Passage("Start");currentPassage.text="";var id=currentPassage.id=1;tale.get("Transcript").text.split(/\n/g).forEach(function(b){var a=b.indexOf(">");if(a===-1){currentPassage.text+=b+"\n"}else{id+=1;var c="Turn "+id;currentPassage.text+=b.slice(0,a)+"\n ''>'' [["+b.slice(a+1)+"|"+c+"]]\n";tale.passages[currentPassage.title]=currentPassage;currentPassage=new Passage(c);currentPassage.id=id;currentPassage.text=""}});currentPassage.text=currentPassage.text.trim()||"<<set window.close()>>";tale.passages[currentPassage.title]=currentPassage;Wikifier.formatters.forEach(function(a){if(a.name=="list"){a.match="^(?!.)(?=.)"}});

2) Paste the entire transcript, from beginning to end, into a passage named "Transcript".

When you run the game, the Start passage and all other passages will be replaced with passages derived from the transcript. Each passage will only have 1 command.

Limitations:
* This assumes that the ">" character is only used for player commands throughout the entire story - and it also assumes that all player commands end in a line break. If these aren't the case, there'll be trouble.
* If the transcript contains other text structures which correspond to Twine syntax, those may unwittingly activate. This script removes one fairly common occurrence - HTML bullet points denoted by * - but leaves the others present.

Suggestions:
* Play in the Jonah format (and optionally remove the title using CSS).
* Use basic CSS to make the links larger (similar to how MS Paint Adventures displays its page links).

FAQ:
Q: Is this Lighan ses Lion compliant???
A: Naturally!

Twine: either(), a random picker function

Update: This script is now built into Twine 1.4! It is no longer necessary to install it.

This is a very short script that allows you to use a function called "either" in Twine's <<set>>, <<print>> and <<if>> macros. Give it several string or number values, separated by commas, and it will pick one of them randomly to use in the macro.

Obsolete script removed: use Twine 1.4

This functions largely identically to the Game Maker choose() function. You can use it like this:

<<print either("a dusty glade", "a sinister vale", "a murky gully", "a desolate gulch") >> - Prints one of the strings, chosen randomly.

<<set $value = either(0,1,2,3,4)>> - Sets $value to one of the numbers.

<<if either(true, false)>> - This is true 50% of the time. Equivalent to <<if Math.random() lt 0.5>>

In the first case, you can see it's functionally similar to the <<rnd>> macro - but, as demonstrated, it can also be used inside <<set>> as well as <<print>>.

If you patch the <<display>> macro, you can also use it to display a random passage, serving as a more primitive kind of <<randomp>>:

<<display either("Cellar", "Garden", "Observatory")>>

Feel free to report any bugs to @webbedspace.

Twine: <<if>> and whitespace

Update: This behaviour has been changed in Twine 1.4! It no longer applies.

This is just a summary of how <<if>>, by default, handles whitespace characters (that is, line breaks, spaces, tabs, and such.)

The behaviour

The rule is: the <<if>> macro removes all whitespace contained between the <<if>>, <<endif>> or <<else>> tags and any non-whitespace contained text.

Consider this code sample, in which the line breaks are marked:

The magenta line breaks (those contained between the <<if>> and <<endif>> and the actual text) will be removed by the macro. Thus, this will render as follows:

A slice of marmalade toast on a plate,
a bit of quiet,
and a spot of tea.

A slice of marmalade toast 
on a plate,
a bit of quiet,
and a spot of tea.

Resisting this behaviour

The <<if>> macro was primarily designed for inserting whole paragraphs. This behaviour is, however, less useful for, say, inserting sentence fragments into paragraphs, which often have leading spaces. There are a few ways to get around this. Such as:

  • Empty comment syntax

    If you pad the interiors of <<if>> macros with an empty comment tag "/%%/", then the behaviour will be overridden, because the comment tags will be treated as text despite not appearing in the final story.

    First line. <<if $a gt 0>>/%%/
    Second line.
    /%%/<<endif>>
    This might be the most basic method of all listed here.

  • Using <<print>>
    The <<print>> macro can also be used.

    A complete sentence<<if $a gt 0>>
    <<print " with an extra amendment">>
    <<endif>>.

    However, it isn't obvious how to add line breaks using this method. There is a way, obscure though it is:

    <<if $a gt 0>><<print "First line." + String.fromCharCode(13) + "Second line.">>
    <<endif>>

  • Using a HTML <br>

    If you want to preserve a line break inside <<if>>, you can do so by the forceful method of using inline HTML.

    First line.<<if $a gt 0>>
    <br>Second line.
    <<endif>>
    

    [*]Patching <<if>> to not remove whitespace

    Much like most undesirable Twine behaviour, this can be patched out on a story-by-story basis. The script code for doing so is as follows... however, using this will conflict with my <<else if>> script, so you can't have one and the other.

    version.extensions.ifMacros={major:1,minor:1,revision:0};macros["if"]={handler:function(place,macroName,params,parser){
    var conditions=[],clauses=[],srcOffset=parser.source.indexOf(">>",parser.matchStart)+2,src=parser.source.slice(srcOffset),endPos=-1,currentCond=parser.fullArgs(),currentClause="",t=0,nesting=0;
    for(var i=0;i<src.length;i++){if(src.substr(i,9)=="<<endif>>"){nesting--;if(nesting<0){endPos=srcOffset+i+9;
    conditions.push(currentCond);clauses.push(currentClause);break;}}if((src.substr(i,6)=="<<else")&&nesting==0){conditions.push(currentCond);
    clauses.push(currentClause);currentClause="";t=src.indexOf(">>",i+6);if(src.substr(i+6,4)==" if "){currentCond=Wikifier.parse(src.slice(i+10,t));
    }else{currentCond="true";}i=t+2;}if(src.substr(i,5)=="<<if "){nesting++;}currentClause+=src.charAt(i);
    }try{if(endPos!=-1){parser.nextMatch=endPos;for(i=0;i<clauses.length;i++){if(eval(conditions.shift())){new Wikifier(place,clauses[i ]);
    break;}}}else{throwError(place,"can't find matching endif");}}catch(e){throwError(place,"bad condition: "+e.message);
    }}};

    The future
    The next version of Twine 1 may change the behaviour of <<if>> to not remove whitespace. I'm still debating how or whether this should be enabled, and how to enable current story code to continue to function correctly.

  • Twine: <<autolink>>, a macro to automatically make links.

    This macro, when surrounding a span of text, will search through the text, finds words that are also names of passages in the story, and converts them to internal links.

    * Since Twine passage names are case-sensitive, this search is also case-sensitive.
    * It currently only matches single ASCII words (letters and numbers) and hyphens contained within, but currently not apostrophe-containing words.

    (function(){version.extensions.autolinkMacro={major:1,minor:0,revision:0};
    macros.autolink={handler:function(g,e,f,b){function tagcontents(starttag,endtag,k){var a=b.source.slice(k),l=0,c="";
    for(var i=0;i<a.length;i++){var w=endtag.length;if(a.substr(i,w)==endtag){if(l==0){b.nextMatch=k+i+w;
    return c}else{l--;c+=a.charAt(i)}}else{if(a.substr(i,starttag.length)==starttag){l++
    }c+=a.charAt(i)}}return""}var k=b.source.indexOf(">>",b.matchStart)+2,d=tagcontents("<<"+e,"<<end"+e+">>",k),bs=String.fromCharCode(92),re=new RegExp("(["+bs+"w-]+)","g"),words,len;
    do{words=re.exec(d);if(words&&tale.has(words[0])){len=words[0].length;
    d=d.slice(0,words.index)+"[["+words[0]+"]]"+d.slice(words.index+len);
    re.lastIndex+=(len)}}while(words);new Wikifier(g,d)}};macros.endautolink={handler:function(){}}
    }());

    Usage example:
    * <<autolink>>The most popular pets are dogs and cats<<endautolink>> - Assuming the passage "dogs" exists, that becomes "dogs".

    Not too sure of the name - might change it later.

    Feel free to report any bugs to @webbedspace.

    Twine: improved YouTube background audio macros

    This code is largely based on some original macros posted to the Google Group by, er, somebody. These take a YouTube video ID and plays the audio of that video invisibly in the background of your Twine game. The player will require Flash installed, of course.

    http://www.glorioustrainwrecks.com/files/TwineMacros-YouTubeAudio-2.1.0.txt (Last updated 16/3/14)

    What I've done is upgrade it in the following ways:
    * The volume slider is removed, on the basis that most authors will probably not have much call for it. Also, it required jQuery and jQuery UI, which not only inflated the footprint of the script, but may have interfered with other jQuery-using Twine scripts.
    * It supports multiple sound tracks per story, and lets you play them simultaneous with each other if you so wish.
    * The script will attempt to preload all of the videos, ensuring that they play as soon as their macros are called. (Note: currently it only preloads the tracks from the start, so this performance gain may not be seen when starting playback from the middle of the track.)
    * A few more macros are provided, roughly in line with my HTML5 audio macros: <<unloopbgm>> and <<pausebgm>>.
    * The code has less implicit globals. (This is a meaningless distinction.)
    * There should be more informative error messages for specific error codes.

    The macro names themselves are similar, but I made the following changes: <<playbgm>> now plays the audio once, and <<loopbgm>> causes it to constantly loop. Otherwise, they function identically.

    Note: For browser security reasons, local HTML files are prohibited from accessing network files through a Flash embed. So, you have to test these macros by running the game from a web server.

    Usage examples
    *<<playbgm iHBWZn9re0U 32>> plays the YouTube video starting from 32 seconds in.
    *<<playbgm iHBWZn9re0U>> plays the YouTube video from the beginning, or resumes it if it was paused.
    *<<stopbgm iHBWZn9re0U>> stops the sound track.
    *<<loopbgm iHBWZn9re0U>> makes the sound track constantly loop (although it may not quite be seamless).
    *<<unloopbgm iHBWZn9re0U>> stops it from looping.
    *<<pausebgm iHBWZn9re0U>> pauses the sound track.
    *<<stopbgm iHBWZn9re0U>> stops playback and resets its position to the start.

    Feel free to report any bugs to @webbedspace.

    Twine: Combined <<Replace>> Macro Set

    Last updated: 26-Feb-14
    I've worked hard to combine my <<replace>>, <<timedreplace>>, <<revision>>, <<hoverreplace>> and <<once>> macros, and all their variations, into a single codebase, and included some new variations as well. Here's my combined "Replace Macro Set":

    http://l.j-factor.com/twine/macros/ReplaceMacros.min.js (version 1.1.4)

    CSS
    The first good news is that you only need this much default CSS for all these macros:

    .revision-span-in {
    	opacity: 0;
    }
    .revision-span:not(.revision-span-out) {
    	transition: 1s; -webkit-transition: 1s;
    }
    .revision-span-out {
    	position:absolute;
    	opacity: 0;
    }
    The reason I require this CSS is to enable people to modify it to make their own effects. You can, for instance, change this to an instant transition by removing the middle CSS block (with the "transition" property line.)

    List of macros
    This set includes the following "revision macros":
    * <<insert>>, <<replace>>, <<continue>> — triggered by clicking their contents.
    * <<timedreplace>>, <<timedinsert>>, <<timedremove>>, <<timedcontinue>>, <<timedcycle>> — triggered by time.
    * <<hoverreplace>>, <<hoverremove>> — triggered by the mouse touching their contents, only temporarily.
    * <<mousereplace>>, <<mouseremove>>, <<mousecontinue>>, <<mousecycle>> — triggered by the mouse touching their contents, permanently.
    * <<once>>, <<later>> — triggered by visiting the passage a certain number of times in the game session.
    * <<insertion>>, <<revision>>, <<removal>>, <<cycle>> — triggered by a separate "triggering macro".

    It also includes these "triggering macros":
    * <<revise>>, <<revert>>, <<randomise>> — hyperlinks.
    * <<mouserevise>>, <<hoverrevise>> — sections that the mouse can touch.

    Changed syntax
    * <<replacewith>>, used to separate sections in <<timedreplace>>, has been replaced with <<becomes>>.
    * You can now have multiple sections in most of the revision macros, by separating them with <<becomes>> or <<gains>> macros:

    These function just like they do within <<revision>> macros - <<becomes>> replaces the previous section with the next section, and <<gains>> merely appends the next section.
    * To reconcile differences between <<replace>> and <<timedreplace>>'s syntax, some macros (currently just the <<replace>>, <<mousereplace>> and <<hoverreplace>> varieties, as well as <<mousecontinue>>) have multiple syntax for specifying sections - the "normal" syntax, where each section is separated by <<becomes>>/<<gains>>, and a "shorthand" syntax, where the first several sections are strings within the opening tag:

    The final section must be between the opening tag and the <<endreplace>> macro tag, though.
    For most of these, the shorthand strings behave as if the <<becomes>> macro separates them. The exception to this is the "insert" named macros, wherein the strings will behave as if the <<gains>> macro separates them.

    New macros
    * <<later>> is the antonym of <<once>> - it displays its contained text on the second and subsequent visits.
    * <<mousecycle>> creates a section of text that cycles between different versions whenever the mouse touches it. It's similar to <<cyclinglink>> in some respects, though you can't yet easily bind a variable to change when it changes.
    * <<timedcycle>> constantly rotates through its sections without stopping. It is similar to <<timedloop>> and may well replace it in future.

    Examples

    In any of these examples, <<becomes>> can be substituted with <<gains>>.

    Feel free to report any bugs to @webbedspace.
    Don't use the attached file. It's out of date.

    Twine: mix HTML tags and Twine markup syntax

    Update: This script is now built into Twine 1.4! It is no longer necessary to install it.

    Just now I was thinking to myself, "Why is it that inline HTML must be stuck between the <html> tags in Twine, anyway? Would it be possible to make HTML usable intersperced with Twine syntax?" So I decided to give it a try. The resulting Javascript code is as follows:

    Obsolete script removed: use Twine 1.4

    This code will allow you to put HTML tags in Twine passage text, and have them function in the compiled game. Note: it requires all HTML tags to be properly closed, with the exception of the so-called "void tags" that never have a closing pair (br, hr, area, img, input, embed, param, source, track). Unmatched end tags will be ignored!

    Feel free to report any bugs to @webbedspace.

    Twine: apply CSS to individual characters

    Update: The Javascript on this page is now built into Twine 1.4! It is no longer necessary to install it.

    This script causes every single character in passages to be wrapped in a <span class="char"> element. This enables a number of mildly interesting CSS effects.

    Obsolete script removed: use Twine 1.4

    These characters have the class of "char", and also a class equal to themselves ("a" for the letter "a", "2" for "2", etc.) It's recommended that you use the :nth-child pseudo-class to select them. Some potential CSS effects that can be performed include the following (examples only):

    Horizontally spin characters on mouseover:
    (works best with large text)

    .char:not(.space):hover {
      transform: rotateY(1440deg);
      -webkit-transform: rotateY(1440deg);
    }
    .char:not(.space) {
      display: inline-block;
      transition: transform 2s ease-out;
      -webkit-transition: -webkit-transform 2s ease-out;
    }
    

    Wavy text:

     .char{ position:relative; }
    .char:nth-child(8n) { top:0px; }
    .char:nth-child(8n+1) { top:-1px; }
    .char:nth-child(8n+2) { top:-1.5px; }
    .char:nth-child(8n+3) { top:-1px; }
    .char:nth-child(8n+4) { top:-0px; }
    .char:nth-child(8n+5) { top: 1px; }
    .char:nth-child(8n+6) { top: 1.5px; }
    .char:nth-child(8n+7) { top: 1px; }

    Animated wavy text:

    .passage {
      font-size: 3em;
    }
    .char { 
      position:relative;
    }
    .char:nth-child(8n) { 
      animation: wavetext 4s 0s infinite;
      -webkit-animation: wavetext 4s 0s infinite;
    }
    .char:nth-child(8n+1) { 
      animation: wavetext 4s -0.5s infinite;
      -webkit-animation: wavetext 4s -0.5s infinite;
    }
    .char:nth-child(8n+2) { 
      animation: wavetext 4s -1s infinite;
      -webkit-animation: wavetext 4s -1s infinite;
    }
    .char:nth-child(8n+3) { 
      animation: wavetext 4s -1.5s infinite;
      -webkit-animation: wavetext 4s -1.5s infinite;
    }
    .char:nth-child(8n+4) { 
      animation: wavetext 4s -2s infinite;
      -webkit-animation: wavetext 4s -2s infinite;
    }
    .char:nth-child(8n+5) { 
      animation: wavetext 4s -2.5s infinite;
      -webkit-animation: wavetext 4s -2.5s infinite;
    }
    .char:nth-child(8n+6) { 
      animation: wavetext 4s -3s infinite;
      -webkit-animation: wavetext 4s -3s infinite;
    }
    .char:nth-child(8n+7) { 
      animation: wavetext 4s -3.5s infinite;
      -webkit-animation: wavetext 4s -3.5s infinite;
    }
    @keyframes wavetext {
      0%, 100% { top: 0em; } 50% { top: 0.5em; }
    }
    @-webkit-keyframes wavetext {
      0%, 100% { top: 0em; } 50% { top: 0.5em; }
    }

    Rapid rainbow text:

    .char:nth-child(8n) { color:hsl(45,100%,75%); }
    .char:nth-child(8n+1) {color:hsl(90,100%,75%); }
    .char:nth-child(8n+2) {color:hsl(135,100%,75%); }
    .char:nth-child(8n+3) {color:hsl(180,100%,75%); }
    .char:nth-child(8n+4) {color:hsl(225,100%,75%); }
    .char:nth-child(8n+5) {color:hsl(270,100%,75%); }
    .char:nth-child(8n+6) {color:hsl(315,100%,75%); }
    .char:nth-child(8n+7) {color:hsl(0,100%,75%); }

    Illuminate letters on mouseover

    .char { 
      transition: all 5s; -webkit-transition: all 5s;
      opacity: 0.4;
    }
    .char:hover {
      transition: all 0.1s; -webkit-transition: all 0.1s;
      opacity:1;
      text-shadow: 0 0 1em white;
    }
    

    Erase text on mouseover

    .char { 
      transition: opacity 999s step-end; -webkit-transition: opacity 999s step-end;
    }
    .char:hover {
      opacity:0;
      transition: opacity 1ms; -webkit-transition: opacity 1ms;
    }
    

    Remove all the T's in the passage text:
    .char.t {
      display:none;
    }

    Change "u" to "U":

    .char.u {
      visibility:hidden;
    }
    .char.u::before {
      content: "U";
      position:absolute;
      visibility:visible;
    }

    These are to be considered basic examples - prompts for more practical uses.

    This code also enables some particularly interesting Javascript visual effects to be performed, which I shall explore in a future blog post.

    Feel free to report any bugs to @webbedspace.

    Twine: Improved back and forward buttons in Sugarcane

    Update: this feature is now built into Twine 1.4! This behaviour is now the default and this page is no longer necessary.

    This script allows Sugarcane to use HTML5 history management instead of URL hash strings to alter the browser history. This means that various non-deterministic game state changes (random numbers, player data input, state changes inside <<replace>> macros, etc.) will be properly remembered when you use the browser's Back button. This code also updates <<back>> and <<return>> and the Rewind menu.

    Obsolete script removed: use Twine 1.4

    Feel free to report any bugs to @webbedspace.

    Twine macro: <<once>>

    This little macro shows a span of text only the first time you encounter it - for all subsequent times you visit a passage containing it, it will be absent. This can be done with variables, but this form lets you use a single pair of macro tags. This can be good for, let's say, IF-style verbose opening descriptions of scenes or rooms.

    Install my <<Replace>> Macro Set to use this macro.

    You can use <<becomes>> or <<gains>> to specify text that should appear only on the second visit, or the third, and so forth.

    Usage examples:
    * <<once>>I'm going now. Goodbye.<<endonce>>
    * <<once>>You arrive at the bathhouse.<<becomes>>You return to the bathhouse a second time.<<endonce>>
    * <<once>>You arrive at the garage.<<becomes>>Back at the garage.<<becomes>>Third visit to the garage.<<endonce>>

    Notes:
    * If many <<once>> macros containing exactly identical text are used in different passages, seeing one of them will hide the others.

    Version history

    1. 16-6-13 - Updated regarding Combined Replace Macro Set.
    2. 12-5-13 - Initial.

    Feel free to report any bugs to @webbedspace.

    Syndicate content