![]() |
freedroidRPG
1.0+git
The best game in town
|
Note: This manual describes how to technically write a dialog file.
It does not (yet) contain any information on how to structure the nodes of a dialog, or how to write a good story.
To interact with the FreedroidRPG core, some Lua functions are provided, defined in src/lua.c and map/script_helpers.lua. The documentation of those functions is to be written.
To interact with FreedroidRPG game objects Lua bindings are provided. For instance, FDtux is the Lua binding of Tux, the internal data defining the player and her ingame's character.
The Lua bindings are documented in the Modules section of the Doxygen doc.
A dialog file is written in Lua, and, in its basic form, it must have the following structure:
Note: FirstTime
and EveryTime
are optionals.
A dialog
is thus a Lua program returning an array, containing 2 optional named fields, and a series of unnamed fields containing node definitions.
In its basic form, a node is a Lua array containing some specific named fields:
with:
id
: A string uniquely identifying the node in the dialog [mandatory].text
: The text to display in the Options List
on the chat screen (see image below), if the node is enabled
[optional, default: "NOTEXT"].echo_text
: If false, do not echo the option's text in the Chat Log
[optional, default: true].topic
: A string defining to which topic group
the node is attached [optional].code
: The action to execute when the node is selected by the user. It can call, for instance, functions to output text in the Chat Log
(see image below) [mandatory]
By default, a node is disabled on the first run of a dialog. Two methods are available to enable a node when a dialog is first loaded:
show()
function in the FirstTime
code: The current value of the enabled
field of each dialog's node is saved when the dialog is closed, and restored on next call to the dialog. A dialog's state is thus consistent between calls.
Note: The current topic
stack is not saved when a dialog is closed, by design choice.
Internally, FirstTime and EveryTime are actual nodes, in order to keep consistency of the dialog's nodes.
For instance,
is internally transformed into:
As was stated in the introduction, there are two sets of functions that can be used to interact with the game:
FDnpc
binding (not yet available...). To use them, a reference of an actual NPC is to be created (using the FDrpg
Lua module), so that those functions will be applied to that specific NPC. Such a reference is usually called an instance.get_gold()
function is available in the FDtux
class. So, we first set the local var Tux
as an instance of FDtux
, and then call get_gold()
on that instance: More than often, the Tux
instance is needed in every node's script. To avoid the need to create that instance in each script, we prefer to make it be available to the whole dialog, using a local upvalue as was shown in the very first listing:
This also enables the use of Tux
even outside of node's scripts, for instance in text
definitions (see Computed text
fields).
Besides the use of a static string to set the value of a text
field, it is also possible to use a computed value. Two forms are available:
text
value is computed each time the dialog is loaded, and the text
content will not change while the chat screen is open.text
value is a function, which will be called each time the text is to be displayed. Thus, its content can change during a chat.Note about internationalization: gettext
markers have to be added if a string needs to be translated. For instance, using the first form:
But then 2 separate unrelated strings are defined in the l10n template file (.po), confusing the translators. You should rather use one single string, with text formatting:
The Lua code of a node can access the current node and current dialog by adding input parameters to the code's function:
There are currently no real actual needs for this feature, but it can be used to implement some dynamicity.
For instance, to change a node's text, one can write:
To change another node's content, for instance, the dialog's interface can be used to get a reference to that other node:
A special node description can be used to insert computed nodes (i.e. nodes programmatically defined) in a dialog:
generator_fun is a function call. It can also be a function definition, which will be called only when the dialog is loaded.
The called function must return an array of nodes. Those generated nodes are inserted into the current dialog, at the same level than the other nodes. There is thus no kind of node hierarchy in a dialog.
Once the generated nodes are inserted, the topic
value (if set, this is an optional field) is set on each of them.
Note: Due to the flat nature of the resulting dialog structure, care must be taken so that the ids of the generated nodes do not collide with the ids of other nodes. One way to ensure it is to use an id prefix:
This later example does not really make sense, as is. Obviously, the generated nodes could have been directly written into the dialog.
However, it introduces the possibility to insert nodes defined in an external file, through the use of an include()
function:
An actual use of computed nodes will be shown later in this manual, but here is a simple example:
Important Note: Due to the way include()
is implemented, local variables of the main dialog are unavailable to the nodes inserted by a call to include()
.
Being a Lua script, loaded and executed by the dialog engine when a chat is started, a dialog file can use standard Lua features, such as local upvalues. Those can be used to improve dialog script readability.
For instance, the last example of the former paragraph could be written:
or:
Obviously, putting the names
array outside of the function definitions only makes sense if the array is used in other places.
Note: Do not forget the local
keyword to avoid to screw up the Lua global environment.
We will use the Dude
dialog as an example of how to solve some specific needs by the use of the dialog engine API. Not all dialogs involve this kind of lua programming, so this is somehow an extreme example of the dialog engine features.
The Dude
dialog contains a list of nodes used to start a chat with all the NPCs of the game.
We will only detail some parts of the dialog, and use a lightly different implementation of the code to ease the explanations. See the Dude.lua file for the actual code.
We have to automatically create a list of nodes to start all dialogs present in the dialogs/ subdir of the game (defined in the FDdialog.dialogs_dir variable).
We use a FDutils.system function to scan that subdir and get a list of the files that it contains (apart from some exceptions such as subdialog files):
For each found file, we have to remove the .lua file extension to get the NPC name, and we create a node to start a dialog with that NPC.
So we end up with the following generator node:
In the EveryTime
node of the dialog, we want to display a list of all node's ids.
The nodes are stored in the dialog.nodes
array, so we can loop on its content to retrieve all nodes and display their ids:
In the EveryTime
node, once the list of available options is displayed, we want to let the player input the name of one NPC, i.e. one of the node's id that we inserted into the dialog, and execute the code of the selected node.
A call to user_input_string()
is used to interact with the player and get the name of a dialog:
We then search for that name in the dialog node's list:
And finally, if found, we execute the node's code:
So we end up with:
The exit
node is obviously also in the dialog node's list. It means that the exit
node will also be displayed by the EveryTime
code, and that if the player inputs "exit" then the exit
code is called and the dialog ends.
There is thus no need for a special treatment of static nodes.
This chapter gives some advice and answers to some common implementations questions.
A subdialog is a part of a dialog that can be considered as a dialog on its own: it has a starting node and a ending node, it has its own logic, and the main dialog is paused (i.e. none of the option texts of main dialog is displayed) while the subdialog is running.
In this recipe, we also consider that the dialog can be used by several main dialogs, and thus is defined in its own file (see Computed nodes).
In order to pause the main dialog, we will use a topic
for the subdialog nodes:
topic
.Let's start with a conceptual definition of the main dialog:
There is no way to stop in the middle of the execution of the nodeX's code and to restart it after the subdialog ends, so we have to split this node in two parts, one part to start the subdialog and one part to take back control after the subdialog's end. Starting the subdialog means to (1) push the topic and (2) call the starting node of the subdialog (we will call it sub.everytime
since that node is to be executed every time the subdialog is run):
When the subdialog ends, it has to call after_subdialog
to restart the main dialog. The skeleton of the subdialog is then:
In the Subdialog recipe, we saw how to cook a subdialog, but it was based on the assumption that the subdialog appears only once in the main dialog.
How to adapt that recipe if the subdialog is to be run in two parts of the main dialog ?
Let's start again with the conceptual definition of the main dialog:
Applying the subdialog recipe leads to:
Now the question is about the content of the sub.end
subdialog's node: what node name to put in the next()
call ?
If the subdialog was started by nodeX
, we have to continue on after_subdialog_from_nodeX
. If the subdialog was started by nodeY
, we have to continue on after_subdialog_from_nodeY
.
It seems obvious that a variable is needed, containing the name of the node to return to, and we would tend to set its content in nodeX
and nodeY
. For instance, for nodeX
, we could write:
with the following subdialog description:
But it will not work ! Why ? Reread the Computed nodes section. Did you see the Important Note ? If so, you know that a local variable of the main dialog can not be accessed from an included subdialog. It means that in subdialog.lua
the variable continue_with
is undefined...
We want to avoid to use global variables, to not pollute the global Lua environment of dialogs.
Remind that dialogs and nodes are Lua arrays, and you may know that a field can be dynamically added to a Lua array. That's the solution: instead of using a local variable, we will add a field to a Lua array that can be accessed from the sub.end
node.
We can use the dialog array, this way:
We can also use the sub.end
node array, this way:
We would tend to use the second approach (which conceptually creates a sub.end
local variable) in this use case, and use the first approach when we need to globally share a variable with several included nodes.