Creating a list of links based on an array containing text strings

Twine Version: 2.4.1
Story Format: 2.36.1

I am using ChapelR’s simple inventory (v2.3.0) but want to have my own inventory display passage so I can customise how the inventory is displayed. Rather than the default list of text strings I want a list of links which when clicked add a description of the clicked item in a paragraph below. Clicking a different item would then overwrite the description. I have the following code which displays the list of text strings as links but then gives an error when I click on one:

<<newinventory '$testInv'>>
<<pickup '$testInv' 'cat food'>>
<<pickup '$testInv' 'apple'>>

<<if $testInv.inv.length > 0>>
<<for $i to 0; $i lt $testInv.inv.length; $i++>>
<<link $testInv.inv[$i]>><<replace "#InvDesc">><<include $testInv.inv[$i]>><</replace>><</link>>
<</for>>
<<else>>
  Your inventory is empty.
<</if>>

<span id="InvDesc"></span>

I have paragraphs set up titled for each potential inventory item. So in this case paragraphs for apple and cat food.

The error returned is:
Error: cannot execute macro <>: Story.has title parameter cannot be undefined <<include $pInv.inv[$i]>>

Oddly if I hard code a value in place of $i then the include works although obviously just with the same passage all the time.

Could anyone point out what I am doing wrong here please?

With a quick glance the first trouble I think: when you’re clicking the link, $i has changed to the last value the loop allows for.
You might use the <<capture>> macro http://www.motoslave.net/sugarcube/2/docs/#macros-macro-capture to avoid the problem.

I’ve not tested it for lack of time right now, but you can try something like this in your loop:

<<for $i to 0; $i lt $testInv.inv.length; $i++>>
<<capture $i>>
<<link $testInv.inv[$i]>><<replace "#InvDesc">><<include $testInv.inv[$i]>><</replace>><</link>>
<</capture>>
<</for>>

Also, in most cases you should use a temporary variable in a loop, _i would be better than $i in this instance.

1 Like

souppilouliouma’s diagnosis is correct.

Whenever you need to read the value of a variable within an asynchronous block of code that will have been changed by the time the code is run, you need to capture the variable and thus its value.

Asynchronous here meaning code that runs out-of-sync with the normal sequence of events. For example, the code within a <<link>> macro, since it runs only whenever the link is activated by the player.

Here’s a corrected example using <<capture>> with a temporary loop variable:

<<for _i to 0; _i lt $testInv.inv.length; _i++>>
<<capture _i>
<<link `$testInv.inv[_i]`>>
	<<replace "#InvDesc">><<include `$testInv.inv[_i]`>><</replace>>
<</link>>
<</capture>>
<</for>>

Here’s another example using <<capture>> with the range-form of <<for>>:

<<for _item range $testInv.inv>>
<<capture _item>>
<<link _item>>
	<<replace "#InvDesc">><<include _item>><</replace>>
<</link>>
<</capture>>
<</for>>
2 Likes

Thanks for the answers and the additional explanation. Much appreciated.