Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Hub and spoke locations: hacking Jonah for open worlds

edited January 2015 in Workshop
I've noticed this idea has come up a few times: sometimes authors wonder if they can combine Jonah-style stretch-text and then occasionally clear the screen for important 'hub' locations. Check out my tiny demo so you can get the idea. I'm going to use the terms 'hub' and 'spoke' to refer to the major location passages and the minor actions respectively. When moving to a hub, the page is cleared and a location title is printed. When moving to a spoke, it's simply appended to the bottom of the passage in stretch-text style.

This concept is actually extremely problematic, because Twine doesn't seem to be designed to work this way. If people are learning Twine, and maybe they are used to traditional text adventures, perhaps they just want to allow you to, say, take a pencil from a table. So they innocently type:

::Room
There is a [[pencil]] here, ripe for the taking, as well as a juicy-looking [[nectarine]].

::pencil
<<set $havepencil to 1>>[[Taken|Room]].
But having the page clear itself just to say "Taken" is hardly ideal. So the obvious thing to do is to switch to Jonah and use CSS to remove the passage titles. Trouble is, the player still has to click on a "back" link to return to the location they were previously in, even though that passage is still displayed on screen. If the player tries to immediately take the nectarine by clicking the perfectly visible link, Jonah will automatically rewind, and now the player doesn't have the pencil anymore. And if the author wanted to move the inventory items around, they'd have to make sure to update all the 'back' links accordingly. This is not ideal.

So I started off by disabling Jonah's automatic rewinding, just by replacing the Wikifier.linkFunction function and removing the relevant lines:

Wikifier.linkFunction = function(title, el, callback) {
return function() {
if (title) {
state.display(title, el, null, callback);
}
else if (typeof callback == "function") {
callback();
}
};
};
Then we can obviate the 'back' links in the spoke passages. But that's a problem. If the player takes the pencil, then bookmarks the page and reloads, they will end up back at the pencil passage with no way of returning to a hub, which would practically break the game. So, I decided to force Jonah to always take you back to the most recent hub location whenever the page is loaded, if there is one. In the restore function, I added these lines:

if (vars.variables['room']) this.display(vars.variables['room'], null, "back");
else this.display(vars.passage.title, null, "back");
Then, a prerender function is used to store the current passage title in a variable called 'room', but only if the passage is tagged as a "hub":

prerender.hub = function (content) {
if (this.tags.indexOf("hub")==0) {
new Wikifier(content, '<<clearall>><<set $room = "'+this.title+'">>');
new Wikifier(content, '!!'+this.title);
}
}
This is still problematic, however, because if the player saves and then reloads, any control logic that was executed in a spoke node will not be saved. So again, the player will take the pencil, bookmark, reload, go back to the hub, but won't have the pencil. Confusing. So, I just forced Jonah to resave the variables after every display operation and to update the hash accordingly. At the end of the display function:

this.saveVariables(D, source, callback);
this.hash = this.save();
if (tale.canUndo()) window.location.hash = this.hash;
Finally, I made a quick and dirty <<clearall>> macro for manually clearing the screen:

macros['clearall'] = {
handler: function(place, macroName, params, parser) {
removeChildren($('passages'));
},
init: function() { }
}
This macro is called in the prerender function so that any passage tagged 'hub' will first clear the screen, in addition to saving the current hub location in the room variable and printing a title header. The default title header is removed in CSS:

.passage .title { display:none; }
Hence the resulting code is very simple:

::Room [hub]
There is a [[pencil]] here, ripe for the taking, as well as a juicy-looking [[nectarine]].

::pencil
<<set $havepencil to 1>>Taken.
The player can take the pencil and nectarine in two clicks. If they save and reload, they will still have the goods, and they will be returned to the hub location.

One alternative implementation that occurred to me is to use Sugarcube's DOM manipulation macros in combination with <<click>> macros to append the text to the end of the current passage. Something like this:

::Room
There is a <<click "pencil">><<append "#end">><<pencil>><</append>><</click>> here, ripe for the taking, as well as a juicy-looking [[nectarine]].
<span id="end"></span>
This is extremely messy. It's possible to make a new macro to automate the process, but it's not worth it in this case. If the player takes the pencil and then saves and reloads the game, they will no longer have it, and the game won't say so. This is because the game state is only updated on a passage transition, but this is not a passage transition anymore.

Anyway, my JavaScript "skills" are very sketchy and everything I've done here is hack-y and sledgehammer-esque, so I'd appreciate some feedback. Maybe you can think of some problems or special cases that won't work.

Finally it's also a useful exploration of how Twine games work and how they are expected to work. Twine works fine when the decisions you make are things like "Go to the moon", "Drive to 34th West Avenue", "Sabotage the mission", "Jump through the window", "Smash him over the head with a vase", and so on, because these actions are expected to drive the story forward considerably, warranting a passage transition. But if the action is something simple, but triggers a state change, like "take pencil", well... we've seen where that leads us!

Source for the demo attached.

Comments

  • Excellent review and work around, I would like to make one small clarification though.

    Twine is just the GUI you use to collate/edit the passages that will be contained within the HTML file you build/generate using it.

    It is the Story Formats that control how the core part of your story works, what default features / macros it has and the generally layout of the HTML generated by the story format's build-in javascript engine. One common feature that most of the existing formats have in common is the general way they handle History and Variable storage.

    It is true that the current ones are not really designed to handle RPG features like Inventories, Shops, Equipment, Combat, Visiting the same location multiple times (Hubs), etc but because all of the existing story formats are Open Source there is no reason that one cant be cloned (Forked) and changed to support these types of features. Similar to how you did in your review except on a more permanent bases.
Sign In or Register to comment.