Difference between revisions of "LSL Protocol/Restrained Living Relay/Other Implementations/Maike Short's Relay/Alpha Version"

From Second Life Wiki
Jump to navigation Jump to search
(experimental alpha version)
 
 
(3 intermediate revisions by one other user not shown)
Line 1: Line 1:
Note: This is alpha quality code. Please help testing or use [[LSL Protocol/Restrained Life Relay/Other Implementations/Maike Short's Relay|the last stable version]] instead.
== Code ==
== Code ==


Line 12: Line 14:
// While this script used to be based on Marine's sample implementations it was
// While this script used to be based on Marine's sample implementations it was
// so heavily modified and extended that there is little of the original code
// so heavily modified and extended that there is little of the original code
// remaining. So please do not bother Marine with any problems but report them to  
// remaining. So please do not bother Marine with any problems but report them to
// https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Bugs_and_Pendings_Features
// https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Bugs_and_Pendings_Features
//
//
Line 19: Line 21:
// Chloe1982 Constantine, Ilana Debevec, Kitty Barnett and many others.
// Chloe1982 Constantine, Ilana Debevec, Kitty Barnett and many others.
//
//
// Many thanks to she who started it all ... Marine Kelley.  
// Many thanks to she who started it all ... Marine Kelley.
//
//
// This script is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness  
// This script is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness
// or performance. It may be distributed in its full source code with this header and  
// or performance. It may be distributed in its full source code with this header and
// disclaimer and is not to be sold without permission. Optionally it may be distributed
// disclaimer and is not to be sold without permission. Optionally it may be distributed
// in a 'no mod' form within Second Life™ PROVIDED that either a link to the full source
// in a 'no mod' form within Second Life™ PROVIDED that either a link to the full source
// IS provided (ie. this page or a .zip or .rar) within the object the code is contained  
// IS provided (ie. this page or a .zip or .rar) within the object the code is contained
// AND/OR a off-world contact point (ie. email) that the source code may be requested from.
// AND/OR a off-world contact point (ie. email) that the source code may be requested from.
// This somewhat unusual (no-mod) distribution is allowed to maintain the integrity and
// This somewhat unusual (no-mod) distribution is allowed to maintain the integrity and
Line 37: Line 39:
//                    Constants
//                    Constants
// ---------------------------------------------------
// ---------------------------------------------------
 
integer RLVRS_PROTOCOL_VERSION = 1040; // version of the protocol, stated on the specification page
integer RLVRS_PROTOCOL_VERSION = 1040; // version of the protocol, stated on the specification page
string RLVRS_IMPL_VERSION = "Based on Maike's Implemenation 1.040.a ALPHA"; // version of the implementation for debugging
string RLVRS_IMPL_VERSION = "Based on Maike's Implemenation 1.040.a ALPHA"; // version of the implementation for debugging
 
string PREFIX_RL_COMMAND = "@";
string PREFIX_RL_COMMAND = "@";
string PREFIX_METACOMMAND = "!";
string PREFIX_METACOMMAND = "!";
Line 76: Line 78:
//                      Variables
//                      Variables
// ---------------------------------------------------
// ---------------------------------------------------
 
integer mode;
integer mode;


Line 91: Line 93:
list units;
list units;
list unitObjects;
list unitObjects;
list unitObjectTrusted;
list trustedObjects;
list unitObjectTrustedTimeout;
list trustedTimeout;
list unitPendingTimeout;
list unitPendingTimeout;
list unitPendingChannel;
list unitPendingChannel;
Line 120: Line 122:
{
{


integer pos = llListFindList(unitObjects, [id]);
    integer pos = llListFindList(unitObjects, [id]);
if (pos > -1)
    if (pos > -1)
{
    {
return pos;
        return pos;
}
    }
    return -1;
}
 


integer count = llGetListLength(units);
integer isTrusted(key id)
integer i;
{
for (i = 0; i <= count; i++)
debug("isTrusted?");
{
    if (findKnownProcessingUnit(id) > -1)
if (llList2Key(unitObjects, i) == NULL_KEY)
    {
{
        return TRUE;
key currentID = llList2Key(unitObjectTrusted, i);
    }
if (id == currentID)
    debug("not known");
{
if (llGetUnixTime() < llList2Integer(unitObjectTrustedTimeout, i))
{
return pos;
}
}
}
}


return -1;
    integer count = llGetListLength(units);
    integer i;
    for (i = 0; i <= count; i++)
    {
        key currentID = llList2Key(trustedObjects, i);
        if (id == currentID)
        {
            if (llGetUnixTime() < llList2Integer(trustedTimeout, i))
            {
                debug("known trust index: " + (string) i);
                return TRUE;
            }
        }
    }
    debug("untrusted");
    return FALSE;
}
}


Line 150: Line 162:
integer findProcessingUnit(key id)
integer findProcessingUnit(key id)
{
{
integer pos = findKnownProcessingUnit(id);
    integer pos = findKnownProcessingUnit(id);
if (pos > -1)
    if (pos > -1)
{
    {
return pos;
        return pos;
}
    }


pos = llListFindList(unitObjects, [NULL_KEY]);
    pos = llListFindList(unitObjects, [NULL_KEY]);
if (pos < 0)
    if (pos < 0)
{
    {
llOwnerSay("Sorry there is currently no processing unit available.");
        llOwnerSay("Sorry there is currently no processing unit available.");
}
    }
return pos;
    return pos;
}
}


Line 168: Line 180:
garbageCollection()
garbageCollection()
{
{
integer count = llGetListLength(units);
    // clear objects that are out of range
integer i;
    integer count = llGetListLength(units);
for (i = 0; i < count; i++)
    integer i;
{  
    for (i = 0; i < count; i++)
key id = llList2Key(unitObjects, i);
    {
if (id)
        key id = llList2Key(unitObjects, i);
{
        if (id)
if (!isObjectNear(id))
        {
{
            if (!isObjectNear(id))
debug("Object id=" + (string) id + " is out of range, releaseing.");
            {
llMessageLinked(llList2Integer(units, i), DELEGATE_PROCESSING_LINK_CHANNEL, "release,gc,!release", id);
                debug("Object id=" + (string) id + " is out of range, releaseing.");
unitObjects = llListReplaceList(unitObjects, [NULL_KEY], i, i);
                llMessageLinked(llList2Integer(units, i), DELEGATE_PROCESSING_LINK_CHANNEL, "release,gc,!release", id);
removePermissionDialogListener(i, TRUE);
                unitObjects = llListReplaceList(unitObjects, [NULL_KEY], i, i);
}
                removePermissionDialogListener(i, TRUE);
}
            }
}
        }
    }
 
    // clear force sit trust list
    count = llGetListLength(trustedObjects);
    integer deleteTo = -1;
    for (i = 0; i < count; i++)
    {
        integer time = llList2Integer(trustedTimeout, i);
        if (time < llGetUnixTime())
        {
            deleteTo = i;
        }
    }
   
    if (deleteTo > -1)
    {
        trustedObjects = llDeleteSubList(trustedObjects, 0, i);
        trustedTimeout = llDeleteSubList(trustedTimeout, 0, i);
    }
}
}


Line 189: Line 220:
integer inAnySession()
integer inAnySession()
{
{
garbageCollection();
    garbageCollection();
integer count = llGetListLength(units);
    integer count = llGetListLength(units);
integer i;
    integer i;
for (i = 0; i < count; i++)
    for (i = 0; i < count; i++)
{  
    {
key id = llList2Key(unitObjects, i);
        key id = llList2Key(unitObjects, i);
if (id)
        if (id)
{
        {
return TRUE;
            return TRUE;
}
        }
}
    }
return FALSE;
    return FALSE;
}
}


Line 207: Line 238:
fillUnitLists()
fillUnitLists()
{
{
unitObjects = [];
    unitObjects = [];
unitPendingTimeout = [];
    unitPendingTimeout = [];
unitObjectTrusted = [];
    trustedObjects = [];
unitObjectTrustedTimeout = [];
    trustedTimeout = [];
unitPendingChannel = [];
    unitPendingChannel = [];
unitPendingListener = [];
    unitPendingListener = [];
unitPendingTimeout = [];
    unitPendingTimeout = [];


integer count = llGetListLength(units);
    integer count = llGetListLength(units);
integer i;
    integer i;
for (i = 0; i < count; i++)
    for (i = 0; i < count; i++)
{
    {
unitObjects += [NULL_KEY];
        unitObjects += [NULL_KEY];
unitObjectTrusted += [NULL_KEY];
        unitPendingTimeout += [0];
unitObjectTrustedTimeout += [0];
        unitPendingChannel += [-1];
unitPendingTimeout += [0];
        unitPendingListener += [-1];
unitPendingChannel += [-1];
    }
unitPendingListener += [-1];
}
}
}


Line 232: Line 261:
// ---------------------------------------------------
// ---------------------------------------------------


 
// acknowledge or reject
// acknowledge or reject
ack(string cmd_id, key id, string cmd, string ack)
ack(string cmd_id, key id, string cmd, string ack)
Line 239: Line 268:
}
}


// cmd begins with a '@'  
// cmd begins with a '@'
sendRLCmd(string cmd)
sendRLCmd(string cmd)
{
{
Line 316: Line 345:


// Is this a simple request for information or a meta command like !release?
// Is this a simple request for information or a meta command like !release?
integer isSimpleRequest()  
integer isSimpleRequest()
{
{
     integer len = llGetListLength(currentMessageCommandList);
     integer len = llGetListLength(currentMessageCommandList);
Line 338: Line 367:
integer isSimpleAtomicCommand(integer i)
integer isSimpleAtomicCommand(integer i)
{
{
string cmd = llList2String(currentMessageCommandList, i);
    string cmd = llList2String(currentMessageCommandList, i);
cmd = llToLower(llStringTrim(cmd, STRING_TRIM));  
    cmd = llToLower(llStringTrim(cmd, STRING_TRIM));
     // check right hand side of the "=" - sign
     // check right hand side of the "=" - sign
     integer index = llSubStringIndex (cmd, "=");
     integer index = llSubStringIndex (cmd, "=");
     if (index > -1) // there is a "="  
     if (index > -1) // there is a "="
     {
     {
         // check for a number after the "="
         // check for a number after the "="
Line 362: Line 391:
     {
     {
         return llGetSubString(cmd, 0, 11) == "!visionclear"
         return llGetSubString(cmd, 0, 11) == "!visionclear"
        || llGetSubString(cmd, 0, 6) != "!vision";
            || llGetSubString(cmd, 0, 6) != "!vision";
     }
     }


Line 374: Line 403:
         return TRUE;
         return TRUE;
     }
     }
 
 
     // this one is not "simple".
     // this one is not "simple".
     return FALSE;
     return FALSE;
Line 398: Line 427:




// verifies the permission. This includes mode  
// verifies the permission. This includes mode
// (off, permission, auto) of the relay and the
// (off, permission, auto) of the relay and the
// identity of the object (owned by parcel people).
// identity of the object (owned by parcel people).
Line 432: Line 461:
     if (mode == MODE_ASK || !trustworthy)
     if (mode == MODE_ASK || !trustworthy)
     {
     {
    debug("asking");
        debug("asking");
integer unit = findProcessingUnit(id);
        integer unit = findProcessingUnit(id);
if (unit < 0)
        if (unit < 0)
{
        {
return FALSE;
            return FALSE;
}
        }
         integer pendingTime = llGetUnixTime() + PERMISSION_DIALOG_TIMEOUT;
         integer pendingTime = llGetUnixTime() + PERMISSION_DIALOG_TIMEOUT;
unitObjects = llListReplaceList(unitObjects, [id], unit, unit);
        unitObjects = llListReplaceList(unitObjects, [id], unit, unit);
unitPendingTimeout = llListReplaceList(unitPendingTimeout, [pendingTime], unit, unit);
        unitPendingTimeout = llListReplaceList(unitPendingTimeout, [pendingTime], unit, unit);


    execute(id, DELEGATE_PENDING_LINK_CHANNEL);
        execute(id, DELEGATE_PENDING_LINK_CHANNEL);


         if (llKey2Name(llGetOwnerKey(id)) != "")
         if (llKey2Name(llGetOwnerKey(id)) != "")
Line 452: Line 481:
         vector pos = llList2Vector(temp, 0);
         vector pos = llList2Vector(temp, 0);
         string controller = getControllerName();
         string controller = getControllerName();
       
 
         string text = name  
         string text = name
             + " at <" + (string) ((integer) pos.x)
             + " at <" + (string) ((integer) pos.x)
             + ", " + (string) ((integer) pos.y)
             + ", " + (string) ((integer) pos.y)
Line 461: Line 490:
         {
         {
             text = text + " operated by " + controller;
             text = text + " operated by " + controller;
         }      
         }
         text = text + " would like to control your viewer." + warning + "\n\nDo you accept ?";
         text = text + " would like to control your viewer." + warning + "\n\nDo you accept ?";


askForPermission(text, unit, id);
        askForPermission(text, unit, id);
         return FALSE;
         return FALSE;
     }
     }
Line 473: Line 502:
integer getUniqueDialogChannel()
integer getUniqueDialogChannel()
{
{
integer channel;
    integer channel;
do
    do
{
    {
channel = (integer) llFrand(DIALOG_MAX_CHANNEL) + DIALOG_BASE_CHANNEL;
        channel = (integer) llFrand(DIALOG_MAX_CHANNEL) + DIALOG_BASE_CHANNEL;
}
    }
while (llListFindList(unitPendingChannel, [channel]) > -1);
    while (llListFindList(unitPendingChannel, [channel]) > -1);
return channel;
    return channel;
}
}


askForPermission(string text, integer unit, key id)
askForPermission(string text, integer unit, key id)
{
{
 
     debug("Asking for permission isNotEmbedded=" + (string) isNotEmbedded);
     debug("Asking for permission isNotEmbedded=" + (string) isNotEmbedded);
     integer channel = getUniqueDialogChannel();
     integer channel = getUniqueDialogChannel();


unitPendingChannel = llListReplaceList(unitPendingChannel, [channel], unit, unit);
    unitPendingChannel = llListReplaceList(unitPendingChannel, [channel], unit, unit);
unitPendingTimeout = llListReplaceList(unitPendingTimeout, [llGetUnixTime() + PERMISSION_DIALOG_TIMEOUT], unit, unit);
    unitPendingTimeout = llListReplaceList(unitPendingTimeout, [llGetUnixTime() + PERMISSION_DIALOG_TIMEOUT], unit, unit);
   
 
     if (isNotEmbedded)
     if (isNotEmbedded)
     {
     {
    integer handle = llListen(channel, "", llGetOwner(), "");
        integer handle = llListen(channel, "", llGetOwner(), "");
unitPendingListener = llListReplaceList(unitPendingListener, [handle], unit, unit);
        unitPendingListener = llListReplaceList(unitPendingListener, [handle], unit, unit);
         llDialog (llGetOwner(), text, ["Yes", "No"], channel);
         llDialog (llGetOwner(), text, ["Yes", "No"], channel);
     }
     }
Line 500: Line 529:
     llSetTimerEvent(PERMISSION_DIALOG_TIMEOUT);
     llSetTimerEvent(PERMISSION_DIALOG_TIMEOUT);
     debug("Asking for permission");
     debug("Asking for permission");
 
}
}


Line 523: Line 552:
}
}


 
// lift all the restrictions (called by turning the relay off)
// lift all the restrictions (called by turning the relay off)
releaseRestrictions()
releaseRestrictions()
{
{
debug("releasing all restrictions");
    debug("releasing all restrictions");
integer count = llGetListLength(units);
    integer count = llGetListLength(units);
integer i;
    integer i;
for (i = 0; i < count; i++)
    for (i = 0; i < count; i++)
{  
    {
key id = llList2Key(unitObjects, i);
        key id = llList2Key(unitObjects, i);
if (id)
        if (id)
{
        {
llMessageLinked(llList2Integer(units, i), DELEGATE_PROCESSING_LINK_CHANNEL, "release,x,!release", id);
            llMessageLinked(llList2Integer(units, i), DELEGATE_PROCESSING_LINK_CHANNEL, "release,x,!release", id);
}
        }
}
    }


     sendRLCmd(RVL_COMMAND_END);
     sendRLCmd(RVL_COMMAND_END);
fillUnitLists();
    fillUnitLists();
}
}


Line 547: Line 576:
integer isPending(key id)
integer isPending(key id)
{
{
integer unit = findKnownProcessingUnit(id);
    integer unit = findKnownProcessingUnit(id);
return llList2Integer(unitPendingTimeout, unit) > llGetUnixTime();
    return llList2Integer(unitPendingTimeout, unit) > llGetUnixTime();
}
}


Line 573: Line 602:
     }
     }


garbageCollection();
    garbageCollection();


     if (!isObjectNear(id))  
     if (!isObjectNear(id))
     {
     {
         handleCommandsWhichAreAcceptedOutOfRange(id);
         handleCommandsWhichAreAcceptedOutOfRange(id);
Line 581: Line 610:
     }
     }


     if (findKnownProcessingUnit(id) < 0)
     if (!isTrusted(id))
     {
     {
         debug("asking for permission because source is NULL_KEY");
         debug("asking for permission because source is NULL_KEY");
Line 590: Line 619:
     }
     }


if (isPending(id))
    if (isPending(id))
{
    {
    execute(id, DELEGATE_PENDING_LINK_CHANNEL);
        execute(id, DELEGATE_PENDING_LINK_CHANNEL);
}
    }
     else
     else
     {
     {
    execute(id, DELEGATE_PROCESSING_LINK_CHANNEL);
        execute(id, DELEGATE_PROCESSING_LINK_CHANNEL);
sendRLCmd(RVL_COMMAND_START);
        sendRLCmd(RVL_COMMAND_START);
     }
     }
}
}
Line 603: Line 632:
execute(key id, integer channel)
execute(key id, integer channel)
{
{
integer unit = findProcessingUnit(id);
    integer unit = findProcessingUnit(id);
if (unit < 0)
    if (unit < 0)
{
    {
return;
        return;
}
    }
debug("currentMessage: " + currentMessage);
    debug("delegating command to unit " + (string) unit + " for object " + (string) id);
llMessageLinked(llList2Integer(units, unit), channel, currentMessage, id);
    unitObjects = llListReplaceList(unitObjects, [id], unit, unit);
    llMessageLinked(llList2Integer(units, unit), channel, currentMessage, id);
}
}


Line 632: Line 662:
processDialogResponse(integer channel)
processDialogResponse(integer channel)
{
{
debug("processDialogResponse on #" + (string) channel + ": " + currentMessage);
    debug("processDialogResponse on #" + (string) channel + ": " + currentMessage);
integer unit = llListFindList(unitPendingChannel, [channel]);
    integer unit = llListFindList(unitPendingChannel, [channel]);
if (unit < 0)
    if (unit < 0)
{
    {
debug("Unknown channel: " + (string) channel);
        debug("Unknown channel: " + (string) channel);
return;
        return;
}
    }
removePermissionDialogListener(unit, FALSE);
    removePermissionDialogListener(unit, FALSE);


     if (currentMessage == "Yes") // pending request authorized => process it
     if (currentMessage == "Yes") // pending request authorized => process it
     {
     {
llMessageLinked(llList2Integer(units, unit), DELEGATE_GO_LINK_CHANNEL, "", llList2Key(unitObjects, unit));
        llMessageLinked(llList2Integer(units, unit), DELEGATE_GO_LINK_CHANNEL, "", llList2Key(unitObjects, unit));
sendRLCmd(RVL_COMMAND_START);
        sendRLCmd(RVL_COMMAND_START);
     }
     }
     else
     else
     {
     {
llMessageLinked(llList2Integer(units, unit), DELEGATE_CLEAR_PENDING_LINK_CHANNEL, "", llList2Key(unitObjects, unit));
        llMessageLinked(llList2Integer(units, unit), DELEGATE_CLEAR_PENDING_LINK_CHANNEL, "", llList2Key(unitObjects, unit));
unitObjects = llListReplaceList(unitObjects, [NULL_KEY], unit, unit);
        unitObjects = llListReplaceList(unitObjects, [NULL_KEY], unit, unit);
     }
     }
}
}




 
// ---------------------------------------------------
// ---------------------------------------------------
//            initialisation and login handling
//            initialisation and login handling
// ---------------------------------------------------
// ---------------------------------------------------
 
init() {
init() {
     sendRLCmd("@clear");
     sendRLCmd("@clear");
     llOwnerSay("Waiting for plugins to initialize...");
     llOwnerSay("Waiting for plugins to initialize...");
      
 
     // lookin for processing units
     integer count = llGetNumberOfPrims();
     integer count = llGetNumberOfPrims();
     integer i;
     integer i;
     for (i = 1; i <= count; i++)
     for (i = 1; i <= count; i++)
     {
     {
    if (llSubStringIndex(llGetLinkName(i), "Relay Unit") == 0)
        if (llSubStringIndex(llGetLinkName(i), "Relay Unit") == 0)
    {
        {
    units += i;
            units += i;
    }
        }
     }
     }
fillUnitLists();
    fillUnitLists();
      
 
     // wait some more so that it is ensured that all scripts had a chance to setup there event queue
     llSleep(5);
     llSleep(5);


     mode = MODE_ASK;
     mode = MODE_ASK;
     llListen(RLVRS_CHANNEL, "", "", "");
     llListen(RLVRS_CHANNEL, "", "", "");


    // check that we are not compiled non-mono
     if (llGetFreeMemory() < 10000)
     if (llGetFreeMemory() < 10000)
     {
     {
         llSay(0, llKey2Name(llGetOwner()) + ", your relay is having very little free script memory and is likly to crash during use. Please verify that it was compiled using Mono.");
         llSay(0, llKey2Name(llGetOwner()) + ", your relay is having very little free script memory and is likly to crash during use. Please verify that it was compiled using Mono.");
     }
     }
    // tell other scripts about the reset
     llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "init", (key) ((string) RLVRS_PROTOCOL_VERSION));
     llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "init", (key) ((string) RLVRS_PROTOCOL_VERSION));


Line 720: Line 754:
             return;
             return;
         }
         }
         mode = ((integer) ((string) id)) % 3;  
        integer oldMode = mode;
         showModeDescription();
         mode = ((integer) ((string) id)) % 3;
         if (oldMode != mode)
        {
            showModeDescription();
        }
         return;
         return;
     }
     }
Line 731: Line 769:
     }
     }


if (message == "debug")
    if (message == "debug")
{
    {
llOwnerSay("--------------------------------------------------------");
        llOwnerSay("--------------------------------------------------------");
llOwnerSay("Free Memory: " + (string) llGetFreeMemory());
        llOwnerSay("Free Memory: " + (string) llGetFreeMemory());
llOwnerSay("units: "+ llList2CSV(units));
        llOwnerSay("units: "+ llList2CSV(units));
llOwnerSay("unitObjects: "+ llList2CSV(unitObjects));
        llOwnerSay("unitObjects: "+ llList2CSV(unitObjects));
llOwnerSay("unitObjectTrusted: "+ llList2CSV(unitObjectTrusted));
        llOwnerSay("trustedObjects: "+ llList2CSV(trustedObjects));
llOwnerSay("unitObjectTrustedTimeout: "+ llList2CSV(unitObjectTrustedTimeout));
        llOwnerSay("trustedTimeout: "+ llList2CSV(trustedTimeout));
llOwnerSay("unitPendingTimeout: "+ llList2CSV(unitPendingTimeout));
        llOwnerSay("unitPendingTimeout: "+ llList2CSV(unitPendingTimeout));
llOwnerSay("unitPendingChannel: "+ llList2CSV(unitPendingChannel));
        llOwnerSay("unitPendingChannel: "+ llList2CSV(unitPendingChannel));
llOwnerSay("unitPendingListener: "+ llList2CSV(unitPendingListener));
        llOwnerSay("unitPendingListener: "+ llList2CSV(unitPendingListener));
}
    }
}
}


Line 748: Line 786:
processFeedbackFromUnits(integer sender, key id, string message)
processFeedbackFromUnits(integer sender, key id, string message)
{
{
integer unit = llListFindList(units, [sender]);
    integer unit = llListFindList(units, [sender]);
if (unit < 0)
    if (unit < 0)
{
    {
debug("Got feedback from unkown unit");
        debug("Got feedback from unkown unit");
return;
        return;
}
    }
 
if (message == "forcesit")
    if (message == "forcesit")
{
    {
unitObjectTrusted = llListReplaceList(unitObjectTrusted, [id], unit, unit);
        trustedObjects += [id];
unitObjectTrustedTimeout = llListReplaceList(unitObjectTrustedTimeout, [llGetUnixTime() + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT], unit, unit);
        trustedTimeout += [llGetUnixTime() + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT];
}
    }
else if (message == "handover")
    else if (message == "handover")
{
    {
unitObjects = llListReplaceList(unitObjects, [id], unit, unit);
        unitObjects = llListReplaceList(unitObjects, [id], unit, unit);
}
    }
else if ((message == "pendingrelease") || (message == "release"))
    else if ((message == "pendingrelease") || (message == "release"))
{
    {
unitObjects = llListReplaceList(unitObjects, [NULL_KEY], unit, unit);
        unitObjects = llListReplaceList(unitObjects, [NULL_KEY], unit, unit);
removePermissionDialogListener(unit, TRUE);
        removePermissionDialogListener(unit, TRUE);
if (!inAnySession())
        if (!inAnySession())
{
        {
sendRLCmd(RVL_COMMAND_END);
            sendRLCmd(RVL_COMMAND_END);
}
        }
}
    }
}
}


// removes a listener and its information for a request permissiong dialog
// removes a listener and its information for a request permissiong dialog
removePermissionDialogListener(integer unit, integer clear)
removePermissionDialogListener(integer unit, integer clear)
{
{
integer listener = llList2Integer(unitPendingListener, unit);
    integer listener = llList2Integer(unitPendingListener, unit);
if (listener > -1)
    if (listener > -1)
{
    {
llListenRemove(listener);
        llListenRemove(listener);
unitPendingListener = llListReplaceList(unitPendingListener, [-1], unit, unit);
        unitPendingListener = llListReplaceList(unitPendingListener, [-1], unit, unit);
}
    }
unitPendingTimeout = llListReplaceList(unitPendingTimeout, [0], unit, unit);
    unitPendingTimeout = llListReplaceList(unitPendingTimeout, [0], unit, unit);
integer channel = llList2Integer(unitPendingChannel, unit);
    integer channel = llList2Integer(unitPendingChannel, unit);
if ((channel > -1) && clear)
    if ((channel > -1) && clear)
{
    {
llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "dialogtimeout", (key) ((string) channel));
        llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "dialogtimeout", (key) ((string) channel));
llMessageLinked(llList2Integer(units, unit), DELEGATE_CLEAR_PENDING_LINK_CHANNEL, "", llList2Key(unitObjects, unit));
        llMessageLinked(llList2Integer(units, unit), DELEGATE_CLEAR_PENDING_LINK_CHANNEL, "", llList2Key(unitObjects, unit));
unitObjects = llListReplaceList(unitObjects, [NULL_KEY], unit, unit);
        unitObjects = llListReplaceList(unitObjects, [NULL_KEY], unit, unit);
}
    }
unitPendingChannel = llListReplaceList(unitPendingChannel, [-1], unit, unit);
    unitPendingChannel = llListReplaceList(unitPendingChannel, [-1], unit, unit);
}
}


// removes all listeners
// removes all listeners
removeAllPermissionDialogListener()
removeAllPermissionDialogListener()
{
{
integer count = llGetListLength(units);
    integer count = llGetListLength(units);
integer i;
    integer i;
for (i = 0; i < count; i++)
    for (i = 0; i < count; i++)
{
    {
removePermissionDialogListener(i, TRUE);
        removePermissionDialogListener(i, TRUE);
}
    }
}
}


Line 841: Line 879:
         init();
         init();
     }
     }
 
     on_rez(integer start_param)
     on_rez(integer start_param)
     {
     {
Line 848: Line 886:


     // attach(NULL_KEY) is not executed in child prims on detach, but only hours later on reattach
     // attach(NULL_KEY) is not executed in child prims on detach, but only hours later on reattach
attach(key id)
    attach(key id)
{
    {
         if (id == NULL_KEY)
         if (id == NULL_KEY)
{
        {
integer count = llGetListLength(units);
            integer count = llGetListLength(units);
integer i;
            integer i;
for (i = 0; i < count; i++)
            for (i = 0; i < count; i++)
{  
            {
key object = llList2Key(unitObjects, i);
                key object = llList2Key(unitObjects, i);
if (object)
                if (object)
{
                {
ack("release", object, "!release", "ok");
                    ack("release", object, "!release", "ok");
}
                }
}
            }
}
        }
}
    }


     listen(integer channel, string name, key id, string message)
     listen(integer channel, string name, key id, string message)
     {
     {
currentMessageToken = [];
        currentMessageToken = [];
currentMessageCommands = "";
        currentMessageCommands = "";
currentMessageCommandList = [];
        currentMessageCommandList = [];


currentMessage = message;
        currentMessage = message;
message = ""; // free memory
        message = ""; // free memory


         if (channel==RLVRS_CHANNEL)
         if (channel==RLVRS_CHANNEL)
Line 880: Line 918:
         else if (channel >= DIALOG_BASE_CHANNEL)
         else if (channel >= DIALOG_BASE_CHANNEL)
         {
         {
        if (id == llGetOwner())
            if (id == llGetOwner())
        {
            {
            processDialogResponse(channel);
                processDialogResponse(channel);
        }
            }
         }
         }
     }
     }
   
 
     link_message(integer sender, integer channel, string message, key id)
     link_message(integer sender, integer channel, string message, key id)
     {
     {
currentMessageToken = [];
        currentMessageToken = [];
currentMessageCommands = "";
        currentMessageCommands = "";
currentMessageCommandList = [];
        currentMessageCommandList = [];
currentMessage = message;
        currentMessage = message;


         if (channel == CMD_LINK_CHANNEL)
         if (channel == CMD_LINK_CHANNEL)
Line 900: Line 938:
         else if (channel == MANAGER_FREEDBACK_LINK_CHANNEL)
         else if (channel == MANAGER_FREEDBACK_LINK_CHANNEL)
         {
         {
        processFeedbackFromUnits(sender, id, message);
            processFeedbackFromUnits(sender, id, message);
         }
         }
         else if (channel == INTERPRIM_CHANNEL)
         else if (channel == INTERPRIM_CHANNEL)
         {
         {
handleTouch(id);
            handleTouch(id);
         }
         }
         else if (channel >= DIALOG_BASE_CHANNEL)
         else if (channel >= DIALOG_BASE_CHANNEL)
         {
         {
        processDialogResponse(channel);
            processDialogResponse(channel);
         }
         }
     }
     }
Line 914: Line 952:
     touch_start(integer num_detected)
     touch_start(integer num_detected)
     {
     {
    handleTouch(llDetectedKey(0));
        handleTouch(llDetectedKey(0));
     }
     }


     changed(integer change)
     changed(integer change)
     {
     {
         if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP))  
         if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP))
         {
         {
             llResetScript();
             llResetScript();
Line 925: Line 963:
     }
     }


timer()
    timer()
{
    {
removeAllPermissionDialogListener();
        removeAllPermissionDialogListener();
}
    }
}
}
</lsl>
</lsl>
Line 944: Line 982:
// While this script used to be based on Marine's sample implementations it was
// While this script used to be based on Marine's sample implementations it was
// so heavily modified and extended that there is little of the original code
// so heavily modified and extended that there is little of the original code
// remaining. So please do not bother Marine with any problems but report them to  
// remaining. So please do not bother Marine with any problems but report them to
// https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Bugs_and_Pendings_Features
// https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Bugs_and_Pendings_Features
//
//
Line 951: Line 989:
// Chloe1982 Constantine, Ilana Debevec, Kitty Barnett and many others.
// Chloe1982 Constantine, Ilana Debevec, Kitty Barnett and many others.
//
//
// Many thanks to she who started it all ... Marine Kelley.  
// Many thanks to she who started it all ... Marine Kelley.
//
//
// This script is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness  
// This script is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness
// or performance. It may be distributed in its full source code with this header and  
// or performance. It may be distributed in its full source code with this header and
// disclaimer and is not to be sold without permission. Optionally it may be distributed
// disclaimer and is not to be sold without permission. Optionally it may be distributed
// in a 'no mod' form within Second Life™ PROVIDED that either a link to the full source
// in a 'no mod' form within Second Life™ PROVIDED that either a link to the full source
// IS provided (ie. this page or a .zip or .rar) within the object the code is contained  
// IS provided (ie. this page or a .zip or .rar) within the object the code is contained
// AND/OR a off-world contact point (ie. email) that the source code may be requested from.
// AND/OR a off-world contact point (ie. email) that the source code may be requested from.
// This somewhat unusual (no-mod) distribution is allowed to maintain the integrity and
// This somewhat unusual (no-mod) distribution is allowed to maintain the integrity and
Line 973: Line 1,011:
//                    Constants
//                    Constants
// ---------------------------------------------------
// ---------------------------------------------------
 
integer RLVRS_PROTOCOL_VERSION = 1040; // version of the protocol, stated on the specification page
integer RLVRS_PROTOCOL_VERSION = 1040; // version of the protocol, stated on the specification page
string RLVRS_IMPL_VERSION = "Based on Maike's Implemenation 1.040.1"; // version of the implementation for debugging
string RLVRS_IMPL_VERSION = "Based on Maike's Implemenation 1.040.1"; // version of the implementation for debugging
 
string PREFIX_RL_COMMAND = "@";
string PREFIX_RL_COMMAND = "@";
string PREFIX_METACOMMAND = "!";
string PREFIX_METACOMMAND = "!";


integer RLVRS_CHANNEL  = -1812221819;  // RLVRS in numbers
integer RLVRS_CHANNEL  = -1812221819;  // RLVRS in numbers
integer RLV_REFUSED_LINK_CHANNEL =  -1373421311;
integer STATUS_LINK_CHANNEL = -1373421300;
integer STATUS_LINK_CHANNEL = -1373421300;
integer RLV_LINK_CHANNEL = -1373421301;
integer RLV_LINK_CHANNEL = -1373421301;
Line 1,052: Line 1,091:
// ---------------------------------------------------
// ---------------------------------------------------


 
// acknowledge or reject
// acknowledge or reject
ack(string cmd_id, key id, string cmd, string ack)
ack(string cmd_id, key id, string cmd, string ack)
Line 1,059: Line 1,098:
}
}


// cmd begins with a '@'  
// cmd begins with a '@'
sendRLCmd(string cmd)
sendRLCmd(string cmd)
{
{
Line 1,095: Line 1,134:
     }
     }


if (pendingMessage == "")
    if (pendingMessage == "")
{
    {
pendingMessage = currentMessage;
        pendingMessage = currentMessage;
}
    }
else
    else
{
    {
    pendingMessage = pendingMessage + "|" + currentMessageCommands;
        pendingMessage = pendingMessage + "|" + currentMessageCommands;
}
    }
}
}


Line 1,134: Line 1,173:
execute(key id)
execute(key id)
{
{
currentMessage = "";
    currentMessage = "";
currentMessageCommands = "";
    currentMessageCommands = "";
     string cmd_id = llList2String(currentMessageToken, 0); // CheckAttach
     string cmd_id = llList2String(currentMessageToken, 0); // CheckAttach
     currentMessageToken = [];
     currentMessageToken = [];
Line 1,168: Line 1,207:
}
}


// executes a command for the restrained life viewer  
// executes a command for the restrained life viewer
// with some additinal magic like book keeping
// with some additinal magic like book keeping
executeRLVCommand(string cmd_id, string id, string command)
executeRLVCommand(string cmd_id, string id, string command)
Line 1,182: Line 1,221:
     string behavName = llList2String (tokens, 0);  // @sit
     string behavName = llList2String (tokens, 0);  // @sit
     string option = llList2String (tokens, 1);    // <uuid>
     string option = llList2String (tokens, 1);    // <uuid>
   
 
     debug("executeRLVCommand: behav=!" + behav + "! param=!" + param + "!");
     debug("executeRLVCommand: behav=!" + behav + "! param=!" + param + "!");


     if (isQuery(behavName) && ((integer) param <= 0))
     if (isQuery(behavName) && ((integer) param <= 0))
     {
     {
         // if this is a query command, don't accept public chat,  
         // if this is a query command, don't accept public chat,
         // negative numbers, or pseudo channels like
         // negative numbers, or pseudo channels like
         // "n", "add", "y", "rem", "force",  ...
         // "n", "add", "y", "rem", "force",  ...
Line 1,202: Line 1,241:
                 sendRLCmd(RVL_COMMAND_START);
                 sendRLCmd(RVL_COMMAND_START);
                 llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id);
                 llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id);
             }  
             }
             restrictions += [behav];
             restrictions += [behav];
         }
         }
Line 1,214: Line 1,253:
             return;
             return;
         }
         }
       
 
         if (ind > -1)  
         if (ind > -1)
         {
         {
             restrictions = llDeleteSubList (restrictions, ind, ind);
             restrictions = llDeleteSubList (restrictions, ind, ind);
Line 1,230: Line 1,269:
         {
         {
             debug("rejected force-command: behav=!" + behav + "! behavName=!" + behavName + "!");
             debug("rejected force-command: behav=!" + behav + "! behavName=!" + behavName + "!");
            llMessageLinked(LINK_SET, RLV_REFUSED_LINK_CHANNEL, behav, source);
             ack(cmd_id, id, command, "ko");
             ack(cmd_id, id, command, "ko");
             return;
             return;
Line 1,259: Line 1,299:
// check for @clear
// check for @clear
// Note: @clear MUST NOT be used because the restrictions will be reapplied on next login
// Note: @clear MUST NOT be used because the restrictions will be reapplied on next login
// (but we need this check here because "!release|@clear" is a BROKEN attempt to work around
// if implemented straight forward in the relay. So we need to treat @clear as !release
// a bug in the first relay implementation. You should refuse to use relay versions < 1013
// instead.)
workaroundForAtClear(string command)
workaroundForAtClear(string command)
{
{
Line 1,283: Line 1,321:
             {
             {
                 debug("clearing lastForceSitDestination");
                 debug("clearing lastForceSitDestination");
    llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "forcesit", NULL_KEY);
                llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "forcesit", NULL_KEY);
                 lastForceSitDestination = NULL_KEY;
                 lastForceSitDestination = NULL_KEY;
             }
             }
         }
         }
     }
     }
   
 
     if (param != "force")
     if (param != "force")
     {
     {
Line 1,300: Line 1,338:
     }
     }
     lastForceSitDestination = (key) option;
     lastForceSitDestination = (key) option;
    // TODO: This may be too late in the event queue, after the restrictions are already send.
     llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "forcesit", lastForceSitDestination);
     llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "forcesit", source);
 
     lastForceSitTime = llGetUnixTime();
     lastForceSitTime = llGetUnixTime();
     debug("remembered force sit");
     debug("remembered force sit");
Line 1,312: Line 1,349:
     list tokens = llParseString2List(commandString, ["/"], []);
     list tokens = llParseString2List(commandString, ["/"], []);
     string command = llList2String(tokens, 0);
     string command = llList2String(tokens, 0);
   
 
     if (command == PREFIX_METACOMMAND + "version") // checking relay protocol version
     if (command == PREFIX_METACOMMAND + "version") // checking relay protocol version
     {
     {
Line 1,331: Line 1,368:
         return FALSE;
         return FALSE;
     }
     }
     else if (command == PREFIX_METACOMMAND + "pong") // object is still available for us  
     else if (command == PREFIX_METACOMMAND + "pong") // object is still available for us
     {
     {
         loginWaitingForPong = FALSE;
         loginWaitingForPong = FALSE;
Line 1,337: Line 1,374:
     else if (command == PREFIX_METACOMMAND + "visionclear")
     else if (command == PREFIX_METACOMMAND + "visionclear")
     {
     {
    visionRestricted = FALSE;
        visionRestricted = FALSE;
         ack(cmd_id, id, commandString, "ok");
         ack(cmd_id, id, commandString, "ok");
         if (llGetListLength(restrictions) == 0)
         if (llGetListLength(restrictions) == 0)
         {
         {
             releaseRestrictions();
             releaseRestrictions();
       }  
       }
       llMessageLinked(LINK_SET, GPU_CHANNEL, commandString, source);
       llMessageLinked(LINK_SET, GPU_CHANNEL, commandString, source);
     }
     }
     else if (command == PREFIX_METACOMMAND + "vision")
     else if (command == PREFIX_METACOMMAND + "vision")
     {
     {
    integer attached = llGetAttached();
        integer attached = llGetAttached();
    // TODO: Check  that prim is there  
        // TODO: Check  that prim is there
         if (attached >= ATTACH_HUD_CENTER_2 || attached < ATTACH_HUD_BOTTOM_RIGHT)
         if (attached >= ATTACH_HUD_CENTER_2 || attached < ATTACH_HUD_BOTTOM_RIGHT)
         {
         {
Line 1,354: Line 1,391:
             {
             {
                 llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id);
                 llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id);
            sendRLCmd(RVL_COMMAND_START);
                sendRLCmd(RVL_COMMAND_START);
             }  
             }
        llMessageLinked(LINK_SET, GPU_CHANNEL, commandString, source);
            llMessageLinked(LINK_SET, GPU_CHANNEL, commandString, source);
        ack(cmd_id, id, commandString, "ok");
            ack(cmd_id, id, commandString, "ok");
        visionRestricted = TRUE;
            visionRestricted = TRUE;
        source = id;
            source = id;
         }
         }
         else
         else
         {
         {
        ack(cmd_id, id, commandString, "ko");
            ack(cmd_id, id, commandString, "ko");
         }
         }
     }
     }
     else if (command == PREFIX_METACOMMAND + "who")
     else if (command == PREFIX_METACOMMAND + "who")
     {
     {
    ack(cmd_id, id, commandString, "ok");
        ack(cmd_id, id, commandString, "ok");
     }
     }
     else
     else
Line 1,393: Line 1,430:
         sendRLCmd(RVL_COMMAND_START);
         sendRLCmd(RVL_COMMAND_START);
         llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id);
         llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id);
     }  
     }
     pingWorldObject();
     pingWorldObject();
}
}
Line 1,402: Line 1,439:
     string search = behav + "=";
     string search = behav + "=";


     // don't do the expensive parsing (string operations are very slow in pre-  
     // don't do the expensive parsing (string operations are very slow in pre-
     // mono LSL) in case we can detect fast that this one is not in the list.
     // mono LSL) in case we can detect fast that this one is not in the list.
     if (llSubStringIndex(pendingMessage, search) < 0)
     if (llSubStringIndex(pendingMessage, search) < 0)
Line 1,424: Line 1,461:
                 commands = llDeleteSubList(commands, i, i);
                 commands = llDeleteSubList(commands, i, i);
                 modified = TRUE;
                 modified = TRUE;
             }  
             }
         }
         }
     }
     }
Line 1,436: Line 1,473:
         else
         else
         {
         {
        llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "pendingrelease", source);
            llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "pendingrelease", source);
             clearPendingMessages();
             clearPendingMessages();
         }
         }
Line 1,443: Line 1,480:
}
}


 
// lift all the restrictions (called by !release and by turning the relay off)
// lift all the restrictions (called by !release and by turning the relay off)
releaseRestrictions()
releaseRestrictions()
Line 1,461: Line 1,498:
     llMessageLinked(LINK_SET, GPU_CHANNEL, "!visionclear", source);
     llMessageLinked(LINK_SET, GPU_CHANNEL, "!visionclear", source);
     llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "release", source);
     llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "release", source);
  llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "release", source);
      llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "release", source);
     source = NULL_KEY;
     source = NULL_KEY;
}
}
Line 1,490: Line 1,527:
     debug("listen: " + currentMessage);
     debug("listen: " + currentMessage);
     splitMessage();
     splitMessage();
if (channel == DELEGATE_PENDING_LINK_CHANNEL)
{
gluePendingMessages();
}
else if (channel == DELEGATE_CLEAR_PENDING_LINK_CHANNEL)
{
clearPendingMessages();
}
else if (channel == DELEGATE_GO_LINK_CHANNEL)
{
currentMessage = pendingMessage;
splitMessage();
execute(id);
}
else if (channel == DELEGATE_PROCESSING_LINK_CHANNEL)
{
    debug("Executing: " + (string) source);
    execute(id);
}
else if (channel == CMD_LINK_CHANNEL)
{
    if (currentMessage == "refusedForceCommands")
    {
        refusedForceCommands = llParseString2List((string) id, [","], []);
    }
    else if (currentMessage == "embeddedunit")
    {
        isNotEmbedded = FALSE;
    }
    else if (currentMessage == "debug")
    {
    DEBUG = (integer) ((string) id);
    }
    else if (currentMessage == "listrestrictions")
    {
    if (source)
    {
        list temp = llGetObjectDetails(source, [OBJECT_POS]);
        vector pos = llList2Vector(temp, 0);
        string text = llKey2Name(source) 
            + " at <" + (string) ((integer) pos.x)
            + ", " + (string) ((integer) pos.y)
            + ", " + (string) ((integer) pos.z)
            + "> (" + (string) ((integer) llVecDist(pos, llGetRootPosition())) + "m): ";
    llOwnerSay(text + llList2CSV(restrictions));
    }
    }
}
}


 
    if (channel == DELEGATE_PENDING_LINK_CHANNEL)
    {
// ---------------------------------------------------
        gluePendingMessages();
//            initialisation and login handling
    }
// ---------------------------------------------------
    else if (channel == DELEGATE_CLEAR_PENDING_LINK_CHANNEL)
    {
init() {
        clearPendingMessages();
     sendRLCmd("@clear");
    }
     if (llGetFreeMemory() < 10000)
    else if (channel == DELEGATE_GO_LINK_CHANNEL)
    {
        currentMessage = pendingMessage;
        splitMessage();
        execute(id);
    }
    else if (channel == DELEGATE_PROCESSING_LINK_CHANNEL)
    {
        debug("Executing: " + (string) source);
        execute(id);
    }
    else if (channel == CMD_LINK_CHANNEL)
    {
        if (currentMessage == "refusedForceCommands")
        {
            refusedForceCommands = llParseString2List((string) id, [","], []);
        }
        else if (currentMessage == "embeddedunit")
        {
            isNotEmbedded = FALSE;
        }
        else if (currentMessage == "debug")
        {
            DEBUG = (integer) ((string) id);
        }
        else if (currentMessage == "listrestrictions")
        {
            if (source)
            {
                list temp = llGetObjectDetails(source, [OBJECT_POS]);
                vector pos = llList2Vector(temp, 0);
                string text = llKey2Name(source)
                    + " at <" + (string) ((integer) pos.x)
                    + ", " + (string) ((integer) pos.y)
                    + ", " + (string) ((integer) pos.z)
                    + "> (" + (string) ((integer) llVecDist(pos, llGetRootPosition())) + "m): ";
                llOwnerSay(text + llList2CSV(restrictions));
            }
        }
    }
}
 
 
 
// ---------------------------------------------------
//            initialisation and login handling
// ---------------------------------------------------
 
init() {
     sendRLCmd("@clear");
     if (llGetFreeMemory() < 10000)
     {
     {
         llSay(0, llKey2Name(llGetOwner()) + ", your relay is having very little free script memory and is likly to crash during use. Please verify that it was compiled using Mono.");
         llSay(0, llKey2Name(llGetOwner()) + ", your relay is having very little free script memory and is likly to crash during use. Please verify that it was compiled using Mono.");
Line 1,587: Line 1,624:
     if (source)
     if (source)
     {
     {
pingWorldObject();
        pingWorldObject();
     }
     }
}
}
Line 1,594: Line 1,631:
{
{
     ack("ping", source, "ping", "ping");
     ack("ping", source, "ping", "ping");
timerTickCounter = 0;
    timerTickCounter = 0;
llSetTimerEvent(1.0);
    llSetTimerEvent(1.0);
loginWaitingForPong = TRUE;
    loginWaitingForPong = TRUE;
}
}


Line 1,657: Line 1,694:
         init();
         init();
     }
     }
 
     on_rez(integer start_param)
     on_rez(integer start_param)
     {
     {
Line 1,681: Line 1,718:
     link_message(integer sender, integer channel, string message, key id)
     link_message(integer sender, integer channel, string message, key id)
     {
     {
currentMessageToken = [];
        currentMessageToken = [];
currentMessageCommands = "";
        currentMessageCommands = "";
currentMessageCommandList = [];
        currentMessageCommandList = [];


currentMessage = message;
        currentMessage = message;
message = ""; // free memory
        message = ""; // free memory
debug("link message: " + (string) channel + " message: " + currentMessage + " id=" + (string) id);
        debug("link message: " + (string) channel + " message: " + currentMessage + " id=" + (string) id);
processLinkMessage(channel, id);
        processLinkMessage(channel, id);
     }
     }


     changed(integer change)
     changed(integer change)
     {
     {
         if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP))  
         if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP))
         {
         {
             llResetScript();
             llResetScript();
         }
         }
         if (change & (CHANGED_REGION | CHANGED_TELEPORT))  
         if (change & (CHANGED_REGION | CHANGED_TELEPORT))
         {
         {
    if (loginWaitingForPong)
            if (loginWaitingForPong)
    {
            {
    if (source)
                if (source)
    {
                {
        ack("ping", source, "ping", "ping");
                    ack("ping", source, "ping", "ping");
    }
                }
    }
            }
         }
         }
     }
     }
Line 1,713: Line 1,750:
=== Extended Controller ===
=== Extended Controller ===


This script is totally optional.
This script is totally optional. It add the following things:
* A dialog to do some actions like listing the active objects and giving a help note card
* Buttons to trust the object, owner or group you are controlled by at the moment
* Button to clear the trust list
* adds a "temporary mute" button in the permission query dialog (for objects that spam your every minute until you accept)
* adds support for realkey (if your drag the script and object from one of Marine's items and put it in)
* adds support for struggling (if you drag the Lockable script from one of Marine's items and put it in. Note: That item will of course not work anymore until you put it back in)


<lsl>
<lsl>
Line 1,720: Line 1,763:
// By Maike Short
// By Maike Short
//
//
// Please report bugs at  
// Please report bugs at
// https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Bugs_and_Pendings_Features
// https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Bugs_and_Pendings_Features
//
//
Line 1,727: Line 1,770:
// Chloe1982 Constantine, Ilana Debevec, Kitty Barnett and many others.
// Chloe1982 Constantine, Ilana Debevec, Kitty Barnett and many others.
//
//
// Many thanks to she who started it all ... Marine Kelley.  
// Many thanks to she who started it all ... Marine Kelley.
//
//
// This script is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness  
// This script is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness
// or performance. It may be distributed in its full source code with this header and  
// or performance. It may be distributed in its full source code with this header and
// disclaimer and is not to be sold without permission. Optionally it may be distributed
// disclaimer and is not to be sold without permission. Optionally it may be distributed
// in a 'no mod' form within Second Life™ PROVIDED that either a link to the full source
// in a 'no mod' form within Second Life™ PROVIDED that either a link to the full source
// IS provided (ie. this page or a .zip or .rar) within the object the code is contained  
// IS provided (ie. this page or a .zip or .rar) within the object the code is contained
// AND/OR a off-world contact point (ie. email) that the source code may be requested from.
// AND/OR a off-world contact point (ie. email) that the source code may be requested from.
// This somewhat unusual (no-mod) distribution is allowed to maintain the integrity and
// This somewhat unusual (no-mod) distribution is allowed to maintain the integrity and
Line 1,764: Line 1,807:
list FILTER_BUTTONS = ["Filter Strip", "Filter Sit", "Filter TP"];
list FILTER_BUTTONS = ["Filter Strip", "Filter Sit", "Filter TP"];


list activeSessions = [];
// permission query handling
integer mode = MODE_ASK;
integer mode = MODE_ASK;
list activeSessions = [];
list pending = [];        // strided list: "C"+channel, listener, id
list pending = [];        // strided list: "C"+channel, listener, id
list temporaryMuteList;
list temporaryMuteList;


// filter of force commands
integer filterStrip = FALSE;
integer filterStrip = FALSE;
integer filterSit = FALSE;
integer filterSit = FALSE;
integer filterTP = TRUE;
integer filterTP = TRUE;


// trust handling
list trustedGroups = [];
list trustedGroups = [];
list trustedOwners = [];
list trustedOwners = [];
list trustedObjects = [];
list trustedObjects = [];
// ----------------------------------------------------
//                    Dialog Handling
// ----------------------------------------------------




Line 1,786: Line 1,829:
integer listenHandle = 0;
integer listenHandle = 0;


// shows a dialog
// ----------------------------------------------------
dialog(key id, string message, list buttons)
//            Little Helper Functions
{
// ----------------------------------------------------
    enableDialogListener();
    llDialog(id, message, buttons, dialogChannel);
}


// enable a timed listener for  dialog buttons
debug(string message)
enableDialogListener()
{
{
     dialogChannel = (integer)(llFrand(-1000000000.0) - 1000000000.0);
     if (DEBUG)
     disableDialogListener();
     {
    listenHandle = llListen(dialogChannel, "", "", "");
        llOwnerSay("DEBUG: " + message);
    llSetTimerEvent(120);
}
 
// disable the listener for dialog buttons
disableDialogListener()
{
    if (listenHandle != 0) {
        llListenRemove(listenHandle);
        listenHandle = 0;
     }
     }
    llSetTimerEvent(0);
}
// ----------------------------------------------------
//            Little Helper Functions
// ----------------------------------------------------
debug(string message)
{
if (DEBUG)
{
llOwnerSay("DEBUG: " + message);
}
}
}




// tries to find the needle in the hey-stack case insensitive.
// tries to find the needle in the hey-stack case insensitive.
// But checks that the needle is at least minLength characters long.  
// But checks that the needle is at least minLength characters long.
integer contains(string hey, string needle)
integer contains(string hey, string needle)
{
{
Line 1,841: Line 1,857:
     // prepare hay for easier matching
     // prepare hay for easier matching
     hey = llToLower(llStringTrim(hey, STRING_TRIM));
     hey = llToLower(llStringTrim(hey, STRING_TRIM));
   
 
     integer index = llSubStringIndex(hey, needle);
     integer index = llSubStringIndex(hey, needle);
     return index > -1;
     return index > -1;
Line 1,863: Line 1,879:
     return contains(itemName, targetName);
     return contains(itemName, targetName);
}
}






// ----------------------------------------------------
// ----------------------------------------------------
//               Permission Handling
//                       Dialog
// ----------------------------------------------------
// ----------------------------------------------------




// shows a dialog
dialog(key id, string message, list buttons)
{
    enableDialogListener();
    llDialog(id, message, buttons, dialogChannel);
}


// is this object trusted to execute commands without asking for permission?
// enable a timed listener for dialog buttons
integer isTrusted(key id)
enableDialogListener()
{
{
     if (llListFindList(trustedObjects, [id]) > -1)
    dialogChannel = (integer)(llFrand(-1000000000.0) - 1000000000.0);
     {
    disableDialogListener();
         return TRUE;
    listenHandle = llListen(dialogChannel, "", "", "");
     }
    llSetTimerEvent(120);
 
}
 
// disable the listener for dialog buttons
disableDialogListener()
{
    if (listenHandle != 0) {
        llListenRemove(listenHandle);
        listenHandle = 0;
    }
    llSetTimerEvent(0);
}
 
 
 
// ----------------------------------------------------
//                Permission Handling
// ----------------------------------------------------
 
 
// is this object trusted to execute commands without asking for permission?
integer isTrusted(key id)
{
     if (llListFindList(trustedObjects, [id]) > -1)
     {
         return TRUE;
     }
 
     list temp = llGetObjectDetails(id, [OBJECT_OWNER, OBJECT_GROUP]);
     list temp = llGetObjectDetails(id, [OBJECT_OWNER, OBJECT_GROUP]);
     if (llListFindList(trustedOwners, [llList2Key(temp, 0)]) > -1)
     if (llListFindList(trustedOwners, [llList2Key(temp, 0)]) > -1)
Line 1,966: Line 2,013:
integer inSession()
integer inSession()
{
{
return llGetListLength(activeSessions) > 0;
    return llGetListLength(activeSessions) > 0;
}
}


Line 1,976: Line 2,023:
processBackdoor(key id)
processBackdoor(key id)
{
{
debug("processBackdoor: id=" + (string) id);
    debug("processBackdoor: id=" + (string) id);
if (inSession())
    if (inSession())
{
    {
debug("processBackdoor");
        debug("processBackdoor");
string objects = "";
        string objects = "";
integer count = llGetListLength(activeSessions);
        integer count = llGetListLength(activeSessions);
integer i;
        integer i;
for (i = 0; i < count; i++)
        for (i = 0; i < count; i++)
{
        {
if (i > 0)
            if (i > 0)
{
            {
objects += ", ";
                objects += ", ";
}
            }
objects += llKey2Name(llList2Key(activeSessions, i));
            objects += llKey2Name(llList2Key(activeSessions, i));
}
        }
dialog(id, "\nDo you want to release " + llKey2Name(llGetOwner()) + " from " + objects + "?", ["Yes", "No"]);
        dialog(id, "\nDo you want to release " + llKey2Name(llGetOwner()) + " from " + objects + "?", ["Yes", "No"]);
}
    }
}
}


processDialogResponseByOthers(key id, string message)
processDialogResponseByOthers(key id, string message)
{
{
debug("processDialogResponse: id=" + (string) id + "  message=" + message);
    debug("processDialogResponse: id=" + (string) id + "  message=" + message);
if (message != "Yes")
    if (message != "Yes")
{
    {
return;
        return;
}
    }


debug("processDialogResponse: should free");
    debug("processDialogResponse: should free");
if (inSession())
    if (inSession())
  {
      {
debug("processDialogResponse: unlocked");
        debug("processDialogResponse: unlocked");
  llMessageLinked(LINK_THIS, CMD_LINK_CHANNEL, "unlock", id);
          llMessageLinked(LINK_THIS, CMD_LINK_CHANNEL, "unlock", id);
  activeSessions = [];
          activeSessions = [];
  }
      }
}
}


Line 2,025: Line 2,072:
         return;
         return;
     }
     }
   
 
     debug("inSession(): " + (string) inSession() + " " + llList2CSV(activeSessions));
     debug("inSession(): " + (string) inSession() + " " + llList2CSV(activeSessions));


     list buttons = ["Clear Trust"];
     list buttons = ["Clear Trust"];
       
 
     if (inSession())
     if (inSession())
     {
     {
Line 2,036: Line 2,083:
     else
     else
     {
     {
    if (llGetInventoryType("*RealKey") == INVENTORY_SCRIPT)
        if (llGetInventoryType("*RealKey") == INVENTORY_SCRIPT)
    {
        {
    buttons += ["Real Key...", "Help"];
            buttons += ["Real Key...", "Help"];
    }
        }
    else
        else
    {
        {
    buttons += [" ", "Help"];
            buttons += [" ", "Help"];
    }
        }
     }
     }
   
 
     buttons += FILTER_BUTTONS;
     buttons += FILTER_BUTTONS;


     if (inSession())
     if (inSession())
     {
     {
    if (llGetInventoryType("Lockable") == INVENTORY_SCRIPT)
        if (llGetInventoryType("Lockable") == INVENTORY_SCRIPT)
    {
        {
    buttons += MODE_BUTTONS_STRUGGLE;
            buttons += MODE_BUTTONS_STRUGGLE;
    }
        }
    else
        else
    {
        {
        buttons += MODE_BUTTONS_DISABLED;
            buttons += MODE_BUTTONS_DISABLED;
    }
        }
     }
     }
     else
     else
Line 2,069: Line 2,116:
processDialogResponse(integer channel, key id, string message)
processDialogResponse(integer channel, key id, string message)
{
{
if (channel == dialogChannel)
    if (channel == dialogChannel)
{
    {
disableDialogListener();
        disableDialogListener();
}
    }
if (id == llGetOwner())
    if (id == llGetOwner())
{
    {
processDialogResponseByOwner(channel, id, message);
        processDialogResponseByOwner(channel, id, message);
}
    }
else
    else
{
    {
processDialogResponseByOthers(id, message);
        processDialogResponseByOthers(id, message);
}
    }
}
}


Line 2,123: Line 2,170:
     }
     }


// struggling
    // struggling
     pos = llListFindList(MODE_BUTTONS_STRUGGLE, [message]);
     pos = llListFindList(MODE_BUTTONS_STRUGGLE, [message]);
     if (pos > -1)
     if (pos > -1)
     {
     {
    llMessageLinked(LINK_THIS, 0, "Cmd:" + message, id);
        llMessageLinked(LINK_THIS, 0, "Cmd:" + message, id);
    showDialog(id);
        showDialog(id);
    return;
        return;
     }
     }


Line 2,138: Line 2,185:
     else if (message == "Trust Owner")
     else if (message == "Trust Owner")
     {
     {
    trustOwner();
        trustOwner();
     }
     }
     else if (message == "Trust Object")
     else if (message == "Trust Object")
     {
     {
    trustObject();
        trustObject();
     }
     }
     else if (message == "Clear Trust")
     else if (message == "Clear Trust")
     {
     {
    trustedGroups = [];
        trustedGroups = [];
         trustedObjects = [];
         trustedObjects = [];
         trustedOwners = [];
         trustedOwners = [];
Line 2,154: Line 2,201:
     else if (message == "Help")
     else if (message == "Help")
     {
     {
     // TODO: Search for notecard with substring "help"
     giveHelp(id);
        string notecard = llGetInventoryName(INVENTORY_NOTECARD, 0);
        llGiveInventory(id, notecard);
     }
     }
     else if (message == "Real Key...")
     else if (message == "Real Key...")
     {
     {
    llMessageLinked(LINK_THIS, 11, "*RealKey", id);
        llMessageLinked(LINK_THIS, 11, "*RealKey", id);
     }
     }
     else if (message == "Temp Mute")
     else if (message == "Temp Mute")
     {
     {
  integer index = llListFindList(pending, ["C" + (string) channel]);
          integer index = llListFindList(pending, ["C" + (string) channel]);
if (index > -1)
        if (index > -1)
{
        {
key toMute = llList2Key(pending, index + 2);
            key toMute = llList2Key(pending, index + 2);
        temporaryMuteList += toMute;
            temporaryMuteList += toMute;
        llWhisper(0, "Muting object \"" + llKey2Name(toMute) + "\" until next login.");
            llWhisper(0, "Muting object \"" + llKey2Name(toMute) + "\" until next login.");
        llMessageLinked(LINK_THIS, channel, "No", id);
            llMessageLinked(LINK_THIS, channel, "No", id);
}
        }
    removePendingEntry((string) channel);
        removePendingEntry((string) channel);
     }
     }
     else if (message == "List Objects")
     else if (message == "List Objects")
     {
     {
    llMessageLinked(LINK_SET, CMD_LINK_CHANNEL, "listrestrictions", NULL_KEY);
        llMessageLinked(LINK_SET, CMD_LINK_CHANNEL, "listrestrictions", NULL_KEY);
     }
     }
     else if ((message == "Yes") || (message == "No"))
     else if ((message == "Yes") || (message == "No"))
     {
     {
         llMessageLinked(LINK_THIS, channel, message, id);
         llMessageLinked(LINK_THIS, channel, message, id);
    removePendingEntry((string) channel);
        removePendingEntry((string) channel);
     }
     }
}
}
Line 2,188: Line 2,233:
trustGroup()
trustGroup()
{
{
integer count = llGetListLength(activeSessions);
    integer count = llGetListLength(activeSessions);
integer i;
    integer i;
for (i = 0; i < count; i++)
    for (i = 0; i < count; i++)
{
    {
key object = llList2Key(activeSessions, i);
        key object = llList2Key(activeSessions, i);
    list temp = llGetObjectDetails(object, [OBJECT_GROUP]);
        list temp = llGetObjectDetails(object, [OBJECT_GROUP]);
    key group = llList2Key(temp, 0);
        key group = llList2Key(temp, 0);
    if (group == NULL_KEY)
        if (group == NULL_KEY)
    {
        {
    return;
            return;
    }
        }
    trustedGroups = addOnce(trustedGroups, group);
        trustedGroups = addOnce(trustedGroups, group);
    llOwnerSay("Trusting objects owned by group secondlife:///app/group/" + (string) group + "/about");
        llOwnerSay("Trusting objects owned by group secondlife:///app/group/" + (string) group + "/about");
}
    }
}
}


trustOwner()
trustOwner()
{
{
integer count = llGetListLength(activeSessions);
    integer count = llGetListLength(activeSessions);
integer i;
    integer i;
for (i = 0; i < count; i++)
    for (i = 0; i < count; i++)
{
    {
key object = llList2Key(activeSessions, i);
        key object = llList2Key(activeSessions, i);
    list temp = llGetObjectDetails(object, [OBJECT_OWNER]);
        list temp = llGetObjectDetails(object, [OBJECT_OWNER]);
    key owner = llList2Key(temp, 0);
        key owner = llList2Key(temp, 0);
    trustedOwners = addOnce(trustedOwners, owner);
        trustedOwners = addOnce(trustedOwners, owner);
    llOwnerSay("Trusting objects owned by " + (string) owner + " (" + llKey2Name(owner) + ").");
        llOwnerSay("Trusting objects owned by " + (string) owner + " (" + llKey2Name(owner) + ").");
}
    }
}
}


trustObject()
trustObject()
{
{
integer count = llGetListLength(activeSessions);
    integer count = llGetListLength(activeSessions);
    integer i;
    for (i = 0; i < count; i++)
    {
        key object = llList2Key(activeSessions, i);
        trustedObjects = addOnce(trustedObjects, object);
        llOwnerSay("Trusting object " + (string) object + " called " + llKey2Name(object));
    }
}
 
// give the help notecard
giveHelp(key id)
{
integer count = llGetInventoryNumber(INVENTORY_NOTECARD);
integer i;
integer i;
for (i = 0; i < count; i++)
for (i = 0; i < count; i++)
{
{
key object = llList2Key(activeSessions, i);
        string notecard = llGetInventoryName(INVENTORY_NOTECARD, i);
    trustedObjects = addOnce(trustedObjects, object);
        if (llSubStringIndex(notecard, "Relay") > -1)
    llOwnerSay("Trusting object " + (string) object + " called " + llKey2Name(object));
        {
            llGiveInventory(id, notecard);
        }
}
}
}
}


// removed an entry from the pending list
// removed an entry from the pending list
removePendingEntry(string channel)
removePendingEntry(string channel)
{
{
  integer pos = llListFindList(pending, ["C" + channel]);
      integer pos = llListFindList(pending, ["C" + channel]);
if (pos > -1)
    if (pos > -1)
{
    {
integer handle = llList2Integer(pending, pos + 1);
        integer handle = llList2Integer(pending, pos + 1);
llListenRemove(handle);
        llListenRemove(handle);
pending = llDeleteSubList(pending, pos, pos + 2);
        pending = llDeleteSubList(pending, pos, pos + 2);
}
    }
}
}


Line 2,255: Line 2,314:
     if (channel == MY_OPEN_CHANNEL)
     if (channel == MY_OPEN_CHANNEL)
     {
     {
    if (id == llGetOwner())
        if (id == llGetOwner())
    {
        {
        processOpenText(id, message);
            processOpenText(id, message);
    }
        }
     }
     }
     else
     else
Line 2,279: Line 2,338:
processAsk(key id, string message)
processAsk(key id, string message)
{
{
list tokens = llParseString2List(message, ["|"], []);
    list tokens = llParseString2List(message, ["|"], []);
integer channel = (integer) llList2String(tokens, 0);
    integer channel = (integer) llList2String(tokens, 0);
string text = llList2String(tokens, 1);
    string text = llList2String(tokens, 1);


     if (isTrusted(id))
     if (isTrusted(id))
Line 2,292: Line 2,351:
         {
         {


        integer handle = llListen(channel, "", llGetOwner(), "");
            integer handle = llListen(channel, "", llGetOwner(), "");
             pending += ["C" + (string) channel, handle, id];
             pending += ["C" + (string) channel, handle, id];
             llDialog(llGetOwner(), text, ["Yes", "No", "Temp Mute"], channel);
             llDialog(llGetOwner(), text, ["Yes", "No", "Temp Mute"], channel);
Line 2,298: Line 2,357:
         else
         else
         {
         {
        llMessageLinked(LINK_THIS, channel, "No", id);
            llMessageLinked(LINK_THIS, channel, "No", id);
         }
         }
     }
     }
Line 2,309: Line 2,368:
         // id: the id of the object controlling the relay
         // id: the id of the object controlling the relay
         // sent when a session is started (permissions has already been granted if required.
         // sent when a session is started (permissions has already been granted if required.
    debug("before start inSession(): " + (string) inSession() + " " + llList2CSV(activeSessions));
        debug("before start inSession(): " + (string) inSession() + " " + llList2CSV(activeSessions));
         activeSessions = addOnce(activeSessions, id);
         activeSessions = addOnce(activeSessions, id);
  debug("after start inSession(): " + (string) inSession() + " " + llList2CSV(activeSessions));
        debug("after start inSession(): " + (string) inSession() + " " + llList2CSV(activeSessions));
         llMessageLinked(LINK_THIS, 0, "Cmd:Lock", id);
         llMessageLinked(LINK_THIS, 0, "Cmd:Lock", id);
     }
     }
Line 2,317: Line 2,376:
     {
     {
         // sent when a session is finished
         // sent when a session is finished
    integer pos = llListFindList(activeSessions, [id]);
        integer pos = llListFindList(activeSessions, [id]);
    if (pos > -1)
        if (pos > -1)
    {
        {
    activeSessions = llDeleteSubList(activeSessions, pos, pos);
            activeSessions = llDeleteSubList(activeSessions, pos, pos);
        if (inSession())
            if (!inSession())
        {
            {
        llMessageLinked(LINK_THIS, 0, "Cmd:Unlock", llGetKey());
                llMessageLinked(LINK_THIS, 0, "Cmd:Unlock", llGetKey());
        }      
            }
         }
         }
     }
     }
Line 2,340: Line 2,399:
         // and displaying the relay-mode.
         // and displaying the relay-mode.
         // Note: Linked messages for those events are sent anyway, only enable embedded
         // Note: Linked messages for those events are sent anyway, only enable embedded
         //      mode if you do not want the relay to handle these situations on its own.  
         //      mode if you do not want the relay to handle these situations on its own.
         register();
         register();
     }
     }
     else if (message == "dialogtimeout")
     else if (message == "dialogtimeout")
     {
     {
    removePendingEntry((string) id);
        removePendingEntry((string) id);
     }
     }
}
}
Line 2,353: Line 2,412:
processUnlock(key id, string message)
processUnlock(key id, string message)
{
{
if (message == "Lockable")
    if (message == "Lockable")
     {
     {
    if (inSession())
        if (inSession())
    {
        {
    llMessageLinked(LINK_THIS, CMD_LINK_CHANNEL, "unlock", NULL_KEY);
            llMessageLinked(LINK_THIS, CMD_LINK_CHANNEL, "unlock", NULL_KEY);
    activeSessions = [];
            activeSessions = [];
    }
        }
     }
     }
}
}
Line 2,370: Line 2,429:
         llListen(MY_OPEN_CHANNEL, "", llGetOwner(), "");
         llListen(MY_OPEN_CHANNEL, "", llGetOwner(), "");
     }
     }
   
 
     attach(key id)
     attach(key id)
     {
     {
         temporaryMuteList = [];
         temporaryMuteList = [];
     }  
     }


     listen(integer channel, string name, key id, string message)
     listen(integer channel, string name, key id, string message)
     {
     {
    debug("listen: " + name + " message=" + message);
        debug("listen: " + name + " message=" + message);
         processListen(channel, id, message);
         processListen(channel, id, message);
     }
     }
Line 2,386: Line 2,445:
         if (channel == RLV_LINK_CHANNEL)
         if (channel == RLV_LINK_CHANNEL)
         {
         {
        if (sender == LINK_ROOT)
            if (sender == LINK_ROOT)
        {
            {
            processRLV(id, message);
                processRLV(id, message);
        }
            }
         }
         }
         else if (channel == STATUS_LINK_CHANNEL)
         else if (channel == STATUS_LINK_CHANNEL)
Line 2,401: Line 2,460:
         else if (channel == LOCKABLE_UNLOCK_LINK_CHANNEL)
         else if (channel == LOCKABLE_UNLOCK_LINK_CHANNEL)
         {
         {
        processUnlock(id, message);
            processUnlock(id, message);
         }
         }
         else if ((channel == 0) && (message == "Backdoor"))
         else if ((channel == 0) && (message == "Backdoor"))
         {
         {
        processBackdoor(id);
            processBackdoor(id);
         }
         }
         else if (channel == INTERPRIM_CHANNEL)
         else if (channel == INTERPRIM_CHANNEL)
         {
         {
        showDialog(id);
            showDialog(id);
         }
         }
     }
     }
   
 
     touch_start(integer num_detected)
     touch_start(integer num_detected)
     {
     {
Line 2,418: Line 2,477:
     }
     }


timer()
    timer()
{
    {
disableDialogListener();
        disableDialogListener();
}
    }


     changed(integer change)
     changed(integer change)
Line 2,431: Line 2,490:
     }
     }
}
}
</lsl>
</lsl>

Latest revision as of 12:23, 25 February 2010

Note: This is alpha quality code. Please help testing or use the last stable version instead.

Code

Relay Manager

Put this script once into the root prim.

<lsl> // RestrainedLife Viewer Relay Script // // By Maike Short // // While this script used to be based on Marine's sample implementations it was // so heavily modified and extended that there is little of the original code // remaining. So please do not bother Marine with any problems but report them to // https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Bugs_and_Pendings_Features // // Thanks to Felis Darwin, Chorazin Allen, Azoth Amat, Gregor Mougin, // Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow, // Chloe1982 Constantine, Ilana Debevec, Kitty Barnett and many others. // // Many thanks to she who started it all ... Marine Kelley. // // This script is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness // or performance. It may be distributed in its full source code with this header and // disclaimer and is not to be sold without permission. Optionally it may be distributed // in a 'no mod' form within Second Life™ PROVIDED that either a link to the full source // IS provided (ie. this page or a .zip or .rar) within the object the code is contained // AND/OR a off-world contact point (ie. email) that the source code may be requested from. // This somewhat unusual (no-mod) distribution is allowed to maintain the integrity and // reliability of the creator reference of in-world objects (scripts). Changes and // improvement to this code must be shared with the community so that we ALL benefit.

integer DEBUG = FALSE;


// --------------------------------------------------- // Constants // ---------------------------------------------------

integer RLVRS_PROTOCOL_VERSION = 1040; // version of the protocol, stated on the specification page string RLVRS_IMPL_VERSION = "Based on Maike's Implemenation 1.040.a ALPHA"; // version of the implementation for debugging

string PREFIX_RL_COMMAND = "@"; string PREFIX_METACOMMAND = "!";

integer RLVRS_CHANNEL = -1812221819; // RLVRS in numbers integer STATUS_LINK_CHANNEL = -1373421300; integer RLV_LINK_CHANNEL = -1373421301; integer CMD_LINK_CHANNEL = -1373421302; integer ASK_LINK_CHANNEL = -1373421304; integer GPU_CHANNEL = -4360493; integer DELEGATE_PENDING_LINK_CHANNEL = -1373421305; integer DELEGATE_PROCESSING_LINK_CHANNEL = -1373421306; integer DELEGATE_CLEAR_PENDING_LINK_CHANNEL = -1373421307; integer MANAGER_FREEDBACK_LINK_CHANNEL = -1373421308; integer DELEGATE_GO_LINK_CHANNEL = -1373421309; integer DIALOG_BASE_CHANNEL = 1000000; integer DIALOG_MAX_CHANNEL = 1000000000; integer INTERPRIM_CHANNEL = -1373421730;

integer MAX_OBJECT_DISTANCE = 100; // 100m is llShout distance integer MAX_TIME_AUTOACCEPT_AFTER_FORCESIT = 60; // seconds

integer PERMISSION_DIALOG_TIMEOUT = 60;

integer MODE_OFF = 0; integer MODE_ASK = 1; integer MODE_AUTO = 2;

list MODE_DESCRIPTIONS = ["RLV Relay is OFF", "RLV Relay is ON (permission needed)", "RLV Relay is ON (auto-accept)"];

string RVL_COMMAND_START = "@detach=n"; string RVL_COMMAND_END = "@detach=y";

// --------------------------------------------------- // Variables // ---------------------------------------------------

integer mode;

integer isNotEmbedded = TRUE;


// current message // using global variables because the content can get huge and uses up a lot stack memory otherwise string currentMessage = ""; list currentMessageToken = []; string currentMessageCommands; list currentMessageCommandList;

list units; list unitObjects; list trustedObjects; list trustedTimeout; list unitPendingTimeout; list unitPendingChannel; list unitPendingListener;


// --------------------------------------------------- // Helper functions // ---------------------------------------------------


debug(string x) {

   if (DEBUG)
   {
       llOwnerSay("DEBUG (" + (string) llGetFreeMemory() + "): " + x);
   }

}


// --------------------------------------------------- // Prim Management // ---------------------------------------------------

// find the processing unit for this object, -1 if the object is not known integer findKnownProcessingUnit(key id) {

   integer pos = llListFindList(unitObjects, [id]);
   if (pos > -1)
   {
       return pos;
   }
   return -1;

}


integer isTrusted(key id) { debug("isTrusted?");

   if (findKnownProcessingUnit(id) > -1)
   {
       return TRUE;
   }
   debug("not known");
   integer count = llGetListLength(units);
   integer i;
   for (i = 0; i <= count; i++)
   {
       key currentID = llList2Key(trustedObjects, i);
       if (id == currentID)
       {
           if (llGetUnixTime() < llList2Integer(trustedTimeout, i))
           {
               debug("known trust index: " + (string) i);
               return TRUE;
           }
       }
   }
   debug("untrusted");
   return FALSE;

}


// finds an available processing unit, selects a new one if the object is unknown, -1 if none is available integer findProcessingUnit(key id) {

   integer pos = findKnownProcessingUnit(id);
   if (pos > -1)
   {
       return pos;
   }
   pos = llListFindList(unitObjects, [NULL_KEY]);
   if (pos < 0)
   {
       llOwnerSay("Sorry there is currently no processing unit available.");
   }
   return pos;

}


// removes id of objects that are out of range garbageCollection() {

   // clear objects that are out of range
   integer count = llGetListLength(units);
   integer i;
   for (i = 0; i < count; i++)
   {
       key id = llList2Key(unitObjects, i);
       if (id)
       {
           if (!isObjectNear(id))
           {
               debug("Object id=" + (string) id + " is out of range, releaseing.");
               llMessageLinked(llList2Integer(units, i), DELEGATE_PROCESSING_LINK_CHANNEL, "release,gc,!release", id);
               unitObjects = llListReplaceList(unitObjects, [NULL_KEY], i, i);
               removePermissionDialogListener(i, TRUE);
           }
       }
   }
   // clear force sit trust list
   count = llGetListLength(trustedObjects);
   integer deleteTo = -1;
   for (i = 0; i < count; i++)
   {
       integer time = llList2Integer(trustedTimeout, i);
       if (time < llGetUnixTime())
       {
           deleteTo = i;
       } 
   }
   
   if (deleteTo > -1)
   {
       trustedObjects = llDeleteSubList(trustedObjects, 0, i);
       trustedTimeout = llDeleteSubList(trustedTimeout, 0, i);
   }

}

// is any processing unit active? integer inAnySession() {

   garbageCollection();
   integer count = llGetListLength(units);
   integer i;
   for (i = 0; i < count; i++)
   {
       key id = llList2Key(unitObjects, i);
       if (id)
       {
           return TRUE;
       }
   }
   return FALSE;

}


// fills the unit lists to have as many entries as "units" fillUnitLists() {

   unitObjects = [];
   unitPendingTimeout = [];
   trustedObjects = [];
   trustedTimeout = [];
   unitPendingChannel = [];
   unitPendingListener = [];
   unitPendingTimeout = [];
   integer count = llGetListLength(units);
   integer i;
   for (i = 0; i < count; i++)
   {
       unitObjects += [NULL_KEY];
       unitPendingTimeout += [0];
       unitPendingChannel += [-1];
       unitPendingListener += [-1];
   }

}

// --------------------------------------------------- // Low Level Communication // ---------------------------------------------------


// acknowledge or reject ack(string cmd_id, key id, string cmd, string ack) {

   llShout(RLVRS_CHANNEL, cmd_id + "," + (string) id + "," + cmd + "," + ack);

}

// cmd begins with a '@' sendRLCmd(string cmd) {

   if (cmd != "")
   {
       if (isNotEmbedded)
       {
           llOwnerSay(cmd);
       }
       llMessageLinked(LINK_SET, RLV_LINK_CHANNEL, cmd, NULL_KEY);
   }

}

// splits the relay message into tokens splitMessage() {

   currentMessageToken = llParseString2List(currentMessage, [","], []);
   currentMessageCommands = llList2String(currentMessageToken, 2);
   currentMessageCommandList = llParseString2List(currentMessageCommands, ["|"], []);

}

// check that this command is for us and not someone else integer verifyWeAreTarget() {

   if (llGetListLength(currentMessageToken) != 3) // this is not a normal command
   {
       return FALSE;
   }
   return (llList2String(currentMessageToken, 1) == llGetOwner()); // talking to me ?

}


// --------------------------------------------------- // Permission Handling // ---------------------------------------------------


// check whether the object is in llShout distance.

integer isObjectNear(key id)
{
    vector myPosition = llGetRootPosition();
    list temp = llGetObjectDetails(id, ([OBJECT_POS]));
    vector objPosition = llList2Vector(temp,0);
    if (objPosition == <0, 0, 0>)
    {
        // object is not in this sim nor in one of the adjacent sims
        return FALSE;
    }
    float distance = llVecDist(objPosition, myPosition);
    return distance <= MAX_OBJECT_DISTANCE;
}

// do a basic check on the identity of the object trying to issue a command integer isObjectIdentityTrustworthy(key id) {

   key parcel_owner=llList2Key (llGetParcelDetails (llGetPos (), [PARCEL_DETAILS_OWNER]), 0);
   key parcel_group=llList2Key (llGetParcelDetails (llGetPos (), [PARCEL_DETAILS_GROUP]), 0);
   key object_owner=llGetOwnerKey(id);
   key object_group=llList2Key (llGetObjectDetails (id, [OBJECT_GROUP]), 0);
   debug("owner= " + (string) parcel_owner + " / " + (string) object_owner);
   debug("group= " + (string) parcel_group + " / " + (string) object_group);
   if (object_owner==llGetOwner ()        // IF I am the owner of the object
     || object_owner==parcel_owner        // OR its owner is the same as the parcel I'm on
     || (object_group==parcel_group && parcel_group != NULL_KEY)        // OR its group is the same as the parcel I'm on
   )
   {
       return TRUE;
   }
   return FALSE;

}


// Is this a simple request for information or a meta command like !release? integer isSimpleRequest() {

   integer len = llGetListLength(currentMessageCommandList);
   integer i;
   // now check every single atomic command
   for (i=0; i < len; ++i)
   {
       if (!isSimpleAtomicCommand(i))
       {
          return FALSE;
       }
   }
   // all atomic commands passed the test
   return TRUE;

}

// is this a simple atomar command // (a command which only queries some information or releases restrictions) // (e. g.: cmd ends with "=" and a number (@version, @getoutfit, @getattach) or is a !-meta-command) integer isSimpleAtomicCommand(integer i) {

   string cmd = llList2String(currentMessageCommandList, i);
   cmd = llToLower(llStringTrim(cmd, STRING_TRIM));
   // check right hand side of the "=" - sign
   integer index = llSubStringIndex (cmd, "=");
   if (index > -1) // there is a "="
   {
       // check for a number after the "="
       string param = llGetSubString (cmd, index + 1, -1);
       if ((integer)param!=0 || param=="0") // is it an integer (channel number)?
       {
           return TRUE;
       }
       // removing restriction
       if ((param == "y") || (param == "rem"))
       {
           return TRUE;
       }
   }
   // check for a leading ! (meta command)
   if (llSubStringIndex(cmd, PREFIX_METACOMMAND) == 0)
   {
       return llGetSubString(cmd, 0, 11) == "!visionclear"
            || llGetSubString(cmd, 0, 6) != "!vision";
   }
   // check for @clear
   // Note: @clear MUST NOT be used because the restrictions will be reapplied on next login
   // (but we need this check here because "!release|@clear" is a BROKEN attempt to work around
   // a bug in the first relay implementation. You should refuse to use relay versions < 1013
   // instead.)
   if (cmd == "@clear")
   {
       return TRUE;
   }
   // this one is not "simple".
   return FALSE;

}


// accept !release even if out of range handleCommandsWhichAreAcceptedOutOfRange(key id) {

   integer unit = findKnownProcessingUnit(id);
   if (unit < 0)
   {
       return;
   }
   if (llListFindList(currentMessageCommandList, ["!release"]) > -1)
   {
       debug("accepted !release although it was out of range");
       releaseRestrictions();
   }

}


// verifies the permission. This includes mode // (off, permission, auto) of the relay and the // identity of the object (owned by parcel people). integer verifyPermission(key id, string name) {

   // is it switched off?
   if (mode == MODE_OFF)
   {
       return FALSE;
   }
   if (llGetListLength (currentMessageToken) < 3)
   {
       return FALSE;
   }


   // accept harmless commands silently
   if (isSimpleRequest())
   {
       return TRUE;
   }
   // check whether this object belongs here
   integer trustworthy = isObjectIdentityTrustworthy(id);
   string warning = "";
   if (!trustworthy)
   {
       warning = "\n\nWARNING: This object is not owned by the people owning this parcel. Unless you know the owner, you should deny this request.";
   }
   // ask in permission-request-mode and/OR in case the object identity is suspisous.
   if (mode == MODE_ASK || !trustworthy)
   {
       debug("asking");
       integer unit = findProcessingUnit(id);
       if (unit < 0)
       {
           return FALSE;
       }
       integer pendingTime = llGetUnixTime() + PERMISSION_DIALOG_TIMEOUT;
       unitObjects = llListReplaceList(unitObjects, [id], unit, unit);
       unitPendingTimeout = llListReplaceList(unitPendingTimeout, [pendingTime], unit, unit);
       execute(id, DELEGATE_PENDING_LINK_CHANNEL);
       if (llKey2Name(llGetOwnerKey(id)) != "")
       {
           name += " (owned by " + llKey2Name(llGetOwnerKey(id)) + ")";
       }
       list temp = llGetObjectDetails(id, [OBJECT_POS]);
       vector pos = llList2Vector(temp, 0);
       string controller = getControllerName();
       string text = name
           + " at <" + (string) ((integer) pos.x)
           + ", " + (string) ((integer) pos.y)
           + ", " + (string) ((integer) pos.z)
           + "> (" + (string) ((integer) llVecDist(pos, llGetRootPosition())) + "m)";
       if (controller != "")
       {
           text = text + " operated by " + controller;
       }
       text = text + " would like to control your viewer." + warning + "\n\nDo you accept ?";
       askForPermission(text, unit, id);
       return FALSE;
   }
   return TRUE;

}

// returns a unique channel number integer getUniqueDialogChannel() {

   integer channel;
   do
   {
       channel = (integer) llFrand(DIALOG_MAX_CHANNEL) + DIALOG_BASE_CHANNEL;
   }
   while (llListFindList(unitPendingChannel, [channel]) > -1);
   return channel;

}

askForPermission(string text, integer unit, key id) {

   debug("Asking for permission isNotEmbedded=" + (string) isNotEmbedded);
   integer channel = getUniqueDialogChannel();
   unitPendingChannel = llListReplaceList(unitPendingChannel, [channel], unit, unit);
   unitPendingTimeout = llListReplaceList(unitPendingTimeout, [llGetUnixTime() + PERMISSION_DIALOG_TIMEOUT], unit, unit);
   if (isNotEmbedded)
   {
       integer handle = llListen(channel, "", llGetOwner(), "");
       unitPendingListener = llListReplaceList(unitPendingListener, [handle], unit, unit);
       llDialog (llGetOwner(), text, ["Yes", "No"], channel);
   }
   llMessageLinked(LINK_SET, ASK_LINK_CHANNEL, (string) channel + "|" + text, id);
   llSetTimerEvent(PERMISSION_DIALOG_TIMEOUT);
   debug("Asking for permission");

}

// gets the name of the last controller or "" // if there is either no !who-command, it is for NULL_KEY, // or the avatar is not in the sim string getControllerName() {

   string controller = "";
   integer i;
   integer count = llGetListLength(currentMessageCommandList);
   for (i = 0; i < count; i++)
   {
       string command = llList2String(currentMessageCommandList, i);
       list commandTokens = llParseString2List(command, ["/"], []);
       if (llList2String(commandTokens, 0) == "!who")
       {
           controller = llKey2Name(llList2String(commandTokens, 1));
       }
   }
   return controller;

}


// lift all the restrictions (called by turning the relay off) releaseRestrictions() {

   debug("releasing all restrictions");
   integer count = llGetListLength(units);
   integer i;
   for (i = 0; i < count; i++)
   {
       key id = llList2Key(unitObjects, i);
       if (id)
       {
           llMessageLinked(llList2Integer(units, i), DELEGATE_PROCESSING_LINK_CHANNEL, "release,x,!release", id);
       }
   }
   sendRLCmd(RVL_COMMAND_END);
   fillUnitLists();

}


// is this object not confirmed yet? integer isPending(key id) {

   integer unit = findKnownProcessingUnit(id);
   return llList2Integer(unitPendingTimeout, unit) > llGetUnixTime();

}


// processes a message send on the relay channel processRelayMessage(string name, key id) {

   if (mode == MODE_OFF)
   {
       debug("deactivated - ignoring commands");
       return; // mode is 0 (off) => reject
   }
   debug("LISTEN: " + currentMessage);
   splitMessage();
   if (llGetListLength(currentMessageToken) < 3)
   {
       return;
   }
   if (!verifyWeAreTarget())
   {
      return;
   }
   garbageCollection();
   if (!isObjectNear(id))
   {
       handleCommandsWhichAreAcceptedOutOfRange(id);
       return;
   }
   if (!isTrusted(id))
   {
       debug("asking for permission because source is NULL_KEY");
       if (!verifyPermission(id, name))
       {
           return;
       }
   }
   if (isPending(id))
   {
       execute(id, DELEGATE_PENDING_LINK_CHANNEL);
   }
   else
   {
       execute(id, DELEGATE_PROCESSING_LINK_CHANNEL);
       sendRLCmd(RVL_COMMAND_START);
   }

}

execute(key id, integer channel) {

   integer unit = findProcessingUnit(id);
   if (unit < 0)
   {
       return;
   }
   debug("delegating command to unit " + (string) unit + " for object " + (string) id);
   unitObjects = llListReplaceList(unitObjects, [id], unit, unit);
   llMessageLinked(llList2Integer(units, unit), channel, currentMessage, id);

}

// --------------------------------------------------- // mode and dialog handling // ---------------------------------------------------


// shows current mode as string showModeDescription() {

   if (isNotEmbedded)
   {
       llOwnerSay(llList2String(MODE_DESCRIPTIONS, mode));
   }
   llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "show_mode", (key) ((string) mode));

}


// process the Yes/No buttons of the permission dialog processDialogResponse(integer channel) {

   debug("processDialogResponse on #" + (string) channel + ": " + currentMessage);
   integer unit = llListFindList(unitPendingChannel, [channel]);
   if (unit < 0)
   {
       debug("Unknown channel: " + (string) channel);
       return;
   }
   removePermissionDialogListener(unit, FALSE);
   if (currentMessage == "Yes") // pending request authorized => process it
   {
       llMessageLinked(llList2Integer(units, unit), DELEGATE_GO_LINK_CHANNEL, "", llList2Key(unitObjects, unit));
       sendRLCmd(RVL_COMMAND_START);
   }
   else
   {
       llMessageLinked(llList2Integer(units, unit), DELEGATE_CLEAR_PENDING_LINK_CHANNEL, "", llList2Key(unitObjects, unit));
       unitObjects = llListReplaceList(unitObjects, [NULL_KEY], unit, unit);
   }

}


// --------------------------------------------------- // initialisation and login handling // ---------------------------------------------------

init() {

   sendRLCmd("@clear");
   llOwnerSay("Waiting for plugins to initialize...");
   // lookin for processing units
   integer count = llGetNumberOfPrims();
   integer i;
   for (i = 1; i <= count; i++)
   {
       if (llSubStringIndex(llGetLinkName(i), "Relay Unit") == 0)
       {
           units += i;
       }
   }
   fillUnitLists();
   // wait some more so that it is ensured that all scripts had a chance to setup there event queue
   llSleep(5);
   mode = MODE_ASK;
   llListen(RLVRS_CHANNEL, "", "", "");
   // check that we are not compiled non-mono
   if (llGetFreeMemory() < 10000)
   {
       llSay(0, llKey2Name(llGetOwner()) + ", your relay is having very little free script memory and is likly to crash during use. Please verify that it was compiled using Mono.");
   }
   // tell other scripts about the reset
   llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "init", (key) ((string) RLVRS_PROTOCOL_VERSION));
   llOwnerSay("Initialized and ready");
   debug("Free Memory: " + (string) llGetFreeMemory());

}


processLinkMessage(key id, string message) {

   if (message == "safeword")
   {
       llSay(0, llKey2Name(llGetOwner()) + " said the safeword. Freeing and deactivating relay.");
       releaseRestrictions();
       mode = MODE_OFF;
       showModeDescription();
       return;
   }
   if (message == "unlock")
   {
       if (id)
       {
           llSay(0, llKey2Name(id) + " freed " + llKey2Name(llGetOwner()) + " by unlocking the relay.");
       }
       releaseRestrictions();
       return;
   }
   if (message == "mode")
   {
       if (inAnySession())
       {
           llOwnerSay("Sorry, you cannot change the relay mode while it is active.");
           return;
       }
       integer oldMode = mode;
       mode = ((integer) ((string) id)) % 3;
       if (oldMode != mode)
       {
           showModeDescription();
       }
       return;
   }
   if (message == "embedded")
   {
       isNotEmbedded = FALSE;
       return;
   }
   if (message == "debug")
   {
       llOwnerSay("--------------------------------------------------------");
       llOwnerSay("Free Memory: " + (string) llGetFreeMemory());
       llOwnerSay("units: "+ llList2CSV(units));
       llOwnerSay("unitObjects: "+ llList2CSV(unitObjects));
       llOwnerSay("trustedObjects: "+ llList2CSV(trustedObjects));
       llOwnerSay("trustedTimeout: "+ llList2CSV(trustedTimeout));
       llOwnerSay("unitPendingTimeout: "+ llList2CSV(unitPendingTimeout));
       llOwnerSay("unitPendingChannel: "+ llList2CSV(unitPendingChannel));
       llOwnerSay("unitPendingListener: "+ llList2CSV(unitPendingListener));
   }

}


processFeedbackFromUnits(integer sender, key id, string message) {

   integer unit = llListFindList(units, [sender]);
   if (unit < 0)
   {
       debug("Got feedback from unkown unit");
       return;
   }
   if (message == "forcesit")
   {
       trustedObjects += [id];
       trustedTimeout += [llGetUnixTime() + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT];
   }
   else if (message == "handover")
   {
       unitObjects = llListReplaceList(unitObjects, [id], unit, unit);
   }
   else if ((message == "pendingrelease") || (message == "release"))
   {
       unitObjects = llListReplaceList(unitObjects, [NULL_KEY], unit, unit);
       removePermissionDialogListener(unit, TRUE);
       if (!inAnySession())
       {
           sendRLCmd(RVL_COMMAND_END);
       }
   }

}

// removes a listener and its information for a request permissiong dialog removePermissionDialogListener(integer unit, integer clear) {

   integer listener = llList2Integer(unitPendingListener, unit);
   if (listener > -1)
   {
       llListenRemove(listener);
       unitPendingListener = llListReplaceList(unitPendingListener, [-1], unit, unit);
   }
   unitPendingTimeout = llListReplaceList(unitPendingTimeout, [0], unit, unit);
   integer channel = llList2Integer(unitPendingChannel, unit);
   if ((channel > -1) && clear)
   {
       llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "dialogtimeout", (key) ((string) channel));
       llMessageLinked(llList2Integer(units, unit), DELEGATE_CLEAR_PENDING_LINK_CHANNEL, "", llList2Key(unitObjects, unit));
       unitObjects = llListReplaceList(unitObjects, [NULL_KEY], unit, unit);
   }
   unitPendingChannel = llListReplaceList(unitPendingChannel, [-1], unit, unit);

}

// removes all listeners removeAllPermissionDialogListener() {

   integer count = llGetListLength(units);
   integer i;
   for (i = 0; i < count; i++)
   {
       removePermissionDialogListener(i, TRUE);
   }

}


// touched by user => cycle through OFF/ON_PERMISSION/ON_ALWAYS modes handleTouch(key toucher) {

   if (!isNotEmbedded)
   {
       return;
   }
   if (toucher != llGetOwner())
   {
       return;
   }
   if (inAnySession())
   {
       llOwnerSay("Sorry, you cannot change the relay mode while it is active.");
       llMessageLinked(LINK_SET, CMD_LINK_CHANNEL, "listrestrictions", NULL_KEY);
       return;
   }
   mode = (mode + 1) % 3;
   if (mode == MODE_OFF)
   {
       releaseRestrictions();
   }
   showModeDescription();

}


default {

   state_entry()
   {
       init();
   }
   on_rez(integer start_param)
   {
       showModeDescription();
   }
   // attach(NULL_KEY) is not executed in child prims on detach, but only hours later on reattach
   attach(key id)
   {
       if (id == NULL_KEY)
       {
           integer count = llGetListLength(units);
           integer i;
           for (i = 0; i < count; i++)
           {
               key object = llList2Key(unitObjects, i);
               if (object)
               {
                   ack("release", object, "!release", "ok");
               }
           }
       }
   }
   listen(integer channel, string name, key id, string message)
   {
       currentMessageToken = [];
       currentMessageCommands = "";
       currentMessageCommandList = [];
       currentMessage = message;
       message = ""; // free memory
       if (channel==RLVRS_CHANNEL)
       {
           processRelayMessage(name, id);
       }
       else if (channel >= DIALOG_BASE_CHANNEL)
       {
           if (id == llGetOwner())
           {
               processDialogResponse(channel);
           }
       }
   }
   link_message(integer sender, integer channel, string message, key id)
   {
       currentMessageToken = [];
       currentMessageCommands = "";
       currentMessageCommandList = [];
       currentMessage = message;
       if (channel == CMD_LINK_CHANNEL)
       {
           processLinkMessage(id, message);
       }
       else if (channel == MANAGER_FREEDBACK_LINK_CHANNEL)
       {
           processFeedbackFromUnits(sender, id, message);
       }
       else if (channel == INTERPRIM_CHANNEL)
       {
           handleTouch(id);
       }
       else if (channel >= DIALOG_BASE_CHANNEL)
       {
           processDialogResponse(channel);
       }
   }
   touch_start(integer num_detected)
   {
       handleTouch(llDetectedKey(0));
   }
   changed(integer change)
   {
       if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP))
       {
            llResetScript();
       }
   }
   timer()
   {
       removeAllPermissionDialogListener();
   }

} </lsl>


Processing Unit

Put this script into the child prim and name them "Relay Unit n". It is important that you put it into one prim, save and compile it to mono. Than copy the script into your inventory and drag it into the other prims. This way the same compiled code will be used.

<lsl> // RestrainedLife Viewer Relay Script // // By Maike Short // // While this script used to be based on Marine's sample implementations it was // so heavily modified and extended that there is little of the original code // remaining. So please do not bother Marine with any problems but report them to // https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Bugs_and_Pendings_Features // // Thanks to Felis Darwin, Chorazin Allen, Azoth Amat, Gregor Mougin, // Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow, // Chloe1982 Constantine, Ilana Debevec, Kitty Barnett and many others. // // Many thanks to she who started it all ... Marine Kelley. // // This script is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness // or performance. It may be distributed in its full source code with this header and // disclaimer and is not to be sold without permission. Optionally it may be distributed // in a 'no mod' form within Second Life™ PROVIDED that either a link to the full source // IS provided (ie. this page or a .zip or .rar) within the object the code is contained // AND/OR a off-world contact point (ie. email) that the source code may be requested from. // This somewhat unusual (no-mod) distribution is allowed to maintain the integrity and // reliability of the creator reference of in-world objects (scripts). Changes and // improvement to this code must be shared with the community so that we ALL benefit.

integer DEBUG = FALSE;


// list of refused commands. Use [] to allow everything or // ["@detach", "@remoutfit"] to prevent striping for example list refusedForceCommands = [];

// --------------------------------------------------- // Constants // ---------------------------------------------------

integer RLVRS_PROTOCOL_VERSION = 1040; // version of the protocol, stated on the specification page string RLVRS_IMPL_VERSION = "Based on Maike's Implemenation 1.040.1"; // version of the implementation for debugging

string PREFIX_RL_COMMAND = "@"; string PREFIX_METACOMMAND = "!";

integer RLVRS_CHANNEL = -1812221819; // RLVRS in numbers integer RLV_REFUSED_LINK_CHANNEL = -1373421311; integer STATUS_LINK_CHANNEL = -1373421300; integer RLV_LINK_CHANNEL = -1373421301; integer CMD_LINK_CHANNEL = -1373421302; integer DIALOG_LINK_CHANNEL = -1373421303; integer ASK_LINK_CHANNEL = -1373421304; integer DELEGATE_PENDING_LINK_CHANNEL = -1373421305; integer DELEGATE_PROCESSING_LINK_CHANNEL = -1373421306; integer DELEGATE_CLEAR_PENDING_LINK_CHANNEL = -1373421307; integer MANAGER_FREEDBACK_LINK_CHANNEL = -1373421308; integer DELEGATE_GO_LINK_CHANNEL = -1373421309; integer GPU_CHANNEL = -4360493;

integer MAX_TIME_AUTOACCEPT_AFTER_FORCESIT = 60; // seconds

integer PERMISSION_DIALOG_TIMEOUT = 30; integer LOGIN_DELAY_WAIT_FOR_PONG = 20; integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60;

list MODE_DESCRIPTIONS = ["RLV Relay is OFF", "RLV Relay is ON (permission needed)", "RLV Relay is ON (auto-accept)"];

string RVL_COMMAND_START = "@this-is-a-script-generated-message-beyond-the-control-of-the-agent=n,detach=n"; string RVL_COMMAND_END = "@detach=y";

// --------------------------------------------------- // Variables // ---------------------------------------------------

list restrictions; // restrictions currently applied (without the "=n" part) integer visionRestricted; key source; // UUID of the object I'm commanded by, always equal to NULL_KEY if restrictions is empty, always set if not

key pendingId; // UUID of initiator of pending request (first request of a session in ask mode) string pendingMessage; // message of pending request integer pendingTime;

// used on login integer timerTickCounter; // count the number of time events on login (forceSit has to be delayed a bit) integer loginWaitingForPong; integer loginPendingForceSit;

key lastForceSitDestination; integer lastForceSitTime;

integer isNotEmbedded = TRUE;


// current message // using global variables because the content can get huge and uses up a lot stack memory otherwise string currentMessage = ""; list currentMessageToken = []; string currentMessageCommands; list currentMessageCommandList;

// --------------------------------------------------- // Helper functions // ---------------------------------------------------


debug(string x) {

   if (DEBUG)
   {
       llOwnerSay("DEBUG (" + (string) llGetFreeMemory() + "): " + x);
   }

}


// --------------------------------------------------- // Low Level Communication // ---------------------------------------------------


// acknowledge or reject ack(string cmd_id, key id, string cmd, string ack) {

   llShout(RLVRS_CHANNEL, cmd_id + "," + (string) id + "," + cmd + "," + ack);

}

// cmd begins with a '@' sendRLCmd(string cmd) {

   if (cmd != "")
   {
       if (isNotEmbedded)
       {
           llOwnerSay(cmd);
       }
       llMessageLinked(LINK_SET, RLV_LINK_CHANNEL, cmd, source);
   }

}

// splits the relay message into tokens splitMessage() {

   currentMessageToken = llParseString2List(currentMessage, [","], []);
   currentMessageCommands = llList2String(currentMessageToken, 2);
   currentMessageCommandList = llParseString2List(currentMessageCommands, ["|"], []);

}


// --------------------------------------------------- // Permission Handling // ---------------------------------------------------


gluePendingMessages() {

   if (llStringLength(pendingMessage) > 2000)
   {
       llSay(0, "Releasing restrictions because of command flood.");
       releaseRestrictions();
   }
   if (pendingMessage == "")
   {
       pendingMessage = currentMessage;
   }
   else
   {
       pendingMessage = pendingMessage + "|" + currentMessageCommands;
   }

}


// accept !release even if out of range handleCommandsWhichAreAcceptedOutOfRange(key id) {

   if (id != source)
   {
       return;
   }
   if (llGetListLength (currentMessageToken) < 3)
   {
       return;
   }
   if (llListFindList(currentMessageCommandList, ["!release"]) > -1)
   {
       debug("accepted !release although it was out of range");
       releaseRestrictions();
   }

}


// --------------------------------------------------- // Executing of commands // ---------------------------------------------------

// execute a non-parsed message // this command could be denied here for policy reasons execute(key id) {

   currentMessage = "";
   currentMessageCommands = "";
   string cmd_id = llList2String(currentMessageToken, 0); // CheckAttach
   currentMessageToken = [];
   integer len = llGetListLength (currentMessageCommandList);
   integer i;
   string command;
   string prefix;
   for (i=0; i<len; ++i) // execute every command one by one
   {
       // a command is a RL command if it starts with '@' or a metacommand if it starts with '!'
       command = llList2String(currentMessageCommandList, i);
       prefix = llGetSubString(command, 0, 0);
       if (prefix == PREFIX_RL_COMMAND) // this is a RLV command
       {
           executeRLVCommand(cmd_id, id, command);
       }
       else if (prefix == PREFIX_METACOMMAND) // this is a metacommand, aimed at the relay itself
       {
           integer continue = executeMetaCommand(cmd_id, id, command);
           if (!continue)
           {
               return;
           }
       }
   }
   if ((llGetListLength(restrictions) == 0) && !visionRestricted)
   {
       releaseRestrictions();
   }

}

// executes a command for the restrained life viewer // with some additinal magic like book keeping executeRLVCommand(string cmd_id, string id, string command) {

   command = llToLower(command);
   // we need to know whether whether is a rule or a simple command
   list tokens = llParseString2List(command, ["="], []);
   string behav = llList2String(tokens, 0); // @getattach:skull
   string param = llList2String(tokens, 1); // 2222
   integer ind = llListFindList(restrictions, [behav]);
   tokens = llParseString2List(behav, [":"], []); // @sit:<uuid>
   string behavName = llList2String (tokens, 0);  // @sit
   string option = llList2String (tokens, 1);     // <uuid>
   debug("executeRLVCommand: behav=!" + behav + "! param=!" + param + "!");
   if (isQuery(behavName) && ((integer) param <= 0))
   {
       // if this is a query command, don't accept public chat,
       // negative numbers, or pseudo channels like
       // "n", "add", "y", "rem", "force",  ...
       ack(cmd_id, id, command, "ko");
       return;
   }
   if (param=="n" || param=="add") // add to restrictions
   {
       if (ind < 0)
       {
           if ((llGetListLength(restrictions) == 0) && !visionRestricted)
           {
               sendRLCmd(RVL_COMMAND_START);
               llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id);
           }
           restrictions += [behav];
       }
       source = id; // we know that source is either NULL_KEY or id already
   }
   else if (param == "y" || param == "rem") // remove from restrictions
   {
       if (behavName == "@this-is-a-script-generated-message-beyond-the-control-of-the-agent")
       {
           ack(cmd_id, id, command, "ko");
           return;
       }
       if (ind > -1)
       {
           restrictions = llDeleteSubList (restrictions, ind, ind);
       }
       if ((llGetListLength(restrictions) == 0) && !visionRestricted)
       {
           releaseRestrictions();
       }
       removeFromPendingList(behav);
   }
   else if (param == "force")
   {
       if (llListFindList(refusedForceCommands, [behavName]) >= 0)
       {
           debug("rejected force-command: behav=!" + behav + "! behavName=!" + behavName + "!");
           llMessageLinked(LINK_SET, RLV_REFUSED_LINK_CHANNEL, behav, source);
           ack(cmd_id, id, command, "ko");
           return;
       }
   }
   else if (((integer) param <= 0) && (behavName != "@clear"))
   {
       // this is either an unknown param (not "n", "add", "y", "rem", "force")
       // or a query which should be answered on the public chat channel 0.
       // note negative channels are displayed as /-x on public chat.
       ack(cmd_id, id, command, "ko");
       return;
   }
   workaroundForAtClear(command);
   rememberForceSit(behavName, option, param);
   sendRLCmd(command); // execute command
   ack(cmd_id, id, command, "ok"); // acknowledge

}

// is this a query? integer isQuery(string behav) {

   return ((llGetSubString(behav, 0, 3) == "@get") ||
           (llGetSubString(behav, 0, 7) == "@version") ||
           (llGetSubString(behav, 0, 10) == "@findfolder"));

}

// check for @clear // Note: @clear MUST NOT be used because the restrictions will be reapplied on next login // if implemented straight forward in the relay. So we need to treat @clear as !release workaroundForAtClear(string command) {

   if (command == "@clear")
   {
       releaseRestrictions();
   }

}

// remembers the time and object if this command is a force sit rememberForceSit(string behavName, string option, string param) {

   // clear lastForceSitDestination in case we are now prevented from standing up and
   // the force sit was long ago. Note: restrictions is checked to prevent the
   // clearance in case @unsit is just send again on login
   if (behavName == "@unsit")
   {
       if (llListFindList(restrictions, ["@unsit"]) < 0)
       {
           if (lastForceSitTime + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT < llGetUnixTime())
           {
               debug("clearing lastForceSitDestination");
               llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "forcesit", NULL_KEY);
               lastForceSitDestination = NULL_KEY;
           }
       }
   }
   if (param != "force")
   {
       return;
   }
   debug("'force'-command:" + behavName + "/" + option);
   if (behavName != "@sit")
   {
       return;
   }
   lastForceSitDestination = (key) option;
   llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "forcesit", lastForceSitDestination);
   lastForceSitTime = llGetUnixTime();
   debug("remembered force sit");

}

// executes a meta command which is handled by the relay itself integer executeMetaCommand(string cmd_id, string id, string commandString) {

   list tokens = llParseString2List(commandString, ["/"], []);
   string command = llList2String(tokens, 0);
   if (command == PREFIX_METACOMMAND + "version") // checking relay protocol version
   {
       ack(cmd_id, id, command, (string) RLVRS_PROTOCOL_VERSION);
   }
   else if (command == PREFIX_METACOMMAND + "implversion") // checking relay version
   {
       ack(cmd_id, id, command, RLVRS_IMPL_VERSION);
   }
   else if (command == PREFIX_METACOMMAND + "release") // release all the restrictions (end session)
   {
       releaseRestrictions();
       ack(cmd_id, id, command, "ok");
   }
   else if (command == PREFIX_METACOMMAND + "handover")
   {
       handleHandOver(id, tokens);
       return FALSE;
   }
   else if (command == PREFIX_METACOMMAND + "pong") // object is still available for us
   {
       loginWaitingForPong = FALSE;
   }
   else if (command == PREFIX_METACOMMAND + "visionclear")
   {
       visionRestricted = FALSE;
       ack(cmd_id, id, commandString, "ok");
       if (llGetListLength(restrictions) == 0)
       {
           releaseRestrictions();
      }
      llMessageLinked(LINK_SET, GPU_CHANNEL, commandString, source);
   }
   else if (command == PREFIX_METACOMMAND + "vision")
   {
       integer attached = llGetAttached();
       // TODO: Check  that prim is there
       if (attached >= ATTACH_HUD_CENTER_2 || attached < ATTACH_HUD_BOTTOM_RIGHT)
       {
           if (llGetListLength(restrictions) == 0)
           {
               llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id);
               sendRLCmd(RVL_COMMAND_START);
           }
           llMessageLinked(LINK_SET, GPU_CHANNEL, commandString, source);
           ack(cmd_id, id, commandString, "ok");
           visionRestricted = TRUE;
           source = id;
       }
       else
       {
           ack(cmd_id, id, commandString, "ko");
       }
   }
   else if (command == PREFIX_METACOMMAND + "who")
   {
       ack(cmd_id, id, commandString, "ok");
   }
   else
   {
       ack(cmd_id, id, command, "ko");
   }
   return TRUE;

}


handleHandOver(string id, list tokens) {

   debug("handover");
   key targetObject = (key) llList2String(tokens, 1);
   integer keepRestrictions = (integer) llList2String(tokens, 2);
   if (!keepRestrictions)
   {
       releaseRestrictions();
   }
   source = targetObject;
   llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "handover", source);
   if (llGetListLength(restrictions) == 0)
   {
       sendRLCmd(RVL_COMMAND_START);
       llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "start", id);
   }
   pingWorldObject();

}

// removes a restriction from the list of pending commands removeFromPendingList(string behav) {

   string search = behav + "=";
   // don't do the expensive parsing (string operations are very slow in pre-
   // mono LSL) in case we can detect fast that this one is not in the list.
   if (llSubStringIndex(pendingMessage, search) < 0)
   {
       return;
   }
   list tokens = llParseString2List(pendingMessage, [","], []);
   list commands = llParseString2List(llList2String(tokens, 2), ["|"], []);
   integer modified = FALSE;
   integer len = llGetListLength(commands);
   integer i;
   for (i = len - 1; i >= 0; i--)
   {
       string cmd = llList2String(commands, i);
       if (llSubStringIndex(cmd, search) == 0)
       {
           if (llSubStringIndex(cmd, "=n") > -1 || llSubStringIndex(cmd, "=add") > -1)
           {
               commands = llDeleteSubList(commands, i, i);
               modified = TRUE;
           }
       }
   }
   if (modified)
   {
       if (llGetListLength(commands) > 0)
       {
           pendingMessage = llList2String(tokens, 0) + "," + llList2String(tokens, 1) + "," + llDumpList2String(commands, "|");
       }
       else
       {
           llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "pendingrelease", source);
           clearPendingMessages();
       }
   }

}


// lift all the restrictions (called by !release and by turning the relay off) releaseRestrictions() {

   integer i;
   integer len = llGetListLength (restrictions);
   for (i = 0; i < len; ++i)
   {
       sendRLCmd(llList2String (restrictions, i) + "=y");
   }
   sendRLCmd(RVL_COMMAND_END);
   lastForceSitDestination = NULL_KEY;
   restrictions = [];
   loginPendingForceSit = FALSE;
   clearPendingMessages();
   visionRestricted = FALSE;
   llMessageLinked(LINK_SET, GPU_CHANNEL, "!visionclear", source);
   llMessageLinked(LINK_SET, STATUS_LINK_CHANNEL, "release", source);
      llMessageLinked(LINK_ROOT, MANAGER_FREEDBACK_LINK_CHANNEL, "release", source);
   source = NULL_KEY;

}

// sends an !release,ok to the world object if we are in an active session tellWorldObjectAboutCanceledSession() {

   if (source != NULL_KEY)
   {
       ack("release", source, "!release", "ok");
   }

}


// deletes the list of pending messsages clearPendingMessages() {

   // clear pending request
   pendingId = NULL_KEY;
   pendingMessage = "";
   pendingTime = 0;

}


processLinkMessage(integer channel, key id) {

   debug("listen: " + currentMessage);
   splitMessage();
    if (channel == DELEGATE_PENDING_LINK_CHANNEL)
    {
        gluePendingMessages();
    }
    else if (channel == DELEGATE_CLEAR_PENDING_LINK_CHANNEL)
    {
        clearPendingMessages();
    }
    else if (channel == DELEGATE_GO_LINK_CHANNEL)
    {
        currentMessage = pendingMessage;
        splitMessage();
        execute(id);
    }
    else if (channel == DELEGATE_PROCESSING_LINK_CHANNEL)
    {
       debug("Executing: " + (string) source);
       execute(id);
    }
   else if (channel == CMD_LINK_CHANNEL)
   {
       if (currentMessage == "refusedForceCommands")
       {
           refusedForceCommands = llParseString2List((string) id, [","], []);
       }
       else if (currentMessage == "embeddedunit")
       {
           isNotEmbedded = FALSE;
       }
       else if (currentMessage == "debug")
       {
           DEBUG = (integer) ((string) id);
       }
       else if (currentMessage == "listrestrictions")
       {
           if (source)
           {
               list temp = llGetObjectDetails(source, [OBJECT_POS]);
               vector pos = llList2Vector(temp, 0);
               string text = llKey2Name(source)
                   + " at <" + (string) ((integer) pos.x)
                   + ", " + (string) ((integer) pos.y)
                   + ", " + (string) ((integer) pos.z)
                   + "> (" + (string) ((integer) llVecDist(pos, llGetRootPosition())) + "m): ";
               llOwnerSay(text + llList2CSV(restrictions));
           }
       }
   }

}


// --------------------------------------------------- // initialisation and login handling // ---------------------------------------------------

init() {

   sendRLCmd("@clear");
   if (llGetFreeMemory() < 10000)
   {
       llSay(0, llKey2Name(llGetOwner()) + ", your relay is having very little free script memory and is likly to crash during use. Please verify that it was compiled using Mono.");
   }
   debug("Free Memory: " + (string) llGetFreeMemory());

}


// sends the known restrictions (again) to the RL-viewer // (call this functions on login) reinforceKnownRestrictions() {

   integer i;
   integer len = llGetListLength(restrictions);
   string restr;
   debug("source=" + (string) source);
   if (len > 0)
   {
       sendRLCmd(RVL_COMMAND_START);
   }
   for (i=0; i < len; ++i)
   {
       restr = llList2String(restrictions, i);
       debug("restr=" + restr);
       sendRLCmd(restr + "=n");
       if (restr == "@unsit")
       {
           loginPendingForceSit = TRUE;
       }
   }

}

// send a ping request and start a timer pingWorldObjectIfUnderRestrictions() {

   loginWaitingForPong = FALSE;
   if (source)
   {
       pingWorldObject();
   }

}

pingWorldObject() {

   ack("ping", source, "ping", "ping");
   timerTickCounter = 0;
   llSetTimerEvent(1.0);
   loginWaitingForPong = TRUE;

}

sendForceSitDuringLogin() {

   key sitTarget = source;
   if (lastForceSitDestination)
   {
       sitTarget = lastForceSitDestination;
   }
   debug("Force sit during login on " + (string) sitTarget + " (source=" + (string) source + ", lastForceSitDestination=" + (string) lastForceSitDestination + ")");
   sendRLCmd ("@sit:" + (string) sitTarget + "=force");

}


// processes a timer event processTimer() {

   timerTickCounter++;
   debug("timer (" + (string) timerTickCounter + "): waiting for pong: " + (string) loginWaitingForPong + " pendingForceSit: " + (string) loginPendingForceSit);
   if (loginWaitingForPong && (timerTickCounter >= LOGIN_DELAY_WAIT_FOR_PONG))
   {
       llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because the device is not available.");
       loginWaitingForPong = FALSE;
       loginPendingForceSit = FALSE;
       releaseRestrictions();
   }
   if (loginPendingForceSit)
   {
       integer agentInfo = llGetAgentInfo(llGetOwner());
       if (agentInfo & AGENT_SITTING)
       {
           loginPendingForceSit = FALSE;
           debug("is sitting now");
       }
       else if (timerTickCounter == LOGIN_DELAY_WAIT_FOR_FORCE_SIT)
       {
           llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because sitting down again was not possible.");
           loginPendingForceSit = FALSE;
           releaseRestrictions();
       }
       else
       {
           sendForceSitDuringLogin();
       }
   }
   if (!loginPendingForceSit && !loginWaitingForPong)
   {
       llSetTimerEvent(0.0);
   }

}


default {

   state_entry()
   {
       init();
   }
   on_rez(integer start_param)
   {
       // relogging, we must refresh the viewer and ping the object if any
       reinforceKnownRestrictions();
       pingWorldObjectIfUnderRestrictions();
   }
   attach(key id)
   {
       if (id == NULL_KEY)
       {
           tellWorldObjectAboutCanceledSession();
           releaseRestrictions();
       }
   }
   timer()
   {
       processTimer();
   }
   link_message(integer sender, integer channel, string message, key id)
   {
       currentMessageToken = [];
       currentMessageCommands = "";
       currentMessageCommandList = [];
       currentMessage = message;
       message = ""; // free memory
       debug("link message: " + (string) channel + " message: " + currentMessage + " id=" + (string) id);
       processLinkMessage(channel, id);
   }
   changed(integer change)
   {
       if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP))
       {
            llResetScript();
       }
       if (change & (CHANGED_REGION | CHANGED_TELEPORT))
       {
           if (loginWaitingForPong)
           {
               if (source)
               {
                   ack("ping", source, "ping", "ping");
               }
           }
       }
   }

} </lsl>

Extended Controller

This script is totally optional. It add the following things:

  • A dialog to do some actions like listing the active objects and giving a help note card
  • Buttons to trust the object, owner or group you are controlled by at the moment
  • Button to clear the trust list
  • adds a "temporary mute" button in the permission query dialog (for objects that spam your every minute until you accept)
  • adds support for realkey (if your drag the script and object from one of Marine's items and put it in)
  • adds support for struggling (if you drag the Lockable script from one of Marine's items and put it in. Note: That item will of course not work anymore until you put it back in)

<lsl> // Extended Controller for RestrainedLife Viewer Relay Script // // By Maike Short // // Please report bugs at // https://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Bugs_and_Pendings_Features // // Thanks to Felis Darwin, Chorazin Allen, Azoth Amat, Gregor Mougin, // Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow, // Chloe1982 Constantine, Ilana Debevec, Kitty Barnett and many others. // // Many thanks to she who started it all ... Marine Kelley. // // This script is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness // or performance. It may be distributed in its full source code with this header and // disclaimer and is not to be sold without permission. Optionally it may be distributed // in a 'no mod' form within Second Life™ PROVIDED that either a link to the full source // IS provided (ie. this page or a .zip or .rar) within the object the code is contained // AND/OR a off-world contact point (ie. email) that the source code may be requested from. // This somewhat unusual (no-mod) distribution is allowed to maintain the integrity and // reliability of the creator reference of in-world objects (scripts). Changes and // improvement to this code must be shared with the community so that we ALL benefit.

integer DEBUG = FALSE;

integer STATUS_LINK_CHANNEL = -1373421300; integer RLV_LINK_CHANNEL = -1373421301; integer CMD_LINK_CHANNEL = -1373421302; integer ASK_LINK_CHANNEL = -1373421304; integer INTERPRIM_CHANNEL = -1373421730; integer LOCKABLE_UNLOCK_LINK_CHANNEL = -7;

integer MODE_OFF = 0; integer MODE_ASK = 1; integer MODE_AUTO = 2;

list MODE_DESCRIPTIONS = ["RLV Relay is OFF", "RLV Relay is ON (permission needed)", "RLV Relay is ON (auto-accept)"];

// ----------------------------------------------------

integer MY_OPEN_CHANNEL = 77;

list MODE_BUTTONS_STRUGGLE = ["Tug", "Squirm", "Struggle"]; list MODE_BUTTONS_DISABLED = ["(Off)", "(Ask)", "(Auto)"]; list MODE_BUTTONS_ENABLED = ["Off", "Ask", "Auto"]; list MODE_BUTTONS_ALL = ["(Off)", "(Ask)", "(Auto)", "Off", "Ask", "Auto"]; list FILTER_BUTTONS = ["Filter Strip", "Filter Sit", "Filter TP"];

list activeSessions = [];

// permission query handling integer mode = MODE_ASK; list pending = []; // strided list: "C"+channel, listener, id list temporaryMuteList;

// filter of force commands integer filterStrip = FALSE; integer filterSit = FALSE; integer filterTP = TRUE;

// trust handling list trustedGroups = []; list trustedOwners = []; list trustedObjects = [];


// remebers the channel and the listener for dialog responses integer dialogChannel = 0; integer listenHandle = 0;

// ---------------------------------------------------- // Little Helper Functions // ----------------------------------------------------

debug(string message) {

   if (DEBUG)
   {
       llOwnerSay("DEBUG: " + message);
   }

}


// tries to find the needle in the hey-stack case insensitive. // But checks that the needle is at least minLength characters long. integer contains(string hey, string needle) {

   // prepare needle for easier matching
   needle = llToLower(llStringTrim(needle, STRING_TRIM));
   // accept * as wildcard
   if (needle == "*")
   {
       needle = "";
   }
   // prepare hay for easier matching
   hey = llToLower(llStringTrim(hey, STRING_TRIM));
   integer index = llSubStringIndex(hey, needle);
   return index > -1;

}


// add an entry to the list if and only if it is not already there list addOnce(list theList, key entry) {

   if (llListFindList(theList, [entry]) < 0)
   {
       theList += [entry];
   }
   return theList;

}

// am I (this item) target of this message? integer isMessageForMe(string targetName) {

   string itemName = llGetObjectName();
   return contains(itemName, targetName);

}


// ---------------------------------------------------- // Dialog // ----------------------------------------------------


// shows a dialog dialog(key id, string message, list buttons) {

   enableDialogListener();
   llDialog(id, message, buttons, dialogChannel);

}

// enable a timed listener for dialog buttons enableDialogListener() {

   dialogChannel = (integer)(llFrand(-1000000000.0) - 1000000000.0);
   disableDialogListener();
   listenHandle = llListen(dialogChannel, "", "", "");
   llSetTimerEvent(120);

}

// disable the listener for dialog buttons disableDialogListener() {

   if (listenHandle != 0) {
       llListenRemove(listenHandle);
       listenHandle = 0;
   }
   llSetTimerEvent(0);

}


// ---------------------------------------------------- // Permission Handling // ----------------------------------------------------


// is this object trusted to execute commands without asking for permission? integer isTrusted(key id) {

   if (llListFindList(trustedObjects, [id]) > -1)
   {
       return TRUE;
   }
   list temp = llGetObjectDetails(id, [OBJECT_OWNER, OBJECT_GROUP]);
   if (llListFindList(trustedOwners, [llList2Key(temp, 0)]) > -1)
   {
       return TRUE;
   }
   if (llListFindList(trustedGroups, [llList2Key(temp, 1)]) > -1)
   {
       return TRUE;
   }
   return FALSE;

}


// ---------------------------------------------------- // Filter Handling // ----------------------------------------------------


string createFilterText() {

   string res1 = "Allowed Force: ";
   string res2 = "Disallowed Force: ";
   if (filterStrip)
   {
       res2 += "stripping, ";
   }
   else
   {
       res1 += "stripping, ";
   }
   if (filterSit)
   {
       res2 += "sit, ";
   }
   else
   {
       res1 += "sit, ";
   }
   if (filterTP)
   {
       res2 += "teleport";
   }
   else
   {
       res1 += "teleport";
   }
   return res1 + "\n" + res2 + "\n";

}

sendFilter() {

   list filter;
   if (filterStrip)
   {
       filter = ["@detach", "@remoutfit"];
   }
   if (filterSit)
   {
       filter += ["@sit"];
   }
   if (filterTP)
   {
       filter += ["@tpto"];
   }
   string cmd = llDumpList2String(filter, ",");
   llMessageLinked(LINK_SET, CMD_LINK_CHANNEL, "refusedForceCommands", (key) cmd);

}


processRLV(key id, string message) {

   llOwnerSay(message);

}

processShowMode(integer mode) {

   llOwnerSay(llList2String(MODE_DESCRIPTIONS, mode));

}


integer inSession() {

   return llGetListLength(activeSessions) > 0;

}

// ---------------------------------------------------- // Realkey Handling // ----------------------------------------------------


processBackdoor(key id) {

   debug("processBackdoor: id=" + (string) id);
   if (inSession())
   {
       debug("processBackdoor");
       string objects = "";
       integer count = llGetListLength(activeSessions);
       integer i;
       for (i = 0; i < count; i++)
       {
           if (i > 0)
           {
               objects += ", ";
           }
           objects += llKey2Name(llList2Key(activeSessions, i));
       }
       dialog(id, "\nDo you want to release " + llKey2Name(llGetOwner()) + " from " + objects + "?", ["Yes", "No"]);
   }

}

processDialogResponseByOthers(key id, string message) {

   debug("processDialogResponse: id=" + (string) id + "  message=" + message);
   if (message != "Yes")
   {
       return;
   }
   debug("processDialogResponse: should free");
   if (inSession())
      {
       debug("processDialogResponse: unlocked");
          llMessageLinked(LINK_THIS, CMD_LINK_CHANNEL, "unlock", id);
          activeSessions = [];
      }

}


// ---------------------------------------------------- // Dialog Handling // ----------------------------------------------------


showDialog(key id) {

   if (id != llGetOwner())
   {
       return;
   }
   debug("inSession(): " + (string) inSession() + " " + llList2CSV(activeSessions));
   list buttons = ["Clear Trust"];
   if (inSession())
   {
       buttons += ["List Objects", "Help", "Trust Group", "Trust Owner", "Trust Object"];
   }
   else
   {
       if (llGetInventoryType("*RealKey") == INVENTORY_SCRIPT)
       {
           buttons += ["Real Key...", "Help"];
       }
       else
       {
           buttons += [" ", "Help"];
       }
   }
   buttons += FILTER_BUTTONS;
   if (inSession())
   {
       if (llGetInventoryType("Lockable") == INVENTORY_SCRIPT)
       {
           buttons += MODE_BUTTONS_STRUGGLE;
       }
       else
       {
           buttons += MODE_BUTTONS_DISABLED;
       }
   }
   else
   {
       buttons += MODE_BUTTONS_ENABLED;
   }
   dialog(llGetOwner(), "Restraint Life Relay\n" + createFilterText(), buttons);

}

processDialogResponse(integer channel, key id, string message) {

   if (channel == dialogChannel)
   {
       disableDialogListener();
   }
   if (id == llGetOwner())
   {
       processDialogResponseByOwner(channel, id, message);
   }
   else
   {
       processDialogResponseByOthers(id, message);
   }

}


processDialogResponseByOwner(integer channel, key id, string message) {

   // Mode
   integer pos = llListFindList(MODE_BUTTONS_ALL, [message]);
   if (pos > -1)
   {
       if (inSession())
       {
           llOwnerSay("Sorry, you cannot change the relay mode while it is active.");
       }
       else
       {
           mode = pos % 3;
           llMessageLinked(LINK_SET, CMD_LINK_CHANNEL, "mode", (key) ((string) mode));
       }
       return;
   }
   // filter buttons
   pos = llListFindList(FILTER_BUTTONS, [message]);
   if (pos > -1)
   {
       if (message == "Filter Strip")
       {
           filterStrip = !filterStrip;
       }
       else if (message == "Filter Sit")
       {
           filterSit = !filterSit;
       }
       else if (message == "Filter TP")
       {
           filterTP = !filterTP;
       }
       sendFilter();
       showDialog(id);
       return;
   }
   // struggling
   pos = llListFindList(MODE_BUTTONS_STRUGGLE, [message]);
   if (pos > -1)
   {
       llMessageLinked(LINK_THIS, 0, "Cmd:" + message, id);
       showDialog(id);
       return;
   }
   if (message == "Trust Group")
   {
       trustGroup();
   }
   else if (message == "Trust Owner")
   {
       trustOwner();
   }
   else if (message == "Trust Object")
   {
       trustObject();
   }
   else if (message == "Clear Trust")
   {
       trustedGroups = [];
       trustedObjects = [];
       trustedOwners = [];
       llOwnerSay("Trusted objects, owners and groups cleared.");
       showDialog(id);
   }
   else if (message == "Help")
   {
   	giveHelp(id);
   }
   else if (message == "Real Key...")
   {
       llMessageLinked(LINK_THIS, 11, "*RealKey", id);
   }
   else if (message == "Temp Mute")
   {
          integer index = llListFindList(pending, ["C" + (string) channel]);
       if (index > -1)
       {
           key toMute = llList2Key(pending, index + 2);
           temporaryMuteList += toMute;
           llWhisper(0, "Muting object \"" + llKey2Name(toMute) + "\" until next login.");
           llMessageLinked(LINK_THIS, channel, "No", id);
       }
       removePendingEntry((string) channel);
   }
   else if (message == "List Objects")
   {
       llMessageLinked(LINK_SET, CMD_LINK_CHANNEL, "listrestrictions", NULL_KEY);
   }
   else if ((message == "Yes") || (message == "No"))
   {
       llMessageLinked(LINK_THIS, channel, message, id);
       removePendingEntry((string) channel);
   }

}


trustGroup() {

   integer count = llGetListLength(activeSessions);
   integer i;
   for (i = 0; i < count; i++)
   {
       key object = llList2Key(activeSessions, i);
       list temp = llGetObjectDetails(object, [OBJECT_GROUP]);
       key group = llList2Key(temp, 0);
       if (group == NULL_KEY)
       {
           return;
       }
       trustedGroups = addOnce(trustedGroups, group);
       llOwnerSay("Trusting objects owned by group secondlife:///app/group/" + (string) group + "/about");
   }

}

trustOwner() {

   integer count = llGetListLength(activeSessions);
   integer i;
   for (i = 0; i < count; i++)
   {
       key object = llList2Key(activeSessions, i);
       list temp = llGetObjectDetails(object, [OBJECT_OWNER]);
       key owner = llList2Key(temp, 0);
       trustedOwners = addOnce(trustedOwners, owner);
       llOwnerSay("Trusting objects owned by " + (string) owner + " (" + llKey2Name(owner) + ").");
   }

}

trustObject() {

   integer count = llGetListLength(activeSessions);
   integer i;
   for (i = 0; i < count; i++)
   {
       key object = llList2Key(activeSessions, i);
       trustedObjects = addOnce(trustedObjects, object);
       llOwnerSay("Trusting object " + (string) object + " called " + llKey2Name(object));
   }

}

// give the help notecard giveHelp(key id) { integer count = llGetInventoryNumber(INVENTORY_NOTECARD); integer i; for (i = 0; i < count; i++) {

       string notecard = llGetInventoryName(INVENTORY_NOTECARD, i);
       if (llSubStringIndex(notecard, "Relay") > -1)
       {
           llGiveInventory(id, notecard);
       } 

} }

// removed an entry from the pending list removePendingEntry(string channel) {

      integer pos = llListFindList(pending, ["C" + channel]);
   if (pos > -1)
   {
       integer handle = llList2Integer(pending, pos + 1);
       llListenRemove(handle);
       pending = llDeleteSubList(pending, pos, pos + 2);
   }

}

processOpenText(key id, string message) {

   if (isMessageForMe(message))
   {
       showDialog(id);
   }

}

processListen(integer channel, key id, string message) {

   if (channel == MY_OPEN_CHANNEL)
   {
       if (id == llGetOwner())
       {
           processOpenText(id, message);
       }
   }
   else
   {
       processDialogResponse(channel, id, message);
   }

}

// --------------------------------------------------- // Low Level API Handling // ---------------------------------------------------

register() {

   llMessageLinked(LINK_SET, CMD_LINK_CHANNEL, "embedded", NULL_KEY);
   llMessageLinked(LINK_SET, CMD_LINK_CHANNEL, "mode", (key) ((string) mode));
   sendFilter();

}

processAsk(key id, string message) {

   list tokens = llParseString2List(message, ["|"], []);
   integer channel = (integer) llList2String(tokens, 0);
   string text = llList2String(tokens, 1);
   if (isTrusted(id))
   {
       llMessageLinked(LINK_SET, channel, "Yes", id);
   }
   else
   {
       if (llListFindList(temporaryMuteList, [id]) < 0)
       {
           integer handle = llListen(channel, "", llGetOwner(), "");
           pending += ["C" + (string) channel, handle, id];
           llDialog(llGetOwner(), text, ["Yes", "No", "Temp Mute"], channel);
       }
       else
       {
           llMessageLinked(LINK_THIS, channel, "No", id);
       }
   }

}

processStatus(key id, string message) {

   if (message == "start")
   {
       // id: the id of the object controlling the relay
       // sent when a session is started (permissions has already been granted if required.
       debug("before start inSession(): " + (string) inSession() + " " + llList2CSV(activeSessions));
       activeSessions = addOnce(activeSessions, id);
       debug("after start inSession(): " + (string) inSession() + " " + llList2CSV(activeSessions));
       llMessageLinked(LINK_THIS, 0, "Cmd:Lock", id);
   }
   else if (message == "release")
   {
       // sent when a session is finished
       integer pos = llListFindList(activeSessions, [id]);
       if (pos > -1)
       {
           activeSessions = llDeleteSubList(activeSessions, pos, pos);
           if (!inSession())
           {
               llMessageLinked(LINK_THIS, 0, "Cmd:Unlock", llGetKey());
           }
       }
   }
   else if (message == "show_mode")
   {
       // id: integer for 0 off, 1 ask, 2 auto
       mode = (integer) ((string) id);
       processShowMode(mode);
   }
   else if (message == "init")
   {
       // message = "init"
       // send on initialization, respond with "embedded" on CMD_LINK_CHANNEL to take over
       // controls for touch, forwarding RLV commands to the viewer, asking for permission
       // and displaying the relay-mode.
       // Note: Linked messages for those events are sent anyway, only enable embedded
       //       mode if you do not want the relay to handle these situations on its own.
       register();
   }
   else if (message == "dialogtimeout")
   {
       removePendingEntry((string) id);
   }

}


// unlocked by Lockable script processUnlock(key id, string message) {

   if (message == "Lockable")
   {
       if (inSession())
       {
            llMessageLinked(LINK_THIS, CMD_LINK_CHANNEL, "unlock", NULL_KEY);
            activeSessions = [];
       }
   }

}


default {

   state_entry()
   {
       register();
       llListen(MY_OPEN_CHANNEL, "", llGetOwner(), "");
   }
   attach(key id)
   {
       temporaryMuteList = [];
   }
   listen(integer channel, string name, key id, string message)
   {
       debug("listen: " + name + " message=" + message);
       processListen(channel, id, message);
   }
   link_message(integer sender, integer channel, string message, key id)
   {
       if (channel == RLV_LINK_CHANNEL)
       {
           if (sender == LINK_ROOT)
           {
               processRLV(id, message);
           }
       }
       else if (channel == STATUS_LINK_CHANNEL)
       {
           processStatus(id, message);
       }
       else if (channel == ASK_LINK_CHANNEL)
       {
           processAsk(id, message);
       }
       else if (channel == LOCKABLE_UNLOCK_LINK_CHANNEL)
       {
           processUnlock(id, message);
       }
       else if ((channel == 0) && (message == "Backdoor"))
       {
           processBackdoor(id);
       }
       else if (channel == INTERPRIM_CHANNEL)
       {
           showDialog(id);
       }
   }
   touch_start(integer num_detected)
   {
       showDialog(llDetectedKey(0));
   }
   timer()
   {
       disableDialogListener();
   }
   changed(integer change)
   {
       if (change & CHANGED_OWNER)
       {
           llResetScript();
       }
   }

} </lsl>