Difficulty Defining Any Javascript Function

Twine Version: Using Twine 2.7.0 and SugarCube 2.36.1

Hi there! I apologize for asking what will most likely be a simple question. For reference, I have searched through this forum, general Google queries, and even asked ChatGPT for help, and I keep hitting the same brick wall. I should also point out that I am generally a programming novice, so much of the code I have been seeing is not extremely intuitive, but I’m figuring it out.

GENERIC PROBLEM - My problem is thus - I cannot get ANY Javascript function to operate correctly in a Twine game.

SPECIFIC PROBLEM - I was looking for an easier way to add elements to an array using a Javascript function. These elements are named statuses that the player can pick up while traversing my game. The statuses the player gains are checked at various points. Twine is saying every attempt to run the Javascript function is undefined, however.

CURRENT SUCCESS - I am able to add these statuses just fine right now, without Javascript. In the StoryInit passage I initialize my array thusly:

<<set $statuses to []>>

Then in a relevant passage, when a status is gained, I have:

<<nobr>>* Gain status A1.
<<if $statuses.includes('A1 - Garik is pleased')>>
  <<else>>
   <<set $statuses.push('A1 - Garik is pleased')>>
 <</if>><</nobr>>

Again, this works just fine. However I realized that I could probably do this slightly easier with a Javascript function. Instead of doing the whole code every time, turn it into a function and just run the function with the new status name. So I added this code to the Story JavaScript section of Twine:

 // Story JavaScript section
function gainStatus(statusName) {
  if (!$statuses.includes(statusName)) {
    $statuses.push(statusName);
  }
}

Then, in the relevant passage, I replaced the old code with:

<<run gainStatus('A1 - Garik is pleased')>>

However, when that happens and I reach that passage, I receive the error

“Error: <>: bad evaluation: gainStatus is not defined”

I troubleshot the best that I could, attempting to even change the Javascript code to:

window.gainStatus = function(statusName) {
  if (!$statuses.includes(statusName)) {
    $statuses.push(statusName);
  }
}

Then I tried to call ANY Javascript function and I kept getting similar error messages that the function was not defined. Yes, the Javascript code is being placed in the right location, and yes, the game language is in SugarCube.

Since my old code is working fine I’m not too worried, but I am perplexed as to why I can’t get a Javascript function to operate as it should. I am sure there is some simple thing I am overlooking, to which I deeply apologize. But any light that someone could help shed on this situation would be greatly appreciated.

1 Like

Hi there

There’s two problems with your function, scope, and content, which I will tackle separately.

Scope

All TwineScript (the code inside a passage) runs in a separate isolated scope, as does the stuff in your Story Javascript. If you define a function in Story Javascript it will cease to exist when the Story Javascript finishes, unless you attach it to a global object.

Now, as you’ve noticed you can attach your functions to window, but SugarCube provides a special global object setup to make it easier and to avoid name clashes with other window functions.

Content

Inside your function, you are referring to $statuses which is a story variable, however the automatic recognition of $ (or _) only happens inside a passage (or technically, for any code processed by the wikifier, but that’s mostly passage code). In pure Javascript you need to access that variable by it’s actual global location State.variables.statuses.

Alternatively you can use variables() (which gives you an alias to State.variables), or the State.getVar() and State.setVar() functions, which recognise the $ or _ sigils, and which work like State.getVar("$varname"). However for working with an array, it’s easier just to use the actual location.

So your final working code should be:

setup.gainStatus = function gainStatus(statusName) {
  if (!State.variables.statuses.includes(statusName)) {
    State.variables.statuses.push(statusName);
  }
}

And then you’d call it with:

<<run setup.gainStatus('A1 - Garik is pleased')>>
4 Likes

First, thank you so very much for your insight and assistance! This is truly a great learning experience for me and I appreciate it. Learning about the concept of the scope and content has been extremely helpful.

On the downside, the new code is still producing an error - however, it’s one I haven’t seen before, so I’m calling that progress. :slight_smile: The new error reads:

Error: <>: bad evaluation: Cannot read properties of undefined (reading ‘statuses’)

I started a new Twine project with only storyInit reading

<<set $statuses to []>>

the Story JavaScript section containing the suggested code, and a story passage calling the function as you stated. Unfortunately, I am receiving that above error even if I initialize $statuses within the story passage itself.

I apologize if I am overlooking something somehow, but I truly do appreciate your help so far. And it’s not imperative that I solve this issue, as I can do what I want in a slightly more cumbersome way. I’m just curious now about what I might be doing wrong. :stuck_out_tongue:

1 Like

I’m so sorry, I seem to have had a genuine brain-fart.

I explained the use of State.variables and then put Story.variables in the example instead.

I should probably also have pointed out that SugarCube has a pushUnique() function, that does the same thing as your gainStatus, so the following would also work:

<<set $statuses.pushUnique("A1 — Garik is pleased")>>
2 Likes

This is perfect! You are a gentleman and a scholar, thank you! Your update worked exactly as you said, and this is exactly what I needed!

I very well may have spent more time trying to fix this “easier” improvement to my game than it will save, but at least I can use this knowledge going forward. XD

EDIT: Oh, and thank you for telling me about the pushUnique() function. Of course there would be a functionality that already does what I was looking for. XD

2 Likes