Table of Contents

About

The polygon wanderer makes Corrade wander within a polygon defined by its perimeter points.

Configuration

The script requires a notecard named configuration to be placed in the same primitive as the script. Here is a sample notecard:

####################### START CONFIGURATION ##################################
 
# All these settings must correspond to the settings in Corrade.ini.
 
# This is the UUID of the Corrade bot.
corrade = "97515305-5f46-4521-9568-a879b8c1f1c9"
 
# 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 contour of a polygon represented by regional point-vectors, for example:
#
#     1 +----+ 2
#        |      | 
#     5 +      + 3
#          \   /
#            + 4
# 
# The points must be named in sequence, ie: point_1, point_2, etc... By following
# the contour of the polygon that you wish Corrade to wander within.
#
# The following values represent the contour of a polygon with 12 points:
point_1 = <90.140633, 195.659195, 0>
point_2 = <93.146393, 162.897415, 0>
point_3 = <90.38028, 156.655594, 0>
point_4 = <79.35807, 138.610291, 0>
point_5 = <93.638596, 124.921394, 0>
point_6 = <110.910965, 120.750427, 0>
point_7 = <125.259796, 116.540215, 0>
point_8 = <140.697433, 151.190826, 0>
point_9 = <133.573517, 163.03215, 0>
point_10 = <122.473473, 161.892914, 0>
point_11 = <118.320236, 167.628906, 0>
point_12 = <107.47805, 198.410461, 0>
 
# The maximal time in seconds allowed for Corrade to remain stationary.
wait = 60
 
####################### END CONFIGURATION ###################################

Setup

The configuration notecard has to be changed accordingly and, aside from the obvious settings such as the group and password and Corrade UUID, the points have to be set-up. Setting up the points is very easy, simply get the positions of some points by razzing primitives and then collecting their region positions. The example above, was set-up on the ground using a few points as reference to define a polygon.

Code

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
//
// This is an automatic teleporter, and patrol script for the Corrade
// Second Life / OpenSim bot. You can find more details about the bot
// by following the URL: http://was.fm/secondlife/scripted_agents/corrade
//
// The purpose of this script is to demonstrate patroling with Corrade and 
// you are free to use, change, and commercialize it under the GNU/GPLv3 
// license which can be found at: http://www.gnu.org/licenses/gpl.html
//
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
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: 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    //
///////////////////////////////////////////////////////////////////////////
list wasDualQuicksort(list a, list b) {
    if(llGetListLength(a) <= 1) return a+b;
 
    float pivot_a = llList2Float(a, 0);
    a = llDeleteSubList(a, 0, 0);
    vector pivot_b = llList2Vector(b, 0);
    b = llDeleteSubList(b, 0, 0);
 
    list less = [];
    list less_b = [];
    list more = [];
    list more_b = [];
 
    do {
        if(llList2Float(a, 0) > pivot_a) {
            less += llList2List(a, 0, 0);
            less_b += llList2List(b, 0, 0);
            jump continue;
        }
        more += llList2List(a, 0, 0);
        more_b += llList2List(b, 0, 0);
@continue;
        a = llDeleteSubList(a, 0, 0);
        b = llDeleteSubList(b, 0, 0);
    } while(llGetListLength(a));
    return wasDualQuicksort(less, less_b) + [ pivot_a ] + [ pivot_b ] + wasDualQuicksort(more, more_b);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
// determines whether the segment AB intersects the segment CD
integer wasSegmentIntersect(vector A, vector B, vector C, vector D) {
    vector s1 = <B.x - A.x, B.y - A.y, B.z - A.z>;
    vector s2 = <D.x - C.x, D.y - C.y, D.y - C.z>;
 
    float d = (s1.x * s2.y -s2.x * s1.y);
 
    if(d == 0) return FALSE;
 
    float s = (s1.x * (A.y - C.y) - s1.y * (A.x - C.x)) / d;
    float t = (s2.x * (A.y - C.y) - s2.y * (A.x - C.x)) / d;
 
    // intersection at <A.x + (t * s1.x), A.y + (t * s1.y), A.z + (t * s1.z)>;
    return (integer)(s >= 0 && s <= 1 && t >= 0 && t <= 1 && 
            A.z + t*(B.z - A.z) == C.z + s*(D.z - C.z));
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
//    www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html    //
///////////////////////////////////////////////////////////////////////////
integer wasPointInPolygon(vector p, list polygon) {
    integer inside = FALSE;
    integer i = 0;
    integer nvert = llGetListLength(polygon);
    integer j = nvert-1;
    do {
        vector pi = llList2Vector(polygon, i);
        vector pj = llList2Vector(polygon, j);
        if ((pi.y > p.y) != (pj.y > p.y))
            if(p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x)
                inside = !inside;
        j = i++;
    } while(i<nvert);
    return inside;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
//    www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html    //
///////////////////////////////////////////////////////////////////////////
list wasPointToPolygon(list polygon, vector point) {
    integer i = llGetListLength(polygon)-1;
    list l = [];
    do {
        l = llListInsertList(l, (list)llVecDist(point, llList2Vector(polygon, i)), 0);
    } while(--i>-1);
    l = wasDualQuicksort(l, polygon);
    return [llList2Float(l, 0), llList2Vector(l, 1)];
 
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
//    www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html    //
///////////////////////////////////////////////////////////////////////////
vector wasPolygonCentroid(list polygon, vector start, float tollerance, integer power) {
    // calculate the distance to the point farthest away from the start.
    list wpf = wasPointToPolygon(polygon, start);
    float dist = llList2Float(wpf, 0);
    vector next = llList2Vector(wpf, 1);
 
    // now calculate the next jump point
    next = start + ((dist/power)/dist) * (next-start);
 
    // if it falls withing the tollerance range, return it;
    if(llVecMag(start-next) < tollerance) return next;
    return wasPolygonCentroid(polygon, next, tollerance, power*power);
}
 
///////////////////////////////////////////////////////////////////////////
//    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) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasPolygonPoint(list polygon) {
    vector c = wasPolygonCentroid(polygon, llList2Vector(polygon, 0), 0.05, 2);
    float r = llList2Float(wasPointToPolygon(polygon, c), 0);
    vector d;
    do {
        d = c + wasCirclePoint(r);
    } while(wasPointInPolygon(d, polygon) == FALSE);
    return d;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
// determines whether the path between the current positon m and the 
// computed next position d will intersect any two sides of the polygon
vector wasPolygonPath(vector m, list polygon) {
    integer c = llGetListLength(polygon) - 1;
    vector d = wasPolygonPoint(polygon);
    integer i = 0;
    do {
        vector s = llList2Vector(polygon, c);
        vector p = llList2Vector(polygon, c-1);
        // project in plane
        if(wasSegmentIntersect(
            <m.x, m.y, 0>, 
            <d.x, d.y, 0>, 
            <s.x, s.y, 0>, 
            <p.x, p.y, 0>))
            ++i;
    } while(--c > 0);
    if(i > 1) return wasPolygonPath(m, polygon);
    return d;
}
 
///////////////////////////////////////////////////////////////////////////
//    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 = "";
float RADIUS = 0;
float WAIT = 0;
list POLYGON = [];
 
// for holding Corrade's current location
vector location = ZERO_VECTOR;
 
// 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() {
        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: group");
                return;
            }
            PASSWORD = llList2String(
                          tuples,
                          llListFindList(
                              tuples, 
                              [
                                  "password"
                              ]
                          )
                      +1);
            if(PASSWORD == "") {
                llOwnerSay("Error in configuration notecard: password");
                return;
            }
 
            // BEGIN POLYGON
            integer i = llGetListLength(tuples)-1;
            do {
                string n = llList2String(tuples, i);
                if(llSubStringIndex(n, "point_") != -1) {
                    list l = llParseString2List(n, ["_"], []);
                    if(llList2String(l, 0) == "point") {
                        integer x = llList2Integer(
                                l, 
                                1
                        )-1;
                        // extend the polygon to the number of points
                        while(llGetListLength(POLYGON) < x)
                            POLYGON += "";
                        // and insert the point at the location
                        POLYGON = llListReplaceList(
                            POLYGON, 
                            (list)(
                                (vector)(
                                "<" + llList2CSV(
                                    llParseString2List(
                                        llList2String(
                                            tuples, 
                                            llListFindList(
                                                tuples, 
                                                (list)n
                                            )
                                            +1
                                        ), 
                                        ["<", ",", ">"], 
                                        []
                                    )
                                ) + ">")
                            ), 
                            x,
                            x
                        );
                    }
                }
            } while(--i>-1);
            // now clean up any empty slots
            i = llGetListLength(POLYGON)-1;
            do {
                if(llList2String(POLYGON, i) == "")
                    POLYGON = llDeleteSubList(POLYGON, i, i);
            } while(--i > -1);
            // END POLYGON
 
            WAIT = llList2Float(
                          tuples,
                          llListFindList(
                              tuples, 
                              [
                                  "wait"
                              ]
                          )
                      +1);
            if(WAIT == 0) {
                llOwnerSay("Error in configuration notecard: wait");
                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 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;
        }
        llSensor("", (key)CORRADE, AGENT, 10, TWO_PI);
    }
    no_sensor() {
        // DEBUG
        llOwnerSay("Teleporting Corrade...");
        llInstantMessage((key)CORRADE, 
            wasKeyValueEncode(
                [
                    "command", "teleport",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    "entity", "region",
                    "region", wasURLEscape(llGetRegionName()),
                    "position", wasURLEscape(
                        (string)(
                            llGetPos() + wasCirclePoint(RADIUS)
                        )
                    ),
                    "callback", callback
                ]
            )
        );
        llSensorRepeat("", (key)CORRADE, AGENT, 10, TWO_PI, 60);
    }
    sensor(integer num) {
        llSetTimerEvent(0);
        state wander;
    }
    http_request(key id, string method, string body) {
        llHTTPResponse(id, 200, "OK");
        if(wasKeyValueGet("command", body) != "teleport" ||
            wasKeyValueGet("success", body) != "True") {
            // DEBUG
            llOwnerSay("Teleport failed...");
            return;
        }
        llSetTimerEvent(0);
        state wander;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}
 
state wander {
    state_entry() {
        // DEBUG
        llOwnerSay("Wandering ready...");
        // initialize location from current location
        location = llGetPos();
        llSetTimerEvent(1 + llFrand(WAIT));
    }
    timer() {
        llRequestAgentData((key)CORRADE, DATA_ONLINE);
    }
    dataserver(key id, string data) {
        if(data != "1") {
            // DEBUG
            llOwnerSay("Corrade is not online, sleeping...");
            llResetScript();
            return;
        }
        // DEBUG
        llOwnerSay("Sending next move...");
        // get the next location
        location = wasPolygonPath(location, POLYGON);
        vector pos = llGetPos();
        llInstantMessage(CORRADE,
            wasKeyValueEncode(
                [
                    "command", "walkto",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    "position", wasURLEscape(
                        (string)(<location.x, location.y, pos.z>)
                    ),
                    "vicinity", "1",
                    "duration", "2500"
                ]
            )
        );
        llSetTimerEvent(1 + llFrand(WAIT));
    }
    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/patrol/polygon.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.