LSL Protocol/Restrained Living Relay/Other Implementations/Dominatech RLV Relay

== Dominatech RLV Relay ==
© 2009 Julia Banshee
This is an open source RLV relay script that supports relaying from multiple devices.
It it fully compatible with version 1.030 of the RLV Relay Specification, but has one non-standard extention, a "!getstatus" command that returns the restrictions held only by the one device requesting the status. This (or similar functionality) has been proposed for inclusion in a future version of the official specification.
See the discussion page for some issues with multi-device relays.
So without further ado...
// Dominatech RLV Relay Script 2.0
// Copyright (C) 2009 Julia Banshee
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See http://www.gnu.org/licenses/gpl.html for terms of this license.
integer relayChannel = -1812221819;
string version = "1030";
string implversion = "Dominatech Relay 2.0";
integer backChannel; // for @getsitid
key sitting;
list objects;
list restrictions;
list pingObjects;
list pingRestrictions;
key pingSitting;
integer pingSitRetry;
integer lit;
integer askMode;
list allowedObjects;
list rejectedObjects;
list pendingObjects;
list pendingCommands;
integer permChannel;
integer permListener;
integer permClose;
updateStatus(integer r)
{
 if ( r )
 {
 if ( ! lit )
 {
 llMessageLinked(LINK_SET, TRUE, "relay-lock", NULL_KEY);
 llSetColor(<1.0, 0.0, 0.0>, ALL_SIDES);
 lit = TRUE;
 }
 }
 else
 {
 if ( lit )
 {
 llMessageLinked(LINK_SET, FALSE, "relay-lock", NULL_KEY);
 llSetColor(<0.0, 1.0, 0.0>, ALL_SIDES);
 lit = FALSE;
 }
 }
}
clear(key obj)
{
 integer o = llListFindList(objects, [obj]);
 if ( o >= 0 )
 {
 integer i;
 while ( (i = llListFindList(restrictions, [o])) >= 0 )
 {
 string restr = llList2String(restrictions, i + 1);
 restrictions = llDeleteSubList(restrictions, i, i + 1);
 if ( llListFindList(restrictions, [restr]) < 0 )
 llOwnerSay("@" + restr + "=y");
 }
 }
 if ( llGetListLength(restrictions) == 0 )
 {
 objects = [];
 updateStatus(FALSE);
 }
}
clearSome(key obj, string param)
{
 integer o = llListFindList(objects, [obj]);
 if ( o >= 0 )
 {
 integer len = llGetListLength(restrictions);
 integer i;
 for ( i = 0 ; i < len ; i += 2 )
 {
 if ( llList2Integer(restrictions, i) == o )
 {
 string restr = llList2String(restrictions, i + 1);
 if ( llSubStringIndex(restr, param) >= 0 )
 {
 restrictions = llDeleteSubList(restrictions, i, i + 1);
 if ( llListFindList(restrictions, [restr]) < 0 )
 llOwnerSay("@" + restr + "=y");
 i -= 2;
 len -= 2;
 }
 }
 }
 }
 if ( llGetListLength(restrictions) == 0 )
 {
 objects = [];
 updateStatus(FALSE);
 }
}
rlvCommand(key id, string cmd)
{
 integer e = llSubStringIndex(cmd, "=");
 if ( e >= 0 )
 {
 string restr = llGetSubString(cmd, 1, e - 1);
 string param = llGetSubString(cmd, e + 1, -1);
 if ( param == "n" || param == "add" )
 {
 llOwnerSay(cmd);
 integer i;
 integer o = llListFindList(objects, [id]);
 if ( o < 0 )
 {
 o = llGetListLength(objects);
 objects += id;
 i = -1;
 }
 else i = llListFindList(restrictions, [o, restr]);
 if ( i < 0 )
 {
 restrictions += [o, restr];
 updateStatus(TRUE);
 }
 if ( restr == "unsit" )
 llOwnerSay("@getsitid=" + (string)backChannel);
 }
 else if ( param == "y" || param == "rem" )
 {
 integer o = llListFindList(objects, [id]);
 if ( o >= 0 )
 {
 integer i = llListFindList(restrictions, [o, restr]);
 if ( i >= 0 )
 {
 restrictions = llDeleteSubList(restrictions, i, i + 1);
 if ( llGetListLength(restrictions) == 0 )
 {
 objects = [];
 updateStatus(FALSE);
 }
 }
 }
 if ( llListFindList(restrictions, [restr]) < 0 )
 llOwnerSay(cmd);
 }
 else
 {
 if ( restr == "clear" )
 clearSome(id, param);
 else
 llOwnerSay(cmd);
 }
 }
 else
 {
 if ( cmd == "@clear" )
 clear(id);
 else
 llOwnerSay(cmd);
 }
}
string getStatus(integer o) // it's okay if o is not an existing object, or -1, we just result an empty string
{
 string result = "";
 integer len = llGetListLength(restrictions);
 integer i;
 for ( i = 0 ; i < len ; i += 2 )
 if ( llList2Integer(restrictions, i) == o )
 result += "/" + llList2String(restrictions, i + 1);
 return result;
}
integer allowCommand(key id, string cmd)
{
 if ( ! askMode ) return TRUE; // not in ask mode, automatically allow
 if ( llListFindList(allowedObjects, [id]) >= 0 ) return TRUE; // this object has permission
 if ( llGetOwnerKey(id) == llGetOwner() ) return TRUE; // owner's own objects always have permission
 if ( llGetSubString(cmd, -2, -1) == "=n" ) return FALSE; // adding restrictions not allowed without permission
 if ( llGetSubString(cmd, -4, -1) == "=add" ) return FALSE; // adding restrictions not allowed without permission
 if ( llGetSubString(cmd, -6, -1) == "=force" ) return FALSE; // forcing actions not allowed without permission
 return TRUE; // anything else is okay (e.g. @version)
}
default
{
 state_entry()
 {
 llSetObjectName(implversion);
 
 lit = TRUE;
 updateStatus(FALSE);
 
 llListen(relayChannel, "", NULL_KEY, "");
 llListen(backChannel = 16777216 + (integer)llFrand(16777216.0), "", llGetOwner(), "");
 llSetTimerEvent(5.0);
 
 llMessageLinked(LINK_SET, -123, llGetScriptName() + " free: " + (string)llGetFreeMemory(), NULL_KEY);
 }
 
 listen(integer ch, string name, key id, string msg)
 {
 if ( ch == relayChannel )
 {
 list pack = llCSV2List(msg);
 key target = llList2String(pack, 1);
 if ( target == llGetOwner() )
 {
 string cmdid = llList2String(pack, 0);
 list cmds = llParseString2List(llList2String(pack, 2), ["|"], []);
 integer len = llGetListLength(cmds);
 integer i;
 for ( i = 0 ; i < len ; ++i )
 {
 string cmd = llList2String(cmds, i);
 if ( llGetSubString(cmd, 0, 0) == "@" )
 {
 if ( allowCommand(id, cmd) )
 {
 rlvCommand(id, cmd);
 llShout(relayChannel, cmdid + "," + (string)id + "," + cmd + ",ok");
 }
 else if ( llListFindList(rejectedObjects, [id]) >= 0 )
 {
 llShout(relayChannel, cmdid + "," + (string)id + "," + cmd + ",ko");
 }
 else
 {
 integer p = llListFindList(pendingObjects, [id]);
 if ( p < 0 )
 {
 p = llGetListLength(pendingObjects);
 pendingObjects += id;
 
 list buttons;
 if ( p )
 buttons = ["Yes " + (string)p, "No " + (string)p];
 else
 buttons = ["Yes", "No"];
 string object = llKey2Name(id);
 string ownedby = llKey2Name(llGetOwnerKey(id));
 if ( ownedby ) object += " (owned by " + ownedby + ")";
 if ( ! permListener )
 permListener = llListen(permChannel = -16777216 - (integer)llFrand(16777216.0), "", llGetOwner(), "");
 llDialog(llGetOwner(), "The object " + object
 + " is attempting to use your Restrained Life Viewer relay. Do you wish to allow it?", buttons, permChannel);
 permClose = llGetUnixTime() + 120;
 }
 if ( llListFindList(pendingCommands, [p, cmdid, cmd]) < 0 ) // some devices spam the same command over and over, don't keep growing list
 pendingCommands += [p, cmdid, cmd];
 }
 }
 else if ( cmd == "!release" )
 {
 clear(id);
 llShout(relayChannel, cmdid + "," + (string)id + "," + cmd + ",ok");
 }
 else if ( cmd == "!getstatus" )
 {
 integer o = llListFindList(objects, [id]);
 llShout(relayChannel, cmdid + "," + (string)id + "," + cmd + "," + getStatus(o));
 }
 else if ( cmd == "!version" )
 {
 llShout(relayChannel, cmdid + "," + (string)id + "," + cmd + "," + version);
 }
 else if ( cmd == "!implversion" )
 {
 llShout(relayChannel, cmdid + "," + (string)id + "," + cmd + "," + implversion);
 }
 else if ( cmd == "!pong" )
 {
 integer p = llListFindList(pingObjects, [id]);
 if ( p >= 0 )
 {
 llOwnerSay("Got pong, you're out of luck. Reapplying restrictions from " + name + "...");
 integer o = llListFindList(objects, [id]);
 if ( o < 0 )
 {
 o = llGetListLength(objects);
 objects += id;
 }
 string cmd = "";
 string pre = "@";
 integer i;
 while ( (i = llListFindList(pingRestrictions, [p])) >= 0 )
 {
 string restr = llList2String(pingRestrictions, i + 1);
 if ( restr == "unsit" )
 {
 if ( pingSitting )
 {
 llOwnerSay("@sit:" + (string)pingSitting + "=force");
 pingSitRetry = 12; // keep trying for a minute -- it can take that long for things to rez
 }
 }
 cmd += pre + restr + "=n";
 pre = ",";
 restrictions += [o, restr];
 pingRestrictions = llDeleteSubList(pingRestrictions, i, i + 1);
 }
 if ( cmd )
 llOwnerSay(cmd);
 updateStatus(TRUE);
 if ( llGetListLength(pingRestrictions) == 0 )
 pingObjects = [];
 }
 }
 }
 }
 else if ( target == llGetKey() )
 {
 string cmdid = llList2String(pack, 0);
 if ( cmdid == "uniquecheck" )
 llOwnerSay("You are wearing another RLV relay (" + name + "). Remove it. You should never wear more than one RLV relay at a time.");
 }
 return;
 }
 
 if ( ch == permChannel )
 {
 integer o = (integer) llGetSubString(msg, -2, -1);
 key obj = llList2Key(pendingObjects, o);
 if ( llGetSubString(msg, 0, 2) == "Yes" )
 {
 allowedObjects = obj + llList2List(allowedObjects, 0, 8);
 integer i;
 while ( (i = llListFindList(pendingCommands, [o])) >= 0 )
 {
 string cmd = llList2String(pendingCommands, i + 2);
 rlvCommand(obj, cmd);
 llShout(relayChannel, llList2String(pendingCommands, i + 1) + "," + (string)obj + "," + cmd + ",ok");
 pendingCommands = llDeleteSubList(pendingCommands, i, i + 2);
 }
 if ( llGetListLength(pendingCommands) == 0 )
 pendingObjects = [];
 }
 else
 {
 rejectedObjects = obj + llList2List(rejectedObjects, 0, 8);
 integer i;
 while ( (i = llListFindList(pendingCommands, [o])) >= 0 )
 {
 string cmd = llList2String(pendingCommands, i + 2);
 llShout(relayChannel, llList2String(pendingCommands, i + 1) + "," + (string)obj + "," + cmd + ",ko");
 pendingCommands = llDeleteSubList(pendingCommands, i, i + 2);
 }
 if ( llGetListLength(pendingCommands) == 0 )
 pendingObjects = [];
 }
 return;
 }
 
 if ( ch == backChannel )
 {
 sitting = msg;
 return;
 }
 }
 
 on_rez(integer param)
 {
 if ( objects )
 {
 integer len = llGetListLength(objects);
 integer i;
 for ( i = 0 ; i < len ; ++i )
 if ( llListFindList(restrictions, [i]) >= 0 )
 llShout(relayChannel, "ping," + (string)llList2Key(objects, i) + ",ping,ping");
 }
 
 pingObjects = objects;
 pingRestrictions = restrictions;
 pingSitting = sitting;
 pingSitRetry = 0;
 
 objects = [];
 restrictions = [];
 sitting = NULL_KEY;
 updateStatus(FALSE);
 
 allowedObjects = [];
 rejectedObjects = [];
 pendingObjects = [];
 pendingCommands = [];
 }
 
 timer()
 {
 if ( llGetAgentInfo(llGetOwner()) & AGENT_ON_OBJECT )
 {
 llOwnerSay("@getsitid=" + (string)backChannel);
 pingSitRetry = 0;
 }
 else
 {
 sitting = NULL_KEY;
 if ( pingSitRetry )
 {
 llOwnerSay("@sit:" + (string)pingSitting + "=force");
 --pingSitRetry;
 }
 }
 
 if ( permListener )
 {
 if ( llGetUnixTime() >= permClose )
 {
 pendingObjects = [];
 pendingCommands = [];
 llListenRemove(permListener);
 permListener = 0;
 }
 }
 }
 
 link_message(integer src, integer num, string msg, key id)
 {
 if ( msg == "relay-ask" )
 {
 askMode = num;
 if ( askMode )
 {
 allowedObjects = objects;
 rejectedObjects = [];
 pendingObjects = [];
 pendingCommands = [];
 }
 else if ( permListener )
 {
 allowedObjects = [];
 rejectedObjects = [];
 pendingObjects = [];
 pendingCommands = [];
 llListenRemove(permListener);
 permListener = 0;
 }
 return;
 }
 
 if ( msg == "relay-status" )
 {
 integer len = llGetListLength(objects);
 if ( len )
 {
 integer i;
 for ( i = 0 ; i < len ; ++i )
 llMessageLinked(LINK_SET, num, getStatus(i), llList2Key(objects, i));
 }
 else
 llMessageLinked(LINK_SET, num, "", NULL_KEY);
 return;
 }
 
 if ( msg == "relay-reset" )
 {
 integer len = llGetListLength(objects);
 integer i;
 for ( i = 0 ; i < len ; ++i )
 llShout(relayChannel, "safeword," + (string)llList2Key(objects, i) + ",!release,ok");
 llResetScript();
 return;
 }
 }
 
 attach(key id)
 {
 if ( id )
 llWhisper(relayChannel, "uniquecheck," + (string)id + ",!version");
 }
 
 changed(integer change)
 {
 if ( change & CHANGED_OWNER )
 llResetScript();
 }
}