Wednesday, June 4, 2014

Working on the conversation engine

This bit's tough. I've elected to try to build my own "scripting" engine, instead of integrating Lua ... because I want to see if I can design and build such a thing.

My goal isn't to build a DSL, but rather design a JSON schema which can be used to describe the world, and interpret them.

One type of scriptable world element is the conversation engine. Given an XML structure (.plist format native to IOS), that looks something like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>entityName</key>
        <string>Senopati</string>

        <key>entryPoints</key>
        <array>
            <string>1</string>
        </array>

        <key>dialog</key>
        <dict>

            <key>1</key>
            <dict>
                <key>conditions</key>
                <array>
                    <dict>
                        <key>type</key>
                        <string>flag</string>
                        <key>params</key>
                        <dict>
                            <key>operation</key>
                            <string>equals</string>
                            <key>flagName</key>
                            <string>test</string>
                            <key>flagValue</key>
                            <string>true</string>
                        </dict>
                    </dict>
                </array>

                <key>text</key>
                <string>Is your heart resolved, Putra?</string>

                <key>responses</key>
                <dict>
                    <key>1</key>
                    <dict>
                        <key>text</key>
                        <string>Yes, honored Seno. I am prepared.</string>
                        <key>ifSelected</key>
                        <dict>
                            <key>nextDialog</key>
                            <string>3</string>
                        </dict>
                    </dict>

                    <key>2</key>
                    <dict>
                        <key>text</key>
                        <string>I am not yet ready, beloved Seno. I need more time.</string>
                        <key>ifSelected</key>
                        <dict>
                            <key>nextDialog</key>
                            <string>2</string>
                        </dict>
                    </dict>
                </dict>

                <key>isEntryPoint</key>
                <string>yes</string>
            </dict>

            <key>2</key>
            <dict>
                <key>text</key>
                <string>I understand. But hurry! there is not much time.</string>

                <key>nextDialog</key>
                <string>_END_</string>
            </dict>

            <key>3</key>
            <dict>
                <key>text</key>
                <string>I am glad, as I cannot submit this sanctuary to my will much longer.</string>

                <key>nextDialog</key>
                <string>4</string>
            </dict>

            <key>4</key>
            <dict>
                <key>text</key>
                <string>My strength is waning, and I shall rejoin my brothers and sisters, lost these ten years.</string>

                <key>nextDialog</key>
                <string>_END_</string>
            </dict>

        </dict>

    </dict>
</plist>

... I want to be able to build an 'intepreter' that can setup the typical in-game dialog interface that looks like this:
The basic flow behind this is as follows:
  1. For each target NPC build a series of dialog nodes.
  2. One or more of these dialog nodes are declared as "entry points" to the conversation
    1. These are guarded by conditions
    2. The conditions check certain quest flags ("have I talked to this person before" flag; "do I have the magic doohickey" flag)
    3. The entry points should be mutually exclusive: no two entry points should be valid at any given time.
  3. Dialog nodes present their text, and
  4. An array of possible responses, whose appearance is guarded by conditions in the same way as described in (2).
  5. Clicking on a response will emit an event, which is usually going to be the event that brings you to the next node of the dialog tree, or
    1. End the conversation
    2. Trigger some other world event (maybe give you an item, set a quest flag, or something).
So far the conversation engine appears to work.
Given this test code:
[self.context.gameState.flags setObject:@"true" forKey:@"test"];

Entity *npc = [self.context.entityManager getByName:@"Senopati"];
Dialog* dialog = [self.context.dialogManager startDialog:npc.id];
NSLog(@"%@",dialog.text);

NSMutableArray *responses = [self.context.dialogManager computeAvailableResponsesFor:dialog];

Response *response = [responses objectAtIndex:1];
NSLog(@"%@",response.text);

Dialog* next = [self.context.dialogManager computeNextDialogFor:dialog :response];
NSLog(@"%@",next.text);

while ( next ) {
    next = [self.context.dialogManager computeNextDialogFor:next];
    if ( next ) {
        NSLog(@"%@",next.text);
    }
}
I'm exercising the conversation XML properly:
2014-06-04 22:05:40.689 rpg[18762:70b] Putra, is your heart prepared? 
2014-06-04 22:05:40.690 rpg[18762:70b] Not yet, beloved Seno. I am not ready.
 2014-06-04 22:05:40.690 rpg[18762:70b] I understand. But hurry! there is not much time.
Looking promising! Now to implement the UI bits.

No comments:

Post a Comment