Skip to content

Instantly share code, notes, and snippets.

@mhekel
Last active May 9, 2024 23:17
Show Gist options
  • Select an option

  • Save mhekel/bbebf9d577b8034ec9cf70d55ee8b4af to your computer and use it in GitHub Desktop.

Select an option

Save mhekel/bbebf9d577b8034ec9cf70d55ee8b4af to your computer and use it in GitHub Desktop.
page

Demo of Dynamic Dialogs (a Library script)

/// -----------------------------------------------------------------------------
/// @file   dlg_l_demo.nss
/// @author Michael A. Sinclair (Squatting Monk) <squattingmonk@gmail.com>
/// @brief  Dynamic Dialogs (library script)
/// -----------------------------------------------------------------------------
/// This library contains some example dialogs that show the features of the Core
/// Dialogs system. You can use it as a model for your own dialog libraries.
/// -----------------------------------------------------------------------------

#include "dlg_i_dialogs"
#include "util_i_library"

// -----------------------------------------------------------------------------
//                                  Poet Dialog
// -----------------------------------------------------------------------------
// This dialog shows how to use continue, back, and end nodes in a dialog.
// -----------------------------------------------------------------------------

const string POET_DIALOG       = "PoetDialog";
const string POET_DIALOG_INIT  = "PoetDialog_Init";
const string POET_DIALOG_PAGE  = "PoetDialog_Page";
const string POET_DIALOG_QUIT  = "PoetDialog_Quit";

const string POET_PAGE_MAIN = "Main Page";
const string POET_PAGE_INFO = "Continue Node Info";
const string POET_PAGE_POOR = "Not enough GP";
const string POET_PAGE_MARY = "Poem: Mary Had A Little Lamb";
const string POET_PAGE_SICK = "Poem: Sick";
const string POET_PAGE_END  = "Poem Finished";

void PoetDialog_Init()
{
    string sPage;

    // Main landing page
    SetDialogPage(POET_PAGE_MAIN);
    AddDialogPage(POET_PAGE_MAIN, "Would you like to hear a poem? 1GP per recital!");
    AddDialogNode(POET_PAGE_MAIN, POET_PAGE_INFO, "Who are you?");
    AddDialogNode(POET_PAGE_MAIN, POET_PAGE_MARY, "Mary Had A Little Lamb");
    AddDialogNode(POET_PAGE_MAIN, POET_PAGE_SICK, "Sick, by Shel Silverstein");
    AddDialogNode(POET_PAGE_MAIN, POET_PAGE_POOR, "I can't afford that.");
    EnableDialogEnd("Goodbye", POET_PAGE_MAIN);

    // PC asked "Who are you?"
    AddDialogPage(POET_PAGE_INFO,
        "I am demonstrating continued pages. Continued pages are used when " +
        "you want to have several pages of text with a continue button in " +
        "between.");
    AddDialogPage(POET_PAGE_INFO,
        "To continue a page, simply call the AddDialogPage() function using " +
        "the same page name. A child page will be created and a \"Continue\" " +
        "node will be added to the parent page. Do this as many times as is " +
        "necessary to add sufficient text.");
    AddDialogPage(POET_PAGE_INFO,
        "The child pages are full pages like any other. They can have their " +
        "own nodes, etc. The AddDialogPage() function returns the name of " +
        "the child page, so you can catch it and change things without " +
        "having to keep track of how many pages are in the chain.");
    AddDialogPage(POET_PAGE_INFO,
        "You can add automated nodes such as end or back responses. In this " +
        "example, I get mad and attack you if you interrupt my beautiful " +
        "poetry, so I suggest not doing that.");
    sPage = AddDialogPage(POET_PAGE_INFO,
        "You can also add \"Continue\" nodes to a page using the function " +
        "ContinueDialogPage(). This function links a page to an existing " +
        "page. It's useful for ending a chain of continued pages. In fact, " +
        "that's what links the \"Continue\" node below back to the main page.");
    ContinueDialogPage(sPage, POET_PAGE_MAIN);

    // PC does not have enough money to list to the chosen poem.
    AddDialogPage(POET_PAGE_POOR,
        "Oh, I'm sorry. It seems you don't have enough coin on you. I don't " +
        "recite poetry for free, you know.");
    AddDialogNode(POET_PAGE_POOR, POET_PAGE_INFO,
        "Can you tell me who you are instead?");
    EnableDialogEnd("Goodbye", POET_PAGE_POOR);

    // PC chose poem "Mary Had A Little Lamb"
    AddDialogPage(POET_PAGE_MARY,
            "Mary had a little lamb,\n" +
            "Little lamb,\nLittle lamb.\n" +
            "Mary had a little lamb.\n" +
            "Its fleece was white as snow.");
    sPage = AddDialogPage(POET_PAGE_MARY,
            "Everywhere that Mary went,\n" +
            "Mary went,\nMary went,\n" +
            "Everywhere that Mary went,\n" +
            "The lamb was sure to go.");
    EnableDialogEnd("I've had enough of this. Good day!", sPage);

    sPage = AddDialogPage(POET_PAGE_MARY,
            "It followed her to school one day,\n" +
            "School one day,\nSchool one day,\n" +
            "It followed her to school one day,\n" +
            "Which was against the rules.");
    EnableDialogEnd("Booooring! Learn to recite poetry!", sPage);

    sPage = AddDialogPage(POET_PAGE_MARY,
            "It made the children laugh and play,\n" +
            "Laugh and play,\nLaugh and play,\n" +
            "It made the children laugh and play,\n" +
            "To see a lamb at school.");
    EnableDialogEnd("Oh, gods! I thought it would never end!", sPage);
    ContinueDialogPage(sPage, POET_PAGE_END);

    // PC chose poem "Sick, by Shel Silverstein"
    AddDialogPage(POET_PAGE_SICK,
            "I cannot go to school today,\n" +
            "Said little Peggy Ann McKay.\n" +
            "I have the measles and the mumps,\n" +
            "A gash, a rash and purple bumps.");
    AddDialogPage(POET_PAGE_SICK,
            "My mouth is wet, my throat is dry,\n" +
            "I'm going blind in my right eye.\n" +
            "My tonsils are as big as rocks,\n" +
            "I've counted sixteen chicken pox\n" +
            "And there's one more--that's seventeen,\n" +
            "And don't you think my face looks green?");
    AddDialogPage(POET_PAGE_SICK,
            "My leg is cut--my eyes are blue--\n" +
            "It might be instamatic flu.\n" +
            "I cough and sneeze and gasp and choke,\n" +
            "I'm sure that my left leg is broke--\n" +
            "My hip hurts when I move my chin,\n" +
            " My belly button's caving in.");
    sPage = AddDialogPage(POET_PAGE_SICK,
            "My back is wrenched, my ankle's sprained,\n" +
            "My 'pendix pains each time it rains.\n" +
            "My nose is cold, my toes are numb.\n" +
            "I have a sliver in my thumb.\n" +
            "My neck is stiff, my voice is weak,\n" +
            "I hardly whisper when I speak.");
    EnableDialogEnd("I've had enough of this. Good day!", sPage);

    sPage = AddDialogPage(POET_PAGE_SICK,
            "My tongue is filling up my mouth,\n" +
            "I think my hair is falling out.\n" +
            "My elbow's bent, my spine ain't straight,\n" +
            "My temperature is one-o-eight.\n" +
            "My brain is shrunk, I cannot hear,\n" +
            "There is a hole inside my ear.");
    EnableDialogEnd("Booooring! Learn to recite poetry!", sPage);

    sPage = AddDialogPage(POET_PAGE_SICK,
            "I have a hangnail, and my heart is--what?\n" +
            "What's that? What's that you say?\n" +
            "You say today is... Saturday?\n" +
            "G'bye, I'm going out to play!");
    EnableDialogEnd("Oh, gods! I thought it would never end!", sPage);
    ContinueDialogPage(sPage, POET_PAGE_END);

    // Poem is finished
    AddDialogPage(POET_PAGE_END, "I hope you enjoyed it! Would you like to " +
        "hear another poem? Only 1GP per recital!");
    SetDialogNodes(POET_PAGE_END, POET_PAGE_MAIN);
    EnableDialogEnd("That's it for me, thanks.", POET_PAGE_END);
}

void PoetDialog_Page()
{
    string sPage = GetDialogPage();
    object oPC = GetPCSpeaker();
    int nGold = GetGold(oPC);

    if (sPage == POET_PAGE_MAIN || sPage == POET_PAGE_END)
    {
        // This only happens on the first run
        if (GetDialogNode() == DLG_NODE_NONE)
            ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING);

        if (nGold < 1)
            FilterDialogNodes(1, 2);
        else
            FilterDialogNodes(3);
    }
    else if (sPage == POET_PAGE_MARY || sPage == POET_PAGE_SICK)
    {
        if (nGold < 1)
            SetDialogPage(POET_PAGE_POOR);
        else
            TakeGoldFromCreature(1, oPC);
    }

    // We don't use history in this dialog, so don't store it up
    ClearDialogHistory();
}

void PoetDialog_Quit()
{
    string sPage = GetDialogPage();

    if (GetStringLeft(sPage, 5) == "Poem:")
    {
        object oPC = GetPCSpeaker();
        ActionSpeakString("You'll pay for that!");
        ActionDoCommand(SetIsTemporaryEnemy(oPC));
        ActionAttack(oPC);
    }
    else
    {
        ActionSpeakString("Perhaps another time, then? Good day!");
        ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING);
    }
}

// -----------------------------------------------------------------------------
//                                 Anvil Dialog
// -----------------------------------------------------------------------------
// This dialog demonstrates dynamic node generation and automatic pagination.
// -----------------------------------------------------------------------------

const string ANVIL_DIALOG      = "AnvilDialog";
const string ANVIL_PAGE_MAIN   = "Main Page";
const string ANVIL_PAGE_ITEM   = "Item Chosen";
const string ANVIL_PAGE_ACTION = "Item Action";
const string ANVIL_ITEM        = "AnvilItem";

void AnvilDialog()
{
    switch (GetDialogEvent())
    {
        case DLG_EVENT_INIT:
        {
            AddDialogToken("Action");
            AddDialogToken("Item");

            EnableDialogEnd();
            SetDialogPage(ANVIL_PAGE_MAIN);
            AddDialogPage(ANVIL_PAGE_MAIN, "Select an item:");

            AddDialogPage(ANVIL_PAGE_ITEM, "What would you like to do with the <Item>?");
            AddDialogNode(ANVIL_PAGE_ITEM, ANVIL_PAGE_ACTION, "Clone it", "Copy");
            AddDialogNode(ANVIL_PAGE_ITEM, ANVIL_PAGE_ACTION, "Destroy it", "Destroy");
            EnableDialogNode(DLG_NODE_BACK, ANVIL_PAGE_ITEM);

            AddDialogPage(ANVIL_PAGE_ACTION, "<Action>ing <Item>");
            EnableDialogNode(DLG_NODE_BACK, ANVIL_PAGE_ACTION);
            SetDialogTarget(ANVIL_PAGE_MAIN, ANVIL_PAGE_ACTION, DLG_NODE_BACK);
        } break;

        case DLG_EVENT_PAGE:
        {
            object oPC = GetPCSpeaker();
            string sPage = GetDialogPage();
            int nNode = GetDialogNode();

            if (sPage == ANVIL_PAGE_MAIN)
            {
                DeleteDialogNodes(sPage);
                DeleteObjectList(DLG_SELF, ANVIL_ITEM);
                object oItem = GetFirstItemInInventory(oPC);

                while (GetIsObjectValid(oItem))
                {
                    AddDialogNode(sPage, ANVIL_PAGE_ITEM, GetName(oItem));
                    AddListObject(DLG_SELF, oItem, ANVIL_ITEM);
                    oItem = GetNextItemInInventory(oPC);
                }
            }
            else if (sPage == ANVIL_PAGE_ITEM)
            {
                object oItem = GetListObject(DLG_SELF, nNode, ANVIL_ITEM);
                SetLocalObject(DLG_SELF, ANVIL_ITEM, oItem);
                CacheDialogToken("Item", GetName(oItem));
            }
            else if (sPage == ANVIL_PAGE_ACTION)
            {
                int nNode = GetDialogNode();
                string sData = GetDialogData(ANVIL_PAGE_ITEM, nNode);
                object oItem = GetLocalObject(DLG_SELF, ANVIL_ITEM);
                CacheDialogToken("Action", sData);

                if (sData == "Copy")
                    CopyItem(oItem, oPC);
                else if (sData == "Destroy")
                    DestroyObject(oItem);
            }
        } break;
    }
}

// -----------------------------------------------------------------------------
//                                 Token Dialog
// -----------------------------------------------------------------------------
// This dialog demonstrates the use of dialog tokens.
// -----------------------------------------------------------------------------

const string TOKEN_DIALOG = "TokenDialog";
const string TOKEN_PAGE_MAIN = "Main Page";
const string TOKEN_PAGE_INFO = "Token Info";
const string TOKEN_PAGE_LIST = "Token List";

void TokenDialog()
{
    if (GetDialogEvent() != DLG_EVENT_INIT)
        return;

    EnableDialogEnd();
    SetDialogLabel(DLG_NODE_CONTINUE, "<StartAction>[Listen]</Start>");

    SetDialogPage(TOKEN_PAGE_MAIN);

    AddDialogPage(TOKEN_PAGE_MAIN,
        "Hello, <FirstName>. Isn't this a fine <quarterday>?");
    AddDialogNode(TOKEN_PAGE_MAIN, TOKEN_PAGE_INFO, "Who are you?");
    AddDialogNode(TOKEN_PAGE_MAIN, TOKEN_PAGE_LIST, "What tokens are available?");

    AddDialogPage(TOKEN_PAGE_INFO,
        "I'm demonstrating the use of dialog tokens. A token is a word or " +
        "choice in angle brackets such as <token>FullName</token>. Tokens " +
        "can be embedded directly into page or node text when initializing " +
        "the dialog and are evaluated at display time. This means you don't " +
        "need to know the value of the token to make the text work.");
    AddDialogPage(TOKEN_PAGE_INFO,
        "There are lots of tokens available, such as <token>class</token>, " +
        "<token>race</token>, and <token>level</token>.\n\n" +
        "That's how I can know you're a level <level> <racial> <class>.");
    AddDialogPage(TOKEN_PAGE_INFO,
        "You can also add your own tokens using AddDialogToken() in your " +
        "dialog's init script. This function takes three arguments:\n\n" +
        "- sToken: the token text (not including the brackets)\n" +
        "- sEvalScript: a library script that sets the token value\n" +
        "- sValues: an optional CSV list of possible values for the token");
    AddDialogPage(TOKEN_PAGE_INFO,
        "The library script referenced by sEvalScript can read the token " +
        "and possible values and then set the proper value for the token. " +
        "Refer to dlg_l_tokens.nss to see how this is done.");
    AddDialogPage(TOKEN_PAGE_INFO,
        "Some tokens can have uppercase and lowercase variants. When the " +
        "token is all lowercase, the value will be converted to lowercase; " +
        "otherwise the value will be used as is. For example:\n\n" +
        "<token>Class</token> -> <Class>\n" +
        "<token>class</token> -> <class>");
    AddDialogPage(TOKEN_PAGE_INFO,
        "Some tokens cannot be converted to lowercase; if there are any " +
        "uppercase characters in sToken, the text must be typed exactly and " +
        "its value will always be exact. For example:\n\n" +
        "<token>FullName</token> -> <FullName>\n" +
        "<token>fullname</token> -> <fullname>");
    string sPage = AddDialogPage(TOKEN_PAGE_INFO,
        "Tokens are specific to a dialog, so if you add your own tokens, you " +
        "don't have to worry about making them unique across all dialogs. " +
        "You could have a <token>value</token> token in two different " +
        "dialogs that are evaluated by different library scripts. This gives " +
        "you a lot of flexibility when designing dialogs.");
    EnableDialogNode(DLG_NODE_BACK, sPage);
    SetDialogTarget("Main Page", sPage, DLG_NODE_BACK);

    AddDialogPage(TOKEN_PAGE_LIST,
        "Gender tokens (case insensitive):\n\n" +
        "- <token>bitch/bastard</token> -> <bitch/bastard>\n" +
        "- <token>boy/girl</token> -> <boy/girl>\n" +
        "- <token>brother/sister</token> -> <brother/sister>\n" +
        "- <token>he/she</token> -> <he/she>\n" +
        "- <token>him/her</token> -> <him/her>\n" +
        "- <token>his/her</token> -> <his/her>\n" +
        "- <token>his/hers</token> -> <his/hers>\n" +
        "- <token>lad/lass</token> -> <lad/lass>\n" +
        "- <token>lord/lady</token> -> <lord/lady>\n" +
        "- <token>male/female</token> -> <male/female>\n" +
        "- <token>man/woman</token> -> <man/woman>\n" +
        "- <token>master/mistress</token> -> <master/mistress>\n" +
        "- <token>mister/missus</token> -> <mister/missus>\n" +
        "- <token>sir/madam</token> -> <sir/madam>");
    AddDialogPage(TOKEN_PAGE_LIST,
        "Alignment tokens (case insensitive):\n\n" +
        "- <token>alignment</token> -> <alignment>\n" +
        "- <token>good/evil</token> -> <good/evil>\n" +
        "- <token>law/chaos</token> -> <law/chaos>\n" +
        "- <token>lawful/chaotic</token> -> <lawful/chaotic>");
    AddDialogPage(TOKEN_PAGE_LIST,
        "Character tokens (case insensitive):\n\n" +
        "- <token>class</token> -> <class>\n" +
        "- <token>classes</token> -> <classes>\n" +
        "- <token>level</token> -> <level>\n" +
        "- <token>race</token> -> <race>\n" +
        "- <token>races</token> -> <races>\n" +
        "- <token>racial</token> -> <racial>\n" +
        "- <token>subrace</token> -> <subrace>\n\n" +
        "Character tokens (case sensitive):\n\n" +
        "- <token>Deity</token> -> <Deity>");
    AddDialogPage(TOKEN_PAGE_LIST,
        "Time tokens (case insensitive):\n\n" +
        "- <token>day/night</token> -> <day/night>\n" +
        "- <token>gameday</token> -> <gameday>\n" +
        "- <token>gamedate</token> -> <gamedate>\n" +
        "- <token>gamehour</token> -> <gamehour>\n" +
        "- <token>gameminute</token> -> <gameminute>\n" +
        "- <token>gamemonth</token> -> <gamemonth>\n" +
        "- <token>gamesecond</token> -> <gamesecond>\n" +
        "- <token>gametime12</token> -> <gametime12>\n" +
        "- <token>gametime24</token> -> <gametime24>\n" +
        "- <token>gameyear</token> -> <gameyear>\n" +
        "- <token>quarterday</token> -> <quarterday>");
    AddDialogPage(TOKEN_PAGE_LIST,
        "Name tokens (case sensitive):\n\n" +
        "- <token>FirstName</token> -> <FirstName>\n" +
        "- <token>FullName</token> -> <FullName>\n" +
        "- <token>LastName</token> -> <LastName>\n" +
        "- <token>PlayerName</token> -> <PlayerName>");
    sPage = AddDialogPage(TOKEN_PAGE_LIST,
        "Special tokens (case sensitive):\n\n" +
        "- <token>StartAction</token>foo<token>/Start</token> -> " +
            "<StartAction>foo</Start>\n" +
        "- <token>StartCheck</token>foo<token>/Start</token> -> " +
            "<StartCheck>foo</Start>\n" +
        "- <token>StartHighlight</token>foo<token>/Start</token> -> " +
            "<StartHighlight>foo</Start>\n\n" +
        "Special tokens (case insensitive):\n\n" +
        "- <token>token</token>foo<token>/token</token> -> <token>foo</token>");
    EnableDialogNode(DLG_NODE_BACK, sPage);
    SetDialogTarget("Main Page", sPage, DLG_NODE_BACK);
}


void OnLibraryLoad()
{
    RegisterLibraryScript(POET_DIALOG_INIT);
    RegisterLibraryScript(POET_DIALOG_PAGE);
    RegisterLibraryScript(POET_DIALOG_QUIT);

    RegisterDialogScript(POET_DIALOG, POET_DIALOG_INIT, DLG_EVENT_INIT);
    RegisterDialogScript(POET_DIALOG, POET_DIALOG_PAGE, DLG_EVENT_PAGE);
    RegisterDialogScript(POET_DIALOG, POET_DIALOG_QUIT, DLG_EVENT_END | DLG_EVENT_ABORT);

    RegisterLibraryScript(ANVIL_DIALOG);
    RegisterDialogScript (ANVIL_DIALOG);

    RegisterLibraryScript(TOKEN_DIALOG);
}

void OnLibraryScript(string sScript, int nEntry)
{
    if      (sScript == POET_DIALOG_PAGE) PoetDialog_Page();
    else if (sScript == POET_DIALOG_QUIT) PoetDialog_Quit();
    else if (sScript == POET_DIALOG_INIT) PoetDialog_Init();
    else if (sScript == ANVIL_DIALOG)     AnvilDialog();
    else if (sScript == TOKEN_DIALOG)     TokenDialog();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment