About

This template is an automated hunt system that demonstrates various interactions with the Corrade scripting bot.

Demonstration

Marketplace Item

Requirements

The script requires the following Corrade permissions:

  • movement
  • notifications
  • interact
  • inventory
  • economy

to be enabled.

Also, the following notifications:

  • permission

must be enabled.

The script assumes that the hunt item requests debit permission from the owner and the hunt item must be in Corrade's inventory. The marketplace item contains a copy of the lootable coins bag that must be traceried to the bot's inventory.

Configuration

Along with the script, a notecard called configuration must be created and placed in the same primitive as the script. A sample configuration is the following:

######################### START CONFIGURATION #############################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "a6c43ad8-9086-46ee-9683-76e047062f05"
 
# 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 hunt item to rez.
item = "[WaS-K] Coin Bag"
 
# At each of these points of interest (POI), Corrade will rez a hunt item.
# The POIs must be named in sequence, ie: POI_1, POI_2, etc...  and you can 
# add as many POIs as you like.
POI_1 = <166.024597, 145.024246, 3455.350342>
POI_2 = <155.617996, 144.363434, 3455.368896>
POI_3 = <160.74292, 155.446198, 3455.368896>
 
######################### END CONFIGURATION ###############################

Code

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: CC BY 2.0      //
///////////////////////////////////////////////////////////////////////////
//
// This is an automated hunt system template that illustrates various 
// Corrade commands. You can find out more about the Corrade bot by 
// following the URL: http://was.fm/secondlife/scripted_agents/corrade
//
// This script requires the following Corrade permissions:
//   - movement
//   - notifications
//   - interact
//   - inventory
//   - economy
// It also requires the following Corrade notifications:
//   - permission
//
// The template uses the "[WaS-K] Coin Bag" hunt item and Corrade must have 
// the "[WaS-K] Coin Bag" object in its inventory. Other hunt objects are 
// possible by setting "item" in the "configuration" notecard but the item
// must be sent to Corrade such that it is in the bot's inventory.
// 
// The "configuration" notecard inside the primitive must be changed to
// reflect your settings.
//
// In case of panic, please see the full instructions on the project page:
// http://grimore.org/secondlife/scripted_agents/corrade/projects/in_world/automated_hunt_system
// or ask for help in the [Wizardry and Steamworks]:Support group or contact
// Kira Komarov in-world directly.
//
// This script works together with a "configuration" notecard that must be 
// placed in the same primitive as this script. The purpose of this script 
// to demonstrate an automated hunt system using Corrade and you are free
// to use, change, and commercialize it under the CC BY 2.0  license at: 
// https://creativecommons.org/licenses/by/2.0
//
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 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 = llParseString2List(data, ["&", "="], []);
    integer i = llListFindList(a, [ k ]);
    if(i != -1) return llList2String(a, i+1);
    return "";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
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: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
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: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
// 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: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
// unescapes a string in conformance with RFC1738
string wasURLUnescape(string i) {
    return llUnescapeURL(
        llDumpList2String(
            llParseString2List(
                llDumpList2String(
                    llParseString2List(
                        i, 
                        ["+"], 
                        []
                    ), 
                    " "
                ), 
                ["%0D%0A"], 
                []
            ), 
            "\n"
        )
    );
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
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: CC BY 2.0    //
///////////////////////////////////////////////////////////////////////////
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 != "");
    // postcondition: length(s) = 0
    return l + m;
}
 
 
// corrade data
string CORRADE = "";
string GROUP = "";
string PASSWORD = "";
// holds the name of the item to rez
string ITEM = "";
 
// for holding the callback URL
string callback = "";
 
// for notecard reading
integer line = 0;
 
// key-value data will be read into this list
list tuples = [];
 
// holds the location of hunt items
list POI = [];
list poi = [];
 
default {
    state_entry() {
        llSetText("", <1, 1, 1>, 1.0);
        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;
            }
            ITEM = llList2String(
                tuples,
                llListFindList(
                    tuples, 
                    [
                        "item"
                    ]
                )
                +1
            );
            if(ITEM == "") {
                llOwnerSay("Error in configuration notecard: item");
                return;
            }
 
            // BEGIN POI
            integer i = llGetListLength(tuples)-1;
            do {
                string n = llList2String(tuples, i);
                if(llSubStringIndex(n, "POI_") != -1) {
                    list l = llParseString2List(n, ["_"], []);
                    if(llList2String(l, 0) == "POI") {
                        integer x = llList2Integer(
                                l, 
                                1
                        )-1;
                        // extend the polygon to the number of points
                        while(llGetListLength(POI) < x)
                            POI += "";
                        // and insert the point at the location
                        POI = llListReplaceList(
                            POI, 
                            (list)(
                                (vector)(
                                "<" + llList2CSV(
                                    llParseString2List(
                                        llList2String(
                                            tuples, 
                                            llListFindList(
                                                tuples, 
                                                (list)n
                                            )
                                            +1
                                        ), 
                                        ["<", ",", ">"], 
                                        []
                                    )
                                ) + ">")
                            ), 
                            x,
                            x
                        );
                    }
                }
            } while(--i>-1);
            // now clean up any empty slots
            i = llGetListLength(POI)-1;
            do {
                if(llList2String(POI, i) == "")
                    POI = llDeleteSubList(POI, i, i);
            } while(--i > -1);
            // END POI
 
            // DEBUG
            llOwnerSay("Read configuration notecard...");
            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 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(1);
    }
    timer() {
        llRequestAgentData((key)CORRADE, DATA_ONLINE);
    }
    dataserver(key id, string data) {
        if(data != "1") {
            // DEBUG
            llOwnerSay("Corrade is not online, sleeping...");
            llSetTimerEvent(5);
            return;
        }
        state permission;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
state permission {
    state_entry() {
        // DEBUG
        llOwnerSay("Binding to the permission notification...");
        llInstantMessage(CORRADE, 
            wasKeyValueEncode(
                [
                    "command", "notify",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    "action", "set",
                    "type", "permission",
                    "URL", wasURLEscape(callback),
                    "callback", wasURLEscape(callback)
                ]
            )
        );
        llSetTimerEvent(60);
    }
    http_request(key id, string method, string body) {
        llHTTPResponse(id, 200, "OK");
        if(wasKeyValueGet("command", body) != "notify" ||
            wasKeyValueGet("success", body) != "True") {
            // DEBUG
            llOwnerSay("Failed to bind to the permission notification: " + 
                wasURLUnescape(
                    wasKeyValueGet(
                        "error", 
                        body
                    )
                )
            );
            return;
        }
        // DEBUG
        llOwnerSay("Permission notification installed...");
        state menu;
    }
    timer() {
        // alarm hit, permission notification not installed
        llResetScript();
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
state menu {
    state_entry() {
        llSetText("Touch me for menu!", <0, 1, 0>, 1.0);
    }
    touch_start(integer num) {
        if(llDetectedKey(0) != llGetOwner()) return;
        integer comChannel = (integer)("0x8" + llGetSubString(llGetKey(), 0, 6));
        llListen(comChannel, "", llGetOwner(), "");
        llDialog(llGetOwner(), "The menu will allow you to cast and dispel the hunt items.", [ "Cast", "Dispel" ], comChannel);
    }
    listen(integer channel, string name, key id, string message) {
        /* Copy the POI list to recurse over. */
        poi = POI;
        /* Process the dialog messages. */
        if(message == "Cast") state rez;
        if(message == "Dispel") state derez;
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
/* 
 * In order to rez all the items we permute the "poi" list by recursing over states.
 * The "rez_trampoline" provides a trampoline for the "rez" state re-entry. 
 */
state rez_trampoline {
    state_entry() {
        llSetTimerEvent(1);
    }
    timer() {
        state rez;
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
/* 
 * Rez the hunt item from inventory, grant debit permission and trampoline for the
 * next item in the POI list.
 */
state rez {
    state_entry() {
        // If we have rezzed all the objects, then stop rezzing.
        if(llGetListLength(poi) == 0) state menu;
        llSetText("Hunt items left to set-up: " + 
            (string)
                llGetListLength(
                    poi
            ),
            <0, 1, 1>,
            1.0
        );
        // Permute POIs
        string head = llList2String(poi, 0);
        poi = llDeleteSubList(poi, 0, 0);
 
        // DEBUG
        llOwnerSay("Rezzing @ " + head);
 
        llInstantMessage(CORRADE, 
            wasKeyValueEncode(
                [
                    "command", "rez",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    "position", wasURLEscape(head),
                    "item", wasURLEscape(ITEM),
                    "callback", wasURLEscape(callback)
                ]
            )
        );
    }
    http_request(key id, string method, string body) {
        llHTTPResponse(id, 200, "OK");
        // Get the result of rezzing the object.
        if(wasKeyValueGet("command", body) == "rez") {
            if(wasKeyValueGet("success", body) != "True") {
                // DEBUG
                llOwnerSay("Failed to rez the object: " + 
                    wasURLUnescape(
                        wasKeyValueGet(
                            "error", 
                            body
                        )
                    )
                );
                return;
            }
            llOwnerSay("Item rezzed...");
            return;
        }
        // Grant debit permissions to the rezzed object.
        if(wasKeyValueGet("type", body) == "permission" &&
            wasKeyValueGet("permissions", body) == "Debit") {
                llInstantMessage(CORRADE, 
                    wasKeyValueEncode(
                        [
                            "command", "replytoscriptpermissionrequest",
                            "group", wasURLEscape(GROUP),
                            "password", wasURLEscape(PASSWORD),
                            "task", wasKeyValueGet("task", body),
                            "item", wasKeyValueGet("item", body),
                            "region", wasKeyValueGet("region", body),
                            "action", "reply",
                            "permissions", "Debit",
                            "callback", wasURLEscape(callback)
                        ]
                    )
                );
                // DEBUG
                llOwnerSay("Replying to permission request...");
                return;
        }
        // Get the result of granting script permissions.
        if(wasKeyValueGet("command", body) == "replytoscriptpermissionrequest") {
            if(wasKeyValueGet("success", body) != "True") {
                // DEBUG
                llOwnerSay("Failed to grant permissions to the object: " + 
                    wasURLUnescape(
                        wasKeyValueGet(
                            "error", 
                            body
                        )
                    )
                );
                return;
            }
            llOwnerSay("Permissions granted...");
            // Go for the next item in the POI list.
            state rez_trampoline;
        }
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}
 
/*
 * In order to de-rez the hunt items we first teleport Corrade in the vicinity 
 * of the POI and then issue a "derez" command to Corrade.
 * Symmetrically to "rez", the "derez_trampoline" state provides a trampoline 
 * for the "derez" state re-entry.
 */
state derez_trampoline {
    state_entry() {
        llSetTimerEvent(1);
    }
    timer() {
        state derez;
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}
 
state derez {
    state_entry() {
        // If we have derezzed all the objects, then stop rezzing.
        if(llGetListLength(poi) == 0) state menu;
        llSetText("Hunt items left to remove: " + 
            (string)
                llGetListLength(
                    poi
            ),
            <0, 1, 1>,
            1.0
        );
        // Permute POIs
        string head = llList2String(poi, 0);
        poi = llDeleteSubList(poi, 0, 0);
        // DEBUG
        llOwnerSay("Teleporting to: " + (string)head);
        llInstantMessage((key)CORRADE, 
            wasKeyValueEncode(
                [
                    "command", "teleport",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    "region", wasURLEscape(llGetRegionName()),
                    "position", wasURLEscape(head),
                    "entity", "region",
                    "fly", "True",
                    "callback", wasURLEscape(callback)
                ]
            )
        );
    }
    http_request(key id, string method, string body) {
        llHTTPResponse(id, 200, "OK");
        // Get the result of teleporting to the POI.
        if(wasKeyValueGet("command", body) == "teleport") {
            // If the teleport did not succeed and the error was not that the destination
            // was too close, then print the error and stop; otherwise, continue.
            if(wasKeyValueGet("success", body) != "True" && 
                wasKeyValueGet("status", body) != "37559") {
                // DEBUG
                llOwnerSay("Failed to teleport: " + 
                    wasURLUnescape(
                        wasKeyValueGet(
                            "error", 
                            body
                        )
                    )
                );
                return;
            }
            // DEBUG
            llOwnerSay("Teleport succeeded...");
            // If the teleport succeeded, request to derez the item.
            llInstantMessage((key)CORRADE, 
                wasKeyValueEncode(
                    [
                        "command", "derez",
                        "group", wasURLEscape(GROUP),
                        "password", wasURLEscape(PASSWORD),
                        "item", wasURLEscape(ITEM),
                        "range", 5,
                        "callback", wasURLEscape(callback)
                    ]
                )
            );
            return;
        }
        // Get the result of the derez request.
        if(wasKeyValueGet("command", body) == "derez") {
            // If removing the item because the item was not found, then it was 
            // probably consumed during the hunt so carry on to the next destination.
            if(wasKeyValueGet("success", body) != "True" &&
                wasKeyValueGet("status", body) != "22693") {
                // DEBUG
                llOwnerSay("Failed to derez: " + 
                    wasURLUnescape(
                        wasKeyValueGet(
                            "error", 
                            body
                        )
                    )
                );
                return;
            }
            // DEBUG
            llOwnerSay("Derez succeeded...");
            state derez_trampoline;
        }
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}

secondlife/scripted_agents/corrade/projects/in_world/automated_hunt_system.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.