Referencing a Twine array from a Javascript function

Twine Version: 2.4.1

I am struggling to reference and use a Twine array from a Javascript function. As a simple test/example I have:

Twine - StoryInit

<<set $NPC = []>>  
<<run $NPC.push( { name: 'James', location: "kitchen"} )>>
<<run $NPC.push( { name: 'Jenny', location: "garden"} )>>

In my JavaScript I have a small function:

window.cPrompt = function (n) {
	for (let i = 0; i < State.variables.NPC.length; i++) {
		if (State.variables.NPC.name[i] === n) {
			return true;
		}
	}
	return false;
};

Then, to test this a Twine passage:

<<if cPrompt 'James'>> 
	Test: Found James.
<</if>>

When I run this I get:
Error: <>: bad conditional expression in <> clause: Cannot read properties of undefined (reading ‘0’)

If anyone could explain what I have done wrong I would appreciate it. Thanks.

I think I might know what’s wrong here?
$NPC is set as an array at first, but I think you are referencing it as an object in your JavaScript?
State.variables.NPC.name[i]State.variables.NPC[i].name ? Could this work maybe?

You could otherwise set $NPC as an object?

Also, SugarCube has a native looping macro <<for>>.

1 Like

Oh that was so obvious :no_mouth: Thank you so much for pointing it out so politely.

1 Like

[Got caught up in something else and posting this a little late, but….]

I’m surprised you received any error from that, considering your example doesn’t call cPrompt() at all—you forgot the call parentheses. Beyond that, you’re indexing the wrong thing $NPC.name vs. $NPC—i.e., you should be doing $NPC[i].name rather than $NPC.name[i].

Corrections:

Actually calling the function within the conditional expression:

<<if cPrompt('James')>>
	Test: Found James.
<</if>>

Properly indexing the array:

window.cPrompt = function (n) {
	for (let i = 0; i < State.variables.NPC.length; i++) {
		if (State.variables.NPC[i].name === n) {
			return true;
		}
	}
	return false;
};

Though, as a suggestion, if you’re going to reference State.variables multiple times within a function, caching it isn’t a bad idea:

window.cPrompt = function (n) {
	const sv = State.variables;

	for (let i = 0; i < sv.NPC.length; ++i) {
		if (sv.NPC[i].name === n) {
			return true;
		}
	}

	return false;
};

Further, in this case, you could simply cache $NPC instead:

window.cPrompt = function (n) {
	const NPC = State.variables.NPC;

	for (let i = 0; i < NPC.length; ++i) {
		if (NPC[i].name === n) {
			return true;
		}
	}

	return false;
};

Finally. There are native methods—e.g., <Array>.some()—that you could have used instead of rolling your own. For example.

Using <Array>.some() directly within the conditional expression:

<<if $NPC.some(npc => npc.name === 'James')>>
	Test: Found James.
<</if>>

If you prefer using your cPrompt() function, then using <Array>.some() within your function:

window.cPrompt = function (name) {
	return State.variables.NPC.some(npc => npc.name === name);
};
4 Likes