email sol follow sol rss feed of the blog wishlist Sol::Code

DialogTree (d3)

Game dialog tree middleware engine

The D3 API

The D3 API is a pure C interface for maximum compatibility and portability. Note that tinyxml, which is needed for XML format loading, is a c++ library, and thus the code that accesses it is naturally also c++.

Data Structures


struct d3;

The main data structure is 'd3'. One instance of this data structure is meant to live through your whole game, and you pass the pointer to the d3 structure to almost all d3 api functions. In practise the structure contains the tag store and some helper variables to keep up with the dialog state.


struct d3deck;

The d3deck structure contains one discussion, dialogue, or deck of cards. These can come and go. One and only one deck is active at any time.

Init/Deinit


struct d3 * d3_init();
void d3_deinit(struct d3 *aD3);

The init function allocates the data structure, which is used through the lifespan of your game. To free the structure, use d3_deinit().


int d3_set_language(struct d3 *aD3, const char *aLanguage);

This function can be used to set the language used, in case the cards have localized versions of the strings. If some localization is missing, D3 will pick some other language string instead.

In most cases you will never need to use this function.

Savegame Support


int d3_serialize_estimate(struct d3 *aD3);
int d3_serialize(struct d3 *aD3, char *aBuffer, int *aBytesUsed, int aBufferSize);
int d3_deserialize(struct d3 *aD3, char *aBuffer);

In order to save and load the current state, you can use the serialization API. The d3_serialize_estimate() function can be used to estimate the size of the required buffer. (I say 'estimate', but this is byte exact).

The d3_serialize() and d3_deserialize() functions return non-zero on error.

Note that this only serializes the tag store, not the current conversation or the position in said conversation, the currently set language, or anything else. In most cases this won't be an issue (as you probably won't be saving while talking), but in a game which is mostly based on D3 you might want to enable this. In this case you'll have to save which dialog (deck) is being used, as well as which card.

Tag Access


int d3_is_tag(struct d3 *aD3, const char *aTag);
int d3_clear_tag(struct d3 *aD3, const char *aTag);
int d3_clear_all_tags(struct d3 *aD3);
int d3_set_tag(struct d3 *aD3, const char *aTag);
int d3_tag_count(struct d3 *aD3);
const char * d3_tag(struct d3 *aD3, int aTag);

You can also access the tag store directly. You can query whether a certain tag exists with d3_is_tag() and set and clear tags with d3_set_tag() and d3_clear_tag(). To wipe all tags, use d3_clear_all_tags().

To browse the whole tag store you can query how many tags there are, and ask for pointers to tags by order number. The last two pieces of information are only valid as long as the tag store is not modified in any way, through these calls or by playing a dialog. In other words, don't keep pointers to tags.

Example: (print out all current tags)


int count = d3_tag_count(d);
int i;
for (i = 0; i < count; i++)
  printf("%s\n", d3_tag(d, i));

Deck Management


struct d3deck * d3_load_deck_xml(const char *aFilename);
struct d3deck * d3_load_deck_json(const char *aFilename);
struct d3deck * d3_load_deck_bin(const char *aFilename);

Decks are loaded from files using one of the d3_load_deck functions. Various formats are supported, with pros and cons of each. Loading xml formats requires tinyxml, loading json requires jansson. Bin files are custom chunk based binaries which do not require any external libraries, but are not human-readable and may easily break as the format changes.

The functions return NULL on failure.


int d3_free_deck(struct d3deck *aDeck);

To free the memory allocated for a deck, use the d3_free_deck() function.

Warning: Freeing currently active deck is legal, but trying to use the deck after it's been freed most probably causes a crash. Activate some other deck before trying to use it.

The function returns a non-zero value in case of an error.


int d3_use_deck(struct d3 *aD3, struct d3deck *aDeck, const char *aCardID);

To start using a deck, use the d3_use_deck() function. This function also sets the first card of the deck as the active one if the aCardID parameter is zero.

The function returns a non-zero value in case of an error.


const char *d3_get_deck_id(struct d3 *aD3);
const char *d3_get_deck_user_data(struct d3 *aD3);

Finally, these two functions return pointers to the deck id and the deck user data, if any.

Card Management


const char * d3_get_question(struct d3 *aD3);

This function is used to fetch the question text, if any. D3 will pick the localized version of the strings based on what was set with the d3_set_language() call.


const char * d3_get_card_user_data(struct d3 *aD3);

This function is used to fetch the card user data, if any.


const char * d3_get_card_id(struct d3 *aD3);

You can read the current card id using this function. This can be useful if you're saving game in the middle of a dialog.


int d3_goto(struct d3 *aD3, const char *aCardID);

Go to a certain card on the deck.

Returns non-zero if the card is not found, or some other error occurs.

Answer Management


int d3_get_answer_count(struct d3 *aD3);

Get the number of valid answers. If the number is 0, this was an exit point.


const char *d3_get_answer(struct d3 *aD3, int aAnswer);
const char *d3_get_answer_user_data(struct d3 *aD3, int aAnswer);

These functions are used to fetch the text for the answer (localized, again), as well as the user data if any.

If the answer string is NULL, you're supposed to pick that answer immediately.


int d3_choose(struct d3 *aD3, int aAnswer);

Call this function when the user picks an answer. This will cause D3 to create the tag that will stop the answer from being shown again (if needed) and to go to the target card.

Big Picture Example

Here's an example that uses several of the above functions. Some error checking is omitted for clarity.


// Some variables
int answers;            // Number of answers
int i;                  // Loop iterator
int done;               // Loop terminator flag
const char *q, *a;      // Pointers to question and answer text
struct d3 *d;           // D3 engine
struct d3deck *deck;    // A card deck

// Init
d = d3_init();

// Load a conversation
deck = d3_load_deck_xml("talking.xml");

// Use it, start from first card (NULL)
d3_use_deck(d, deck, NULL);

// We're not done yet
done = 0;

while (!done)
{
  // Get question text, print it if any
  q = d3_get_question(d);
  if (q != NULL) 
  { 
    printf("%s\n", q);
  }
  
  // Find out how many answers we have..
  answers = d3_get_answer_count(d);
  if (answers == 0)
  {
    done = 1; // No answers, we're done here
  }
  else
  {
    // If first answer text doesn't exist, it's the auto-choose option  
    if (d3_get_answer(d, 0) == NULL)
    {
      d3_choose(d, 0);
      continue;
    }
    
    // Otherwise, let's print the answers
    for (i = 0; i < answers; i++)
    {
      a = d3_get_answer(d, i);
      if (a) 
      {
        printf("%d) %s\n", i, a);
      }
      else
      {
        printf("%d) Empty (should not happen, bad data)\n", i);
      }      
    }
    
    // I could use scanf, but I'd rather not. Let's just say user defines the input.
    i = some_function_user_writes_to_pick_the_answer(answers);
    
    // and then we pick it.
    d3_choose(d, i);
  }  
}

// Free memory
d3_free_deck(deck);
d3_deinit(d);

// All done!

 

Comments, etc, appreciated, as always.

Site design & Copyright © 2017 Jari Komppa
Possibly modified around: August 06 2011