Tuesday, 25 March 2014

Macros with clauses

So what if I want some text to be displayed only when the player first sees a passage and optionally other text displayed on subsequent visits? We could use if/else/endif with the visited function, but let us see if we can create a macro to do. The passage might look like this:

Hi there. <<first>>
First time here, eh?
Back again already?
<<endfirst>> What are you doing?

The principle is not unlike if/else/endif, and so I created a macro based on that code. I have heavily commented it, so if you have at least a little familiarity with code you could adapt it to other uses (for example, to handle male and female characters).

Note that this is for Sugarcube. For Sugarcane (and I guess other formats), you need to comment out one line, and uncomment another, towards the end - it says exactly which in the code.

macros['first'] = {
  handler: function (place, macroName, params, parser) {
    //var condition = parser.fullArgs();
    // This is the place in the text that the first clause starts
    var srcOffset = parser.source.indexOf('>>', parser.matchStart) + 2;
    // This is the source text, with everything up to the start
    // of the first clause chopped off
    var src = parser.source.slice(srcOffset);
    var endPos = -1;
    var firstClause = '';
    var notClause = '';

    // First we need to scan through the text to extract the first and second clauses.
    for (var i = 0, currentClause = true; i < src.length; i++) {
      // Have we reached the end of the second clause?
      // If so, set endPos (the start of the rest of the text)
      // and terminate the loop
      // The number 12 is the the length of <<endfirst>>
      if (src.substr(i, 12) == '<<endfirst>>') {
        endPos = srcOffset + i + 12;

      // Have we reached the end of the first clause?
      // If so, flip to doing the second clause
      // and jump ahead to skip the <<notfirst>> characters.
      // The number 12 is the the length of <<notfirst>>
      if (src.substr(i, 12) == '<<notfirst>>') {
        currentClause = false;
        i += 12;

      // We are currently in the middle of one of the clauses, so this character
      // should be added to whichever clause we are on.
      if (currentClause) {
        firstClause += src.charAt(i);
      } else {
        notClause += src.charAt(i);

    // this could go wrong, so get ready to catch errors!
    try {

      // Now we want to display the correct clause
      // Note that text instead the clause is trimmed to remove
      // line breaks and spaces at either end
      // This is for Sugarcube, if you are using sugarcane, comment the next line, and uncomment the one after
      if (visited(state.active.title) == 1) {
      // if (visited(state.history[0].passage.title) == 1) {
        new Wikifier(place, firstClause.trim());
      } else {
        new Wikifier(place, notClause.trim());

      // Finally push the parser past the entire expression
      if (endPos != -1) {
        parser.nextMatch = endPos;
      } else {
        throwError(place, "can't find matching endfirst");

    // Oh, and just in case there was a problem, catch those errors
    } catch (e) {
      throwError(place, 'bad condition: ' + e.message);

// Also need to register <<notfirst>> and <<endfirst>> so Twine does
// not throw an error when it hits them.
macros['notfirst'] = macros['endfirst'] = { handler: function() {} };

No comments:

Post a Comment