About

This device is meant to run in conjunction with Corrade and enables the owner to scan remote regions for various region metrics such as the ones presented by the SecondLife land tools (agents, last lag, time dilation, FPS, etc.).

Marketplace Item

Configuration

The configuration for the script is stored on a notecard named configuration that is stored in the same primitive as the script and the regions notecard. The contents of the configuration notecard are the following:

####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "e2187210-79de-4b26-8e46-41e23be0c487"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
####################### END CONFIGURATION ###################################

and must be suited to fit the Corrade configuration.

Regions

An additional notecard named regions must be placed in the same primitive that contains regions line-by-line that Corrade is supposed to scan. An example of the regions notecard is the following:

Puguet Sound
Milkyway Island

Note that an empty line is needed at the end of the notecard.

There is no way to specify the landing point on the destination region and this is due to region statistics applying to all parcels within the region such that the landing point is irrelevant. If need be, due to the teleport command providing a parameter as a landing point, the script could be edited to additionally allow the user to specify landing points.

Code

///////////////////////////////////////////////////////////////////////////
//  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) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasListCountExclude(list input, list exclude) {
    if(llGetListLength(input) == 0) return 0;
    if(llListFindList(exclude, (list)llList2String(input, 0)) == -1)
        return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
    return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasListToCSV(list l) {
    list v = [];
    do {
        string a = llDumpList2String(
            llParseStringKeepNulls(
                llList2String(
                    l,
                    0
                ),
                ["\""],
                []
            ),
            "\"\""
        );
        if(llParseStringKeepNulls(a, [" ", ",", "\n"], []) != (list) a)
            a = "\"" + a + "\"";
        v += a;
        l = llDeleteSubList(l, 0, 0);
    } while(l != []);
    return llDumpList2String(v, ",");
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasCSVToList(string csv) {
    list l = [];
    list s = [];
    string m = "";
    do {
        string a = llGetSubString(csv, 0, 0);
        csv = llDeleteSubString(csv, 0, 0);
        if(a == ",") {
            if(llList2String(s, -1) != "\"") {
                l += m;
                m = "";
                jump continue;
            }
            m += a;
            jump continue;
        }
        if(a == "\"" && llGetSubString(csv, 0, 0) == a) {
            m += a;
            csv = llDeleteSubString(csv, 0, 0);
            jump continue;
        }
        if(a == "\"") {
            if(llList2String(s, -1) != a) {
                s += a;
                jump continue;
            }
            s = llDeleteSubList(s, -1, -1);
            jump continue;
        }
        m += a;
@continue;
    } while(csv != "");
    // invariant: length(s) = 0
    return l + m;
}
 
///////////////////////////////////////////////////////////////////////////
//    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;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
// unescapes a string in conformance with RFC1738
string wasURLUnescape(string i) {
    return llUnescapeURL(
        llDumpList2String(
            llParseString2List(
                llDumpList2String(
                    llParseString2List(
                        i,
                        ["+"],
                        []
                    ),
                    " "
                ),
                ["%0D%0A"],
                []
            ),
            "\n"
        )
    );
}
 
// corrade data
string CORRADE = "";
string GROUP = "";
string PASSWORD = "";
float WAIT = 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 = [];
// regions will be stored here
list regions = [];
string region = "";
 
default {
    state_entry() {
        if(llGetInventoryType("configuration") != INVENTORY_NOTECARD) {
            llOwnerSay("Sorry, could not find a configuration 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: group");
                return;
            }
            PASSWORD = llList2String(
                tuples,
                llListFindList(
                    tuples,
                    [
                        "password"
                    ]
                )
                +1
            );
            if(PASSWORD == "") {
                llOwnerSay("Error in configuration notecard: password");
                return;
            }
            WAIT = (float)llList2String(
                tuples,
                llListFindList(
                    tuples,
                    [
                        "wait"
                    ]
                )
                +1
            );
            if(WAIT == 0) {
                llOwnerSay("Error in configuration notecard: wait");
                return;
            }
            // DEBUG
            llOwnerSay("Read configuration notecard...");
            state read;
        }
        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 read {
    state_entry() {
        if(llGetInventoryType("regions") != INVENTORY_NOTECARD) {
            llOwnerSay("Sorry, could not find a regions inventory notecard.");
            return;
        }
        // DEBUG
        llOwnerSay("Reading regions notecard...");
        line = 0;
        llGetNotecardLine("regions", line);
    }
    dataserver(key id, string data) {
        if(data == EOF) {
            // DEBUG
            llOwnerSay("Read regions notcard...");
            state url;
        }
        if(data == "") jump continue;
        regions += data;
@continue;
        llGetNotecardLine("regions", ++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 detect;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}
 
state detect {
    state_entry() {
        // DEBUG
        llOwnerSay("Detecting if Corrade is online...");
        llSetTimerEvent(5);
    }
    timer() {
        llRequestAgentData((key)CORRADE, DATA_ONLINE);
    }
    dataserver(key id, string data) {
        if(data != "1") {
            // DEBUG
            llOwnerSay("Corrade is not online, sleeping...");
            llSetTimerEvent(30);
            return;
        }
        state notify;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}
 
state notify {
    state_entry() {
        // Timeout in 60s.
        llSetTimerEvent(60);
 
        // DEBUG
        llOwnerSay("Binding to the CAPS notification...");
        llInstantMessage(
            (key)CORRADE,
            wasKeyValueEncode(
                [
                    "command", "notify",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    "action", "set",
                    "type", "CAPS",
                    "URL", wasURLEscape(callback),
                    "callback", wasURLEscape(callback)
                ]
            )
        );
    }
    http_request(key id, string method, string body) {
        llHTTPResponse(id, 200, "OK");
        if(wasKeyValueGet("command", body) != "notify") return;
        if(wasKeyValueGet("success", body) != "True") {
            // DEBUG
            llOwnerSay("Failed to bind to the CAPS notification...");
            llResetScript();
        }
        // DEBUG
        llOwnerSay("CAPS notification installed...");
        state teleport;
    }
    timer() {
        // DEBUG
        llOwnerSay("Timeout binding to the CAPS notifications...");
 
        llResetScript();
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}
 
state teleport {
    state_entry() {
        // Emergency timeout
        llSetTimerEvent(60);
 
        // Check that Corrade is online.
        llSensorRepeat("", NULL_KEY, AGENT, 0.1, 0.1, 5);
 
        // Shuffle the regions and grab the next region.
        region = llList2String(regions, 0);
        regions = llDeleteSubList(regions, 0, 0);
        regions += region;
 
        // The selected region is the current region so reshufle.
        if(region == llGetRegionName()) {
            // DEBUG
            llOwnerSay("Already on current region " + region + ", trying the next region.");
            region = llList2String(regions, 0);
            regions = llDeleteSubList(regions, 0, 0);
            regions += region;
        }
 
        // DEBUG
        llOwnerSay("Teleporting to: " + region);
 
        llInstantMessage(
            (key)CORRADE,
            wasKeyValueEncode(
                [
                    "command", "teleport",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    "entity", "region",
                    "region", wasURLEscape(region),
                    "position", wasURLEscape((string)<128, 128, 50>),
                    "callback", wasURLEscape(callback)
                ]
            )
        );
    }
    http_request(key id, string method, string body) {
        llHTTPResponse(id, 200, "OK");
        if(wasKeyValueGet("command", body) == "teleport") {
            if(wasKeyValueGet("success", body) == "True") {
                // DEBUG
                llOwnerSay("Teleported successfully to: " + region + " and waiting for capabiltiies...");
                return;
            }
            // DEBUG
            llOwnerSay("Failed to teleport to " + region + " due to: " +
                wasURLUnescape(
                    wasKeyValueGet(
                        "error",
                        body
                    )
                )
            );
            // Jump to trampoline for re-entry.
            state teleport_trampoline;
        }
        if(wasKeyValueGet("notification", body) == "CAPS") {
            string capsRegion = wasURLUnescape(
                wasKeyValueGet(
                    "region",
                    body
                )
            );
            string capsAction = wasURLUnescape(
                wasKeyValueGet(
                    "action",
                    body
                )
            );
 
            if(capsRegion == region && capsAction == "start") {
                llOwnerSay("Capabiltiies for region " + region + " successfully connected.");
                state stats_trampoline;
            }
        }
    }
    timer() {
        // DEBUG
        llOwnerSay("Timeout receiving capabilities, attempting emergency teleport...");
        state teleport_trampoline;
    }
    no_sensor() {
        llRequestAgentData((key)CORRADE, DATA_ONLINE);
    }
    dataserver(key id, string data) {
        if(data != "1") {
            // DEBUG
            llOwnerSay("Corrade is not online, sleeping...");
            state detect;
        }
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
state teleport_trampoline {
    state_entry() {
        // DEBUG
        llOwnerSay("Sleeping...");
        llSetTimerEvent(WAIT);
    }
    timer() {
        state teleport;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
state stats_trampoline {
    state_entry() {
        // DEBUG
        llOwnerSay("Sleeping...");
        llSetTimerEvent(1);
    }
    timer() {
        state stats;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
state stats {
    state_entry() {
        // Timeout in one minute.
        llSetTimerEvent(60);
 
        // Check that Corrade is online.
        llSensorRepeat("", NULL_KEY, AGENT, 0.1, 0.1, 5);
 
        // DEBUG
        llOwnerSay("Fetching region statistics...");
        llInstantMessage(
            (key)CORRADE,
            wasKeyValueEncode(
                [
                    "command", "getregiondata",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    "data", wasListToCSV([
                        // For a full list see: https://grimore.org/secondlife/scripted_agents/corrade/api/commands/getregiondata
                        "Stats.LastLag",
                        "Stats.Agents",
                        "Stats.Dilation",
                        "Stats.FPS",
                        "Stats.ActiveScripts",
                        "Stats.ScriptTime",
                        "Stats.Objects",
                        "Stats.PhysicsFPS",
                        "Stats.ScriptTime"
                    ]),
                    "callback", wasURLEscape(callback)
                ]
            )
        );
    }
    http_request(key id, string method, string body) {
        llHTTPResponse(id, 200, "OK");
        // Ignore CAPS notification here.
        if(wasKeyValueGet("notification", body) == "CAPS") {
            return;
        }
        if(wasKeyValueGet("command", body) != "getregiondata" ||
            wasKeyValueGet("success", body) != "True") {
            // DEBUG
            llOwnerSay("Failed to get stats for " + region + " due to: " +
                wasURLUnescape(
                    wasKeyValueGet(
                        "error",
                        body
                    )
                )
            );
            // Jump to trampoline for teleport.
            state teleport_trampoline;
        }
        // DEBUG
        llOwnerSay("Got stats for region: " + region);
        // Get the stats and unescape.
        list stat = wasCSVToList(
            wasURLUnescape(
                wasKeyValueGet(
                    "data",
                    body
                )
            )
        );
        llSetText("-:[ " + region + " ]:- \n" +
            // Show the stats in the overhead text.
            "Agents: " + llList2String(
                stat,
                llListFindList(
                    stat,
                    (list)"Stats.Agents"
                )+1
            ) + "\n" +
            "LastLag: " + llList2String(
                stat,
                llListFindList(
                    stat,
                    (list)"Stats.LastLag"
                )+1
            ) + "\n" +
            "Time Dilation: " + llList2String(
                stat,
                llListFindList(
                    stat,
                    (list)"Stats.Dilation"
                )+1
            ) + "\n" +
            "FPS: " + llList2String(
                stat,
                llListFindList(
                    stat,
                    (list)"Stats.FPS"
                )+1
            ) + "\n" +
            "Physics FPS: " + llList2String(
                stat,
                llListFindList(
                    stat,
                    (list)"Stats.PhysicsFPS"
                )+1
            ) + "\n" +
            "Scripts: " + llList2String(
                stat,
                llListFindList(
                    stat,
                    (list)"Stats.ActiveScripts"
                )+1
            ) + "\n" +
            "Script Time: " + llList2String(
                stat,
                llListFindList(
                    stat,
                    (list)"Stats.ScriptTime"
                )+1
            ) + "\n" +
            "Objects: " + llList2String(
                stat,
                llListFindList(
                    stat, (list)"Stats.Objects"
                )+1
            ),
            <1, 0, 0>,
            1.0
        );
        stat = [];
        state teleport_trampoline;
    }
    no_sensor() {
        llRequestAgentData((key)CORRADE, DATA_ONLINE);
    }
    dataserver(key id, string data) {
        if(data != "1") {
            // DEBUG
            llOwnerSay("Corrade is not online, sleeping...");
            state detect;
        }
    }
    timer() {
        state teleport_trampoline;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}

secondlife/scripted_agents/corrade/projects/in_world/remote_region_scanning.txt ยท Last modified: 2022/12/07 05:43 by office

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.