[Harlowe] Dynamically generating named hooks in (for:) loop

Hi All,

I’m using Twine 2.3.5 with Harlowe 3.1.0. First time asking a question here, so sorry in advance for any mistakes in the post.

I’m trying to set up a passage where players can add crafting ingredients to a recipe, before clicking “proceed” to craft it.

This was fully successful and appeared the way I wanted it to with the manually written code below:

(set:$list to (dm:))
(set:$ITEMquantities to (a:1,2,3,4,5))

|=
{	
|ITEM1hide)[ ]

(if:$list contains "ITEM1")[
	(replace:?ITEM1hide)[
			(Link-repeat:"Remove ")[
				(Set:_Remove to "ITEM1")
				(move:$list's (_Remove) into _buffer)
				(replace:?plan)[$list]
				(replace:?ITEM1hide)[ ]
			]
	]
]

 ITEM1:(dropdown:bind$ITEM1amt,...$ITEMquantities) 
 
 (Link-repeat:"Add")[
 	(Set:$list to it + (dm:"ITEM1",$ITEM1amt))
	(replace:?plan)[$list]
	(replace:?ITEM1hide)[
		(Link-repeat:"Remove ")[
			(Set:_Remove to "ITEM1")
			(move:$list's (_Remove) into _buffer)
			(replace:?plan)[$list]
			(replace:?ITEM1hide)[ ]
		]]
]}



=|

**Crafting plan:**
|plan>[$list]

|==|



[[Proceed]]

Where I would manually replace ITEM1 with the respective item name (i.e. wood, metal, etc…) and copy/paste the new code. This worked so that only once any amount of an item was added, the “remove” link would appear (which is the issue I’m having below).

In an effort to quickly make different passages like this with different lists, I tried using a (for:) loop with and array containing the item names, like so:

(set:$list to (dm:))
(set: $items to (a:"wood","rock","metal"))
(set:$ITEMquantities to (a:'1','2','3','4','5'))

|=

(for: each _item,...$items)[

{(Link-repeat:"Remove ")[
	(Set:_Remove to _item)
	(move:$list's (_Remove) into _buffer)
	(replace:?plan)[$list]		
	]


_item:(dropdown:bind _itemamt,...$ITEMquantities) 
 
(Link-repeat:"Add")[
	(Set:$list to it + (dm:_item,_itemamt))
	(replace:?plan)[$list]
	]}

]


=|

**Crafting plan:**
|plan>[$list]

|==|
[[proceed]] 

This works fine, but only because I removed the reference to the hide-able hook at the beginning, meaning that the “remove” link is ever present (the whole thing fully functional, just not the look I’m going for).

What I would like is something like:

(set:$list to (dm:))
(set: $items to (a:"wood","rock","metal"))
(set:$ITEMquantities to (a:'1','2','3','4','5'))

|=
(for: each _item,...$items)[
{
|_itemhide)[ ]

(if:$list contains _item)[
	(replace:?_itemhide)[
			(Link-repeat:"Remove ")[
				(Set:_Remove to _item)
				(move:$list's (_Remove) into _buffer)
				(replace:?plan)[$list]
				(replace:?_itemhide)[ ]
			]
	]
]

 _item:(dropdown:bind$_itemamt,...$ITEMquantities) 
 
 (Link-repeat:"Add")[
 	(Set:$list to it + (dm:_item,$_itemamt))
	(replace:?plan)[$list]
	(replace:?_itemhide)[
		(Link-repeat:"Remove ")[
			(Set:_Remove to _item)
			(move:$list's (_Remove) into _buffer)
			(replace:?plan)[$list]
			(replace:?_itemhide)[ ]
		]]
]}

]

=|

**Crafting plan:**
|plan>[$list]

|==|
[[proceed]] 

I realize that |_itemhide) by itself isn’t the way to go, I’ve tried using the (hook: _item) macro to generate a new hook in each loop iteration, but it didn’t work, even when I added a unique string to the end in each loop using a string of (i +1). Basically it replaces all the “hide” hooks at once with the same code, so you can only remove the most recent item added (with any of the “remove” links generated). I think there may also be issue is with how I’m referencing it in the (replace:) macro.

Any help on how to generate these hook names and properly reference them with the replace command would be greatly appreciated. I can use a python script to rapidly generate the code from a list, but this only helps if the items and amounts are set before the story starts, not if the $items array is going to be updated mid story.

Thanks!

Hiya PeeweeKiwi,

I realize this is probably much too late to be helpful, but I’m wrestling with a similar problem right now and I’m solving it using macros - to be precise, I’m building up a string containing the command I want, including the dynamically generated hook name, and then I’m using print to evaluate the string as code. A simple example:

|thing5)[BOY HOWDY THIS LANGUAGE IS POWERFUL]
(set: $five to 5)
(set: $macro to "(show: ?thing" + (str: $five) + ")")
(print: $macro)

That’s using show:, but this technique will work with anything, including replace: as in your example.

Hi Pace,

Thanks for the reply. That’s interesting, I didn’t know you could use print/show to execute code. That’ll be handy in the future.

I actually solved the problem a different way, which I’m sharing below in case it’s helpful to you or anyone else reading this:

Basically, rather than using hidden hooks, I used a separate passage to house the current selection and the (Display:) macro to show and update them on the current page.

So the main page would be something like:

|=
(set: $inventory to (a: 
     "wood",
     "rock",
     "metal",
     ))

(set:$list to (ds:))

{
(for: each _item,...$inventory)[
     _item 
     (Link-repeat:"Add")[
          (Set:$list to it + (ds:(_item)))
          (replace:?list)[(display:"List")]
          ]
     (print:"\n\n")
     ]
}

=|
|list>[(display:"List")]

[[Proceed]]          

“List” is then a passage with the following code:

{
(for: each _item,...$list)[
	(link-repeat:'Remove')[
	  	(set: _remove to _item)
	  	(move: ($list)'s (_remove) into _buffer)
	  	(replace:?list)[
		  	(display:"List")
			]
		]
     _item
     (print:"\n\n")
     ]
}

[[Proceed]]

This way, the main passage will display the inventory on the left hand side, and the recipe (list) on the right. Each time an item is added or removed, the (display:) macro is replaced and thus “refreshed”, displaying the updated list. The formatting can be adjusted, the key is just that there is only one named hook being replaced by all links.

This example is simplified to just use an array, but a datamap can be used for both the inventory and the list to account for quantities or other attributes (with a dropdown in the main passage for loop to allow for selection of said quantities or other attributes). Clicking “add” again (or whatever you call the link) would then update the quantity/attribute of an object already in the list. That’s how I’ve been using it and it works fine. The “move” and “buffer” part is necessary to remove items from the datamap, it may be overkill for a simple dataset where:

(set: $dataset to it - _item)

would probably be sufficient. I use datasets and/or datamaps to avoid duplicate entries that are possible with arrays.

Hope this is helpful.

1 Like