Difference between revisions of "LSL Protocol/Restrained Love Relay/Other Implementations/DEM Relay HUD"

From Second Life Wiki
Jump to navigation Jump to search
Line 3: Line 3:
==Think Kink PBA (Personal Bondage Assistant)==
==Think Kink PBA (Personal Bondage Assistant)==


((current release of the tkPBA is '''''v30k'''''))
((current release of the tkPBA is '''''v110r'''''))


The THINK KINK PBA (tkPBA) is our implementation of a multi-object, multi-restriction relay for the Restrained Life viewer in Second Life™. In addition to providing the interface between in-world furniture/toys, it implements some extensions to the RLV specs that allow for identification of WHO is operating a device trying to access the PBA (Relay) and functions to provide vision restrictions without another attachment/hud. These extensions and scripts are released under the following license:
The THINK KINK PBA (tkPBA) is our implementation of a multi-object, multi-restriction relay for the Restrained Life viewer in Second Life™. In addition to providing the interface between in-world furniture/toys, it implements some extensions to the RLV specs that allow for identification of WHO is operating a device trying to access the PBA (Relay) and functions to provide vision restrictions without another attachment/hud. These extensions and scripts are released under the following license:

Revision as of 09:06, 4 May 2012

Think Kink PBA (Personal Bondage Assistant)

((current release of the tkPBA is v110r))

The THINK KINK PBA (tkPBA) is our implementation of a multi-object, multi-restriction relay for the Restrained Life viewer in Second Life™. In addition to providing the interface between in-world furniture/toys, it implements some extensions to the RLV specs that allow for identification of WHO is operating a device trying to access the PBA (Relay) and functions to provide vision restrictions without another attachment/hud. These extensions and scripts are released under the following license:

©left 2009 Think Kink (Think Kink is Ilana Debevec & Lyssa Daehlie)

the Think Kink PBA (tkPBA for short) is released under a modified "CopyLeft" license. The short form without legalese.

This code 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' from 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.

Based on Maike Short's 1030b relay, with thanks to Tahni Taratal, RL Ansome, Nihal Quan and all of the staff of THINK KINK who helped with the shakedown and development of this device. Recursive thanks to everyone that has gone before on the development of the Restrained Life Relay, including (but not limited to) Maike Short, Felis Darwin, Chorazin Allen, Azoth Amat, Gregor Mougin, Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow and she who started it all ... Marine Kelley. An extra special personal thanks to Chloe1982 Constantine who was able to help me put ideas into bits when I was buried whisker deep in both SL and RL issues.

In-world (no mod) copies of this relay may be picked up for free at THINK KINK🖈 or email to Ilana.Debevec@gmail.com for full source. --Ilana Debevec 19:53, 27 February 2009 (UTC)

Design Points

We went into the design of the tkPBA with the intention of making it as flexible as possible in its handling of multiple objects. Originally we looked at the single script concept but quickly discarded this as being rather unwieldy and subject to the whims of LSL to its use of memory. With the all in one design, you can't reliably check on memory utilization, excessive garbage collection with list management and other quirks of LSL. It also left the script more vulnerable to the unexpected stack/heap overflow if you didn't put a limit to the number of devices it would control. Therefore, we split the relay into three parts -

- the DPU (Dominant Processing Unit) handling all the 'global' tasks of the relay (listens, dialogs, mode (off/ask/auto/hardcore), SAFEWORD and implementation of the !who and !vision commands.
- the SPU (Slave Processing Unit) handling 'object specific' tasks (what object and it's restrictions are in place).
- the GPU (Graphics Processing Unit)) the 'bare metal' interface for the !vision command.

With the advent of llSetLinkPrimitiveParamsFast we no longer needed the GPU to be in the actual GPU prim; for reasons of efficiency (and so that I no longer had to keep flipping the PBA to edit the back of it) we've merged the GPU and DPU scripts.

Further, we took the actual architecture of the relay down to the 'prim' level. Since we have made this a HUD ONLY device (a. can't drop scripts in 'no mod' items that may already have their own touch events, b. not enough body attachment points as it is, c. keep it and it's status and control where you can get it at easily), we could divide the scripts among different prims to let them do part of the work.

- the DPU goes in the root, the SPU's go in separate prims (one per # of devices you want to control), the GPU lurks on the back of the root prim and gets prim modification to affect the vision of the user.
- the SPU keeps the UUID of the object currently controlling it in it's description field and the UUID of the last AV that used it stored either in the description or internally depending on whether or not giving control to the specified object is still pending. The DPU, instead of having to pass link messages back and forth, or keep updating an internal list, can find the current state of the object simply by reaching over with llGetObjectDetails(SPUn link number), [OBJECT_DESC]) and get the UUID's of the object/operator or if NULL_KEY see that the SPU is free to use.
- the GPU is a single tiny prim that is glued to the back of the tkPBA that can be manipulated in size, color, transparency, texture, etc... to affect the vision of the victim.

There is, however, with the advent of the tkPBA 110r and later, a big change in philosophy with respect to the relay. The tkPBA has functions beyond those of just the relay (e.g., the LOCKER and other functionality), the details of which are not germane to the discussion of a relay. Yet we're committed to making the relay publicly available, including the scripts. Our solution is to provide the scripts for the DPU/GPU and the SPU; these are all the scripts needed to create an embedded relay. What is missing from them is the controlling script, what we call the supervisor, but its only purpose is to set the state of the relay. Note, you won't see anything about safeword either, the reason is that safeword is not part of the relay spec, other than a recommended good idea! In our case, the supervisor handles safeword processing and the relay simply responds as ordered.

Logo's & Signage

Think Kink has developed logos to brand our products showing them as having the !who and !vision compatibility as well as overall Restrained Life compatibility that eliminates the SL "Eye in Hand" that has some restrictive usage rights. With the publication of these scripts, we are opening them for use by the community as long as attribution is given to Think Kink. Email Ilana.Debevec@gmail.com for a TGA pack of the logos. --Ilana Debevec 19:52, 27 February 2009 (UTC)

--Ilana Debevec 18:44, 5 March 2009 (UTC)

tkPBA Command Extensions

A better place to look at definitions of !x-who and !x-vision is the Open Relay Group pages [1] which Think Kink will continue to support and add a growing number of x-tensions to the PBA.

!who also !x-who

Description
!who is implemented as a meta command to pass the UUID of WHO is trying to operate your relay, not just WHAT device and the OWNER of the device (more often than not, WHO's the operator and WHO's the owner are NOT the same avatar).
!x-who is a synonym for !who and was created when the future of !who was put in doubt
Background
Since the Restrained Life viewer was introduced to the grid, almost from day one we have people coming to our store wanting a way to know WHO is trying to control their relay, it's almost become a mantra "I don't care WHAT it is, I want to know WHO it is!". This gives the tkPBA the ability to give some useful information on the actions of someone that is attempting to 'use' your relay to help you make informed decisions when your relay is in ASK mode.
Syntax
!who/(key)
where (key) is the UUID of the AV that you wish to present to the relay.
Implementation
THINK KINK is implementing this in our devices with the following few caveats -
- !who must be the FIRST entry of a RL Command string that will RESTRICT or DIRECTLY EFFECT someone. So, anything that will (for example) 'lock', 'force', 'deafen', 'mute', etc somone will get a !who
- !who would not be required for things like !release, or anything that UNRESTRICTS someone.
- !who is not additive, only the last !who is tracked for any given SPU (object).
- !who/NULL_KEY is moot, if you don't know !who, don't sent it.
- If no !who is sent, normal object verification rules apply
- NOTE for builders.
On a 'self activated' device (such as an area effect device that, let's say, restricts chat or tp or flying), the !who should be the UUID of the VICTIM!. This makes possible the ASK text such as "You have activated Crafty Avatar's Area of Doom and it is attempting to control your relay, ALLOW/DENY?".
A TRAP device however would generally have someone that 'set' the device (in the case of THINK KINK devices, we have an AUTOLOCK setting) and if the device knows that, it should send the UUID of the PERSON THAT SET THE TRAP ... ie. "Crafty Avatar wants to control your relay using their Dasterdly Device, ALLOW/DENY?"
Further Points of Implementation
The ASK message should change if a !who command comes in, so instead of "Dastardly Device owned by Random Avatar wants to control your relay, ALLOW/DENY?"
- the message becomes "Crafty Avatar wants to control your relay using Random Avatar's Dastardly Device, ALLOW/DENY?"
- if the owner and the operator are the same, then a more succinct message could be "Crafty Avatar wants to control your relay using their Dasterdly Device, ALLOW/DENY?"
- if a !who is present, then should the victim DENY the request, an IM should go back to the 'clicker' "Sitting Duck has denied your attempt to control their relay".

--Ilana Debevec 19:54, 27 February 2009 (UTC)

!x-vision

Description
meta command to control what the victim can see while under restraint. This will allow a full range of vision control of the victim. Full blindness, partial, color, textures, etc..
Implementation
using a small microprim that hides on the back of our RelayHUD, we can expand and texture this to control the sight of the victim. Put them in a dark cell, they go blind. Or in a forcefield change the color, make it partially transparant, put up a texture, etc. We are/will also be using this as a "MouseLook" enforcer to punish a victim when they won't stay in mouselook (get out of mouselook, go totally blind). Currently being implemented in devices from THINK KINK.
Syntax
!x-vision/(color)/(alpha)/(texture)/(repeats)/(offsets)/(rotation)
(color) = color for the HUD covering prim in RGB format <r'g'b> 0-255 or 0-1.0(NOTE: the ' is the seperator instead of , to avoid parsing issues with the rest of the RLV command string. Second note: you cannot mix number ranges either all are in the range 0 to 1.0 or all are in the range 0 to 255)
(alpha) = % transparent to make the HUD prim cover (in alpha format 0.0-1.0 or as a percentage 0 to 100)
(texture) = UUID for a texture to apply to the prim
(repeats) = x/y repeats for the texture, same format as the texture tab on an prim 1.0'1.0
(offsets) = x/y offsets for the texture, same format as the texture tab on an prim 0.0'0.0
(rotation) = rotation of the texture
SPECIAL ENTRY, any of the parameters can be replaced with "*" for 'do not change existing value'
NOTE: the 'default' value of the HUD prim is 100% transparent, white, TEXTURE_BLANK. ie. !x-vision/0.0/<255'255'255>/TEXTURE_BLANK/1.0'1.0/0.0'0.0
if all you want to do is 'blind' someone, then !x-vision/<0'0'0>/1.0/*/*/*/*
Examples
Total blackout "!x-vision/<0'0'0>/1.0/TEXTURE_BLANK/1.0'1.0/0.0'0.0/0.0
Light fog "!x-vision/<128'128'128>/0.5/TEXTURE_BLANK/1.0'1.0/0.0'0.0/0.0"
In a plywood box no matter where they look "!x-vision/<255'255'255>/1.0/TEXTURE_PLYWOOD/1.0'1.0/0.0'0.0"
Compatibility
since this is a metacommand, relays that don't support this should ignore it

--Ilana Debevec 18:29, 5 March 2009 (UTC)

!x-vision/clear

Description
complimentary command to !x-vision. Reset and remove all !x-vision restrictions.
Syntax
!x-vision/clear
Compatibility
same as !x-vision, if the relay can't, then don't

--Ilana Debevec 18:29, 5 March 2009 (UTC)

tkPBA Implementation Scripts

The DPU

The DPU (Dominant Processing Unit) handles all the listens, menus, mode control and other GLOBAL functions, it also manages the GPU (graphical processing unit) functionality.

version tk.RLV1100 DPU (120501.0) -- --Chloe1982 Constantine 12:05, 4 May 2012

<lsl> // tk.RLV110r DPU(120501.0) RLV Basic Relay 1.100 and ORG 1.0 // // ©left 2012 Think Kink (Think Kink is Ilana Debevec & Lyssa Daehlie)

// the Think Kink Restrained Life PBA Relay (tkPBA for short) is released under a modified "CopyLeft" license. // // The short form without legalese.

// This code 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.

// Most Recent release code should be found here: // // http://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Relay/Other_Implementations/Think_Kink_Restrained_Life_PBA

// Based on Maike Short's 1030b relay, with thanks to Tahni Taratal, RL Ansome, Nihal Quan and all of the staff of // THINK KINK who helped with the shakedown and development of this device. Recursive thanks to everyone that has // gone before on the development of the Restraint Life Relay, including (but not limited to) Felis Darwin, Nano Siemens, // Azoth Amat, Gregor Mougin, Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow and Chorazin Allen, Marine Kelley.

// Major and mega thanks to Maike Short for her (all too brief) guidance and inspiration as keeper of the Relay Standards // We all wish you were still here, we won't forget you, ever.

// An extra special personal thanks to Chloe1982 Constantine without whom this would not have happend, She was able // to help me put ideas into bits when I was buried whisker deep in both SL and RL issues.

// Now supporting the ORG (Open Relay Group) v1.0 standards as found at - // // http://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Life_Open_Relay_Group // // Think Kink is proud to be a founding member of ORG and to help the specification GROW naturally and unimpeded.

// In-world (no mod) copies of this relay may be picked up for free at // Think Kink at http://slurl.com/secondlife/Think%20Kink/128/128/501 // or thru the "Get tkPBA" in button in most Think Kink devices

// or email to Chloe1982.Constantine@gmail.com for full source.

// Chloe1982.Constantine 4 May 2012

// I couldn't have written this without both Ilana and Maike. // Chloe 14 February 2009 // But I've changed the relay a lot; although it started as theirs, I am fully responsible for all errors

// if needed to check what is happening here

integer VISION_COMMAND = -4360493; // identifies commands to the GPU list coreIndex = []; list coreData = []; integer GPUPRIM = -50; // If we don't find it, this should be a safe enough number integer O_ALPHA = -6; integer O_COLOR = -5; integer O_TEXTURE = -4; integer O_REPEAT = -3; integer O_OFFSET = -2; integer O_ROTATION = -1; integer O_NUM = 6; // Needs to be -O_ALPHA

string strReplace(string str, string search, string replace) {

   return llDumpList2String(llParseStringKeepNulls((str = "") + str, [search], []), replace);

}

integer invalidVector(vector v, float lowVal, float highVal) {

   if (v.x < lowVal || v.y < lowVal || v.z < lowVal)
       return TRUE;
   if (highVal < lowVal)
       return FALSE;
   if (v.x > highVal || v.y > highVal || v.z > highVal)
       return TRUE;
   return FALSE;

}

setBlind(integer corePrim, string color, string alpha, key texture, string repeats, string offsets, string rot) {

   float na;
   if (alpha == "*")
       na = llList2Float(coreData, O_ALPHA);
   else if ((na = (float)alpha) < 0.0)
       na = 0.0;
   else if (na > 1.0 && na <= 100.0)
       na /= 100.0;
   else if (na > 100.0)
       na = 1.0;
   
   vector nc;
   if (color == "*")
       nc = llList2Vector(coreData, O_COLOR);
   else
   {
       nc = (vector)strReplace(color, "'", ",");
       if (nc.x > 1.0)
           nc = nc/255;
       if (invalidVector(nc, 0.0, 1.0))
           nc = llList2Vector(coreData, O_NUM + O_COLOR);
   }
   
   if (texture)
   {                   // stupid SL
   }
   else if ((string)texture == "*")
       texture = llList2Key(coreData, O_TEXTURE);
   else
       texture = llList2Key(coreData, O_NUM + O_TEXTURE);
   
   float nr;
   if (rot == "*")
       nr = llList2Float(coreData, O_ROTATION);
   else if ((nr = (float)rot) < 0.0)
       nr = 0.0;
   
   coreIndex += [corePrim];
   coreData += [na, nc, texture, makeVector(repeats, 0.0, -1.0, O_REPEAT), makeVector(offsets, -1.0, 1.0, O_OFFSET), nr];
   
   integer p;
   p = llListFindList(coreIndex, [corePrim]);
   if ((p+1) < llGetListLength(coreIndex))
       clearBlind(corePrim);

}

vector makeVector(string str, float lv, float hv, integer offset) {

   vector nr;
   if (str == "")
       return llList2Vector(coreData, O_NUM + offset);
   if (str == "*")
       return llList2Vector(coreData, offset);
   nr = (vector)("<"+strReplace(str, "'", ",")+",0.0>");
   if (invalidVector(nr, lv, hv))
       return llList2Vector(coreData, O_NUM + offset);
   return nr;

}

clearBlind(integer corePrim) {

   integer pos = llListFindList(coreIndex, [corePrim]);
   if (pos == -1)
       return;
   coreIndex = llDeleteSubList(coreIndex, pos, pos);
   integer spos = pos * O_NUM;
   coreData = llDeleteSubList(coreData, spos, spos + O_NUM - 1); 

}

float blindAlpha() {

   integer i = 1;
   integer n = llGetListLength(coreIndex);
   float ra = llList2Float(coreData, O_NUM + O_ALPHA);
   float ta;
   while (i < n)
       if ((ta = llList2Float(coreData, (i++ * O_NUM) + O_NUM + O_ALPHA)) < ra)
           ra = ta;
   return ra;

}

// make cheating (adding exceptions) a bit more difficult by not allowing // attachment to control the viewer as they are not subjected to land building // restrictions

string mmALLOW = "Allow"; string mmDENY = "Deny";

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

string PREFIX_METACOMMAND = "!";

integer RLVRS_CHANNEL = -1812221819; // RLVRS in numbers integer DIALOG_CHANNEL; integer DIALOG_HANDLE; integer STATUS_OPEN_CHANNEL = -1373421300; integer COMMAND_CHANNEL = -1373421302; integer CORE_CONTROL = -1383421304; key null_key = NULL_KEY; // Thanks Darien

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

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

integer mode;

// Think Kink - A few extra variables and constants //

string RELAYTESTCMD = "tk.testrelay"; string VERCMD = "!version"; integer HARDCORE_ON = FALSE; // are we in HARDCORE Mode key whoDoing = null_key; // Who is operating the object integer askCount = 0; // # of open ask requests integer coreAskPrim = 0; // # of core currently using the dialog string ownerName; // who am I? list g_privileged = [];

integer cores = 5;

string corePrims = ""; // prim numbers of the core indicators string coreKeys = ""; string coreAskKeys = ""; integer coreInUse = 0; integer coreAsking = 0;


// --------------------------------------------------- // Core keys management functions // ---------------------------------------------------

key getControlObject(integer link) {

   integer p = 36 * llSubStringIndex(corePrims, (string)link);
   if (p < 0)
       return null_key;
   return (key)llGetSubString(coreKeys, p, p + 35);

}

string replKey(string keyList, string newKey, integer pos) {

   if (pos == 0)
       return newKey + llGetSubString(keyList, 36, -1);
   if (pos == cores - 1)
       return llGetSubString(keyList, 0, 143) + newKey;
   integer endFirst = (pos * 36) - 1;
   integer startSecond = (pos + 1) * 36;
   return llGetSubString(keyList, 0, endFirst) + newKey + llGetSubString(keyList, startSecond, -1);

}

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

// not so simple function to generate the ask dialog text

string askDialogText(key id, integer trustworthy) {

   key owner = llGetOwner();
   key ownerKey = llGetOwnerKey(id);
   string text = "";
   string objectName = llKey2Name(id);
   string objectOwner= llKey2Name(ownerKey);
   string whoDoingName = "";
   
   if (objectOwner == "") objectOwner = "(Name Unavailable)";
   
   if (whoDoing != null_key)  whoDoingName = llKey2Name(whoDoing) + ","; 
   vector pos = llList2Vector(llGetObjectDetails(id, [OBJECT_POS]), 0);

   integer dist = (integer)llVecDist(pos, llGetRootPosition());
   pos.x = (integer)pos.x;
   pos.y = (integer)pos.y;
   pos.z = (integer)pos.z;
   string objectLocation = objectName + "\nlocated at "+ llGetRegionName()
        + " " + (string)pos + " ("
        + (string)dist + "m) ";
        
   string possessive = objectOwner + "'s ";
   if (ownerKey == owner) 
       possessive = "your ";
   else if (ownerKey == whoDoing)
       possessive = "their ";
       
   if (whoDoing == owner)
       text = "You have activated ";
   else if (whoDoing != null_key)
       text = whoDoingName + " using ";
   text += possessive + objectLocation + "\nis attempting to control your viewer";
   if (!trustworthy)
       text += "\nWARNING: This object is not owned by the people owning this parcel.";
   text += "\n\nDo you accept ?";
   return text;

}

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


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

releaseRestrictions() {

   llMessageLinked(LINK_ALL_OTHERS, CORE_CONTROL, "FreeCore", null_key);
   g_privileged = [];

}

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

// Make a dialog box and get the timer started

acceptOrDeny(string message) {

   llSetTimerEvent(30.0);
   //llOwnerSay("Started Accept/Deny timer");
   llListenControl(DIALOG_HANDLE, TRUE);
   llDialog (llGetOwner(), message, [mmALLOW, mmDENY], DIALOG_CHANNEL);
   //llMessageLinked(LINK_THIS, -300, "ASKMENU|" + (string)llGetOwner() + "|" + (string)DIALOG_CHANNEL + "|" + message + "|Allow,Deny", "showMenu");

}

// process the Yes/No buttons of the permission dialog // Needs to be fixed to handle multiple simultaneous requests

key tempKey; processDialogResponse(string message) {

   //llOwnerSay("processDialogResponse:" + message);
   llSetTimerEvent(0);
   
   if (message == mmALLOW)                 // pending request authorized => process it
   {
       llMessageLinked(coreAskPrim, CORE_CONTROL, "ExecuteSaved", null_key);
       if (llListFindList(g_privileged, [tempKey = getControlObject(coreAskPrim)]) == -1)
           g_privileged = [tempKey] + g_privileged;
   }
       
   else if (message == mmDENY)             // pending request denied
   {
       if (whoDoing)
           llInstantMessage(whoDoing, ownerName + " has denied your request for control.");
       llMessageLinked(coreAskPrim, CORE_CONTROL, "FreeCore", null_key);
   }
   
   askCount -= 1;
   
   if (askCount == 0)
   {
       llListenControl(DIALOG_HANDLE, FALSE);
       return;
   }
   
   integer i = llSubStringIndex(corePrims, (string)coreAskPrim);
   integer done = i;
   string iDesc;
   integer prim;
   do
   {
       i = (i + 1) % cores;
       iDesc = (string)llGetLinkPrimitiveParams(prim = (integer)llGetSubString(corePrims, i, i), [PRIM_DESC]);
       //llOwnerSay("core #"+(string)i+" = " + iDesc);
   }
   while (i != done && llStringLength(iDesc) == 36);
   ask(prim);

}

ask(integer link) {

   if (askCount++)
       return;
   string desc = (string)llGetLinkPrimitiveParams(link, [PRIM_DESC]);
   coreAskPrim = link;
   whoDoing = (key)llGetSubString(desc, 37, 72);
   key oid = llGetSubString(desc, 0, 35);
   acceptOrDeny(askDialogText(oid, TRUE));         // Was isObjectIdentityTrustworthy, figure out why

}


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

init() {

   integer temp0 = 0;
   integer temp1 = 0;
   integer temp2 = 0;
   integer temp3 = 0;
   integer temp4 = 0;
   
   key owner = llGetOwner();
   ownerName = llKey2Name(owner);
   
   // Set up the listeners
   llListen (RLVRS_CHANNEL, "", "", "");
   
   DIALOG_CHANNEL = ((integer)(llFrand(99999.0) * -1) - 2);  
   DIALOG_HANDLE = llListen(DIALOG_CHANNEL, "", owner, "");
   llListenControl(DIALOG_HANDLE, FALSE);
   
   //llWhisper(0,"Free Memory: " + (string) llGetFreeMemory());
   integer i = llGetNumberOfPrims();
   string primname;
   do
   {
       primname = llGetLinkName(i);
       
       if (primname == "corestat:0")       temp0 = i;
       else if (primname == "corestat:1")  temp1 = i;
       else if (primname == "corestat:2")  temp2 = i;
       else if (primname == "corestat:3")  temp3 = i;
       else if (primname == "corestat:4")  temp4 = i;
       else if (primname == "tk.GPU:0")    GPUPRIM = i;
   }
   while (--i);
   corePrims = (string)temp0 + (string)temp1 + (string)temp2 + (string)temp3 + (string)temp4;
   coreKeys = NULL_KEY + NULL_KEY + NULL_KEY + NULL_KEY + NULL_KEY;
   if (llStringLength(corePrims) != 5)
       llOwnerSay("Error in relay configuration, got " + corePrims);

// ok, let's REALLY make sure we've init'ed //

   whoDoing            = null_key;         // Who is operating the object
   askCount            = 0;                // # of open ask requests
   coreAskPrim         = 0;                // # of core currently using the dialog
   g_privileged        = [];
   
   coreIndex = [-49];
   coreData = [ 1.0, <1.0, 1.0, 1.0>, TEXTURE_BLANK, <1.0, 1.0, 1.0>, <0.0, 0.0, 0.0>, 0.0];
   llSetLinkPrimitiveParamsFast(GPUPRIM, [PRIM_SIZE, <0.01, 0.01, 0.01>, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 0.0]);
       
   releaseRestrictions();
   //llOwnerSay("GPUPrim:" + (string)GPUPRIM);

}

// // B E G I N S //

default {

   state_entry()
   {
       init();
       //llOwnerSay("Free: " + (string)llGetFreeMemory());
   }
   
   link_message(integer sender, integer channel, string message, key id)
   {
       //llOwnerSay("root link:" + (string)channel + ": " + message);
       
       if (channel == VISION_COMMAND)
       {
           list lCommand = llParseString2List(message, ["/"], []);
           //llOwnerSay("#"+(string)llGetListLength(lCommand)+": "+(string)lCommand);
           string meta_command;
       
           if ((meta_command = llList2String(lCommand, 0)) != "!x-vision")
               return;
       
           if (llList2String(lCommand, 1) == "clear")
               clearBlind(sender);
           else if (llGetListLength(lCommand) < 4)
               return;
           else
               setBlind(sender, llList2String(lCommand, 1), llList2String(lCommand, 2),
                   llList2String(lCommand, 3), llList2String(lCommand, 4),
                   llList2String(lCommand,5), llList2String(lCommand, 6));
       
           if (llGetListLength(coreIndex) == 1)
               llSetLinkPrimitiveParamsFast(GPUPRIM, [PRIM_SIZE, <0.01, 0.01, 0.01>, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 0.0]);
           else
               llSetLinkPrimitiveParamsFast(GPUPRIM, [PRIM_COLOR, ALL_SIDES, llList2Vector(coreData, O_COLOR), blindAlpha(),
                                                      PRIM_TEXTURE, ALL_SIDES, llList2String(coreData, O_TEXTURE), llList2Vector(coreData, O_REPEAT),
                                                                               llList2Vector(coreData, O_OFFSET), llList2Float(coreData, O_ROTATION),
                                                      PRIM_SIZE, <10.0, 10.0, 0.01>]);
       }
       else if (channel == COMMAND_CHANNEL)
       {
           if (message == "CheckRelay")
           {
               integer cx = cores;
               key     corecheck;
               vector  myPos = llGetPos();
               integer isBusy = FALSE;
               integer thisCore;
       
               do
               {
                   cx--;
                   corecheck = getControlObject(thisCore = (integer)llGetSubString(corePrims, cx, cx));
                   if (corecheck != null_key)                                      // just on the off chance the object disappeared
                   {                                                               // or we got away without getting released
                       //llOwnerSay(llList2CSV(["Controlled by:", cx, thisCore, corecheck]));
                       list l = llGetObjectDetails(corecheck, [ OBJECT_POS ]);     // check on the object
               
                       if (llGetListLength(l) == 0)              // ok, we're too far away, let me go
                       {
                           llMessageLinked(LINK_THIS, STATUS_OPEN_CHANNEL,
                               "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because the device cannot be found.", null_key);
                              
                           llMessageLinked(thisCore, CORE_CONTROL, "FreeCore", null_key);
                       }
                       else
                           isBusy = TRUE;
                   }
               }
               while (cx);                                                  // It might take the prims a moment to reset their state
               llMessageLinked(LINK_THIS, isBusy, "RelayState", id);
           }
           else if (message == "SetRelayState")
           {
               integer p = llSubStringIndex(id, ",");
               integer tmode = (integer)((string)id);
               HARDCORE_ON = (integer)llGetSubString(id, p + 1, -1);
               //llOwnerSay(llList2CSV(["SetRelayState", id, p, tmode, mode, HARDCORE_ON]));
               if (mode != MODE_OFF && tmode == MODE_OFF)
                   releaseRestrictions();
               else
                   llWhisper(RLVRS_CHANNEL, RELAYTESTCMD + "," + (string)llGetOwner() + "," + VERCMD);
               if (mode = tmode)
                   llMessageLinked(LINK_ALL_OTHERS, CORE_CONTROL, "RelayMax", (string)(mode - 1) + llGetSubString(id, p, -1));
           }
           else if (message == "ReleaseRelay")
               releaseRestrictions();
           else if (message == "AskThem")
               ask(sender);
           else if (message == "SetSource")
           {
               integer core = llSubStringIndex(corePrims, (string)sender);
               coreKeys = replKey(coreKeys, (string)id, core);
           }
       }
   }

   listen(integer channel, string name, key id, string message)
   {
       //llOwnerSay("listen:" + (string)channel + ": " + message);
       if (channel==RLVRS_CHANNEL)
       {
           //llOwnerSay("Root:RLV listen:" + message);
           integer p = llSubStringIndex(message, ",");
           if (p == -1)
               return;
           string keyPart = llGetSubString(message, p + 1, p + 37);
           if (keyPart != ((string)llGetOwner() + ",") && keyPart != "ffffffff-ffff-ffff-ffff-ffffffffffff,")      // not for us
               return;
           string cmdID = llGetSubString(message, 0, p - 1);
           
           if (cmdID == RELAYTESTCMD && (message = llGetSubString(message, p + 38, -1)) == (VERCMD + ",ok"))
           {
               llOwnerSay("Detected another relay: " + name);
               return;
           }
           
           if (llSubStringIndex(message, ",") != -1)           // this is malformed and not for us
               return;
           
           message = cmdID + "," + message;
           //llOwnerSay("Root:id="+(string)id+":coreKeys="+coreKeys+":index="+(string)llSubStringIndex(coreKeys, (string)id));
           
           if ((p = llSubStringIndex(coreKeys, (string)id)) == -1)        // llListFindList(g_privileged, [id]) != -1)
           {
               //llOwnerSay("Free core index:" + (string)llSubStringIndex(coreKeys, NULL_KEY));
               if ((p = llSubStringIndex(coreKeys, NULL_KEY)) == -1)                    // no core is available
                   return;
       
               if (mode == MODE_OFF)           // Check to see that the relay is on
               {
                   //llOwnerSay("deactivated - ignoring commands");
                   return; // mode is 0 (off) => reject
               }
               coreKeys = replKey(coreKeys, (string)id, p / 36);
           }
           p = p / 36;
           llMessageLinked((integer)llGetSubString(corePrims, p, p), RLVRS_CHANNEL, message, id);
           //llOwnerSay("Sent " + message + " to #" + llGetSubString(corePrims, p, p) + " (" + (string)p + ")");
       }
       
       else if (channel == DIALOG_CHANNEL)
           processDialogResponse(message);
   }
   changed(integer change)
   {
       if (change & CHANGED_OWNER)
           init();
       if (change & CHANGED_REGION)
           g_privileged = [];
   }
   
   attach(key id)
   {
       g_privileged = [];
       if (id != null_key)
           llWhisper(RLVRS_CHANNEL, RELAYTESTCMD  + "," + (string)llGetOwner() + "," + VERCMD);
   }
   
   timer()
   {
       processDialogResponse(mmDENY);
   }

} </lsl>

The SPU

The SPU (Slave Processing Unit) is assigned to an in-world object as it attempts to control the PBA. Each SPU is located in a seperate prim that, in the tkPBA is a single 'pip' inside the loop of the padlock that is the tkPBA HUD. When a SPU is free, the pip is black, when it is actively being controlled by an object, it turns green. When an object is assigned to the SPU but the tkPBA is in 'ASK' mode, the pip turns yellow until permission is granted (green) or denied (black and freed). The description field of the prim holds the UUID of the object currently controlling the SPU and the UUID of the last Avatar to send it a command (if available).

The SPU maintains all the restrictions of the object on the wearer, and generally the 'dirty work' of the relay function.

version tk.RLV1030 SPU (090611.0) --Ilana Debevec 18:24, 5 March 2009 (UTC)

<lsl> // tk.RLV1030 SPU (090611.0)

// ©left 2009 Think Kink (Think Kink is Ilana Debevec, Lyssa Daehlie & Chloe1982 Constantine)

// the Think Kink Restained Life PBA Relay (tkPBA for short) is released under a modified "CopyLeft" license. // The short form without legalese.

// This code 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.

// Based on Maike Short's 1030b relay, with thanks to Tahni Taratal, RL Ansome, Nihal Quah and all of the staff of // THINK KINK who helped with the shakedown and development of this device. Recursive thanks to everyone that has // gone before on the devlopment of the Restraint Life Relay, including (but not limited to) Maike Short, Felis Darwin, // Chorazin Allen, Azoth Amat, Gregor Mougin, Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow and she who // started it all ... Marine Kelley.

// An extra special personal thanks to Chloe1982 Constantine who was able to help me put ideas into bits when I // was buried whisker deep in both SL and RL issues.

// In-world (no mod) copies of this relay may be picked up for free at THINK KINK in LaSalle // or email to Ilana.Debevec@gmail.com for full source.

// Ilana Debevec 25 Feb 2009

// I couldn't have written this without both Ilana and Maike. // Chloe 14 February 2009

integer DEBUG = FALSE;

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

integer RLVRS_PROTOCOL_VERSION = 1030; // version of the protocol, stated on the specification page string RLVRS_IMPL_VERSION = "tkPBA based on Maike's 1.030"; // version of the implementation for debugging

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

integer RLVRS_CHANNEL = -1812221819; // RLVRS in numbers integer STATUS_OPEN_CHANNEL = -1373421300; integer STATUS_SAY_CHANNEL = -1373421301; integer GPU_CHANNEL = -4360493; integer GPU_PRIM;

integer NO_COMMAND = 0; integer FREE_COMMAND = 1; integer HOLD_COMMAND = 2; integer DOIT_COMMAND = 3; integer TEST_COMMAND = 4;

integer MAX_OBJECT_DISTANCE = 100; // 100m is llShout distance

integer LOGIN_DELAY_WAIT_FOR_PONG = 10; integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60;

string RVL_COMMAND_START = "@this-is-a-script-generated-message-beyond-the-control-of-the-agent=n"; string RVL_COMMAND_BLOCK = "@this-is-a-script-generated-message-beyond-the-control-of-the-agent";

string coreName = "0";

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

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

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;

integer holding = FALSE; key whoDoing = NULL_KEY; key devOwner = NULL_KEY; key devQuery = NULL_KEY; string devOName = ""; string whoName = "";


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


debug(string x) {

   if (DEBUG)
   {
       llOwnerSay("DEBUG:Core" + coreName + ": " + 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) {

   debug("Send: " + cmd);
   if (cmd != "")
       llOwnerSay(cmd);

}


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


// check whether the object is in llSay distance. // The specification requires llSay instead of llShout or llRegionSay // to be used to limit the range. But this has to be checked here again // because the objects are not trustworthy. integer isObjectNear(key id) {

   vector myPosition = llGetRootPosition();
   list temp = llGetObjectDetails(id, ([OBJECT_POS]));
   vector objPostition = llList2Vector(temp,0);
   float distance = llVecDist(objPostition, myPosition);
   debug("Object distance: " + (string)distance);
   return distance <= MAX_OBJECT_DISTANCE;

}

// If we already have commands from this object pending // because of a permission request dialog, just add the // new commands at the end. tryToGluePendingCommands(key id, string message) {

   if (llStringLength(pendingMessage) > 4000)
   {
       llMessageLinked(LINK_ROOT, STATUS_OPEN_CHANNEL, llKey2Name(id) + " is flooding commands. Releasing restrictions.", NULL_KEY);
       releaseRestrictions();
   }
   else
   {
       pendingMessage = pendingMessage + "|" + llList2String(llParseString2List(message, [","], []), 2);
   }

}


// accept !release even if out of range handleCommandsWhichAreAcceptedOutOfRange(string message) {

   list list_of_commands = llParseString2List(message, ["|"], []);
   if (llListFindList(list_of_commands, ["!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, (if it were implemenetd) // but this time there will be an acknowledgement execute(key id, string message) {

   list tokens = llParseString2List(message, [","], []);
   string cmd_id = llList2String(tokens, 0); // CheckAttach
   list list_of_commands = llParseString2List(llList2String(tokens, 2), ["|"], []);
   if (llList2Key(tokens, 1) != llGetOwner())
       return;                                 // This shouldn't happen, but can't hurt

   integer len = llGetListLength (list_of_commands);
   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(list_of_commands, 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
       {
           executeMetaCommand(cmd_id, id, command);
       }
   }

}

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

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

}

// 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

   debug("executeRLVCommand: behav=!" + behav + "! param=!" + param + "!");

   if ((behavName == RVL_COMMAND_BLOCK && param != "n") ||
       (isQuery(behavName) && (integer)param <= 0))
   {
       ack(cmd_id, id, command, "ko");
       return;
   }
   else if (param=="n" || param=="add") // add to restrictions
   {
       if (ind < 0)
       {
           if (llGetListLength(restrictions) == 0)
           {
               sendRLCmd(RVL_COMMAND_START);
           } 
           restrictions += [behav];
       }
       setSource(id); // we know that source is either NULL_KEY or id already
   }
   else if (param == "y" || param == "rem") // remove from restrictions
   {
       if (ind > -1) 
       {
           restrictions = llDeleteSubList (restrictions, ind, ind);
       }
       if (llGetListLength(restrictions) == 0)
       {
           setSource(NULL_KEY);
       }
       removeFromPendingList(behav);
   }
   else if (param != "force" && ((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.
       ack(cmd_id, id, command, "ko");
       return;
   }

   workaroundForAtClear(command);
   sendRLCmd(command); // execute command
   ack(cmd_id, id, command, "ok"); // acknowledge

}

// 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.) workaroundForAtClear(string command) {

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

}

setSource(key id) {

   source = id;
   if (holding)
       llSetObjectDesc((string)id + "^" + (string)whoDoing);
   else
       llSetObjectDesc((string)id);
   if (source == NULL_KEY)
       return;
   devOwner = llGetOwnerKey(source);
   devQuery = llRequestAgentData(devOwner, DATA_NAME);

}

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

   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 + "pong")
   {
       loginWaitingForPong = FALSE;
   }
   else if (llGetSubString(command, 0, 3) == "!who" || llGetSubString(command, 0, 5) == "!x-who")
   {
       list wCommands = llParseString2List(command, ["/"], []);
       key k = llList2Key(wCommands, 1);
       if (k)
       {
           whoDoing = k;
           whoName = llKey2Name(k);
       }
   }
   else if (llGetSubString(command, 0, 9) == PREFIX_METACOMMAND + "x-vision/")
   {
       llMessageLinked(GPU_PRIM, GPU_CHANNEL, command, NULL_KEY);
       ack(cmd_id, id, command, "ok");
   }

}

// 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
       {
           clearPendingMessages();
       }
   }

}


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

   holding = FALSE;
   whoDoing = NULL_KEY;
   whoName = "";
   devOName = "";
   devOwner = NULL_KEY;
   
   llSetColor(<0.0, 0.0, 0.0>,ALL_SIDES); //  Black
   llSetAlpha(1.0,ALL_SIDES);
   llMessageLinked(GPU_PRIM, GPU_CHANNEL, "!x-vision/clear", NULL_KEY);
   setSource(NULL_KEY);
   integer i;
   integer len = llGetListLength (restrictions);
   for (i = 0; i < len; ++i)
   {
       sendRLCmd(llList2String (restrictions, i) + "=y");
   }
   restrictions = [];
   loginPendingForceSit = FALSE;
   clearPendingMessages();

}

// 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
   pendingMessage = "";
   pendingTime = 0;

}


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

   debug("Got message (active world object " + (string) source + "): id=" + (string) id + " message=" + message + "\nholding = "+(string)holding);

   if (!isObjectNear(id)) 
   {
       handleCommandsWhichAreAcceptedOutOfRange(message);
       return;
   }
   if (holding)
       tryToGluePendingCommands(id, message);
   else
       execute(id, message);

}

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

init() {

   list thisCore = llParseString2List(llGetObjectName(), [":"], []);
   
   if (llGetListLength(thisCore) !=2) return; // less that 2, no colon, more than 2, too many 
   coreName = llList2String(thisCore,1);      // know what you're doing, I'll trust you
   
   llSetColor(<0.0, 0.0, 0.0>,ALL_SIDES); //  Black
   llSetAlpha(1.0,ALL_SIDES);
   
   integer n = llGetNumberOfPrims();
   integer i;
   for(i = 1; i <= n; ++i) 
       if (llGetLinkName(i) == "ddsg.GPU:0")
           GPU_PRIM = i;
   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)
   {
       ack("ping", source, "ping", "ping");
       timerTickCounter = 0;
       llSetTimerEvent(1.0);
       loginWaitingForPong = TRUE;
   }

}

sendForceSitDuringLogin() {

   debug("Force sit during login on " + (string) source);
   sendRLCmd ("@sit:" + (string) source + "=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))
   {
       llMessageLinked(LINK_ROOT, STATUS_OPEN_CHANNEL, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because " + llKey2Name(source) + " is not available.", NULL_KEY);
       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)
       {
           llMessageLinked(LINK_ROOT, STATUS_OPEN_CHANNEL, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because sitting down again was not possible.", NULL_KEY);
           loginPendingForceSit = FALSE;
           releaseRestrictions();
       }
       else
       {
           sendForceSitDuringLogin();
       }
   }

   if (!loginPendingForceSit && !loginWaitingForPong)
   {
       llSetTimerEvent(0.0);
   }

}

processCmdMessage(integer num, key id, string message) {

   if (num == DOIT_COMMAND)
   {
       llSetColor(<0.0, 1.0, 0.0>,ALL_SIDES); //  Green
       llSetAlpha(1.0,ALL_SIDES);
       holding = FALSE;
       if (id == NULL_KEY)
       {
           setSource(source);
           execute(source, pendingMessage);
       }
       else
       {
           setSource(id);
           execute(source, message);
       }
   }
   else if (num == TEST_COMMAND)
       execute(id, message);
   else if (num == FREE_COMMAND && message == "free")
       releaseRestrictions();
   else if (num == HOLD_COMMAND)
   {
       llSetColor(<1.0, 1.0, 0.0>,ALL_SIDES); //  Yellow
       llSetAlpha(1.0,ALL_SIDES);
       
       holding = TRUE;
       
       list lCommands = llParseString2List(message, [",", "/", "|"], []);
       debug("First command: " + llList2String(lCommands, 2));
       if (llList2String(lCommands, 2) == "!who" || llList2String(lCommands, 2) == "!x-who")
       {
           key k = llList2Key(lCommands, 3);
           debug("key="+(string)k);
           whoDoing = NULL_KEY;
           if (k)
               whoDoing = k;
       }
       
       setSource(id);
       pendingMessage = message;
       pendingTime = llGetUnixTime();
   }

}

displayState() {

   string message = "Idle";
   if (source != NULL_KEY)
   {
       key owner = llGetOwner();
       key devOwner = llGetOwnerKey(source);
       
       string who = "";
       if (whoName != "")
           who = whoName + ", using ";
       
       string whose = "an anonymously owned ";
       if (owner == devOwner)
           whose = "your ";
       else if (devOwner == whoDoing)
           whose = "their ";
       else if (devOName != "")
           whose = devOName + "'s ";
       
       vector pos = llList2Vector(llGetObjectDetails(source, [OBJECT_POS]), 0);
       message = who + whose + llKey2Name(source) + " located at " + llGetRegionName()
           +  " <" + (string) ((integer) pos.x)
           + ", " + (string) ((integer) pos.y)
           + ", " + (string) ((integer) pos.z) + ">";
   }
   llMessageLinked(LINK_ROOT, STATUS_SAY_CHANNEL, "Core #" + coreName + ": " +message, NULL_KEY);

}

default {

   state_entry()
   {
       init();
   }

   on_rez(integer start_param)
   {
       // relogging, we must refresh the viewer and ping the object if any
       // if source is set to something, fire all the stored restrictions
       if (source != NULL_KEY)
       {
           reinforceKnownRestrictions();
           pingWorldObjectIfUnderRestrictions();
       }
   }

   attach(key id)
   {
       if (id == NULL_KEY)
       {
           tellWorldObjectAboutCanceledSession();
           releaseRestrictions();
       }
   }

   timer()
   {
       processTimer();
   }

   link_message(integer sender, integer num, string message, key id)
   {
       debug("Link:" + (string)num + ": " + message);
       
       if (num == NO_COMMAND)
           processRelayMessage(id, message);
       else
           processCmdMessage(num, id, message);
   }
   
   touch_start(integer num)
   {
       // How this could be anything other than 1 I don't know
       displayState();
   }

   changed(integer change)
   {
       if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP)) 
       {
            llResetScript();
       }
   }
   
   dataserver(key query, string data)
   {
       if (query == devQuery)  // I don't see how it could be anything else
           devOName = data;
   }

} </lsl>