Difference between revisions of "Chatbot"

From Second Life Wiki
Jump to navigation Jump to search
m (spell check to STATUS_PHYSICS not STATUS_PHYICS)
m (teleport back down the Z axis so that how the box falls later doesn't override llSetLocalRot)
Line 20: Line 20:
/7 llSetStatus(STATUS_PHYSICS, FALSE);
/7 llSetStatus(STATUS_PHYSICS, FALSE);
/7 llSetPos("+"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport up the Z axis
/7 llSetPos("+"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport up the Z axis
/7 llSetPos("-"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport back down the Z axis
/7 llSetLocalRot(llEuler2Rot(<0.0, 0.0, PI_BY_TWO>))); // face one way
/7 llSetLocalRot(llEuler2Rot(<0.0, 0.0, PI_BY_TWO>))); // face one way
/7 llSetLocalRot(llEuler2Rot(ZERO_ROTATION)); // face another way
/7 llSetLocalRot(llEuler2Rot(ZERO_ROTATION)); // face another way
Line 188: Line 189:
{
{
     return [];
     return [];
}
// Aggregate and return the composite.
list composite(list values)
{
    integer depth = llGetListLength(values);
    if (depth == 3)
    {
        vector vec;
        vec.x = llList2Float(values, 0);
        vec.y = llList2Float(values, 1);
        vec.z = llList2Float(values, 2);
        return [vec];
    }
    if (depth == 4)
    {
        rotation rot;
        rot.x = llList2Float(values, 0);
        rot.y = llList2Float(values, 1);
        rot.z = llList2Float(values, 2);
        rot.s = llList2Float(values, 2);
        return [rot];
    }
    return [];
}
// Return a list of one entry per parameter
// with the extra entries of longer parameters moved to the end.
list pickEach(list entries)
{
    return entries; // FIXME
}
}


Line 226: Line 260:
         return quotient(ps);
         return quotient(ps);
     }
     }
//  else if ("%" == callable)
    else if ("%" == callable)
//  {
    {
//      return remainder(ps);
        return remainder(ps);
//  }
    }
    else if ("<>" == callable)
    {
        return composite(ps);
    }


     // Return a list of results to say callable meaningful and returns results
     // Return a list of results to say callable meaningful and returns results
Line 345: Line 383:
             integer first = llList2Integer(depths, -1);
             integer first = llList2Integer(depths, -1);
             depths = llList2List(depths, 0, -2); // pop the tail
             depths = llList2List(depths, 0, -2); // pop the tail


             // Pop the parameters
             // Pop the parameters
 
           
             integer beyond = llGetListLength(results);
             list entries = [];
             integer last = beyond - 1;
             integer last = llGetListLength(results) - 1;          
            list parameters = [];
             if (first <= last)
             if (first <= last)
             {
             {
                 parameters = llList2List(results, first, last);
                 entries = llList2List(results, first, last);
                 results = llList2List(results, 0, first - 1);
                 results = llList2List(results, 0, first - 1);
             }
             }


             // Join together the components of a vector
             // Choose the callable to receive the parameters


             if (word == ">")
            string callable = "<>";
             if (word == ")")
             {
             {
                 integer depth = llGetListLength(parameters);
                 callable = llList2String(results, -1);
                 if (depth == 3)
                 results = llList2List(results, 0, -2); // pop the tail
                {
                    vector vec;
                    vec.x = llList2Float(parameters, 0);
                    vec.y = llList2Float(parameters, 1);
                    vec.z = llList2Float(parameters, 2);
                    results += [vec];
                }
 
                // Join together the components of a rotation
 
                if (depth == 4)
                {
                    rotation rot;
                    rot.x = llList2Float(parameters, 0);
                    rot.y = llList2Float(parameters, 1);
                    rot.z = llList2Float(parameters, 2);
                    rot.s = llList2Float(parameters, 2);
                    results += [rot];
                }
             }
             }


             // Call with a list of parameters (of non-list types)
             // Call with a list of parameters


             else if (word == ")")
             list pickables = pickEach(entries);
            {
            results += resultOf(callable, pickables);
                string callable = llList2String(results, -1);
                results = llList2List(results, 0, -2); // pop the tail
                results += resultOf(callable, parameters);
            }
         }
         }


Line 402: Line 418:
                 if (word == (quote + quote))
                 if (word == (quote + quote))
                 {
                 {
                     results += "";
                     results += ""; // Push the empty string
                 }
                 }
                 else
                 else
                 {
                 {
                     results += llGetSubString(word, 1, -2);
                     results += llGetSubString(word, 1, -2); // Push a simply quoted string
                 }
                 }
             }
             }
             else
             else
             {
             {
                 results += llList2List(words, index, index);
                 results += llList2List(words, index, index); // Push an entry with its type
             }
             }
         }
         }

Revision as of 07:12, 6 September 2007

Compile and run the LSL you chat on a channel, faster than you can compile and run it thru the 2007-08 SL GUI.

Created by Ppaatt Lynagh. "Allow anyone to copy", "next owner can copy & modify", and "mark item for sale price L$0", are the rights on my copies of this script in SL, as of 2007-09-04.

The LSL commands you can chat to this script include the following demo of kicking a translucent, bouncing, spinning box:

/7 llSetColor(<0.3, 0.3, 0.3>, ALL_SIDES); // darken
/7 llSetColor(<1, 1, 1>, ALL_SIDES); // lighten
/7 llSetAlpha(0.7, ALL_SIDES); // make translucent
/7 llSetText("look at me green", <0.0, 1.0, 0.0>, 1.0); // label
/7 llSetText("look at me black", <0.0, 0.0, 0.0>, 1.0); // label differently
/7 llSetText("", <0.0, 0.0, 0.0>, 1.0); // do not label
/7 llSetStatus(STATUS_PHYSICS, FALSE);
/7 llSetPos("+"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport up the Z axis
/7 llSetPos("-"(llGetPos(), <0.0, 0.0, 2.1>)); // teleport back down the Z axis
/7 llSetLocalRot(llEuler2Rot(<0.0, 0.0, PI_BY_TWO>))); // face one way
/7 llSetLocalRot(llEuler2Rot(ZERO_ROTATION)); // face another way
/7 llSetStatus(STATUS_PHYSICS, TRUE);
/7 llSetBuoyancy(0.9); // bounce well, without floating
/7 llApplyImpulse(<0.0, 0.0, 1.0>, TRUE); // advance along the Z axis
/7 llApplyRotationalImpulse(<0.0, 0.0, 3.0>, TRUE); // yaw about the Z axis
/7 llSetStatus(STATUS_PHYSICS, FALSE); llSetStatus(STATUS_PHYSICS, TRUE); // zero rot inertia
/7 ZERO_VECTOR
/7 llGetAgentSize(llGetLinkKey(llGetNumberOfPrims())) // often not ZERO_VECTOR while avatar sits

The demo quoted above runs well as shown, if you run this script in the usual wood box built by default in the SL GUI (0.5 x 0.5 x 0.5 m). Try sending these commands to other objects to learn more of how the weight and shape of an object influence its response to commands that require STATUS_PHYSICS TRUE.

You should find it easy to add more words of the LSL language, and calls of your own functions, to this script. To keep this article short, this script understands only some of the library function names, the arithmetic operators, the code constant names, the data types, the control flow keywords, and the punctuation marks that may actually interest you.

We welcome you at the discussion tab. We talk of new uses found for this script, in particular new concise demoes under development.

Enjoy,

P.S. The code of the sample script follows:

// http://wiki.secondlife.com/wiki/Chatbot
//
// Compile and run the Lsl you type on a channel,
// faster than you can thru the 2007-08 SL GUI,
// thus "chat with your 'bot" while it runs this script.
//

// Choose one chat channel for hearing commands, echoing commands, and chatting results.

integer theChannel = 7;

// Chat back a copy of the meaningful or meaningless command, only on request.

integer theEchoPlease = TRUE;

// Describe the language of an LSL expression without infix operators.

string escape = "\\";
string quote = "\"";
list separators = ["\t", " ", ",", ";"];
list spacers = [quote, escape, "(", ")", "/", "<", ">", "[", "]"];

// List some frequently useful code values.

list theCodes = [

    TRUE, FALSE,

    PI,
    TWO_PI,
    PI_BY_TWO,
    DEG_TO_RAD,
    RAD_TO_DEG,
    SQRT2,

    NULL_KEY,
    ALL_SIDES,
    EOF,
    ZERO_VECTOR,
    ZERO_ROTATION,

    STATUS_PHYSICS,

    0
];

// List the name of each code.

list theCodenames = [

    "TRUE", "FALSE",

    "PI",
    "TWO_PI",
    "PI_BY_TWO",
    "DEG_TO_RAD",
    "RAD_TO_DEG",
    "SQRT2",

    "NULL_KEY",
    "ALL_SIDES",
    "EOF",
    "ZERO_VECTOR",
    "ZERO_ROTATION",

    "STATUS_PHYSICS",

    "0"
];

// Evaluate any one parameter.

list valueOf(string word)
{
    if (0 <= llSubStringIndex(word, "."))
    {
        return [(float) word];
    }
    else if (0 <= llSubStringIndex("0123456789", llGetSubString(word, 0, 0)))
    {
        return [(integer) word];
    }
    else
    {
        integer index = llListFindList(theCodenames, [word]);
        if (0 <= index)
        {
            return llList2List(theCodes, index, index);
        } else {
            return [word]; // unevaluated
        }
    }
}

// Add and return the sum.

list sum(list values)
{
    if (llGetListEntryType(values, 0) == TYPE_VECTOR)
    {
        return [llList2Vector(values, 0) + llList2Vector(values, 1)];
    }
    return [];
}

// Subtract and return the difference.

list difference(list values)
{
    if (llGetListEntryType(values, 0) == TYPE_VECTOR)
    {
        return [llList2Vector(values, 0) - llList2Vector(values, 1)];
    }
    return [];
}

// Multiply and return the product.

list product(list values)
{
    if (llGetListEntryType(values, 0) == TYPE_ROTATION)
    {
        return [llList2Rot(values, 0) * llList2Rot(values, 1)];
    }
    return [];
}

// Divide and return the quotient.

list quotient(list values)
{
    if (llGetListEntryType(values, 0) == TYPE_ROTATION)
    {
        return [llList2Rot(values, 0) / llList2Rot(values, 1)];
    }
    return [];
}

// Divide and return the remainder.

list remainder(list values)
{
    return [];
}

// Aggregate and return the composite.

list composite(list values)
{
    integer depth = llGetListLength(values);
    if (depth == 3)
    {
        vector vec;
        vec.x = llList2Float(values, 0);
        vec.y = llList2Float(values, 1);
        vec.z = llList2Float(values, 2);
        return [vec];
    }
    if (depth == 4)
    {
        rotation rot;
        rot.x = llList2Float(values, 0);
        rot.y = llList2Float(values, 1);
        rot.z = llList2Float(values, 2);
        rot.s = llList2Float(values, 2);
        return [rot];
    }
    return [];
}

// Return a list of one entry per parameter
// with the extra entries of longer parameters moved to the end.

list pickEach(list entries)
{
    return entries; // FIXME
}

// Pass the parameters to the named routine.
// Return a list of results.
// Return empty if no result, if empty list returned, or if callable meaningless.

list resultOf(string callable, list parameters)
{

    // Abbreviate the word "parameters"

    list ps = parameters;

    // Trace every call, even when the callable is meaningless

    if (theEchoPlease)
    {
        llOwnerSay(callable + "(" + llList2CSV(ps) + ")");
    }

    // Accept verbose prefix arithmetic such as { "+"(vec1, vec2) }

    if ("+" == callable)
    {
        return sum(ps);
    }
    else if ("-" == callable)
    {
        return difference(ps);
    }
    else if ("*" == callable)
    {
        return product(ps);
    }
    else if ("/" == callable)
    {
        return quotient(ps);
    }
    else if ("%" == callable)
    {
        return remainder(ps);
    }
    else if ("<>" == callable)
    {
        return composite(ps);
    }

    // Return a list of results to say callable meaningful and returns results
    // Or fall thru to say callable meaningful but returns no result

    if ("llApplyImpulse" == callable) // (force, local)
    {
        llApplyImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llApplyRotationalImpulse" == callable) // (force, local)
    {
        llApplyRotationalImpulse(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llEuler2Rot" == callable) // (v)
    {
        return [llEuler2Rot(llList2Vector(ps, 0))];
    }
    else if ("llGetAgentSize" == callable) // (id)
    {
        return [llGetAgentSize(llList2Key(ps, 0))];
    }
    else if ("llGetLinkKey" == callable) // (linknum)
    {
        return [llGetLinkKey(llList2Integer(ps, 0))];
    }
    else if ("llGetNumberOfPrims" == callable) // ()
    {
        return [llGetNumberOfPrims()];
    }
    else if ("llGetPos" == callable) // ()
    {
        return [llGetPos()];
    }
    else if ("llGetLocalRot" == callable) // ()
    {
        return [llGetLocalRot()];
    }
    else if ("llGetlRot" == callable) // ()
    {
        return [llGetRot()];
    }
    else if ("llRot2Euler" == callable) // (q)
    {
        return [llRot2Euler(llList2Rot(ps, 0))];
    }
    else if ("llSetAlpha" == callable) // (alpha, face)
    {
        llSetAlpha(llList2Float(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llSetBuoyancy" == callable) // (buoyancy)
    {
        llSetBuoyancy(llList2Float(ps, 0));
    }
    else if ("llSetColor" == callable) // (color, face)
    {
        llSetColor(llList2Vector(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llSetLocalRot" == callable) // (rot)
    {
        llSetLocalRot(llList2Rot(ps, 0));
    }
    else if ("llSetPos" == callable) // (pos)
    {
        llSetPos(llList2Vector(ps, 0));
    }
    else if ("llSetRot" == callable) // (rot)
    {
        llSetRot(llList2Rot(ps, 0));
    }
    else if ("llSetStatus" == callable) // (status, value)
    {
        llSetStatus(llList2Integer(ps, 0), llList2Integer(ps, 1));
    }
    else if ("llSetText" == callable) // (text, color, alpha)
    {
        llSetText(llList2String(ps, 0), llList2Vector(ps, 1), llList2Float(ps, 2));
    }
    
    // Return an empty result if callable meaningless or returns no result
    
    return [];
}

// Interpret one list of words.

list callEach(list words)
{

    // Begin with almost nothing

    list results = [""];
    list depths = [0];

    // Take each action in order

    integer lenWords = llGetListLength(words);
    integer index;
    for (index = 0; index < lenWords; ++index)
    {
        string word = llList2String(words, index);
        
        // Count results of "( ... )" or of "< ... >"
        
        if ((word == "(") || (word == "<"))
        {
            integer beyond = llGetListLength(results);
            depths += [beyond]; // push the depth
        }

        else if ((word == ")") || (word == ">"))
        {

            // Pop the depth

            integer first = llList2Integer(depths, -1);
            depths = llList2List(depths, 0, -2); // pop the tail


            // Pop the parameters
            
            list entries = [];
            integer last = llGetListLength(results) - 1;            
            if (first <= last)
            {
                entries = llList2List(results, first, last);
                results = llList2List(results, 0, first - 1);
            }

            // Choose the callable to receive the parameters

            string callable = "<>";
            if (word == ")")
            {
                callable = llList2String(results, -1);
                results = llList2List(results, 0, -2); // pop the tail
            }

            // Call with a list of parameters

            list pickables = pickEach(entries);
            results += resultOf(callable, pickables);
        }

        // Push any other word as a parameter

        else
        {
            if (llGetSubString(word, 0, 0) == quote)
            {
                if (word == (quote + quote))
                {
                    results += ""; // Push the empty string
                }
                else
                {
                    results += llGetSubString(word, 1, -2); // Push a simply quoted string
                }
            }
            else
            {
                results += llList2List(words, index, index); // Push an entry with its type
            }
        }
    }

    // Succeed

    if (llGetListLength(results) <= 1)
    {
        return [];
    }
    return llList2List(results, 1, -1); // pop the head (only if head is not tail)
}

// Divide the command into words.
// Take care to join together the words of each quotation.
// Return a list of the words of the command.

list valueEach(list words)
{

    // Begin with nothing

    list values = [];
    
    // Divide the command into words, but also shatter quotations

    integer lenWords = llGetListLength(words);
    
    // Consider each word or word of a quotation

    integer index = 0;
    while (index < lenWords)
    {
        string word = llList2String(words, index++);
        
        // Join together the words of a quotation

        if (word == quote)
        {
            string value = "";
            do
            {
                word = llList2String(words, index++);
                if (word != quote)
                {
                    value += word;
                }
            } while ((word != quote) && (index < lenWords));
            
            // Quote the quotation simply, so the quotation is never found in spacers
            
            values += quote + value + quote;
        }

        // Discard slash slash commentary

        else if ((word == "/") && (llList2String(words, index) == "/"))
        {
            return values;
        }

        // Discard separators
        
        else if ((0 <= llListFindList(separators, [word])) || (word == ""))
        {
            ;
        }

        // Keep spacers
        
        else if (0 <= llListFindList(spacers, [word]))
        {
            values += word;
        }

        // Evaluate other words
        
        else
        {
            values += valueOf(word);
        }
    }    

    // Succeed

    return values;
}

// Divide a string into words,
// by returning each of the spacers as a word
// and return as a word each run of chars between words.

list separateEach(string source, list spacers)
{
    list outputs = [source];
    
    // Pass never more than 8 spacers to llParseString2List
    
    integer lenSpacers = llGetListLength(spacers);
    integer ss;
    for (ss = 0; ss < lenSpacers; ss += 8)
    {
        list someSpacers = llList2List(spacers, ss, ss + 7);
        
        // Call llParseString2List again for each result so far
                
        list inputs = outputs;
        outputs = [];
        
        integer lenInputs = llGetListLength(inputs);
        integer ii;
        for (ii = 0; ii < lenInputs; ++ii)
        {
            string input = llList2String(inputs, ii);
            outputs += llParseString2List(input, [], someSpacers);
        }
    }
    
    // Succeed
    
    return outputs;
}

// Hear and echo and obey the chat of the owner at the channel.

default
{
    state_entry()
    {
        llListen(theChannel, "", llGetOwner(), "");
    }
    listen(integer channel, string name, key id, string message)
    {

        // Compile and run

//      llOwnerSay(message);

        list words = separateEach(message, separators + spacers);
//      llOwnerSay((string) llGetListLength(words) + ": " + llList2CSV(words));

        list values = valueEach(words);
//      llOwnerSay((string) llGetListLength(values) + ": " + llList2CSV(values));

        list results = callEach(values);
//      llOwnerSay((string) llGetListLength(results) + ": " + llList2CSV(results));

        // Chat back the results, if not empty

        if (results != [])
        {
            llOwnerSay(llList2CSV(results));
        }
    }
}