Conversation AI
De DigiWiki.
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).