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
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:
Put the code that you want to run after the file loads (like the alert command for testing) inside your someFunction function.
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.
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.
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.
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
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.