Defining stories, nodes, etc.
A story.kts
file is a Kotlin script file, meaning that we will be using Kotlin
code throughout this entire guide.
It is recommended that you use the template for code autocompletion.
Story blocks
A story.kts
file consists in one (or more) story declarations. Stories are
defined using the following syntax.
story {
// The story's content goes here
}
The line beginning with // is a comment, meaning that it is just text we can use to explain our code.
This story { }
is called a story block. We can define some story properties
inside the story block.
story {
title = "My great story!"
author = "Me"
id = "main"
}
This will set a title and an author for our story. You do not have to specify these, but you should give your story a title.
The id
is only necessary if you plan on having multiple stories at the same
time.
Node blocks
We can now define nodes inside of our story. Nodes are defined through node blocks:
story {
title = ...
author = ...
node(1) {
// The node's content goes here
}
}
Here, we are declaring a node with the ID 1. IDs are unique within a story, meaning that two nodes inside the same story may never have the same ID. However, two nodes from two different stories may have the same ID.
IDs can be strings…
node("node id") {
...
}
… or integers
node(42) {
...
}
You can only define nodes inside story blocks. From now on, code examples of nodes will be given assuming that we are inside a story block.
Advanced
Integers are internally converted to string IDs. Make sure to not have an integer ID that corresponds to another node’s string ID (e.g. a
node(1)
and anothernode("1")
are internally seen as the same nodes)
By default, stories start at the node with ID 1. That means that your story.kts
file is expected to have either a node(1)
or a node("1")
. We’ll see how to change that in a later section.
Next, we need to add some text to our node. We do this using a body block.
node(1) {
body {
"""
This is my node's body text
As you can see, it is a pretty awesome node
"""
}
}
The body block consists in a multiline string that is formatted in Markdown. The indentation before the lines does not matter and will be removed (although it needs to be consistent).
IntelliJ IDEA will automatically generate a .trimIndent()
when you write
multiline strings like these. You should remove them, as this is always
internally called by StoryFX.
Why?
String literals in Kotlin are the multi-line strings like the ones you write using story.kts. However, these strings are “raw”, meaning that they also include the indentation you have before them. While you could remove the indentation, it would just make your code look like crap, which is not the goal here. This is why it is necessary to, at some point, remove the indentation. This is usually done by calling
.trimIndent()
on the multiline string, which is why IntelliJ automatically adds it in for you. There is one issue though, and it is that it ends up adding noise to the file. In order to reduce said noise, the.trimIndent()
call is automatically performed by StoryFX before showing the node. Thus, you should remove it from yourstory.kts
file, as it is entirely useless.
Options
Options are what allows a player to go from one node to another. They are defined inside node blocks.
node(1) {
body {
...
}
option { "Go to the second node" }
}
node(2) {
...
}
We are also defining a second node with id 2 to have somewhere to go to.
The option block we just defined will thus display the text “Go to the second
node”. However, we have not told it what node to go to yet. This is done using
the goesTo
function.
option { "Go to the second node" } goesTo 2
Our option will thus go to the node which has the ID 2. goesTo
is a shortcut
which you can use for the simplest cases, such as when the node has a known ID,
and it is in the same story as the node the option is from.
Advanced
goesTo
can also take a lambda providing an ID, for example:goesTo { 2 }
. This allows to choose a node depending on variables. This topic is detailed here
For full customizability, use the does
block. The equivalent to the goesTo
we just saw above is:
option { "Go to the second node" } does { nodeRef(2) }
The does
block is a sequence of code that is executed when our option is
chosen, hence its name: it’s what the option “does”. Here, the code simply
returns the node in our story with id 2
, just like what we had with the
goesTo
.
nodeRef(2)
returns the node with the id 2
in our story. Be very careful not
to use node(2)
here, as that would define a brand new node, whereas we just
want to go to a node that is already defined. nodeRef
means “node reference”,
you can interpret it as “reference to node with id”.
An option that does not have a does
block, or that has a does
block that
returns null
will simply re-go to the current node.
Advanced
There are multiple ways to define nodes, some better than others depending on the context. The syntax we are describing here is most appropriate when defining nodes that have options or other dynamic elements. However, nodes that only consist of text (e.g. final nodes, from which the player has nowhere to go to) should use a slightly different syntax that takes the body block out of the node block.
Option-less nodes
Option-less nodes are final nodes, meaning that they represent an “ending” in your story.
Following what you have seen above, option-less nodes would be written like this:
node(9001) {
body {
"""
This is my last node
It's over **9000**!
"""
}
}
There is actually an easier way of writing option-less nodes:
node(9001) body {
"""
This is my last node
It's over **9000**!
"""
}
We can put the body block right next to the node’s declaration instead of having
to specify it inside the node block. Don’t forget to put the body
, as
otherwise you would just be throwing a string in the node block, which would
have no effect!
Full example
Here is a full example of a story.kts
story
story {
title = "Sample story"
author = "Me"
node(1) {
body {
"""
This is my node's body text.
As you can see, it is a pretty awesome node
"""
}
option { "Go to my second node" } goesTo 2
option { "Go to my third node, named Three" } goesTo "Three"
option { "Go to my third node, named 3" } goesTo 3
option { "Let's just stay here for now" } does { null }
}
node(2) body {
"""
Congrats, you reached the second node!
"""
}
node(3) {
body {
"""
Congrats, this is node 3! Like, the number 3! Hurray!
"""
}
option { "Go to node Three but with letters" } goesTo "Three"
}
node("Three") body {
"""
This is node Three, with letters instead of a number!
"""
}
}
Note
Simple stories like the one above should never be written in the
story.kts
format, because it is completely overkill. They should instead be written using the much simpler and writer-orientedstory.txt
format.
Changing the initial node
If you want your story to start at a node other than the node with id 1
, you
can use the following in your story block to change the initial node.
story {
initialNode { ... }
...
}
The initialNode
is expected to be the same thing as a does
block: it has to
return a node in the end. For almost all cases, you’re good to go with something
like this, where id
is either a string or an integer:
initialNode { nodeRef(id) }