How to use functions from Javascript file loaded using importScripts() in Twine/Twee

EDIT: The replies below have sensible and safe ways to import JavaScript code into a Twee file - the best I have come across online so far - but the biggest problem was that I had downloaded the entire HTML page containing rot.js from GitHub instead of just the raw code found here.

Twine Version: 2.30.0
Story Format: SugarCube (via Tweego)

New to SugarCube and Twee coding but making great progress thanks to the friendly and helpful community responses online. I am not skilled at JavaScript (I can implement examples etc. in Twine) and have reached a complete roadblock here.

I am trying to include rot.js in my Sugarcube2 project. So far, I have downloaded the pre-bundled rot.js file and successfully used the following to include it in my game:

if (window.hasOwnProperty("storyFormat")) {
	// Change this to the path where your HTML file is
	// located if you want to run this from inside Twine.
	setup.Path = "C:/Users/patrick/Desktop/DEV/Spaced";  // Running inside Twine application
} else {
	setup.Path = "";  // Running in a browser
}
setup.JSLoaded = false;
importScripts(setup.Path + "rot.js")
	.then(function() {
		setup.JSLoaded = true;
	}).catch(function(error) {  // eslint-disable-line
		alert("Error: Could not find file 'rot.js'.");
	}
);

I have confirmed that rot.js has been loaded using the following in my start passage:

<<script>>
var IntervalID = 0;
if (setup.JSLoaded) {
	SomeFunction();
} else {
	clearInterval(IntervalID);
	IntervalID = setInterval(JSWait, 100);  // Starts waiting for external JavaScript to load.
}
function JSWait() {  // Checks to see if JavaScript has loaded yet.
	if (setup.JSLoaded) {
		clearInterval(IntervalID);  // Stop waiting
		SomeFunction();
	}
}
function SomeFunction() {
	// Your code that depends on the loaded JavaScript here.
	alert("Rot is loaded now");
   }
<</script>>

Now all I want to do is try to use some basic functions of rot.js from my Twee script, but I canā€™t manage to do it. I tried to use their random number generator, but I get ROT is not defined

<<script>>
alert(ROT.RNG.getUniform());
<</script>>

How do I access the functions from the .js file from within my Twine script? I would love to be able to use them in a custom macro, for example

1 Like

The issue that youā€™re having is that importScripts runs in a separate thread, so the rest of your code is continuing while the JS file is being loaded in the background. The script gets to your alert statement before the file has finished loading. (I was testing this using your script on my end and this is definitely the case.)

Some workarounds are:

  1. Put the code that you want to run after the file loads (like the alert command for testing) inside your someFunction function.

  2. Put a delay for the script you want to run.
    Like: setTimeout(function() { alert(ROT.RNG.getUniform()); }, 100);
    I found 100ms works okay for me, but this is really a poor solution because who knows how long itā€™ll take to download and load the JS file if youā€™re hosting it online.

  3. I found that having it navigate to a second passage immediately (like having <<goto "FirstPassage">> in the Start passage) and having the alert call in that passage does work, but this may just be a second race condition that happens to cancel out the first race condition. Again, this may get b0rked if you put the file online.

If you couldnā€™t guess, Iā€™m suggestion you go with option 1. Thatā€™s by far the safest method.

Edit:
One more thing! I would recommend against putting your importScripts stuff in Start. Put it in StoryInit. The difference between the two is that StoryInit is called every time you load up the Twine page, and Start is only called on a fresh game (itā€™s good for setting initial states and stuff like that, along with it being an intro page or whatever). Anything that you want to be run when someone loads a save file should be put in StoryInit.

2 Likes

A simple solution is to make a slight modification to that code in your JavaScript section, like this:

if (window.hasOwnProperty("storyFormat")) {
	// Change this to the path where your HTML file is
	// located if you want to run this from inside Twine.
	setup.Path = "C:/Users/patrick/Desktop/DEV/Spaced";  // Running inside Twine application
} else {
	setup.Path = "";  // Running in a browser
}
setup.JSLoaded = false;
var lockID = LoadScreen.lock();  // Lock loading screen
importScripts(setup.Path + "rot.js")
	.then(function() {
		setup.JSLoaded = true;
		Engine.show(passage());  // Reloads current passage now so that loaded code can function
		LoadScreen.unlock(lockID);  // Unlock loading screen
	}).catch(function(error) {  // eslint-disable-line
		alert("Error: Could not find file 'rot.js'.");
	}
);

That locks the game in the ā€œloadingā€ screen until the JavaScript code has been imported.

Now the code in your starting passage just needs to be:

<<script>>
	if (setup.JSLoaded) {
		SomeFunction();
	}
<</script>>

If the JavaScript hasnā€™t loaded yet, then nothing happens. Once the JavaScript loads, then the starting passage will get reloaded (and so the above passage code should work this time), and then the loading screen will get unlocked, so that the results will be visible. Also, unlike a <<goto>> macro, the Engine.show() method wonā€™t add to the gameā€™s history, so from the userā€™s perspective it will be like nothing happened.

I should also mention that if ā€œrot.jsā€ fails to load, then the ā€œloadingā€ screen will stay up forever with that code. If you wanted, you could instead have the error handling code send you to a passage which explains how to fix the problem and then unlock the loading screen.

Hope that helps! :slight_smile:

3 Likes

Thank you so much for your help - I really appreciate both of the clear explanations too!

After implementing the changes that you suggested, I was still having problems - the rot.js file that I had downloaded was being imported, but any calls to it were giving me ā€˜undefinedā€™ errors.

Then I looked at rot.js and saw a load of HTML tags inside itā€¦I think Iā€™ve caught a very rookie mistake and downloaded the whole page from github instead of just the script :sweat_smile:

I went here and just saved the raw script and suddenly! Random number call just worked!

Iā€™m happy that Iā€™m importing and using the scripts safely using your techniques now anyway - thank you for your help.

1 Like

The Engine.show() static method takes no parametersā€”i.e., the call should simply be: Engine.show();.