Passing the output of a passage into a variable in Sugarcube 2.33

Twine Version: 2.35
Story Format: Sugarcube 2.33.2

Hi - I’m trying to get the output of various passages into an array, and then to iterate through the array displaying them with various effects and timings. Some passages if rendered on their own would be empty (due to conditional logic or randomisation). I need to be able to not add these to my array as they mess with formatting and timing. Passage.processText() gets me the semi-processed text but the logic contained in it will always be processed when text gets added to the DOM as best I can tell- ie even something which will generate no output won’t give me an empty string. jQuery.wiki() wants to add it straight to the DOM which I don’t want to do yet. Basically I want to get the equivalent of the output of the <> macro straight into a variable. Is there a way I can do this please?

My (not-working) code:

<<set $homeBits=[]>>
<<set $homeBits.push(Story.get("Home top description").processText())>>
<<set _fridge= Story.get("Home Fridge Magnet").processText()>>
<<if _fridge!="">><<set $homeBits.push(_fridge)>><</if>>
<<set _quiz= Story.get("Home Quiz Description").processText()>>
<<if _quiz!="">><<set $homeBits.push(_quiz)>><</if>>
<<set _pamphlet= Story.get("Home Pamphlet Description").processText()>>
<<if _pamphlet!="">><<set $homeBits.push(_pamphlet)>><</if>>
<<set _lamp= Story.get("Home Lamp Description").processText()>>
<<if _lamp!="">><<set $homeBits.push(_lamp)>><</if>>
<<set _vodka= Story.get("Home Vodka Description").processText()>>
<<if _vodka!="">><<set $homeBits.push(_vodka)>><</if>>


<<set _delay=3>>
<<for _i=0; _i<$homeBits.length; _i++>>
<<set _myDelay=(_delay*_i)+"s">>
<<capture _myDelay, $homeBits, _i>>

<<set _left=random(-200,200)>>
<<set _leftstyle="left:"+_left+"px">>
<div class="homebit" @style=_leftstyle><<timed _myDelay t8n>><<print $homeBits[_i]>><</timed>></div>

<</capture>>
<</for>>

Thanks!

I think what you might be looking for is this:

Story.get("passage_name").text

https://www.motoslave.net/sugarcube/2/docs/#passage-api-prototype-getter-text

A better way to do that, instead of using a bunch of passages, would be by using a widget. The reason why is, the more passages you use, the slower the Twine editor gets. It’s generally not a big deal if you have less than 700 or so passages, but if you start getting up there, you’ll wish you’d optimized things from the beginning.

To use a widget for that, you’d put something like this in a non-special non-story passage, with “widget” and “nobr” tags:

<<widget "getText">>
	<<set _outputText = "", _maxOpts = 5>>
	<<if $args.length>>
		<<switch parseInt($args[0], 10)>>
			<<case 0>>
				<<set _outputText = "Home top description text">>
			<<case 1>>
				<<if $magnetFound>>
					<<set _outputText = "Home Fridge Magnet text">>
				<</if>>
			(...etc...)
			<<case 5>>
				<<if $vodkaFound>>
					<<set _outputText = "Home Vodka Description text">>
				<</if>>
		<</switch>>
	<</if>>
<</widget>>

Basically, just put all of those passages into that one widget within each <<case>> in the <<switch>> macro, and set _outputText to whatever text you want them to output based on whatever conditions you want to use. Also, set _maxOpts to whatever the largest <<case>> number is.

Then, in your passage, you’d just do:

<<nobr>>
	<<set _delay = 0>>
	<<getText>>
	<<for _i = 0; _i <= _maxOpts; _i++>>
		<<getText _i>>
		<<if _outputText>>
			<<capture _outputText>>
				<div class="homebit" @style="'left: ' + random(-200, 200) + 'px'">
					<<timed `_delay + "s"` t8n>>
						<<= _outputText>><br>
					<</timed>>
				</div>
			<</capture>>
			<<set _delay += 3>>
		<</if>>
	<</for>>
<</nobr>>

That would first call <<getText>> with no options in order to set _maxOpts, and then it would loop through all of the options in the <<getText>> widget.

If you need to do that code more than once, then you could turn that code into a widget as well (even putting it in the same passage as the <<getText>> widget) and then call that widget as needed.

Also, you should be aware that you were using the attribute directive incorrectly. You can’t just do @style=_leftstyle, you’d have to do that like this: @style="_leftstyle" because the quote marks are required for that to work.

Note that I used a few shortcuts in the above code, such as within the <<timed>> macro call, so if you can’t find the explanation in the SugarCube documentation yourself for any of them, feel free to ask if you still need help understanding how any of that code works.

Hope that helps! :grinning:

Thanks tayruh, but i think Passage.text also returns unprocessed text, so if for eg. my passage contains <<if $someVariable>>blah blah<</if>> and $someVariable==false the return value of that will be “<<if $someVariable>>blah blah<</if>>” and not “”…

Thanks so much for this HiEv -

That’s useful to know about optimisation. I think I’m unlikely to hit 700 passages on this project but still, i’ll try to keep passage numbers down.

What I’m trying to be able to do and what I don’t think I can do with the approach you’re outlining here is keep the different sections modular and re-usable - for eg I might want to use my Home Lamp Description elsewhere and by putting it in a passage (or i guess, in its own widget) I’d be able to do that. But then I can’t see a way of getting the output into a variable, which would then let me either make decisions according to the content of the output (eg display it differently if it’s longer than a certain length, apply a particular css class if it’s only one word etc) or further process that output before displaying it (eg reversing the text, running a find and replace etc).

Perhaps it’s just not possible to do this and what I’m going to need to do is write javascript functions that evaluate the output each time, which is fine I guess, but likely to be a bit more work and a bit harder to hand over to people I’m working with…

Oh. Sorry. I’m not sure which macro you’re trying to emulate because the board ate your tag. You need to wrap it in back ticks to get it to render inline.

Like this: `<<bleh>>`

ah - sorry - i’ve edited…

You could simply make the descriptions as strings on the SugarCube setup object in your JavaScript section or StoryInit passage, and then use that to access the descriptions.

For example, you might have this in your StoryInit passage:

<<set setup.lamp = "Home Lamp Description">>

and then you could use setup.lamp to access that description wherever you wanted. Displaying that description would be as easy as doing <<= setup.lamp>> (<<=>> is shorthand for the <<print>> macro).

As long as you don’t modify any setup values after the StoryInit passage, that should be fine. (Modifying setup values later on would cause the game to behave differently after loading a save, since those values would be set back to the default.)

Hope that helps! :grinning:

Thanks again HiEv - the trouble with that approach is that there is conditional logic and randomisation in the passages I’m calling. I think the approach might need to be creating js functions which build the output for each one and calling those, which doesn’t feel super clean and doesn’t let me use widgets and macros, but I can’t see another way at the moment…

Well, you haven’t shown an example of the kind of code you’re using to generate your descriptions, so it’s hard to say how best to make it work. However, keep in mind that you can put your “conditional logic and randomization” into that string, and when you print it, it will run that code.

Have fun! :grinning:

1 Like

Hey - well there’s a bunch of different things I’m using in the different passages - ifs, switches, eithers etc. Really I’d like it to be extensible so I can separate out the logic and even have it potentially calling other functions/passages. A simple example might be:

<<if $lampOn>>The lamp is on<<else>>The lamp is off<</if>>

another might be:

<<switch random(1,5)>>
<<case 1>>
You are happy.
<<case 2>>
You are sad
<<case 3>>
You notice that not all values of random result in an any output
<</switch>>

I’d guess the way to do this in JS would be

window.HomeBits = function () {
	
};

/* apparently we need this although the code still runs without it */
HomeBits.prototype.clone = function () {
	// Return a new instance containing our own data.
	return new HomeBits(
	);
};

/* apparently we need this although the code still runs without it  */
HomeBits.prototype.toJSON = function () {
	// Return a code string that will create a new instance containing our
	// own data.
	return JSON.reviveWrapper(String.format(
		'new HomeBits()'));
};

HomeBits.prototype.Lamp = function(){
	if(State.active.variables["lampOn"]){return "The lamp is on.";}
	else{return "The lamp is off";}
}
HomeBits.prototype.HappySad = function(){
	switch(Math.ceil(State.random()*5)){
		case 1:
			return "You are happy.";
			break;
		case 2:
			return "You are sad.";
			break;
		case 3:
			return "You realise some return paths return an empty string.";
			break;
		default:
			return"";
	}
}

/* etc etc */

and then in an Init passage:

<<set $home=new HomeBits()>>
<<set $homeBits=[]>>

Home passage

<<nobr>>
<<set _temp=$home.Lamp()>>
<<if _temp!="">><<set $homeBits.push(_temp)>><</if>>
<<set _temp=$home.HappySad()>>
<<if _temp!="">><<set $homeBits.push(_temp)>><</if>>

/*etc etc*/

<<for _i=0; _i<$homeBits.length;_i++>>
<div>
/* code wrapping */
<<= $homeBits[_i]>>
/* code wrapping */
</div>
<</for>>
<</nobr>>

To go back to my earlier example, you could just do this:

<<set setup.lamp = "The lamp is <<if $lampOn>>on<<else>>off<</if>>.">>

Then you could to <<= setup.lamp>>, which would display either “The lamp is on.” or “The lamp is off.”.

Alternately, you could do this:

<<set setup.lamp = function () { return "The lamp is <<if $lampOn>>on<<else>>off<</if>>."; }>>

Then you’d have to do <<= setup.lamp()>> instead.

You could do that like this:

<<set setup.rand = function () { return ["You are happy.", "You are sad.", "You notice that not all values of random result in an any output.", "", ""].random(); }>>

You could then use setup.rand() to get the result from that.

That’s a faaarrr simpler way to do it using JS functions.

Nope. You should never do that. There’s no good reason I can think of to store functions in a story variable, since it will only bloat up the game’s history, which will slow down saves, loads, and passage transitions. There are cases where you might want to store the name of the function as a string in a story variable, and then use that string to call the function, however you should normally just call the function directly.

If you put all of the functions on a single object on the setup object, then you could simply loop through all of the properties on that object. If you need them in a certain order, then push those functions into an array on the setup object so you can iterate through them in order.

Hope that helps! :grinning:

gotcha - thanks for sticking with this!

this one-

<<set setup.lamp = function () { return "The lamp is <<if $lampOn>>on<<else>>off<</if>>."; }>>

doesn’t work for me because if there’s a possibility of displaying nothing I end up with empty divs.

But the second version using setup works perfectly. Thank you! Makes sense about not creating unnecessary variables as well. And functions in an array on setup is exactly what I’m looking for!