bullseyetarget.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    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueSet(string k, string v, string data) {
    if(llStringLength(k) == 0) return "";
    if(llStringLength(v) == 0) return "";
    if(llStringLength(data) == 0) return k + "=" + v;
    integer i = llListFindList(
        llList2ListStrided(
            llParseString2List(data, ["&", "="], []), 
            0, -1, 2
        ), 
    [ k ]);
    if(i != -1) return llDumpList2String(
        llListReplaceList(
            llParseString2List(data, ["&"], []), 
            [ k + "=" + v ], 
        i, i), 
    "&");
    return data + "&" + k + "=" + v;
}
 
///////////////////////////////////////////////////////////////////////////
//    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);
    string pivot_b = llList2String(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);
}
 
default {
    collision_start(integer num) {
        // filter agent collisions
        if(llDetectedType(0) & AGENT) return;
        key agent = llGetOwnerKey(llDetectedKey(0));
        integer range = (integer)wasKeyValueGet("range", llList2String(llGetLinkPrimitiveParams(3, [PRIM_DESC]), 0));
        // only accept players from a minimum 10 meters range.
        if(llVecDist(llGetPos(), llList2Vector(llGetObjectDetails(agent, [OBJECT_POS]), 0)) < range) {
            llInstantMessage(agent, "For the score to register, you must be at least " + (string)range + "m away from the bullseye!");
            return;
        }
        // calculate distance from origin
        vector a = llGetRootPosition();
        vector b = llDetectedPos(0);
        vector t = -(a-b)/llGetRootRotation();
        float d = llSqrt(llPow(t.x, 2) + llPow(t.y, 2));
 
        // get the top scorers
        list tags = [ "1", "2", "3" ];
        list agents = [];
        list scores = [];
        do {
            string tag = llList2String(tags, 0);
            list as = llCSV2List(wasKeyValueGet(tag, llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0)));
            if(llGetListLength(as) != 2) jump continue_get;
            agents += llList2String(as, 0);
            scores += llList2String(as, 1);
@continue_get;
            tags = llDeleteSubList(tags, 0, 0);
        } while(llGetListLength(tags));
 
        // check if the scorer already exists
        string name = llKey2Name(llGetOwnerKey(llDetectedKey(0)));
        // if the player does not exist, register them
        integer i = llListFindList(agents, [name]);
        if(i == -1) {
            agents += name;
            scores += d;
            jump sort;
        }
        // if the score is better, then add it, otherwise keep the score
        float score = llList2Float(scores, i);
        if(d > score) jump sort;
        scores = llListReplaceList(scores, [d], i, i);        
 
@sort;
        // sort them in ascending order (less is better)
        list scorers = wasDualQuicksort(scores, agents);
        // and build the store
        list store = [];
        // also build the text while we are at it
        string text = "";
        do {
            string agent = llList2String(scorers, 1);
            if(agent == "") jump continue_set;
            string score = llList2String(scorers, 0);
            if(score == "") jump continue_set;
            // add to store
            store += llList2CSV([agent, score]);
            text += agent;
            text += " -> ";
            text += score;
            text += "\n";
@continue_set;
            scorers = llDeleteSubList(scorers, 0, 1);
            // only record the 5 top scorers
            if(llGetListLength(store) == 3) jump break;
        } while(llGetListLength(scorers));
 
@break;
 
        // now store the top scorers
        tags = [ "1", "2", "3" ];
        do {
            string desc = llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0);
            llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet(llList2String(tags, 0), llList2String(store, 0), desc)]);
            tags = llDeleteSubList(tags, 0, 0);
            store = llDeleteSubList(store, 0, 0);
        } while(llGetListLength(store));
 
        // finally, display the scorers 
        llMessageLinked(LINK_SET, 0, text, (string)<1,1,0>);
    }
    on_rez(integer num) {
        llResetScript();
    }
}