2012-12-14

How to Create Custom Macros in Twine

This post is a technical article for the Twine hypertext authoring system. I will assume a passing knowledge of Twine/Tweecode and an intermediate knowledge of Javascript. This tutorial applies to version 1.3.5 of Twine - and even further still I assume the latest betas.

There is a bug in the current v1.3.5 Sugarcane and Jonah story formats; custom macros will not work during the display of the "Start" passage. The Responsive story format does not have this problem.

I'll start with a simple macro example then explain it line by line. Put this code into it's own passage. The passage title can be anything. Give the passage the tag "script"

try {
  version.extensions['macrodemoMacro'] = { 
    major:1, minor:0, revision:0 
  };
  macros['macrodemo'] = {
    handler: function(place, macroName, params, parser) {
      new Wikifier(place, "Hello World!");
    },
    init: function() { },
  };
} catch(e) {
  throwError(place,"macrodemo Setup Error: "+e.message); 
}

Let's go through the code a piece at a time (not necessarily in written order).

try {
  ...
} catch(e) {
  throwError(place,"macrodemo Setup Error: "+e.message); 
}

This code sets up basic Javascript exception handling to let you know via a popup dialog when there is a problem in the macro code. Custom macros are inserted into the page at runtime so they can be difficult to debug. This exception handler makes things a bit easier. Technically this is optional, but trust me, it's worth adding. Just change the "macrodemo" text to the name of your macro so you can tell multiple custom macros apart.

  version.extensions['macrodemoMacro'] = { 
    major:1, minor:0, revision:0 
  };

This statement registers the macro with an extensions array. This is totally optional and appears to currently have no effect. If you are making a macro for re-use in many stories then it's a good idea to store version information so why not do it here. Maybe one day it'll be useful. I've taken the macro name and appended "Macro" just to be consistent with the way Twine's in built macros are registered.

  macros['macrodemo'] = {
  ...
  };

This creates the javascript object that holds the custom macro. The macro name in this case is "macrodemo" and you should change this to suit. The title of a macro should not contain spaces, double quotes or any characters that might confuse twine. There are two required functions and you can also add any other macro related variables inside the object.

    init: function() { },

This function is called when Twine is setting up the macro object when the story is started. This is where you should also initialise any variables because init() is called whenever the story is restarted. In many cases an empty function (as above) works just fine. There is no guarantee which order init() functions will be called, except that generally the inbuilt macros have their init() method called first.

    handler: function(place, macroName, params, parser) {
      new Wikifier(place, "Hello World!");
    },

This is the actual code that is executed when your macro is called. A macro is passed four parameters, the most important of which are "place" (an HTMLContainer where text is currently being output to) and "params" (any parameters passed to the macro). Parameters are split into an array on space characters except within "double quotes". The macroName parameter is self-explanatory. The parser gives a reference to the current Tiddlywiki parser object - which is useful only for some advanced cases; e.g. if you were writing a macro that has a corresponding end macro: see the inbuilt if, else and endif macros or my while loop macro for examples.

The example code here shows how to do simple text output which is added in place of the macro call. Most Twine macros will want to do something similar to this.

Once the macro is in place then somewhere in your Twine story go:

Custom macro: <<macrodemo>> 

Rebuild the story and you should see Hello World! Congratulations you have written your first macro.

BUG: Backslash characters do not encode / decode correctly from the passage store. Use a workaround such as: String.fromCharCode(92).

TIP: Since the latest betas support multiple source files via StoryIncludes, I like to put macros into separate text (.twee) files just to make organisation and reuse easier.

Macros are a powerful way of adding functionality to your Twine stories. The full power of Javascript is available to you. If you make a good macro then please share.