About

The script shows a simple example of how you could make Corrade follow you on the entire grid. This involves checking whether Corrade is online, then checking if Corrade is in the current region and teleporting Corrade and finally sending move commands such that Corrade will always be in the vicinity of your avatar. The script can be worn as an attachment or as a HUD.

Marketplace Item

Applications

With the proper set of scripts, Corrade can be a very good pet, following you on the entire grid and, with the help of a HUD it can be made to perform different actions. Corrade's complement of commands allow it to perform a lot of actions that are quite typical of any human-agent on the grid (sitting down, touching objects, talking in chats, and so on…).

Limitations

Regions that have teleport routing set-up and for which you are not the owner will not allow Corrade to teleport. This is a restriction imposed by the server and cannot be circumvented. In such cases, Corrade will stay in the landing area but you are free to send a teleport manually that will move the bot closer to you.

Instructions

Create a new primitive and place a notecard named configuration inside that primitive with the following contents:

File: http://svn.grimore.org/corrade-lsl-templates/source/grid-follow/configuration.txt -

####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "0f4de75b-8292-4a12-86f2-31a55879e161"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# The range in meters to follow the wearer of the follower at.
range = 5
 
####################### END CONFIGURATION ###################################

and edit the configuration keys accordingly to match your Corrade configuration. After that, drop the script from the code section inside the primitive. You can wear the primitive such that Corrade will teleport to you when you change simulators.

Code

File: http://svn.grimore.org/corrade-lsl-templates/source/grid-follow/grid-follow.lsl -

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string k, string data) {
    if(llStringLength(data) == 0) return "";
    if(llStringLength(k) == 0) return "";
    list a = llParseStringKeepNulls(data, ["&", "="], []);
    integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
    if(i != -1) return llList2String(a, 2*i+1);
    return "";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueEncode(list data) {
    list k = llList2ListStrided(data, 0, -1, 2);
    list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
    data = [];
    do {
        data += llList2String(k, 0) + "=" + llList2String(v, 0);
        k = llDeleteSubList(k, 0, 0);
        v = llDeleteSubList(v, 0, 0);
    } while(llGetListLength(k) != 0);
    return llDumpList2String(data, "&");
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasCirclePoint(float radius) {
    float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
    float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
    if(llPow(x,2) + llPow(y,2) <= llPow(radius,2))
        return <x, y, 0>;
    return wasCirclePoint(radius);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasIsAvatarInSensorRange(key avatar) {
    return llListFindList(
        llGetAgentList(
            AGENT_LIST_REGION,
            []
        ),
        (list)((key)avatar)
    ) != -1 &&
        llVecDist(
            llGetPos(),
            llList2Vector(
                llGetObjectDetails(
                    avatar,
                    [OBJECT_POS]
                ),
            0
        )
    ) <= 96;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
// escapes a string in conformance with RFC1738
string wasURLEscape(string i) {
    string o = "";
    do {
        string c = llGetSubString(i, 0, 0);
        i = llDeleteSubString(i, 0, 0);
        if(c == "") jump continue;
        if(c == " ") {
            o += "+";
            jump continue;
        }
        if(c == "\n") {
            o += "%0D" + llEscapeURL(c);
            jump continue;
        }
        o += llEscapeURL(c);
@continue;
    } while(i != "");
    return o;
}
 
// corrade data
string CORRADE = "";
string GROUP = "";
string PASSWORD = "";
integer RANGE = 5;
 
// for holding the callback URL
string callback = "";
 
// for notecard reading
integer line = 0;
 
// key-value data will be read into this list
list tuples = [];
 
default {
    state_entry() {
        // set color for button
        llSetColor(<1,1,0>, ALL_SIDES);
        if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
            llOwnerSay("Sorry, could not find an inventory notecard.");
            return;
        }
        // DEBUG
        llOwnerSay("Reading configuration file...");
        llGetNotecardLine("configuration", line);
    }
    dataserver(key id, string data) {
        if(data == EOF) {
            // invariant, length(tuples) % 2 == 0
            if(llGetListLength(tuples) % 2 != 0) {
                llOwnerSay("Error in configuration notecard.");
                return;
            }
            CORRADE = llList2String(
                          tuples,
                          llListFindList(
                              tuples,
                              [
                                  "corrade"
                              ]
                          )
                      +1);
            if(CORRADE == "") {
                llOwnerSay("Error in configuration notecard: corrade");
                return;
            }
            GROUP = llList2String(
                          tuples,
                          llListFindList(
                              tuples,
                              [
                                  "group"
                              ]
                          )
                      +1);
            if(GROUP == "") {
                llOwnerSay("Error in configuration notecard: password");
                return;
            }
            PASSWORD = llList2String(
                          tuples,
                          llListFindList(
                              tuples,
                              [
                                  "password"
                              ]
                          )
                      +1);
            if(GROUP == "") {
                llOwnerSay("Error in configuration notecard: group");
                return;
            }
            RANGE = llList2Integer(
                          tuples,
                          llListFindList(
                              tuples,
                              [
                                  "range"
                              ]
                          )
                      +1);
            if(RANGE == 0) {
                llOwnerSay("Error in configuration notecard: range");
                return;
            }
            // DEBUG
            llOwnerSay("Read configuration file...");
            state url;
        }
        if(data == "") jump continue;
        integer i = llSubStringIndex(data, "#");
        if(i != -1) data = llDeleteSubString(data, i, -1);
        list o = llParseString2List(data, ["="], []);
        // get rid of starting and ending quotes
        string k = llDumpList2String(
            llParseString2List(
                llStringTrim(
                    llList2String(
                        o,
                        0
                    ),
                STRING_TRIM),
            ["\""], []
        ), "\"");
        string v = llDumpList2String(
            llParseString2List(
                llStringTrim(
                    llList2String(
                        o,
                        1
                    ),
                STRING_TRIM),
            ["\""], []
        ), "\"");
        if(k == "" || v == "") jump continue;
        tuples += k;
        tuples += v;
@continue;
        llGetNotecardLine("configuration", ++line);
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}
 
state url {
    state_entry() {
        // DEBUG
        llOwnerSay("Requesting URL...");
        llRequestURL();
    }
    http_request(key id, string method, string body) {
        if(method != URL_REQUEST_GRANTED) return;
        callback = body;
        // DEBUG
        llOwnerSay("Got URL...");
        state off;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}
 
state off {
    state_entry() {
        // set color for button
        llSetColor(<1,0,0>, ALL_SIDES);
    }
    touch_start(integer num) {
        state on;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}
 
state on {
    state_entry() {
        // set color for button
        llSetColor(<0,1,0>, ALL_SIDES);
        // if Corrade is in-range then just follow
        if(wasIsAvatarInSensorRange(CORRADE)) {
            state follow;
        }
        // DEBUG
        llOwnerSay("Detecting if Corrade is online...");
        llSetTimerEvent(5);
    }
    timer() {
        llRequestAgentData(CORRADE, DATA_ONLINE);
    }
    dataserver(key id, string data) {
        if(data != "1") {
            // DEBUG
            llOwnerSay("Corrade is not online, sleeping...");
            llSetTimerEvent(30);
            return;
        }
        llSensorRepeat("", CORRADE, AGENT, RANGE, TWO_PI, 5);
    }
    no_sensor() {
        // DEBUG
        llOwnerSay("Teleporting Corrade...");
        llInstantMessage(CORRADE,
            wasKeyValueEncode(
                [
                    "command", "teleport",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    "entity", "region",
                    "region", wasURLEscape(llGetRegionName()),
                    "position", llGetPos() + wasCirclePoint((integer)RANGE),
                    "callback", callback
                ]
            )
        );
    }
    sensor(integer num) {
        state follow;
    }
    http_request(key id, string method, string body) {
        llHTTPResponse(id, 200, "OK");
        if(wasKeyValueGet("command", body) == "teleport") {
            integer success = wasKeyValueGet("success", body) == "True";
            if(success) {
                 // DEBUG
                llOwnerSay("Teleport succeeded...");
                state follow;
            }
            // DEBUG
            llOwnerSay("Teleport failed...");
            return;
        }
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
state follow {
    state_entry() {
        // DEBUG
        llOwnerSay("In follow state...");
        // check every second whether Corrade is online
        llRequestAgentData(CORRADE, DATA_ONLINE);
    }
    touch_start(integer num) {
        state off;
    }
    dataserver(key id, string data) {
        // if Corrade is not online
        if(data != "1") state on;
        // Corrade is online, so attempt to dectect
        llSensorRepeat("", CORRADE, AGENT, RANGE, TWO_PI, 1);
    }
    no_sensor() {
        // check if Corrade is in range, and if not, start detecting
        if(!wasIsAvatarInSensorRange(CORRADE)) {
            state on;
        }
        // Corrade is in sensor range, so execute move.
        llInstantMessage(CORRADE,
            wasKeyValueEncode(
                [
                    "command", "walkto",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    // move in a radius around the current primitive.
                    "position", llGetPos() + wasCirclePoint((integer)RANGE),
                    "vicinity", 1,
                    "timeout", 2500
                ]
            )
        );
        llSensorRepeat("", CORRADE, AGENT, (integer)RANGE, TWO_PI, 1);
        llRequestAgentData(CORRADE, DATA_ONLINE);
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}

Index


secondlife/scripted_agents/corrade/projects/in_world/grid_follower.txt · Last modified: 2022/11/24 07:45 by 127.0.0.1

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.