cuffslave.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2013 - 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    //
///////////////////////////////////////////////////////////////////////////
LID(key id) {
    if(id != NULL_KEY && llGetAttached() != 0) {
        llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", (string)((integer)wasKeyValueGet("login", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))+1), llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
        llResetScript();
    }
    llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", "-1", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
}
 
moveTo(vector target, integer bb) {
    llStopMoveToTarget();
    llTargetRemove(moveTarget);
    vector pointTo = target - llGetPos();
    llOwnerSay("@setrot:" + (string)llAtan2(pointTo.x, pointTo.y) + "=force");
    moveTarget = llTarget(target, 2 + bb);
    llMoveToTarget(target, 2);
}
 
key master = NULL_KEY;
integer hasRLV = FALSE;
integer moveTarget = 0;
 
default {
    state_entry() {
        // LID™ reasoning - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
        // if LID™, then login = 1
        // else login = 0 - script running | parameter undefined
        if((integer)wasKeyValueGet("login", llGetObjectDesc()) > 0) {
            llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", "0", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
            state check;
            return;
        }
        llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", "0", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
        state check;
    }
 
    // LID™ reasoning - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
    // order: on_rez -> attach
    //
    // if object attached, then login := login+1
    // else login := -1
    attach(key id) {
        // attached
        if(id != NULL_KEY && llGetAttached() != 0) {
            llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", (string)((integer)wasKeyValueGet("login", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))+1), llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
            // Reset script for llGetCreator() bugture.
            llResetScript();
        }
        // detached
        llSetLinkPrimitiveParamsFast(2, [PRIM_DESC, wasKeyValueSet("login", "-1", llList2String(llGetLinkPrimitiveParams(2, [PRIM_DESC]), 0))]);
    }
    // login = 1 after object attached
    //       = -1 after object detached
}
 
state check {
    state_entry() {
        llSetTimerEvent(25);
        integer _comChannel = 10+(integer)llFrand(10);
        llListen(_comChannel, "", llGetOwner(), "");
        llOwnerSay("@version=" + (string)_comChannel);
    }
 
    timer() {
        llSetTimerEvent(0);
        // Do not pester the avatar; you are here if you don't have RLV.
        hasRLV = FALSE;
        state tellkey;
    }
 
    listen(integer channel, string name, key id, string message) {
        llSetTimerEvent(0);
        hasRLV = TRUE;
        state tellkey;
    }
 
    changed(integer change) {
        llResetScript();
    }
 
    on_rez(integer num) {
        llResetScript();
    }
 
    // LID™ - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
    attach(key id) {
        LID(id);
    }
}
 
state tellkey {
    state_entry() {
        integer comChannel = (integer)("0x8" + llGetSubString(llGetCreator(), 0, 6));
        llListen(comChannel+1, "", "", "");
        llSetTimerEvent(1);
    }
 
    listen(integer channel, string name, key id, string messsage) {
        // alarm
        llSetTimerEvent(1);
        if(messsage == "sync") {
            master = id;
            llWhisper(channel, "sync="+(string)llGetKey());
            return;
        }
        if(messsage == "stop") state stop;
    }
 
    timer() {
        if(master) return;
        llResetScript();
    }
 
    changed(integer change) {
        llResetScript();
    }
 
    on_rez(integer num) {
        llResetScript();
    }
 
    // LID™ - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
    attach(key id) {
        LID(id);
    }
}
 
state stop {
    state_entry() {
        if(hasRLV == FALSE) return;
        llSetTimerEvent(1);
    }
 
    timer() {
        vector pos = llList2Vector(llGetObjectDetails(master, [OBJECT_POS]), 0);
        if(pos == ZERO_VECTOR) llResetScript();
        integer distance = (integer)llVecDist(pos, llGetPos());
        if(distance > 65) llResetScript();
        if(distance < 5) {
            llStopMoveToTarget();
            llTargetRemove(moveTarget);
            return;
        }
        moveTo(
            pos,
            (integer)(llVecDist(llList2Vector(llGetBoundingBox(master), 1), 
            llList2Vector(llGetBoundingBox(master), 0))/2.0)
        );
    }
 
    changed(integer change) {
        llResetScript();
    }
 
    on_rez(integer num) {
        llResetScript();
    }
 
    // LID™ - http://grimore.org/fuss:lsl#log-in_detection_with_attachments
    attach(key id) {
        LID(id);
    }
}