vistitortracker.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
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) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer elapsedDays(string date) {
    list age = llParseString2List(date, ["-"], []);
    list now = llParseString2List(llGetDate(), ["-"], []);
 
    integer ny = llList2Integer(now, 0);
    integer nm = llList2Integer(now, 1);
    integer nd = llList2Integer(now, 2);
 
    integer ay = llList2Integer(age, 0);
    integer am = llList2Integer(age, 1);
    integer ad = llList2Integer(age, 2);
 
    return ((ny / 4 - ny / 100 + ny / 400 + (367 * nm) / 12 + nd) + ny * 365 - 719499) -
        ((ay / 4 - ay / 100 + ay / 400 + (367 * am) / 12 + ad) + ay * 365 - 719499);
}
 
// updates the text on the tracker display
updateText(list v, list t, list a, list m) {
    llMessageLinked(LINK_ROOT, 204000, "V:" + (string)llGetListLength(v), "0");
    integer timeMean = llRound(llListStatistics(LIST_STAT_MEAN, tList));
    string s = "s";
    if(timeMean > 60) {
        timeMean = timeMean/60;
        s = "m";
        jump display;
    }
    if(timeMean > 3600) {
        timeMean = timeMean/3600;
        s = "h";
    }
@display;
    llMessageLinked(LINK_ROOT, 204000, "T:" + (string)timeMean + s, "1");
    llMessageLinked(LINK_ROOT, 204000, "A:" + (string)llRound(llListStatistics(LIST_STAT_MEAN, a)) + "d", "2");
    llMessageLinked(LINK_ROOT, 204000, "M:" + (string)llRound(llListStatistics(LIST_STAT_MEAN, m)) + "MB", "3");
}
 
// purges the lists if the memory is under a treshold
purgeMem(integer mem) {
    if (llGetFreeMemory() > mem) return;
    vList = llDeleteSubList(vList, 0, 0);
    tList = llDeleteSubList(tList, 0, 0);
    aList = llDeleteSubList(aList, 0, 0);
    mList = llDeleteSubList(mList, 0, 0);
}
 
list vList = [];
list tList = [];
list aList = [];
list mList = [];
list ageQuery = [];
 
default {
    state_entry() {
        string mode = wasKeyValueGet("mode", llList2String(llGetLinkPrimitiveParams(6, [PRIM_DESC]), 0));
        if(mode == "region") state region_scanning;
        if(mode == "sensor") state sensor_scanning;
    }
}
 
state sensor_scanning {
    state_entry() {
        integer range = (integer) wasKeyValueGet("range", llList2String(llGetLinkPrimitiveParams(6, [PRIM_DESC]), 0));
        llSensorRepeat("", "", AGENT, range, TWO_PI, 1);
    }
    no_sensor() {
        updateText(vList, tList, aList, mList);
    }
    sensor(integer num) {
        --num;
        do {
            key a = llDetectedKey(num);
            string name = llKey2Name(a);
            integer i = llListFindList(vList, (list)name);
            if(i != -1) {
                tList = llListReplaceList(
                    tList, 
                    [ llList2Integer(tList, i) + 1 ], 
                i, i);
                updateText(vList, tList, aList, mList);
                jump continue;
            }
            // free memory if necessary
            purgeMem(1024);
            ageQuery += [ llRequestAgentData(a, DATA_BORN) ];
            mList += llList2Integer(llGetObjectDetails(a,[OBJECT_SCRIPT_MEMORY]), 0)/1048576;
            vList += [ name ];
            tList += 1;
@continue;
        } while(--num>-1);
    }
    dataserver(key queryid, string data) {
        integer i = llListFindList(ageQuery, [queryid]);
        if(i == -1) return;
        ageQuery = llDeleteSubList(ageQuery, i, i);
        aList = llListReplaceList(aList, [elapsedDays(data)], i, i);
        updateText(vList, tList, aList, mList);
    }
}
 
state region_scanning {
    state_entry() {
        llSetTimerEvent(1);
    }
    timer() {
        // Get agents
        list as = llGetAgentList(AGENT_LIST_REGION, []);
        do {
            key a = llList2Key(as, 0);
            as = llDeleteSubList(as, 0, 0);
            string name = llKey2Name(a);
            integer i = llListFindList(vList, (list)name);
            if(i != -1) {
                tList = llListReplaceList(
                    tList, 
                    [ llList2Integer(tList, i) + 1 ], 
                i, i);
                updateText(vList, tList, aList, mList);
                jump continue;
            }
            // free memory if necessary
            purgeMem(1024);
            ageQuery += [ llRequestAgentData(a, DATA_BORN) ];
            mList += llList2Integer(llGetObjectDetails(a,[OBJECT_SCRIPT_MEMORY]), 0)/1048576;
            vList += [ name ];
            tList += 1;
@continue;
        } while(llGetListLength(as));
    }
    dataserver(key queryid, string data) {
        integer i = llListFindList(ageQuery, [queryid]);
        if(i == -1) return;
        ageQuery = llDeleteSubList(ageQuery, i, i);
        aList = llListReplaceList(aList, [elapsedDays(data)], i, i);
        updateText(vList, tList, aList, mList);
    }
    on_rez(integer num) {
        llResetScript();
    }
}