Skip to content

deferred events

jackdarker edited this page Jun 3, 2021 · 10 revisions

What are deffered events

The twine interactivity is based on action - reaction. The player clicks on a link and a passage is shown.
You might also add randomness to those passages through executed scripts that change whats printed to the screen.

At times, you dont want to cause an immediate reaction on the players action. The player does something but only several passages later he has to face the consequences.
This can be done by setting a js-variable when clicking a link in passage A and later, in passage C, we can check the variable and adjust the output accordingly.
But what if I cant be sure that he will ever get to passage C or he gets there but at a point far to late in timeline. I would have to spam variable-checking not just in passage C but several other passages as well.

So the thought crossed my mind to be able of intercepting events that can occur "somewhere" and "sometimes".
Here are some example what I mean:

Hair grow

  • you have drunk a hair tonic that improves your hair grow rate. The hair will grow slowly, not in a burst.
  • a week later, after waking up, there should now a "your hair has grown"-messages presented to the player and maybe some variables set
  • after the player is done with the message, he continues the game as usual
  • this is just a quality-of-life event; nothing would go wrong if the player misses the message. But it would be disturbing to get "your hair has grown"-messages in the middle of a fight

E-Mail

  • the player sends an email via his mobile, the response will take a while
  • after some time the event triggers, telling the player that he has received mail
  • he might choose to [ignore] it right now or start to [read]

Nemesis is following you

  • one of those guys you have been beating up in the dungeons is following you now, waiting for his chance of payback
  • the player will get reminders that someone is following him and at some point a fight is inevitable.
  • the defeat of the nemesis unlocks story-progress, the game continues where you are
  • the event should only trigger at certain locations (not in civilized areas) and maybe other conditions

We have to be careful about this:

  • there could be multiple events pending at the same time, some of them might be less important then others.
  • if we only display the most important one, there might be events that are so unimportant that they are never shown
  • events might consist of multiple passages and also the sidepanel should be usable
  • we need to tag passages somehow to make the event aware if its ok to run now or not.
  • we should not intercept an ongoing "normal" event or already running deferred event; f.e. the "hair grow"-messages should not pop up in a middle of a discussion with an NPC

F.e. the flow of passages for hair-grow could look like that:
[drink hair tonic]->[tonic doesnt work?]->[go to...] ...several days later...->[go to sleep] -> [wakeup] *! -> [take breakfast]
At the point *! we need to run the deferred event. Instead of showing [take breakfast], only [Next] is displayed and if the player clicks it, the events passage is shown. After clicking [Next] again, he returns to [wakeup].

A simple solution

The most simple approach would be to setup a logic in each passage to check if a deferred event is waiting (a variable is set).
Then disable the normal content of the passage and display the event-content. Reset the variable and call the passage again to show the normal content.
:: wakeup
<%if(s.hairgrow===10){%>
You noticed that since last week, your hair has grown much longer.
s.hairgrow=0;
[[Next|wakeup]]
<%} else {%>
You woke up in the morning.
[[take breakfast]]
<%}%>

The drawback is that we would have to add this logic in all passages where the event should be able to intercept. And for every possible deffered event...

A more general solution

We could add the following code in the passages that can handle certain events and add some functions like pushDeferredEvent on a stack.
The hasDeferreEvent can check the current passage-name and also other conditions assigned to the event.
:: wakeup
<%window.gm.player.location="Bedroom"%>
You woke up in the morning.
<% if(window.gm.hasDeferredEvent()){ %>
<%= window.gm.showDeferredEvent() %> //this should print a link to the event passage
<%} else {%>
[[get out of bed|Bedroom]]
<%}%>

The showDefferedEvent should generate a link to a passage that will move back to the normal flow:
:: HairGrow
You notice that the length of your hair is larger than normal.
<% if(window.gm.hasDeferredEvent()){ %> //check if there are more pending events
<%= window.gm.showDeferredEvent() %>
<%} else { //or just continue with the next passage %>
[[Next|<%=window.gm.player.location%>]]</br>
<%}%>

A better solution

Instead of adding the code like above to every passage, we could handle this in override of window.story.show:

  • this function renders the next passage and I had it already overriden for the back-tracking logic
  • if there is a deffered event on the stack and next passage without tag '_back','_nosave','_nodeffered'
  • push next passage and args onto onhold-stack
  • pop deffered event from stack, set args and show it
  • the deffered event should call 'window.story.show' without parameters to pop 'next'-passage from onhold-stack and show it
  • if deffered event needs to branch to another passage, that should not be called directly beause you would loose memorized 'next'-passage!
    Instead push it on the defferedstack, but at front. Then 'window.story.show("")' and it should be shown.

To sum it up for deffered events:

  • never branch to a normal passage using normal passage links
  • always use window.story.show("") to return to normal story-flow
  • branching to _back-passages is possible
  • if you need to call other passages they also have to be deffered events; use pushDeferredEvent("NextEvent",[],true) to insert them in the queue

Calling a _back-passage (f.e. Inventory) while in deffered event works. But keep in mind that deffered passage is called another time after back-tracking to it, so you might need add some logic here if some game-state is manipulated.
F.e. I use them to change some player stats. To avoid doing this multiple times I do this:

  • pushDefferedEvent is called always with an non-null args-parameter (f.e. [1])
  • the code in the deffered passage checks if the args is not null, otherwise it does not exeute
  • after the code executes, the args are set to null If the code is used to generate a message on the screen, you need to add an workaround or it will be missing after back-tracking:
  • after code execution store the message in window.story.state.tmp.msg
  • if the code execution is skipped pull the message from this variable
  • clear the variable before leaving the passage or the savegame gets bloated
Clone this wiki locally