Dialog engine

From Post-Apocalyptic RPG wiki

Jump to: navigation, search

Implemented code proposal.png This article features an already implemented code proposal.

Implemented Proposals have had code checked in to the SVN repository. The Implemented Proposals should serve as documentation for the actual implementation. Developers should strive to update these as needed.


This page should be used to address the Dialog engine evaluation (see ticket #269) for the upcoming Techdemo2 release.

Contents

Analysis of Techdemo1 DialogEngine Implementation

The following sections describe in detail how the Techdemo1 DialogEngine works and discusses some of the implications of its design.

General Workflow

Techdemo1 DialogEngine Implementation Workflow

As can be seen from the flowchart, the current implementation goes through the following major stages once a dialogue has been initiated:

Initialization

In the initialization stage the DialogEngine loads the dialogue data for a specific NPC and extracts the start dialog node from the dialogue data, which is used to start the dialogue flow. The programmer also has to define some dialogue command callbacks (see #Dialog Commands) and a dictionary of game state data used to check response conditions (see #Response Conditionals).

Process Dialog Commands

Once a start dialogue node is selected the DialogEngine begins processing any dialogue commands it finds in the dialogue node (see #Dialog Commands). Depending upon the commands it encounters the DialogEngine may end the dialog, change the dialogue flow by returning to a previous dialog node, or process player responses that cause the DialogEngine to start processing the commands from a different dialog node.

Process Responses and User Input

Once a 'responses' command is encountered within a dialogue node, the DialogEngine processes any conditional statements for defined responses (see #Response Conditionals) and presents the user with a list of valid responses contingent upon the result of the conditional tests. The user can then select a repy and feed it back into the DialogEngine, which as noted above begins processing the corresponding dialogue node's commands. This cycle continues until an 'end' command is reached, at which point the DialogEngine exits the dialogue.

YAML Dialog Files

Dialog nodes are defined by entries under the "SECTIONS" YAML tag. For example, consider the following dialogue definition file:

#   This file is part of PARPG.

#   PARPG is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.

#   PARPG is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.

#   You should have received a copy of the GNU General Public License
#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.

#   NOTE:  This is not a "real" dialogue file, it is meant to be a template for
#   Any generic fortified mall (techdemo location) denizen
---
NPC: Old Man
AVATAR: gui/icons/crazy_swede.png
START: main_dialog

SECTIONS:
    main_dialog:
        - say: "The old man looks up at you from his chair with a dazed look
            upon his face.
            
            \"...Lucy?\""
        - meet: old_man
        - responses:
            -
                - "Who's Lucy?"
                - help_find_lucy
            -
                - "I found your ring."
                - good_memories
                - "pc.quests['memories'].isActive() and
                    pc.inventory.hasItem('RustyWeddingRing')"
            -
                - "I don't have time for your dulusions old man!"
                - parting_shot
    
    good_memories:
        - say: "An intense expression of joy spreads across the old man's face
            as you hand him the rusty wedding ring. He tries to mouth a
            word of thanks but is so overcome that no words come out. The old
            man begins caressing the ring, oblivious to the decay and rot of
            the world around him."
        - give_item: RustyWeddingRing
        - complete_quest: 'memories'
        - responses:
            - 
                - "[Walk away.]"
                - end
    
    help_find_lucy:
        - say: "The old man looks at you expectedly.
            
            \"Can you help me find Lucy?\""
        - responses:
            -
                - "Where is Lucy?"
                - rusted_lockbox
            -
                - "I have better things to do."
                - parting_shot
    
    rusted_lockbox:
        - say: "The old man gazes over his shoulder to the far side of the
            shanty where a metal lockbox rests on the floor.
            
            \"Can't open it any more... rusted...\""
        - responses:
            -
                - "I'll see what I can do."
                - hopeful
            -
                - "Fix your own problems, old man!"
                - parting_shot
    
    hopeful:
        - say: "The old man cracks a thin smile, then begins rocking back and
            forth in his chair."
        - start_quest: 'memories'
        - responses:
            -
                - "[walk away.]"
                - end
    
    parting_shot:
        - say: "The old man sighs and begins to stare remorsefully at the
            smouldering embers in the fireplace."
        - responses:
            -
                - "[Walk away.]"
                - end

This dialogue definition file defines the following dialogue nodes:

  • main_dialogue
  • help_find_lucy
  • rusted_lockbox
  • parting_shot

Dialog Commands

Each dialogue node contains one or more dialogue commands which tell the DialogEngine how to interpret the dialogue node. There are essentially two kinds of dialogue commands:

  • flow commands, or commands that manipulate the flow of the dialogue
  • user-defined commands supplied by the programmer

Flow Commands

Flow commands are those dialogue commands that change the dialogue flow, usually by modifying the #Dialog Stack. There are four such commands:

  • end, which tells the DialogEngine to end the dialogue
  • back, which tells the DialogEngine to process the previous dialogue node in the stack
  • responses, which defines a set of responses the player can choose from and tells the DialogEngine to put a new dialogue node on the stack and begin processing it once the player has made a choice (more on this later).
  • dialogue, which is a special command that allows use of the end> and back commands outside of a response subcommand (see below).

In many ways the responses command is both the most complex flow command and the most important. The responses command takes one or more response subcommand as positional arguments. TODO

The flow commands end and back must either be declared as the last argument to a response subcommand or as the sole argument to the special dialogue command. Note: currently the dialogue special command has little to no use value as far as I can tell (can anyone think of a situation where dialogue would actually be useful?).

User-defined Commands

These commands are defined at runtime when the programmer supplies a dictionary of callback functions to the DialogEngine. When the DialogEngine encounters a command it first looks up the command in the callback dictionary and, if it finds a match, it calls the callback function with two or more arguments: the game state dictionary used to initialize the DialogEngine, and any arguments to the user-defined command. This is also true for all of the flow commands (except the special dialogue flow command), so in addition to controlling dialogue flow these commands can also be customized by supplying the appropriate callback functions.

Currently, the following user-defined command callbacks are defined (definitions taken from scripts/gui/dialoguegui.py):

  • say(game_state, message): print message to the dialogue gui
  • start_quest(game_state, quest_id): activate the quest with id quest_id
  • restart_quest(game_state, quest_id): restart the quest with id quest_id
  • complete_quest(game_state, quest_id): mark the quest with id quest_id as completed
  • fail_quest(game_state, quest_id): mark the quest with id quest_id as failed
  • increase_value(game_state, quest_id, variable_name, value): increase the quest variable variable_name by value
  • decrease_value(game_state, quest_id, variable_name, value): decrease the quest variable variable_name by value
  • set_value(game_state, quest_name, variable_name, value): set the quest variable variable_name to value
  • meet(game_state, npc_id): mark the NPC with id npc_id as having been met by the player
  • get_stuff(game_state, item_type): move an item of type item_type from the NPC's inventory to the player's inventory
  • give_stuff(game_state, item_type): move an item of type item_type from the player's inventory to the NPC's inventory
  • replace_stuff(game_state, who, old_item_types, new_item_types): replace multiple items of types old_item_types with items of types new_item_types in either the player's (who='pc') or the NPC's (who='npc') inventory
  • replace_thing(game_state, who, old_item_type, new_item_type): replace an item of type old_item_type with an item of type new_item_type in either the player's (who='pc') or the NPC's (who='npc') inventory

Response Conditionals

Response conditionals are conditional statements that determine when and if a response is made available to the player for a particular dialogue node. Response conditionals are used to control the dialogue flow and make the dialogue reflect the current state of the game, such as whether the player has met certain prerequisites or has completed a certain quest.

Response conditionals are specified as Python functions, variables and boolean operators that when evaluated result in a boolean outcome (either True or False in Python). Currently all built-in Python functions, keyword and operators are allowed in response conditionals, but the only PARPG-specific functions and variables that can be used are those that were specified by the game_state dictionary used to initialize the DialogEngine (see #General Workflow).

In the example YAML dialogue file the response "I found your ring." in the main_dialogue dialogue node has the response conditional:

pc.quests['memories'].isActive() and pc.inventory.hasItem('RustyWeddingRing')

which tests whether the player has be given the "memories" quest and has a "RustyWeddingRing" in his/her inventory. When the player reaches the main_dialogue dialogue node this conditional statement is evaluated. If it evaluates to True, then the response text is displayed to the player as a valid reply, otherwise the response is hidden and the player will be unable to select it.

Dialog Stack

The Dialog engine stores a particular dialogue's state as a stack of dialogue node identifiers. For instance, if a player responded "Who's Lucy?" and then "I have better things to do.", the dialogue stack would look like this just before the player ended the dialogue:

['main_dialogue', # <----- bottom of the stack
 'help_find_lucy',
 'parting_shot']  # <----- top of the stack

Note that the dialogue stack is implemented as a simple Python list. Currently the stack is cleared at the end of each dialogue, meaning that dialogues with the same NPC follow exactly the same flow each time assuming no response conditions change. In theory, the stack could be used to persist the state of the dialogue instead of relying upon game state checks to determine dialogue flow, though this might not be the best method of dialogue persistence since the memory needed would scale proportionally to the number of NPC's with persistable dialogues.

Dialog Data Structures

Note: A basic understanding of how PyYAML parses and stores YAML data would probably help in understanding the following discussion.

Dialog data is stored in memory as a composite of primitive PyYAML data structures, namely Python lists and dictionaries. For example, the YAML dialogue file above would be stored as the following Python data structure after being parsed by the DialogEngine:

{'NPC': 'Old Man',
 'AVATAR': 'gui/icons/crazy_swede.png',
 'START': 'main_dialog',
 
 'SECTIONS': {'main_dialog': [{'say': 'The old man looks up at you from his chair with a dazed look upon his face.\n"...Lucy?"'},
                              {'meet': 'old_man'},
                              {'responses': [["Who's Lucy?",
                                              'help_find_lucy'],
                                             ["I don't have time for your dulusions old man!",
                                              'parting_shot']]}],
              'help_find_lucy': [{'say': 'The old man looks at you expectedly.\n"Can you help me find Lucy?"'},
                                 {'responses': [['Where is Lucy?',
                                                 'rusted_lockbox'],
                                                ['I have better things to do.',
                                                 'parting_shot']]}],
              'rusted_lockbox': [{'say': 'The old man gazes over his shoulder to the far side of the shanty where a metal lockbox rests on the floor.\n"Can\'t open it any more... rusted..."'},
                                 {'responses': [["I'll see what I can do.",
                                                 'parting_shot'],
                                                ['Fix your own problems, old man!',
                                                 'parting_shot']]}]},
              'parting_shot': [{'say': 'The old man sighs and begins to stare remorsefully at the smouldering embers in the fireplace.'},
                               {'responses': [['end', 'end']]}]}

In general dialogue data is stored as nested list and dictionary entries in a top-level dictionary. The exceptions are the contents of the 'NPC', 'AVATAR' and 'START' tags, which are all stored as top-level entries in the dictionary.

The contents of the 'SECTIONS' tag are stored in dictionary whose keys are the identifiers of dialogue nodes and whose values are the contents of those dialogue nodes.

The contents of a dialogue node are stored as a list where each entry in the list is a dictionary whose single key is a dialogue command keyword and whose single value is the contents of that command. Responses are stored as a list under the responses dialogue command entry, where each response is stored as a nested list containing the response text, a flow command, and optionally a response conditional (see #Response Conditionals).

Evaluation of the Techdemo1 DialogEngine Implementation

The Techdemo1 DialogEngine implementation was evaluated using the following set of practical guidelines:

  1. The code and its structure should be simple and easy to understand, even without the guidance of its original author;
  2. Subsystems should be as flexible and modular as possible without sacrificing efficiency;
  3. Related sections of code should be encapsulated within the same class and/or module to make it easier to maintain and modify;
  4. Classes and modules should expose a clear interface intended for use in other subsystems;

The following sections describe aspects of the Techdemo1 DialogEngine implementation which violate one or more of these guidelines.

Overly Complicated

The Techdemo1 DialogEngine is very difficult to understand, even for an experienced programmer. The current development team has stated more than once in IRC that they have no idea how the DialogEngine works despite active attempts to understand it. Though part of the reason may be the lack of user and code documentation, the Analysis of Current DialogEngine Implementation above has revealed that there are many aspects of the Techdemo1 DialogEngine which are not easily understood.

There is not a clear differentiation between the different components of the Techdemo1 DialogEngine, nor does it expose a clear interface. For instance, much of the dialogue logic code (commands) are defined in the dialoguegui.py module, which is not particularly intuitive. Methods exposed by the DialogEngine class also encapsulate far too much functionality and so it is not at all clear what these methods actually do.

Finally, the data structures used to store dialogue data in-memory are excessively complex and difficult to understand. The deeply nested Python dictionaries and lists that comprise a dialogue with an NPC are simply not human readable and results in code that is equally unfathomable.

Integrates YAML Parsing Code with Dialog Logic

The Techdemo1 DialogEngine implementation strongly couples the YAML parsing code with the dialogue logic code. What this means in practice is that it would be difficult to switch to a different scripting language in the future, which limits the flexibility of the dialogue system as a whole and makes it much more difficult to interface the DialogEngine with other subsystems and tools. In addition, maintainers of the DialogEngine code would have to learn and understand the PyYAML code as well, making long-term maintenance more difficult.

Ambiguous and Non-Standard YAML Dialog File Syntax

The general structure of the Techdemo1 YAML dialogue file syntax is somewhat non-standard and relies upon a certain amount of background magic to work. For instance, the "SECTIONS" tag implicitly defines a sequence/list of DialogSections and this is how most parsers and validators want to interpret it. Unfortunately the "SECTIONS" tag actually defines the DialogSections in a mapping/dictionary, where the keys are the IDs of the DialogSections and the entries are the attributes of the DialogSection instance (minus the ID). The standard PyYAML parser and Kwalify validator have a very difficult time interpreting the current syntax, which means that we would have to write and maintain our own parsing and validation code to support the current syntax. Besides the duplication of effort another problem with this scheme is that contributors and modders who are familiar with YAML will likely be confused by our non-standard syntax.

The current YAML syntax also has some ambiguities that might be confusing to new dialogue writers. The current implementation relies heavily upon lists of positional arguments with no intuitive explanation for what order the arguments should appear in.

Proposed Changes

Summary

The new dialogue engine will be a major redesign of the existing implementation. The goal of this redesign is to significantly simplify the system and make it much more modular and extensible.

In summary, the following changes will be made:

  • Restructure the core DialogEngine code into a new class, DialogProcessor, to make it easier to understand and rename the dialogue engine module from dialogue.py to dialogueprocessor.py for clarity;
  • Refactor the dialogue logic code into its own dialogueactions.py module;
  • Abstract the dialogue data structures away from their YAML representations and factor the code into its own dialogue.py module;
  • Refactor the YAML parsing code into its own dialogueparsers.py module and interface it with the new dialogue data structures;
  • Reformat the YAML syntax to be more explicit and to more easily interface with the new dialogue data structures;

The following sections describe these changes in detail.

Restructuring of Core DialogEngine Code

The following flowchart describes the workflow of the redesigned DialogEngine:

DialogEngine Techdemo2.png

As can be seen from the flowchart the redesigned DialogProcessor class exposes a clearly-defined interface for programmers and subsystems to use. The following classes and functions define the interface to the dialogue subsystem:

DialogParser

The abstract base class DialogParser and its child class, YamlDialogParser, are responsible for reading and writing serialized dialogue data. Most of the time the parser class will be used to load dialogue data from YAML dialogue files and construct a Dialog instance for storing the dialogue data in-memory. These Dialog instances will then be used by the DialogEngine to process a dialogue.

Functions:

load(yaml_stream) 
Read an open stream of yaml data and attempt to construct a Dialog instance from it.
dump(dialogue, stream) 
Read the dialogue data from a Dialog instance and dump its yaml serialized representation to the open stream.

DialogProcessor

The DialogProcessor is the primary interface to the dialogue subsystem, and is used for processing dialogue data to simulate a dialogue with an NPC. It stores dialogue state as a stack of DialogSections that have been or are currently being processed, allowing a DialogProcessor to keep track of which sections of dialogue the player has visited in the current dialogue. In general, users of the DialogProcessor do not have to directly manipulate the dialogue_section_stack as the interface methods do these manipulations in the background.

Functions:

__init__(dialogue, game_state) 
initialize a new DialogProcessor instance with the dialogue data to process; the game_state parameter is a dictionary of objects that should be made available for testing response conditionals (e.g. the player character and the quest engine).
initiateDialog() 
prepare the DialogProcessor and prepare to start processing the dialogue data it contains.
continueDialog() 
process the current section of dialogue, run any DialogActions it defines, and return a list of valid responses for the player to choose from by evaluating any response conditionals.
reply(dialogue_response) 
after choosing a valid response the player can then give the NPC a reply, which runs any actions associated with the DialogResponse used to reply and selects the next section of dialogue to process based upon the reply.

Refactor Dialog Logic into its Own Module

DialogEngine DialogActions.png

The redesigned dialogue engine replaces the old command callbacks with the dialogueactions.py module, the DialogAction abstract base class and its concrete subclasses to define the dialogue logic.

The DialogAction abstract base class serves two roles in the new dialogue engine. First, it defines the standard interface that all dialogue actions should define. Currently DialogAction simply requires that subclasses define a 'keyword' class attribute, which defines the keyword used in serialized dialogues to denote a particular dialogue action, a generic initialization method, and a call method that accepts a single 'game_state' argument representing the current game state.

Second, the DialogAction class serves as a global reference to all registered, concrete DialogAction subclasses. This feature standardizes the parsing code used to construct DialogActions and allows new DialogActions to be defined without having to modify the parsing code - the parsing code simply looks up a DialogAction subclass by keyword and, if it finds such a subclass, constructs an instance using the arguments defined in the serialized dialogue.

The new DialogAction process preserves the easy extensibility of the old callback system while standardizing the interface to the dialogue logic. The new class-based system also harnesses the power of Python class inheritance which allows for complex sets of DialogAction subclasses to be defined. Examples of how inheritance can be used to define new DialogActions can be seen in the new dialogueactions.py module, which uses an inheritance hierarchy to group together dialogue actions with similar functionality.

New Dialog Data Structures

DialogEngine DataStructures Techdemo2.png

The old YAML in-memory data structures have been replaced with the new class-based data structures defined in the dialogue.py module.

Each NPC's dialogue data is represented by a single Dialog instance in-memory. Dialog data is further divided into DialogSection and DialogResponse instances which store sections of dialogue and possible player responses, respectively.

The new data structures are far more intuitive and easily modified, owing to the fact that these classes make relationships between the various components of a dialogue explicit. They also abstract the dialogue data from their serialized representation (namely the YAML dialogue file syntax), making it easier to switch serialization methods (e.g. to xml) and interface the dialogue data with external tools since it standardizes the dialogue data interface.

Refactor YAML Parsing Code into its Own Module

The new dialogueparsers.py module contains the newly refactored dialogue parsing code, which defines the AbstractDialogParser abstract base class and its concrete subclass YamlDialogParser.

The AbstractDialogParser abstract base class defines the interface that dialogue parsers should implement, namely the following methods:

load(stream) 
Parse an open stream containing a serialized dialogue and attempt to construct and return a Dialog instance.
dump(dialogue, stream) 
Serialize a Dialog instance and write it to an open stream.

The concrete class YamlDialogParser implements the AbstractDialogParser interface for reading and writing YAML dialogue serializations.

Refactoring the dialogue parsing code into its own module and set of classes makes it easier to update and maintain the parsing code without having to deal with dialogue processing and logic code. Abstracting the dialogue parsing code in an object-oriented manner also makes it easier to develop and maintain parsers for different serialization methods (e.g. xml).

Note: Currently the new dialogue parsers do not validate dialogues before reading or writing them. The best method of dialogue validation has not yet been determined, but implementing such validation in the near future should be a priority.

New YAML Dialog File Syntax

The old YAML dialogue file syntax has been modified to be both more explicit and to more closely resemble the new in-memory dialogue data structures.

The following keywords have been changed to be both more explicit and more closely match the attribute names of the in-memory dialogue data structures:

  • 'NPC' is now 'NPC_NAME'
  • 'AVATAR' is now 'AVATAR_PATH'
  • 'START' is now 'START_SECTION'

Dialog sections are now designated as elements in a YAML sequence ('-' in YAML means that the following text/tags are an entry in a sequence). The new 'ID', 'SAY', 'ACTIONS' and 'RESPONSES' tags are used to explicitly specify a DialogSection's id, displayed text, list of DialogActions and list of possible player responses, respectively. These keywords are specified as nested YAML mappings instead of the old positional arguments ('<keyword>: <arguments>' in YAML specifies a mapping between <keyword> and <arguments>, where <arguments> may be a single line of text or a nested YAML sequence or mapping).

DialogResponses are now specified as YAML sequence elements within a 'RESPONSES' tag under a DialogSection. The 'REPLY' and 'GOTO' keywords replace the old positional arguments used to specify the displayed text of the reply and flow command (see below) executed when a reply is chosen by the player, and are both mandatory keywords. The 'CONDITION' keyword is optional and accepts a single YAML scalar argument (i.e. a single line of text) which should evaluate to a valid Python expression that returns a boolean outcome. If specified, the DialogResponse will only be displayed if the 'CONDITION' statement evaluates to "True". The 'ACTIONS' keyword is also optional, a specifies a list of DialogActions to execute when the reply is selected by the player (see below).

Flow commands have been significantly simplified from the techdemo1 syntax. Flow commands are now specified using the 'GOTO' command in a DialogResponse, which accepts a single YAML scalar argument. That argument can be one of the following:

  • 'end', which instructs the DialogEngine to end the current conversation if the player choses the response;
  • 'back', which instructs the DialogEngine to return to the previous DialogSection;
  • ID of a valid DialogSection, which instructs the DialogEngine to move onto a new section of dialogue as appropriate;

Finally, 'ACTIONS' is now an optional keyword that can appear within either a DialogSection or DialogResponse. 'ACTIONS' species a sequence of DialogActions to execute when the DialogSection is processed or DialogResponse is selected by the player as appropriate. See the #Refactor Dialog Logic into its Own Module section for details.

Example of the new syntax:

#   This file is part of PARPG.

#   PARPG is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.

#   PARPG is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.

#   You should have received a copy of the GNU General Public License
#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
---
NPC_NAME: Old Man
AVATAR_PATH: gui/icons/crazy_swede.png
START_SECTION: main_dialogue
SECTIONS:
    - ID: main_dialogue
      SAY: "The old man looks up at you from his chair with a dazed look
        upon his face.
        
        \"...Lucy?\""
      ACTIONS:
        - meet: old_man
      RESPONSES:
        - REPLY: "Who's Lucy?"
          GOTO: help_find_lucy
        
        - REPLY: "I found your ring."
          CONDITION: "quest.hasActiveQuest('memories') and
            pc.inventory.hasItem('RustyWeddingRing')"
          ACTIONS:
            - give_stuff:
                - RustyWeddingRing
            - complete_quest: memories
          GOTO: good_memories
        
        - REPLY: "I don't have time for your delusions old man!"
          GOTO: parting_shot
    
    - ID: good_memories
      SAY: "An intense expression of joy spreads across the old man's face
        as you hand him the rusty wedding ring. He tries to mouth a
        word of thanks but is so overcome that no words come out. The old
        man begins caressing the ring, oblivious to the decay and rot of
        the world around him."
      RESPONSE:
        - REPLY: "[Walk away.]"
          GOTO: end
    
    - ID: help_find_lucy
      SAY: "The old man looks at you expectedly.
        
        \"Can you help me find Lucy?\""
      RESPONSES:
        - REPLY: "Where is Lucy?"
          GOTO: rusted_lockbox
        
        - REPLY: "I have better things to do."
          GOTO: parting_shot
    
    - ID: rusted_lockbox
      SAY: "The old man gazes over his shoulder to the far side of the
        shanty where a metal lockbox rests on the floor.
        
        \"Can't open it any more... rusted...\""
      RESPONSES:
        - REPLY: "I'll see what I can do."
          ACTIONS:
            - start_quest: memories
          GOTO: parting_shot
        
        - REPLY: "Fix your own problems, old man!"
          ACTIONS:
          GOTO: parting_shot
    
    - ID: parting_shot
      SAY: "The old man sighs and begins to stare remorsefully at the
        smouldering embers in the fireplace."
      RESPONSES:
        - REPLY: "[Walk away.]"
          GOTO: end

The benefits of making the YAML syntax more closely represent the in-memory data structures and conform to standard YAML are multifaceted. The new syntax is much more explicit, so contributors and modders who learn the YAML syntax would already have a good understanding of the DialogEngine and how it functions. The new syntax also significantly simplify the YAML dialogue file parsing code, making it easier to maintain in the long run.

Though the new syntax is slightly more verbose and complex than the techdemo1 syntax, the benefits outlined above easily outweigh these relatively minor faults. The YAML dialogue file syntax will undergo continuous evaluation and revision for the next few releases, and comments about the new syntax are welcome.

Personal tools