Don't click here unless you want to be banned.

LSL Wiki : LibraryConversationAI

HomePage :: PageIndex :: RecentChanges :: RecentlyCommented :: UserSettings :: You are crawl338.us.archive.org
A simple script to handle simple conversation trees. Example conversation tree notecard is below the script.
///////////////////////
// User Variables //
///////////////////////

// The name of the note with the conversation tree.
string note = "conversation";

// The following variable determines if conversations
// Are exclusive to the person who initiates them.
integer exclusive = TRUE; 

//////////////////////////
// System Variables //
//////////////////////////

// List layout
// How a line and conversation are arranged
integer STRIDE = 5;     // How many parts to a line
integer STATE = 0;      // The state of this command
integer NEXTSTATE = 1;  // The next state after this command
integer TRIGGER = 2;    // The trigger string
integer MESSAGE = 3;    // The text message
integer COMMAND = 4;    // The link command

// The current section of the notecard.
list currTriggers;
list currMessages;
list currNextStates;
list currCommands;

// The entire contents of the notecard.  Could get large.
// state, goto, trigger, message, command
list conversation;

// Others
list tempL;
integer i;
integer j;
integer l;
integer max;
key k;
key user = NULL_KEY;

/////////////////
// Functions //
/////////////////

// Replaces all occurances of find in src with rep
string replace(string src, string find, string rep)
{
    // Save our selves some work in an easy double edge case
    if (src == find) return rep;
    
    // Prevent infinite loops when find is in rep
    else if (llSubStringIndex(rep,find) != -1) return src;
    
    // Look for find in src
    l = llSubStringIndex(src,find);
    
    // While there are still find in src
    while (l != -1)
    {
        // It is at the begning of src
        if (l == 0)
            src = rep + llGetSubString(src,llStringLength(find),- 1);

        // It is at the end of src
        else if (l == llStringLength(src) - llStringLength(find))
            src = llGetSubString(src,0,0 - llStringLength(find) - 1) + rep;
        
        // It must be in the middle of src
        else
            src = llGetSubString(src,0,l - 1) + rep + 
                    llGetSubString(src,l + llStringLength(find),-1);
        
        // See if there are more
        l = llSubStringIndex(src,find);
    }
    
    return src;
}

// Set the conversation state.
// Note this is a 'fake' state, not an LSL state.
// Conversation is assumed to be sorted by STATE
integer setState(integer s)
{
    // Grab a list of the states
    tempL = llList2ListStrided(conversation,0,-1,STRIDE);
    
    // Find the first occurance of the state s
    i = llListFindList(tempL,[(string)s]);
    
    // If the state doesn't exist, go to the zero state
    if (i == -1)
    {
        i = 0;
        s = 0;
    }
    
    // Find the first occurance of the state s + 1
    j = llListFindList(tempL,[(string)(s + 1)]);
    
    // If there is no next state, must be at the end of the list
    if (j == -1) j = 0;
    // We want through the last of s, which is right before s + 1
    j = j - 1;

    // Convert i and j to locations in conversation
    i = i * STRIDE;
    j = j * STRIDE;
    
    // Pull the chunks out of conversation into their own sections.
    currTriggers = llList2ListStrided(
                        llList2List(conversation,
                                    i + TRIGGER,
                                    j + TRIGGER),
                        0,-1,STRIDE);
                        
    currNextStates = llList2ListStrided(
                        llList2List(conversation,
                                    i + NEXTSTATE,
                                    j + NEXTSTATE),
                        0,-1,STRIDE);
                        
    currMessages = llList2ListStrided(
                        llList2List(conversation,
                                    i + MESSAGE,
                                    j + MESSAGE),
                        0,-1,STRIDE);
                        
    currCommands= llList2ListStrided(
                        llList2List(conversation,
                                    i + COMMAND,
                                    j + COMMAND),
                        0,-1,STRIDE);
                        
    return s;
}

//////////////
// States //
/////////////
default
{
    state_entry()
    {
        // We listen to everything
        llListen(0,"","","");
        
        llWhisper(0,"...setting up system...");
        i = 0;
        conversation = [];
        k = llGetNotecardLine(note,i++);
    }

    listen(integer chan, string name, key id, string mes)
    {
        if ( (exclusive && ( user == NULL_KEY || user == id)) || !exclusive)
        {
            // Cycle through every current trigger
            max = llGetListLength(currTriggers);
            for (i=0;i<max;i++)
            {
                // Look for any occurance of trigger in the line said
                j = llSubStringIndex(mes,llList2String(currTriggers,i));
                
                // We found one!
                if (j != -1)
                {
                    // Whisper the response, replacing -@- with players name.
                    llWhisper(0,replace(llList2String(currMessages,i),"-@-",name));
                    
                    // If there is a link message command, give it.
                    if(llList2String(currCommands,i) != "NULL")
                    {
                        llMessageLinked(LINK_SET,
                            llList2Integer(currNextStates,i),   // Next State
                            llList2String(currCommands,i),      // Command
                            id);                                // Speaker
                    }
                    
                    // Set the next state and user.
                    if ( setState((integer)llList2String(currNextStates,i)) ) user = id;
                    else user = NULL_KEY;
                    
                    // Exit early since we found a response.
                    return;
                }
            }
        }
    }
    
    // Detect a change to trigger a reset of the conversation
    // This could be replaced by another reset mechanism
    changed(integer change)
    {
        if (change & CHANGED_INVENTORY)
        {
            llResetScript();
        }
    }
    
    // We stuff every line in the notecard into
    // conversation with the exception of comments
    dataserver(key query, string data)
    {
        if(query == k)
        {
            k = NULL_KEY;
            
            // Done reading the card
            if (data == EOF)
            {
                // Sort in chunks of STRIDE by STATE
                llListSort(conversation,STRIDE,TRUE);
                
                // All conversations start in state 0
                setState(0);
                
                // Yay, setup is done.
                llWhisper(0,"...setup complete...[" + 
                    (string)(llGetListLength(conversation) / STRIDE) + 
                    " lines :: " + (string)llGetFreeMemory() + 
                    "b free]");
            }
            
            // Ignore blank lines and comments
            else if (llGetSubString(data,0,1) == "//" || data == "")
            {
                k = llGetNotecardLine(note,i++);
            }
            
            // A 'real' line
            else 
            {
                // Parse the line to a list
                tempL = llParseString2List(data,["-=-"],[]);
                
                // If this line is the correct length
                if (llGetListLength(tempL) == STRIDE)
                {
                    //llWhisper(0," -=- " + llList2CSV(tempL));
                    conversation = conversation + tempL;
                    k = llGetNotecardLine(note,i++);
                }
                
                // If it is short, we assume no link message
                else if (llGetListLength(tempL) == STRIDE - 1) // No Link Message
                {
                    conversation = conversation + tempL + ["NULL"] ;
                    k = llGetNotecardLine(note,i++);
                }
                
                // The line is not formatted right.
                else
                {
                    llWhisper(0,"ERROR: line " + 
                        (string)(i - 1) + ", [" + data + "]");
                }
            }
        }
    }
}

Here is an example conversation notecard. It goes on a notecard named "conversation" in the same object as the above script. This note will cause a link message to be sent at the begning and end of the conversation - for simple conversations this is totally unnecesary.
// State, Goto State, Trigger, Message
// This is a response that will never leave the 0 level, you can ask this as much as you want
0-=-0-=-Are you a computer?-=-Yes, I am!

// This starts an actual conversation, and moves to the next conversation level
0-=-1-=-Hello-=-Hi, -@-!-=-StartingConversation

// This is the next level of conversation
// The AI will only respond to these after someone says 'Hello'
1-=-1-=-What color is the sky?-=-Why, -@-, do you know, I think it's blue!

// This will end the conversation by going back to the 0 level
1-=-0-=-Bye-=-Ok, -@-!-=-EndingConversation

Another approach to scripting natural language AI and natural language interaction with commands is through an AIML like markup language. An example of such a markup would be lindenAIML (see LibraryLindenAIML).

Script Library | Examples
There is one comment on this page. [Display comments/form]