Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Macros With Endtags

Say you'd like to wrap tweecode in a function such as:
<<beginmacro>> Macro this text <<endmacro>>
How do you do that? In case it's relevant, let me explain exactly what I've got going.

I have a macro that I pass arguments to in order to generate a dialog box. Things like the speaker's name, x and y position, and the CSS style to use. Right now I have to pass the contents of the CSS window too, so calling the function looks like this.
<<dialogbox "0,0" "bluewindow" "Hello player.">>
This presents obvious problems, most of which being I can't easily use Tweecode inside the argument or it will break. I even wrote my own tiny parser to get around this.
<<dialogbox "0,0" "bluewindow" "Hello #print $player.Name#.">>
I can't create two separate macros, because as I'm sure the people who can answer this question know, you get errors (div tag wasn't closed (even though it was)). Obviously it CAN be done, as <<if>> and <<replace>> show, but am I in over my head? Is it even worth it just so I can get some cleaner code? I'd like to do something like this:
<<dialogbox "0,0" "bluewindow">> Hello <<print $player.Name>>.<<enddialogbox>>

Comments

  • Wait a moment - is this a Javascript macro or a <<display>>-style passage-macro?
  • Its a Javascript macro. It won't work without the stylesheet, but in case you want to take a look.
    String.prototype.replaceAll = function (find, replace) {
    var str = this;
    return str.replace(new RegExp(find, 'g'), replace);
    };

    macros['dialogbox'] = {
    handler: function(place, macroName, params, parser) {

    if(params.length == 2) {
    this.n = params[0];
    this.m = params[1].replaceAll("@", "<<").replaceAll("#", ">>").replaceAll("~", "");

    if(this.n == "Player") {
    this.n = "<<print $playerName>>";}

    this.f = ("<div><messagebox id='namewindow'>" + this.n + ":</messagebox><br><messagebox id='bluewindow'>" + this.m + "</messagebox></div>");
    }

    if(params.length == 3) {
    this.n = params[0];
    this.m = params[1].replaceAll("@", "<<").replaceAll("#", ">>").replaceAll("~", "");
    this.t = params[2];

    if(this.n == "Player") {
    this.n = "<<print $playerName>>";}

    this.f = ("<div><messagebox id='namewindow'>" + this.n + ":</messagebox><br><messagebox id='" + this.t + "'>" + this.m + "</messagebox></div>");
    }

    new Wikifier(place, this.f);
    },
    };
  • I'm assuming that you're using a vanilla header for this.  Regardless, try this.

    A few caveats first:
    • I was unsure why you're were assigning to properties on the macro, rather than simply using local variables, but I kept that behavior anyway.
    • Also, I didn't see a place in your sample code for the coordinate argument from your examples, so the following probably needs extended to include it.
    • Finally, your sample code printed $playerName, while your examples used $player.Name.  The macro currently uses $playerName from your original sample code.
    Prototype:
    <<dialogbox SPEAKER OPTIONAL_ID>>DIALOG_WIKI_TEXT<<enddialogbox>>

    Usage:

    /% using the default ID, "bluewindow" %/
    <<dialogbox "Player">>Hello <<print $player.Name>>.<<enddialogbox>>

    /% using the specified ID, "redwindow" %/
    <<dialogbox "Player" "redwindow">>Hello <<print $player.Name>>.<<enddialogbox>>
    Code:

    version.extensions["dialogboxMacro"] = { major: 2, minor: 0, revision: 0 };
    macros["dialogbox"] =
    {
    handler: function(place, macroName, params, parser)
    {
    if (params.length === 0)
    {
    throwError(place, "<<" + macroName + ">>: no dialog source (speaker) specified");
    return;
    }

    // process the contents of the container macro
    var openTag = macroName
    , closeTag = "end" + macroName
    , start = parser.source.indexOf(">>", parser.matchStart) + 2
    , end = -1
    , tagBegin = start
    , tagEnd = start
    , opened = 1;
    while ((tagBegin = parser.source.indexOf("<<", tagEnd)) !== -1
    &amp;&amp; (tagEnd = parser.source.indexOf(">>", tagBegin)) !== -1)
    {
    var tagName = parser.source.slice(tagBegin + 2, tagEnd)
    , tagDelim = tagName.search(/\s/);
    if (tagDelim !== -1)
    {
    tagName = tagName.slice(0, tagDelim);
    }
    tagEnd += 2;
    switch (tagName)
    {
    case closeTag:
    opened--;
    break;
    case openTag:
    opened++;
    break;
    }
    if (opened === 0)
    {
    end = tagBegin;
    break;
    }
    }

    // if we successfully found an end tag for the macro
    if (end !== -1)
    {
    parser.nextMatch = tagEnd;

    // CoraBlue code...
    this.n = params[0];
    if (this.n === "Player")
    {
    this.n = "<<print $playerName>>";
    }
    this.t = params[1] || "bluewindow";
    this.m = parser.source.slice(start, end);
    this.f = ('<div><messagebox id="namewindow">' + this.n + ':</messagebox><br><messagebox id="' + this.t + '">' + this.m + "</messagebox></div>");

    new Wikifier(place, this.f);
    }
    else
    {
    throwError(place, "<<" + macroName + ">>: cannot find a matching close tag");
    }
    },
    };
    macros["enddialogbox"] = { handler: function () {} };
  • Thank you so much for showing me how to do this. There's some methods in here I haven't quite learned yet (I started reading up on javascript only last week, which explains why I do some things less than optimally) but if I may, can I talk through this to make sure I understand the logic behind it? I just want to learn how to fish, if you get my drift.
    • We're parsing everything between 'dialogbox' and 'enddialogbox' while manually providing a buffer space for the brackets.
    • The enddialogbox macro doesn't even do anything, it's basically just there so Twine can identify the macro without throwing errors.
    • I'm assuming matchstart+2 is there otherwise each dialog box would start with '>>'.
    • I'm assuming we're parsing character by character later on, but I don't know what this is doing tagName.search(/\s/);. Is /\s/ performing some kind of special fuction?
  • CoraBlue wrote:
    We're parsing everything between 'dialogbox' and 'enddialogbox' while manually providing a buffer space for the brackets.

    It matches the opening macro tag, the closing macro tag, and the contents in between.  The start and end indices are only for the contents though, so it can be easily sliced out later.

    CoraBlue wrote:
    The enddialogbox macro doesn't even do anything, it's basically just there so Twine can identify the macro without throwing errors.

    More or less.  Normally, the wikifier doesn't even see the closing tag, it's completely handled by the parent macro.  The <<enddialogbox>> macro is only there to keep the macro formatter from complaining, which will only happen if you have mismatched number of <<enddialogbox>> to <<dialogbox>> (specifically, too many of the closing tag; the macro itself will complain if it can't find a closing tag, because there were too few).  So, it only comes into play if you've mismatched your opening vs. closing tags.

    CoraBlue wrote:
    I'm assuming matchstart+2 is there otherwise each dialog box would start with '>>'.

    Yes.  The <String>.indexOf() method returns where the match started, so since we're matching the closing angle-brackets of the opening tag (">>"), we need to add two to move the start index past them.

    CoraBlue wrote:
    I'm assuming we're parsing character by character later on

    Not at all.  The conditional of the while loop scans for macro tags.  Each time it matches one, it parses the macro's name from the tag and checks to see if it's found the correct closing tag.  It keeps looping until it's exhausted all macro tags in its source or it finds its closing tag.

    If the incrementing and decrementing in the switch statement is what you were thinking of, that's there to handle tag nesting, so that only the correct closing tag will stop the parse.

    CoraBlue wrote:
    , but I don't know what this is doing tagName.search(/\s/);. Is /\s/ performing some kind of special fuction?

    It finds the first space after the macro name in a tag, if any, which allows the code to separate the tag name from any possible arguments.
  • I've had recent experience with heavy amounts of macro driven dialog.  It was not a pleasant one.  What I've learned is that it could have been tackled with far less clutter using a sort of screenplay/graphic novel markdown structure. 

    Consider the following Dialog in tweecode.
    ::Dialog Abort [Dialog]

    Computer:
    "...Ten seconds to auto-destruct..."

    Riker:
    "Captain..."

    Captain:
    "Abort auto-destruct sequence."

    Computer:
    "Riker, William T., do you concur?"

    Riker:
    "Yes! Absolutely! I do indeed concur! Whole-heartedly!"

    Computer:
    "Auto-destruct canceled."

    Captain:
    "..."

    Captain:
    "A simple 'yes' would have sufficed, Number One."

    Riker:
    "I didn't want there to be any chance of a misunderstanding."

    END SCENE
    That could easally be parsed by a special dialog module.  It would parse standard twee syntax.  Individual Speaker styles and formatting could be defined in a separate passage.
    ::Cast List [Actors]

    Captain:
    class = "Picard"


    Captain surprised:
    class = "picardsmerk"

    Computer:
    class = "LCARS"

    Riker:
    class = "babyface.png"
    This wouldn't be difficult to whip together.  Just some thoughts on the matter.
Sign In or Register to comment.