About

With Corrade it is possible to detect the shape gender of a given avatar. The following example script will scan the entire region for avatars and report their gender on local chat, along with their names and keys. Note that in order for the script to work, Corrade has to be on the same simulator as this script.

The script detects the shape gender and one should take into account that does not necessarily make an avatar either female or male in some cases. For example, for an animal avatar or a Sci-Fi avatar, it may be uncertain whether the avatar is male or female. Some creators just use either gender when designing avatars such that the gender of the shape does not necessarily reflect the actual gender of the avatar.

Marketplace Item

Instructions

Create a new primitive and add a notecard named configuration in the newly created primitive with the following contents:

File: http://svn.grimore.org/corrade-lsl-templates/source/avatar-shape-gender-detector/configuration.txt -

############################# START CONFIGURATION ############################
 
# This is a configuration notecard based on the key-value data Wizardry and Steamworks reader. 
# Everything following the "#" character is ignored along with blank lines. Values can be set for various 
# parameters in a simple and understandable format with the following syntax: 
# KEY = "VALUE" 
# Every time you change this configuration notecard, the script will reset itself and the new configuration 
# will be read from this notecard.
 
# The "corrade", "group" and "password" settings must correspond to your settings in Corrade.ini
 
# This is the UUID of the Corrade bot.
corrade = "1272a407-1af3-47f7-abcd-79f81d28637d"
 
# The name of the group - it can also be the UUID of the group.
group = "My Group"
 
# The password for the group.
password = "mypassword"
 
# This represents the scan interval to detect avatars. Note that this script does not rescan avatars.
interval = "10"
 
# This represents the amount of free memory measured in bytes that the script will attempt to maintain. 
# After the memory usage of this script exceeds this amount, the script will start culling the list of 
# detected avatars in an attempt to not overflow and keep the script running.
memory = "1024"
 
############################# END CONFIGURATION #############################

and configure the settings to match your Corrade configuration. After that, add the script from the code section into the same primitive where you placed the configuration notecard.

Code

File: http://svn.grimore.org/corrade-lsl-templates/source/avatar-shape-gender-detector/avatar-shape-gender-detector.lsl -

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: CC BY 2.0      //
///////////////////////////////////////////////////////////////////////////
//
// This is a script that uses the Corrade Second Life / OpenSim bot which 
// is able to detect the gender of avatar shapes. You can find more details 
// about Corrade at: http://grimore.org/secondlife/scripted_agents/corrade
//
// This script works together with a "configuration" notecard that must be 
// placed in the same primitive as this script. The purpose of this script 
// is to demonstrate detecting avatar genders with Corrade and you are free 
// to use, change, and commercialize it provided that you follow the terms 
// of 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) 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    //
///////////////////////////////////////////////////////////////////////////
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;
}
 
///////////////////////////////////////////////////////////////////////////
//    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, ",");
} 
 
// corrade data
string CORRADE = "";
string GROUP = "";
string PASSWORD = "";
integer INTERVAL = 0;
integer MEMORY = 0;
 
// for holding the callback URL
string callback = "";
 
// for notecard reading
integer line = 0;
 
// key-value data will be read into this list
list tuples = [];
// store names and uuids for gender detect
list names = [];
list uuids = [];
// store the keys of detected agents in 
// order to prevent scanning them again
list found = [];
// temporary storage over event handler scope
string name = "";
key uuid = NULL_KEY;
 
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;
            }
            INTERVAL = llList2Integer(
                tuples,
                llListFindList(
                    tuples, 
                    [
                        "interval"
                    ]
                )
            +1);
            if(INTERVAL == 0) {
                llOwnerSay("Error in configuration notecard: interval");
                return;
            }
            MEMORY = llList2Integer(
                tuples,
                llListFindList(
                    tuples, 
                    [
                        "memory"
                    ]
                )
            +1);
            if(MEMORY == 0) {
                llOwnerSay("Error in configuration notecard: memory");
                return;
            }
            // 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(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 scan;
    }
    on_rez(integer num) {
        llResetScript();
    }
    changed(integer change) {
        if((change & CHANGED_INVENTORY) || (change & CHANGED_REGION_START)) {
            llResetScript();
        }
    }
}
 
state scan {
    state_entry() {
        // DEBUG
        llOwnerSay("Scanning agents...");
        list tmp = llGetAgentList(AGENT_LIST_REGION, []);
        do {
            key id = llList2Key(tmp, 0);
            // skip avatars that we have previously detected
            if(llListFindList(found, (list)id) != -1) jump continue;
            uuids += id;
            names += llKey2Name(id);
@continue;
            tmp = llDeleteSubList(tmp, 0, 0);
        } while(llGetListLength(tmp) != 0);
        // recurse over timer, start.
        llSetTimerEvent(1);
    }
    timer() {
        // Pause the timer.
        llSetTimerEvent(0);
        // if the scanning list is empty, switch to detect
        if(llGetListLength(names) == 0) state detect;
        // pop the first name and UUID off the stack
        name = llList2String(names, 0);
        uuid = llList2Key(uuids, 0);
        names = llDeleteSubList(names, 0, 0);
        uuids = llDeleteSubList(uuids, 0, 0);
        // get the full name and send it to Corrade for scanning
        list full = llParseString2List(name, [" "], []);
        llInstantMessage(CORRADE, 
            wasKeyValueEncode(
                [
                    "command", "getavatardata",
                    "group", wasURLEscape(GROUP),
                    "password", wasURLEscape(PASSWORD),
                    "firstname", wasURLEscape(
                        llList2String(
                            full,
                            0
                        )
                    ),
                    "lastname", wasURLEscape(
                        llList2String(
                            full,
                            1
                        )
                    ),
                    "sift", wasURLEscape(
                        wasListToCSV(
                            [
                                "match", wasURLEscape(
                                    ",Index,31,([^,$]+)"
                                )
                            ]
                        )
                    ),
                    "data", "VisualParameters",
                    "callback", wasURLEscape(callback)
                ]
            )
        );
    }
    http_request(key id, string method, string body) {
        llHTTPResponse(id, 200, "OK");
 
        if(wasURLUnescape(
            wasKeyValueGet("success", body)) != "True")
            return;
 
        // request succeeded, so grab the 
        // sex from the visual parameters
        integer sex = (integer) wasURLUnescape(
            wasKeyValueGet(
                "data",
                body
            )
        );
 
        // at this point we know the following:
        // - the name of the scanned avatar stored in "name"
        // - the UUID of the scanned avatar stored in "uuid"
        // - the gender of the avatar shape:
        //   - if sex is 0, then the avatar has a female shape
        //   - otherwise, the avatar has a male shape 
        if(sex != 0) {
            llSay(0, "The avatar " + name + "(" + (string)uuid + ")" + " has a male shape!");
            jump continue_2;
        }
        llSay(0, "The avatar " + name + "(" + (string)uuid + ")" + " has a female shape!");
 
@continue_2;
 
        // this keeps the amount of free memory available to the script above a specified treshold
        if (llGetFreeMemory() < MEMORY)
            found = llDeleteSubList(found, 0, 0);
 
        // to prevent scanning the same avatar again, add the uuid to the found list
        found += uuid;
 
        // Resetart the timer.
        llSetTimerEvent(INTERVAL);
    }
    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/detect_avatar_shape_gender.txt ยท Last modified: 2019/01/03 01:36 (external edit)

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


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