Howdy, Stranger!

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

Macros: Different text when a passage is first read

So what I want is for some text to be displayed only when the player first sees a passage and optionally other text displayed on subsequent visits. The passage might look like this:
Hi there. <<first>>
First time here, eh?
<<notfirst>>
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.

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;
break;
}


// 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() {} };

Comments

  • I asked about (more or less) this a few days ago:

    http://twinery.org/forum/index.php/topic,1560.0.html

    And it turns out that visited() is a pretty tidy way to do what your macro does while also allowing for susequent visits, if I understand correctly.

    However, what would be really, really awesome is a function like you describe that will work anywhere, independent of (but still usable for) passage visits. So I can have a piece of conditional text that could show up anywhere in the story, in a display passage for example, and using this kind of syntax to allow for more than just first time cases:

    <<changingtext>>You are surprised to see a cat here!<<second>>That cat's still hanging around.<<third>>The cat meows plaintively.<<final>>You're getting sick of that damn cat.<<endchangingtext>>

    I realise this is pretty different from what you're doing. Just wishing in the wind here. ;)
  • Ummmm. Really? All that code?
    <<if visited(passage()) is 1>>first time text<<else>>back again text<<endif>>
    If you really want it in a macro you could:
    ::first
    <<nobr>>
    <<set $first to parameter(0)>>
    <<set $back to parameter(1)>>
    <<if visited(passage()) is 1>><<print $first>><<else>><<if $back nqe 0>><<print $back>><<endif>><<endif>><<endnobr>>

    ::A room
    Hi there.
    <<first "First time here?" "Back again?">>
    What are you doing?
    It's good code in your macro, it just seems a complicated way of doing things...

    A little tweak should extend it to mostly useless's issue...
    ::visits
    <<nobr>>
    <<set $parms to [ parameter(0) ]>>
    <<if parameter(1) neq 0>><<set $parms.push(parameter(1))>><<endif>>
    <<if parameter(2) neq 0>><<set $parms.push(parameter(2))>><<endif>>
    <<if parameter(3) neq 0>><<set $parms.push(parameter(3))>><<endif>>
    <<if parameter(4) neq 0>><<set $parms.push(parameter(4))>><<endif>>
    <<if parameter(5) neq 0>><<set $parms.push(parameter(5))>><<endif>>
    <<if parameter(6) neq 0>><<set $parms.push(parameter(6))>><<endif>>
    <<if parameter(7) neq 0>><<set $parms.push(parameter(7))>><<endif>>
    <<if parameter(8) neq 0>><<set $parms.push(parameter(8))>><<endif>>
    <<if parameter(9) neq 0>><<set $parms.push(parameter(9))>><<endif>>
    <<set $ix to visited(passage()) - 1>>
    <<if $ix gte $parms.length>><<set $ix to $parms.length - 1>><<endif>>
    <<print $parms[$ix]>>
    <<endnobr>>
    ::A Room
    <<visits "You are surprised to see a black cat here!" "That cat's still hanging around." "The cat meows plaintively." "You're getting sick of that damn cat.">>
  • Oho, nice! I'll be trying that out later. Thanks!

    EDIT: Actually, hold on. Won't that just work for passage visits?
  • mostly wrote:

    EDIT: Actually, hold on. Won't that just work for passage visits?


    Yes, because both The Pixie and mykael solutions rely on the visited function to work.
  • Yeah, that's what I figured. This would be hella useful if you could use it in any text; otherwise you might as well just use visited, really.
  • Continuing to hijack a thread here, but hey mostly useless, what do you mean? Could you clarify (maybe in a different post, I dunno)?

    Do you mean something like this:
    ::A Passage::

    Some text. More text. <<display 'Conditional Text Passage'>> More text. And more. Okay that's all.

    ::Conditional Text Passage

    <<if $variableA>>something<<else>>something else<<endif>>
    <<if $variableB>>a completely different thing entirely<<else>>or this<<endif>>
    (etc etc)
    ?

    Is that what you mean? I accidentally realized while writing something for Sharpe's last challenge that you can usefully reuse a passage by displaying it wherever you want, and having a lot of conditional code in that passage that makes what it displays vary a lot.

    I hope that makes sense, and you probably already know that, so I'm not sure if I'm understanding you. :P
  • It's purely a tidyness thing. Much neater to have a macro handling it than making a new passage and setting variables for every bit of changing text. My story already looks a bit too sprawly for my liking, and doing it this way would clutter everything up. I realise how OCD that might sound. :D
  • Nah. You're probably just at a higher level of technical ability than me. :P
  • With an independent counter:
    ::setup
    <<set dcount = { }>>

    ::visits
    <<nobr>>
    <<set $counter to parameter(0)>>
    <<set $parms to [ parameter(1) ]>>
    <<if parameter(2) neq 0>><<set $parms.push(parameter(2))>><<endif>>
    <<if parameter(3) neq 0>><<set $parms.push(parameter(3))>><<endif>>
    <<if parameter(4) neq 0>><<set $parms.push(parameter(4))>><<endif>>
    <<if parameter(5) neq 0>><<set $parms.push(parameter(5))>><<endif>>
    <<if parameter(6) neq 0>><<set $parms.push(parameter(6))>><<endif>>
    <<if parameter(7) neq 0>><<set $parms.push(parameter(7))>><<endif>>
    <<if parameter(8) neq 0>><<set $parms.push(parameter(8))>><<endif>>
    <<if parameter(9) neq 0>><<set $parms.push(parameter(9))>><<endif>>
    <<set $ix to dcount[$counter]>>
    <<if $ix gte $parms.length>><<set $ix to $parms.length - 1>><<endif>>
    <<print $parms[$ix]>>
    <<set dcount[$counter] to $ix + 1>>
    <<endnobr>>
    That uses an independent variable dcount to keep track of the number of displays.

    Call it as:
    <<visits "cat" "You are surprised to see a black cat here!" "That cat's still hanging around." "The cat meows plaintively." "You're getting sick of that damn cat.">>
    The cat value is to tell it the name of the counter to use - it automatically get incremented every time the macro is called.
  • @loopernow - that's unlikely! My username is pretty apt, and I've had a hell of a lot of help from the forum with anything remotely complex. Currently I do this kind of thing more or less how you described and I have no idea how to write that macro, I just desire it so I can keep things simple. :)

    @mykael - and there it is! Nice! Its 3-30am and I have work tomorrow, but i will totally try that out when I get home. Thanks!
Sign In or Register to comment.