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

From Second Life Wiki
Jump to navigation Jump to search
 
(40 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{Restrained Life Relay Specs TOC}}
{{Restrained Life Relay Specs TOC}}


==Think Kink PBA (Personal Bondage Assistant)==
==DEM Relay HUD==


((current release of the tkPBA is '''''v30i'''''))
'''OK, so this page is massively out of date - I'm starting to fix it'''


The THINK KINK PBA (tkPAB) 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:
((current release of the Relay HUD is '''''200f'''''))


©left 2009 ''Think Kink'' ('''Think Kink''' is ''Ilana Debevec & Lyssa Daehlie'')
The DEM Relay HUD is an implementation of a multi-object, multi-restriction relay for the Restrained Love viewer (RLV) 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 Relay and functions to provide vision restrictions without another attachment/hud. Note that these vision restrictions are becoming less necessary as RLVa, at least, has added new functionality (particularly the @setoverlay capability). The HUD implements some, but not all, of the features of the Open Relay Group. These extensions and scripts are released under the following license:


the Think Kink PBA (''tkPBA'' for short) is released under a modified "CopyLeft" license. The short form without legalese.
The Relay HUD 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.''
''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 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 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.
''Once based on Maike Short's 1030b relay, with thanks to Tahni Tarantal, RL Ansome, Nihal Quan and everyone else who helped with the shakedown and development of this device. Transitive thanks to everyone that has gone before on the development of the Restrained Life Relay, including (but not limited to) Felis Darwin, Nano Siemens, Azoth Amat, Gregor Mougin, Cerdita Piek, Satomi Ahn, Marissa Mistwallow, Chorazin Allen, and Marine Kelley.''


''In-world (no mod) copies of this relay may be picked up for free at {{SLurl|LaSalle|138|116|30|title=THINK KINK}} or email to Ilana.Debevec@gmail.com for full source. --[[User:Ilana Debevec|Ilana Debevec]] 19:53, 27 February 2009 (UTC)
''The relay has changed a lot since it started as Maike's 1030b. Indeed it might now have changed beyond all recognition; it's been torn apart and rebuilt multiple times - there are still functions that look like the original, but they are few and far between. So don't blame Maike for any issues.''
 
''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. I really owe Maike and, also Satomi, a huge debt for everything, the original relay as well as suggestions for extensions.''
 
''A note about the script; I do all of my scripting using lslForge which has an import function which make it easy to share constants and commonly used functions. I've tried to minimize use of such functions, but I do use a couple of scripts that are not provided here as they contain script elements that I prefer not to share. The only one that really needs a replacement is the one that performs all the menu functions. There is liberal use of constants that are easily replaced with any set of unique numbers.''
 
''These are the pre-processed files; the post-processed ones are not something you want to see.''
 
''In-world (no mod) copies of this relay may be picked up for free at {{SLurl|Adult Hub BDSM|182|157|title=DEM Store}} or email to Chloe1982.Constantine@gmail.com for source.


===Design Points===
===Design Points===
'''THIS PART IS MASSIVELY OUT OF DATE'''
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 -
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 ''(Dominate 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 DPU ''(Dominant Processing Unit)'' handling all the 'global' tasks of the relay (listens, mode (off/ask/auto/hardcore), 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 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.
:- 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.
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.
Line 31: Line 42:


:- 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.
:- 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===
===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. --[[User:Ilana Debevec|Ilana Debevec]] 19:52, 27 February 2009 (UTC)
''Think Kink'' has developed a logo to brand our products showing them RLV 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 of the logo. --[[User:Ilana Debevec|Ilana Debevec]] 19:52, 27 February 2009 (UTC)
 
<gallery>
<gallery>
Image:tkRLV logo.png|tlRLV logo
Image:tkRLV logo.png|tlRLV logo
Image:tkRLV whologo.png|tlRLV !who
Image:tkRLV visionlogo.png|tlRLV !vision
Image:tkRLV whoboth.png|tlRLV both
</gallery>
</gallery>


--[[User:Ilana Debevec|Ilana Debevec]] 18:44, 5 March 2009 (UTC)
In reality, you will get a pack of logos; we use different colors of insert to represent state; the one shown above is for a relay that is on and in ASK mode, we also support Off, Play, Auto, Hardcore (with or without safeword) and remotely controlled (with or without safeword). --[[User:Chloe1982 Constantine|Chloe1982 Constantine]] 09:57, 4 May 2012 (PDT)


==tkPBA Command Extensions==
==tkPBA Command Extensions==
===!who===
A better place to look at definitions of !x-who and !x-vision is the Open Relay Group pages [http://wiki.secondlife.com/wiki/LSL_Protocol/Restrained_Love_Open_Relay_Group] which Think Kink will continue to support and add a growing number of x-tensions to the PBA.
 
===!who also !x-who===
;Description  
;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).
:!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  
;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.
: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.
Line 74: Line 88:
--[[User:Ilana Debevec|Ilana Debevec]] 19:54, 27 February 2009 (UTC)
--[[User:Ilana Debevec|Ilana Debevec]] 19:54, 27 February 2009 (UTC)


===!vision===
===!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..
; 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.
; 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 : !vision/(color)/(alpha)/(texture)/(repeats)/(offsets)/(rotation)
; 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)
::(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)
::(alpha) = % transparent to make the HUD prim cover (in alpha format 0.0-1.0 or as a percentage 0 to 100)
Line 88: Line 102:
:: SPECIAL ENTRY, any of the parameters can be replaced with "*" for 'do not change existing value'
:: 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. !vision/0.0/<255'255'255>/TEXTURE_BLANK/1.0'1.0/0.0'0.0
:: 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 !vision/<0'0'0>/1.0/*/*/*/*
:: if all you want to do is 'blind' someone, then !x-vision/<0'0'0>/1.0/*/*/*/*


; Examples
; Examples
: Total blackout "!vision/<0'0'0>/1.0/TEXTURE_BLANK/1.0'1.0/0.0'0.0/0.0
: Total blackout "!x-vision/<0'0'0>/1.0/TEXTURE_BLANK/1.0'1.0/0.0'0.0/0.0
: Light fog "!vision/<128'128'128>/0.5/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 "!vision/<255'255'255>/1.0/TEXTURE_PLYWOOD/1.0'1.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
; Compatibility : since this is a metacommand, relays that don't support this should ignore it


--[[User:Ilana Debevec|Ilana Debevec]] 18:29, 5 March 2009 (UTC)
--[[User:Ilana Debevec|Ilana Debevec]] 18:29, 5 March 2009 (UTC)


===!visionclear===
===!x-vision/clear===


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


--[[User:Ilana Debevec|Ilana Debevec]] 18:29, 5 March 2009 (UTC)
--[[User:Ilana Debevec|Ilana Debevec]] 18:29, 5 March 2009 (UTC)
Line 110: Line 124:
===The DPU===
===The DPU===


The DPU (Dominant Processing Unit) handles all the listens, menus, mode control and other ''GLOBAL'' functions.  
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.RLV1030 DPU (090305.2) -- --[[User:Ilana Debevec|Ilana Debevec]] 19:05, 4 March 2009 (UTC)
version tk.RLV1100 DPU (120501.0) -- --[[User:Chloe1982 Constantine|Chloe1982 Constantine]] 12:05, 4 May 2012
 
<lsl>
// tk.RLV1030 DPU (090305.2)


<source lang="lsl2">
// tk.RLV110x DPU(130304.0) RLV Basic Relay 1.100 and ORG 1.0
//
// ©left 2009 Think Kink (Think Kink is Ilana Debevec & Lyssa Daehlie)
// ©left 2009 Think Kink (Think Kink is Ilana Debevec & Lyssa Daehlie)


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


// This code is provided AS-IS, OPEN-SOURCE and holds NO WARRANTY of accuracy, completeness or performance.  
// 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
// It may be distributed in its full source code with this header and disclaimer and is not to be sold
// permission. Optionally it may be distributed in a 'no mod' form within Second Life™ PROVIDED that either a
// without permission. Optionally it may be distributed in a 'no mod' form within Second Life™ PROVIDED that  
// link to the full source IS provided (ie. this page or a .zip or .rar) within the object the code is contained
// either a link to the full source IS provided (ie. this page or a .zip or .rar) within the object the code is  
// AND/OR a off-world contact point (ie. email) that the source code may be requested from. This somewhat unusual
// contained AND/OR a off-world contact point (ie. email) that the source code may be requested from. This somewhat
// (no-mod) distribution is allowed to maintain the integrity and reliability of the creator reference of in-world
// unusual (no-mod) distribution is allowed to maintain the integrity and reliability of the creator reference of  
// objects (scripts). Changes and improvement to this code must be shared with the community so that we ALL benefit.
// 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  
// 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  
// 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,  
// gone before on the development of the Restraint Life Relay, including (but not limited to) Felis Darwin, Nano Siemens,
// Chorazin Allen, Azoth Amat, Gregor Mougin, Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow and she who
// Azoth Amat, Gregor Mougin, Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow and Chorazin Allen, Marine Kelley.
// started it all ... 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.


// An extra special personal thanks to Chloe1982 Constantine who was able to help me put ideas into bits when I
// In-world (no mod) copies of this relay may be picked up for free at
// was buried whisker deep in both SL and RL issues.
//     Think Kink at http://slurl.com/secondlife/Think%20Kink/128/128/501
//  or thru the "Get tkPBA" in button in most Think Kink devices


// 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.  
// or email to Ilana.Debevec@gmail.com for full source.  


Line 145: Line 176:


// I couldn't have written this without both Ilana and Maike.
// I couldn't have written this without both Ilana and Maike.
// I wouldn't have written this without Jayne to whom this work is dedicated.
// Chloe 14 February 2009
// Chloe 14 February 2009


integer DEBUG = FALSE;
// if needed to check what is happening here
integer mDEBUG = FALSE;
 
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


// make cheating (adding exceptions) a bit more difficult by not allowing
string strReplace(string str, string search, string replace)
// attachment to control the viewer as they are not subjected to land building
{
// restrictions
    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;
}


integer ALLOW_CONTROL_BY_ATTACHMENTS = TRUE;
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);
}


integer MENU_CHANNEL;                           // Randomize if we need it
vector makeVector(string str, float lv, float hv, integer offset)
integer MENU_HANDLE;                             // We'll only turn this on when we need it
{
    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;
}   


string  mmOFF        = "-OFF-";
clearBlind(integer corePrim)
string  mmAUTO      = "ON (Auto)";
{
string  mmASK        = "ON (Ask)";
    integer pos = llListFindList(coreIndex, [corePrim]);
string  mmHARDCORE1  = "HARDCORE";
    if (pos == -1)
string  mmHARDCORE2  = "CONFIRM HC";
        return;
string  mmSAFE      = "SAFEWORD";
    coreIndex = llDeleteSubList(coreIndex, pos, pos);
string  mmSAFE1      = "CONFIRM 1";
    integer spos = pos * O_NUM;
string  mmSAFE2      = "CONFIRM 2";
    coreData = llDeleteSubList(coreData, spos, spos + O_NUM - 1);  
string  mmSAFE3      = "COMFIRM 3";
}
string  mmSAFEOFF    = "S-WORD OFF";
string  mmSWOCONFIRM = "-CONFIRM-";
string  mmSWOCANCEL  = "-CANCEL-";
string  mmRELEASE    = "RELEASE ME";
string  mmSCANCEL    = "-CANCEL-";
string  mmHCANCEL    = "-STOP!!-";
string  mmNULL      = " ";


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;
}


string  mmMENUTEX1 =  "\n-OFF-              Turn Off/Disable Relay (Slashed)\nON (Auto)        Relay On, Auto Accept (YELLOW)\nON (Ask)          Relay On, Ask Permission (GREEN)";
// make cheating (adding exceptions) a bit more difficult by not allowing
string  mmMENUTEX1a =  "\nHARDCORE      Hardcore Mode (RED)";
// attachment to control the viewer as they are not subjected to land building
string  mmMENUTEX2 =  "\nSAFEWORD      EMERGENCY SAFEWORD";
// restrictions
string  mmMENUTEX2a =  "\nS-WORD OFF    DISABLE Safeword Option";


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


// ---------------------------------------------------
// ---------------------------------------------------
//                    Constants
//                    Constants
Line 193: Line 305:
integer RLVRS_CHANNEL      = -1812221819;  // RLVRS in numbers
integer RLVRS_CHANNEL      = -1812221819;  // RLVRS in numbers
integer DIALOG_CHANNEL;
integer DIALOG_CHANNEL;
integer DIALOG_HANDLE;
integer STATUS_OPEN_CHANNEL = -1373421300;
integer STATUS_OPEN_CHANNEL = -1373421300;
integer STATUS_SAY_CHANNEL = -1373421301;
integer COMMAND_CHANNEL    = -1373421302;
integer DIALOG_HANDLE;
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;


integer NO_COMMAND          = 0;
integer FREE_COMMAND        = 1;
integer HOLD_COMMAND        = 2;
integer DOIT_COMMAND        = 3;
integer TEST_COMMAND        = 4;
integer MODE_OFF = 0;
integer MODE_ASK = 1;
integer MODE_AUTO = 2;
// ---------------------------------------------------
// ---------------------------------------------------
//                      Variables
//                      Variables
Line 213: Line 322:
integer mode;
integer mode;
   
   
// ddsg-  A few extra variables and constants
// Think Kink -  A few extra variables and constants
//
//


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


integer cores = 5;
integer cores = 5;


list    corePrims;                             // prim numbers of the core indicators
string  corePrims           = "";               // prim numbers of the core indicators
string  coreKeys            = "";
string  coreAskKeys        = "";
integer coreInUse          = 0;
integer coreAsking          = 0;
key    source;                                // which toy we're worrying about
integer g_priv              = 0;




Line 236: Line 350:
key getControlObject(integer link)
key getControlObject(integer link)
{
{
     return (key)llGetSubString(llList2String(llGetObjectDetails(llGetLinkKey(link), [OBJECT_DESC]), 0), 0, 35);
     integer p = 36 * llSubStringIndex(corePrims, (string)link);
}
     if (p < 0)
 
         return null_key;
string getCoreDesc(integer core)
     return (key)llGetSubString(coreKeys, p, p + 35);
{
    return (string)llGetObjectDetails(llGetLinkKey(llList2Integer(corePrims, core)), [OBJECT_DESC]);
}
 
integer isBusy()
{
     integer c;
    for (c = 0; c < cores; ++c)
         if (getControlObject(llList2Integer(corePrims, c)) != NULL_KEY)
        {
            lockON();
            return TRUE;
        }
     return FALSE;
}
 
integer findCoreKey(key id)
{
    integer c;
    integer cp;
   
    for (c = 0; c < cores; ++c)
        if (getControlObject(cp = llList2Integer(corePrims, c)) == id)
            return cp;
   
    return -1;
}
 
 
processStatusRequest(string message)
{
}
}


// ---------------------------------------------------
string replKey(string keyList, string newKey, integer pos)
//              Helper functions
// ---------------------------------------------------
debug(string x)
{
    if (DEBUG)
        llOwnerSay("DEBUG:Super: " + x);
}
// checks whether this object is an attachment of an avatar
integer isAttachment(key id)
{
{
     vector objpos1 = llList2Vector(llGetObjectDetails(id, [OBJECT_POS]), 0);
     if (pos == 0)
    vector ownerpos = llList2Vector(llGetObjectDetails(llGetOwnerKey(id), [OBJECT_POS]), 0);
        return newKey + llGetSubString(keyList, 36, -1);
    vector objpos2 = llList2Vector(llGetObjectDetails(id, [OBJECT_POS]), 0);
     if (pos == cores - 1)
    return (llVecMag(objpos1 - ownerpos) <= llVecMag(objpos1 - objpos2));
         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
// ---------------------------------------------------
// 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= " + llKey2Name(parcel_owner) + " / " + llKey2Name(object_owner));
    debug("group= " + llKey2Name(parcel_group) + " / " + llKey2Name(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        // OR its group is the same as the parcel I'm on
    )
    {
        return TRUE;
    }
    return FALSE;
}
// 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(string cmd)
{
    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, 7) != "!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;
}
// Is this a simple request for information or a meta command like !release?
integer isSimpleRequest(list list_of_commands)
{
    integer len = llGetListLength(list_of_commands);
    integer i;
    // now check every single atomic command
    for (i=0; i < len; ++i)
    {
        string command = llList2String(list_of_commands, i);
        if (!isSimpleAtomicCommand(command))
        {
          return FALSE;
        }
    }
    // all atomic commands passed the test
     return TRUE;
}
 
// not so simple function to generate the ask dialog text
 
string askDialogText(key id, integer trustworthy)
{
    string text;
    string objectName = llKey2Name(id);
    string objectOwner= llKey2Name(llGetOwnerKey(id));
    string whoDoingName = "";
   
    if (objectOwner == "") objectOwner = "(Name Unavailable)";
   
    if (whoDoing != NULL_KEY)  whoDoingName = llKey2Name(whoDoing) + ",";
    vector pos = llList2Vector(llGetObjectDetails(id, [OBJECT_POS]), 0);
     string objectLocation = objectName + "\nlocated at "+llGetRegionName()
        +  " <" + (string) ((integer) pos.x)
        + ", " + (string) ((integer) pos.y)
        + ", " + (string) ((integer) pos.z) + "> ("
        + (string) ((integer) llVecDist(pos, llGetRootPosition())) + "m) ";
   
    if (whoDoing == llGetOwner())
    {
        text = "You have activated ";
        if (llGetOwnerKey(id) == llGetOwner())  text += "your ";
        else                                    text += objectOwner + "'s ";
    }
    else
    {
        if (whoDoing != NULL_KEY)                text = whoDoingName + " using ";
        if (llGetOwnerKey(id) == llGetOwner())  text += "your ";
        else if (llGetOwnerKey(id) == whoDoing)  text += "their ";
        else                                    text += objectOwner +"'s ";
    }
 
    text += 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;
}
// looks at a command to see if it is a Who command,
// if so, returns the key, otherwise returns NULL_KEY
key whoIsUsingMe(string command)
{
    list commandParts = llParseString2List(command, ["/"], []);
    if (llList2String(commandParts, 0) != "!who")
        return NULL_KEY;
    if (llList2Key(commandParts, 1))
        return llList2Key(commandParts, 1);
    else
        return NULL_KEY;
}
// 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, integer corePrim, string message)
{
    list tokens = llParseString2List (message, [","], []);
    if (llGetListLength (tokens) < 3)
    {
        return FALSE;
    }
    string commands = llList2String(tokens, 2);
    list list_of_commands = llParseString2List(commands, ["|"], []);
 
    // accept harmless commands silently
    if (isSimpleRequest(list_of_commands))
    {
        llMessageLinked(corePrim, TEST_COMMAND, message, id);
        return FALSE;
    }
   
    if (HARDCORE_ON)
        return TRUE;        // there is no hope
 
    integer trustworthy = isObjectIdentityTrustworthy(id);
    // ask in permission-request-mode and/OR in case the object identity is suspicious.
    //
   
    debug("Mode="+(string)mode+", trustworthy="+(string)trustworthy);
   
    if (IsMode(MODE_ASK) || !trustworthy)
    {
        debug("Sending to #"+(string)corePrim+": " +message);
        llMessageLinked(corePrim, HOLD_COMMAND, message, id);
        askCount += 1;
        if (askCount == 1)
        {
            whoDoing = whoIsUsingMe(llList2String(list_of_commands, 0));
            coreAskPrim = corePrim;
            llListenControl(DIALOG_HANDLE, TRUE);
            dialog(askDialogText(id, trustworthy));
        }
        return FALSE;
    }
    return TRUE;
}
}
   
   
Line 498: Line 375:
//
//


releaseRestrictions ()
releaseRestrictions()
{
{
     llMessageLinked(LINK_ALL_OTHERS, FREE_COMMAND, "free", NULL_KEY);
     llMessageLinked(LINK_ALL_OTHERS, CORE_CONTROL, "FreeCore", null_key);
    coreKeys = NULL_KEY + NULL_KEY + NULL_KEY + NULL_KEY + NULL_KEY;
}
}
   
   
// ---------------------------------------------------
// ---------------------------------------------------
Line 510: Line 387:
// Make a dialog box and get the timer started
// Make a dialog box and get the timer started


dialog(string message)
acceptOrDeny(string message)
{
{
     llSetTimerEvent(30);
     llSetTimerEvent(30.0);
     llDialog (llGetOwner(), message, ["Allow", "Deny"], DIALOG_CHANNEL);
    //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");
}
}
   
   
Line 519: Line 399:
// Needs to be fixed to handle multiple simultaneous requests
// Needs to be fixed to handle multiple simultaneous requests


key tempKey;
processDialogResponse(string message)
processDialogResponse(string message)
{
{
    //llOwnerSay("processDialogResponse:" + message);
     llSetTimerEvent(0);
     llSetTimerEvent(0);
      
      
     if (message == "Allow")                // pending request authorized => process it
     if (message == mmALLOW)                // pending request authorized => process it
         llMessageLinked(coreAskPrim, DOIT_COMMAND, "", NULL_KEY);
    {
         llMessageLinked(coreAskPrim, CORE_CONTROL, "ExecuteSaved", null_key);
    }
          
          
     else if (message == "Deny")            // pending request denied
     else if (message == mmDENY)            // pending request denied
     {
     {
         if (whoDoing)
         if (whoDoing)
             llInstantMessage(whoDoing, ownerName + " has denied your request for control.");
             llInstantMessage(whoDoing, ownerName + " has denied your request for control.");
         llMessageLinked(coreAskPrim, FREE_COMMAND, "free", NULL_KEY);
         llMessageLinked(coreAskPrim, CORE_CONTROL, "FreeCore", null_key);
     }
     }
      
      
     askCount -= 1;
     if ((askCount -= 1) < 0)
        askCount = 0;
      
      
     if (askCount == 0)
     if (askCount == 0)
Line 541: Line 426:
     }
     }
      
      
     integer i = llListFindList(corePrims, [coreAskPrim]);
     integer i = llSubStringIndex(corePrims, (string)coreAskPrim);
     integer done = i;
     integer done = i;
     string iDesc;
     string iDesc;
    integer prim;
     do
     do
     {
     {
         i = (i + 1) % cores;
         i = (i + 1) % cores;
         iDesc = getCoreDesc(i);
         iDesc = (string)llGetLinkPrimitiveParams(prim = (integer)llGetSubString(corePrims, i, i), [PRIM_DESC]);
         debug("core #"+(string)i+" = " + iDesc);
         //llOwnerSay("core #"+(string)i+" = " + iDesc);
     }
     }
   
     while (i != done && llStringLength(iDesc) == 36);
     while (i != done && llStringLength(iDesc) == 36);
     coreAskPrim = llList2Integer(corePrims, i);
     ask(prim);
    whoDoing = (key)llGetSubString(iDesc, 37, 72);
    key oid = llGetSubString(iDesc, 0, 35);
    dialog(askDialogText(oid, isObjectIdentityTrustworthy(oid)));
}
}
   
// get current mode as string and set the hud texture
ask(integer link)
// ddsg-  set the hud textures
//
 
string getModeDescription()  
{
{
     if (HARDCORE_ON)
     //llOwnerSay(llList2CSV(["ask", link, askCount]));
     {
    string desc = (string)llGetLinkPrimitiveParams(link, [PRIM_DESC]);
        llSetTexture ("81ba7946-0e5d-19b9-1ac2-33a36d473198",0);    // RED LOCK  - HARDCORE
     coreAskPrim = link;
        return "HARDCORE: Auto-Accept ON, Relay LOCKED, Buttons REMOVED.";
    whoDoing = (key)llGetSubString(desc, 37, 72);
     }
     source = (key)llGetSubString(desc, 0, 35);
     else if (IsMode(MODE_OFF))
      
     {
     key myOwner = llGetOwner();
         llSetTexture ("f8b60e5a-010d-26bc-e927-6ee3854149ce",0);    // SLASH LOCK - Off
    key toyOwner = llGetOwnerKey(source);
        return "RLV Relay is OFF";
       
     }
    string text = llGetUsername(whoDoing);
     else if (IsMode(MODE_ASK))
     if (whoDoing == myOwner)
     {
         text = "You";
         llSetTexture ("e9e20400-26b4-4054-8504-91d2ab7af1d7",0);    // GREEN LOCK - Ask (safe)
    else if (text == "")
         return "RLV Relay is ON (permission needed)";
        text = "Someone";
     }
     text += ", using ";
     else if (IsMode(MODE_AUTO))
      
     {
     string ownerName = llGetUsername(toyOwner);
        llSetTexture ("cd126a08-945b-f9c5-99fa-90f3abed2f4b",0);    // YELLOW LOCK - Auto (caution)
      
        return "RLV Relay is ON (auto-accept)";
    if (toyOwner == myOwner)
     }
         ownerName = "your";
     else                             
     else if (whoDoing == toyOwner)
        return "FUBAR: Unknown State";
         ownerName = "their";
}
    else if (ownerName == "")
        ownerName = "the";
     else
        ownerName += "'s";
       
     vector pos = llList2Vector(llGetObjectDetails(source, [OBJECT_POS]), 0);
     integer dist = (integer)llVecDist(pos, llGetRootPosition());
    //llOwnerSay(llList2CSV(["ask", source, pos, dist]));
    pos.x = (integer)pos.x;
     pos.y = (integer)pos.y;
    pos.z = (integer)pos.z;
      
     text += ownerName + " " + llKey2Name(source) +  "\nlocated at "+ llGetRegionName()
            + " " + (string)pos + " ("
            + (string)dist + "m)\nis attempting to control your viewer";


// ddsg- set the mode here and update status
//   if (!trustworthy)
//
//       text += "\nWARNING: This object is not owned by the people owning this parcel.";


SetMode(integer vMode)
         text += "\n\nDo you accept ?";
{
      
    mode = vMode;
         acceptOrDeny(text);         // Was isObjectIdentityTrustworthy, figure out why
    llOwnerSay (getModeDescription());
    if (IsMode(MODE_OFF))
    {
         llOwnerSay("@detach=y");
        llOwnerSay("Relay UnLOCKED");
     }
    else
    {
         lockON();
    }
}
 
// ddsg- what's the Mode?
//
integer IsMode(integer tMode)
{
    return tMode == mode;
}
 
// ddsg- clap on, clap... I mean lockON, lockOFF to lock/unlock the relay
//
lockON () 
{
    llOwnerSay("@detach=n");
    llOwnerSay("Relay LOCKED.");
}
}
   
   
Line 629: Line 496:
init()  
init()  
{
{
     integer temp0;
     integer temp0 = 0;
     integer temp1;
     integer temp1 = 0;
     integer temp2;
     integer temp2 = 0;
     integer temp3;
     integer temp3 = 0;
     integer temp4;
     integer temp4 = 0;
    integer i;
      
      
     key owner = llGetOwner();
     key owner = llGetOwner();
Line 642: Line 508:
     llListen (RLVRS_CHANNEL, "", "", "");
     llListen (RLVRS_CHANNEL, "", "", "");
      
      
     MENU_CHANNEL = ((integer)(llFrand(99999.0) * -1) - 2);
     DIALOG_CHANNEL = ((integer)(llFrand(99999.0) * -1) - 2);   
    MENU_HANDLE=llListen(MENU_CHANNEL, "", owner, "");
    llListenControl(MENU_HANDLE, FALSE);
   
    DIALOG_CHANNEL = MENU_CHANNEL - 2;   
     DIALOG_HANDLE = llListen(DIALOG_CHANNEL, "", owner, "");
     DIALOG_HANDLE = llListen(DIALOG_CHANNEL, "", owner, "");
     llListenControl(DIALOG_HANDLE, FALSE);
     llListenControl(DIALOG_HANDLE, FALSE);
    llOwnerSay("@clear");
    SetMode(MODE_ASK);
       
    HARDCORE_ON = FALSE;
    SAFEWORD_ENABLE = TRUE;
      
      
     if (mDEBUG)
     //llWhisper(0,"Free Memory: " + (string) llGetFreeMemory());
        llWhisper(0,"Free Memory: " + (string) llGetFreeMemory());


     integer n = llGetNumberOfPrims();
     integer i = llGetNumberOfPrims();
     string primname;
     string primname;
     for(i = 1; i <= n; ++i)
     do
     {
     {
         primname = llGetLinkName(i);
         primname = llGetLinkName(i);
          
          
         if (primname == "corestat:0")      temp0 = i;
         if (primname == "corestat:0")      temp0 = i;
         if (primname == "corestat:1")       temp1 = i;
         else if (primname == "corestat:1") temp1 = i;
         if (primname == "corestat:2")       temp2 = i;
         else if (primname == "corestat:2") temp2 = i;
         if (primname == "corestat:3")       temp3 = i;
         else if (primname == "corestat:3") temp3 = i;
         if (primname == "corestat:4")       temp4 = i;
         else if (primname == "corestat:4") temp4 = i;
        else if (primname == "tk.GPU:0")    GPUPRIM = i;
     }
     }
     corePrims = [temp0, temp1, temp2, temp3, temp4];
    while (--i);
       
     corePrims = (string)temp0 + (string)temp1 + (string)temp2 + (string)temp3 + (string)temp4;
     releaseRestrictions();
    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
//
// emergency procedure, the subbie is in trouble. SL had borked the system and they are not unlockable
// any other way, short of logging out and loggin in with the normal viewer.
   
// Safeword will require confirmation before restrictions are lifted. After confirming -
//
//
// a !release will be executed to remove all restrictions
// the relay will be turned OFF
//
// a future update will inform the person that did the restrictions that the subbe safeworded (I hope)
safeword()
{
    llListenControl(MENU_HANDLE,TRUE);
   
    llDialog(llGetOwner(),"\nSAFEWORD Start. Confirm THREE times you want to use your safeword."+
                          "\nClick CONFIRM 1 or CANCEL.",
                          [mmNULL,mmSCANCEL,mmNULL, mmSAFE1,mmNULL,mmNULL], MENU_CHANNEL);
}
safeword1()
{
    llListenControl(MENU_HANDLE,TRUE);
   
    llDialog(llGetOwner(),"\nSAFEWORD 1 confirmed, you must confirm two more times. "+
                          "\nClick CONFIRM 2 or CANCEL.",
                          [mmNULL,mmSCANCEL,mmNULL, mmNULL,mmSAFE2,mmNULL],MENU_CHANNEL);
}
safeword2()
{
    llListenControl(MENU_HANDLE,TRUE);
   
    llDialog(llGetOwner(),"\nSAFEWORD 2 confirmed, you must confirm one more time "+
                            "Click CONFIRM 2 or CANCEL.",
                            [mmNULL,mmSCANCEL,mmNULL, mmNULL,mmNULL, mmSAFE3],MENU_CHANNEL);
}
safeword3()
{
    llListenControl(MENU_HANDLE,TRUE);
   
    llDialog(llGetOwner(),"\nSAFEWORD 3 confirmed, final chance."+
                          "\nClick CANCEL to stop SAFEWORD, or RELEASE ME",
                          [mmRELEASE,mmNULL,mmSCANCEL], MENU_CHANNEL);
}
safeword_release()
{
    releaseRestrictions ();
   
    llShout(0,llKey2Name(llGetOwner())+" has SAFEWORDED, please see if they need assistance.");


     if (HARDCORE_ON)
     whoDoing            = null_key;        // Who is operating the object
    {
     askCount            = 0;               // # of open ask requests
        llOwnerSay ("SAFEWORD! Restrictions Released, Relay is STILL ON, better run... fast.");
     coreAskPrim        = 0;               // # of core currently using the dialog
        SetMode(MODE_AUTO);
    }
    else
    {
         llOwnerSay ("SAFEWORD! Restrictions Released, Relay is OFF and UNLOCKED");
        SetMode(MODE_OFF);
     }
}
 
 
safeword_cancel()
{
    llOwnerSay("SAFEWORD cancelled, we now return you to your confinment.");
}
 
safewordOFF()
{
     llListenControl(MENU_HANDLE,TRUE);
      
      
     llDialog(llGetOwner(),"\nYou are about to disable your SAFEWORD OPTION.\n" +
     coreIndex = [-49];
                          "\nThis is NOT reversable with this relay." +
    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];
                          "\nMake sure you really want to do this and either" +
     llSetLinkPrimitiveParamsFast(GPUPRIM, [PRIM_SIZE, <0.01, 0.01, 0.01>, PRIM_COLOR, ALL_SIDES, <1.0, 1.0, 1.0>, 0.0]);
                          "\nCONFIRM or CANCEL this option.",
                          [mmSWOCONFIRM,mmNULL,mmSWOCANCEL], MENU_CHANNEL);
}
 
safewordOFF_CONFIRM()
{
    SAFEWORD_ENABLE = FALSE;
    llOwnerSay("Your SAFEWORD option has been REMOVED. May the gods/goddesss have mercy on you.");
}
 
safewordOFF_CANCEL()
{
     llOwnerSay("SAFEWORD perserved, you still have a way out.");
}
 
// ------------------------------------------------------------------------------------------------
//
// HARDCORE MODE - the is for the dedicated subbe. Entering HARDCORE mode does the following
//
//      Relay is locked on, may not be detached. ever.
//      Auto Accept is turned-on. Permanently. (this may be overriden by access lists [future])
//      All buttons but SAFEWORD are REMOVED.
//      SAFEWORD takes on a new meaning, when you safeword, the current restrictions are released BUT
//      the relay is still locked on, and still in Auto-Accept. Better run quick.
//
 
hardcore_confirm()
{
    llListenControl(MENU_HANDLE,TRUE);
   
    string sSWText = "";
   
    if (!SAFEWORD_ENABLE)
        sSWText =  "\nSAFEWORD has been disabled.";
          
          
     llDialog(llGetOwner(), "\nHARDCORE MODE: **WARNING**"+
     releaseRestrictions();
                          "\nEnabling HARDCORE mode is PERMANENT."+
     //llOwnerSay("GPUPrim:" + (string)GPUPRIM);
                          sSWText +
                          "\nMake SURE you want to do this. "+
                          "\nThis is your ONLY chance to stop",
                          [mmHARDCORE2,mmNULL,mmHCANCEL], MENU_CHANNEL);
}
 
hardcore_lockdown()
{
    llOwnerSay("HARDCORE ENABLED: Welcome to permanent slavery.");
     HARDCORE_ON = TRUE;
    SetMode(MODE_AUTO);
}
 
hardcore_cancel()
{
    llOwnerSay("HARDCORE restrictions cancelled.... but you'll be back... sooner or later.");
}
// check that this command is for us and not someone else
integer verifyWeAreTarget(string message)
{
    list tokens = llParseString2List(message, [","], []);
    if (llGetListLength(tokens) != 3) // this is not a normal command
    {
        return FALSE;
    }
    return (llList2String(tokens, 1) == llGetOwner()); // talking to me ?
}
// checks that the type of object (world object, attachment) is allowed
integer verifySourceType(key id)
{
    if (ALLOW_CONTROL_BY_ATTACHMENTS)
    {
        return TRUE;
    }
    return !isAttachment(id);
}
 
processNewRequest(integer corePrim, string message, key id)
{
    if (IsMode(MODE_OFF))
    {
        debug("deactivated - ignoring commands");
        return; // mode is 0 (off) => reject
    }
   
    if (!(verifyWeAreTarget(message) && verifySourceType(id)))
      return;
    if (!verifyPermission(id, corePrim, message))
        return;
   
    debug("Executing: " + (string)corePrim);
    llMessageLinked(corePrim, DOIT_COMMAND, message, id);
}
}


Line 860: Line 557:
     {
     {
         init();
         init();
    }
         //llOwnerSay("Free: " + (string)llGetFreeMemory());
    attach(key id)
    {
         if (id == NULL_KEY)
            return;
        integer ap = llGetAttached();
        while (ap < ATTACH_HUD_CENTER_2 || ap > ATTACH_HUD_BOTTOM_RIGHT)
        {
            llOwnerSay("The relay must be attached to the HUD and not elsewhere.");
            llOwnerSay("@detach=y");
            llSleep(30);
            ap = llGetAttached();
        }
   
        vector eul = <0.0,270.0,0.0>;
        eul *= DEG_TO_RAD;
        llSetRot(llEuler2Rot(eul));
       
        if (!IsMode(MODE_OFF))
        {
            lockON();
        }
 
        // remind the current mode to the user
        //
       
        llOwnerSay(getModeDescription());
     }
     }
      
      
     link_message(integer sender, integer channel, string message, key id)
     link_message(integer sender, integer channel, string message, key id)
     {
     {
         debug("link:" + (string)channel + ": " + message);
         //llOwnerSay(llList2CSV(["root link:", sender, channel, message, id]));
          
          
         if (channel == STATUS_OPEN_CHANNEL)
         if (channel == VISION_COMMAND)
         {
         {
             llWhisper(0, message);
             list lCommand = llParseString2List(message, ["/"], []);
        }
             //llOwnerSay("#"+(string)llGetListLength(lCommand)+": "+(string)lCommand);
        else if (channel == STATUS_SAY_CHANNEL)
            string meta_command;
        {
       
             llOwnerSay(message);
            if ((meta_command = llList2String(lCommand, 0)) != "!x-vision")
        }
                return;
    }
    listen(integer channel, string name, key id, string message)
    {
        debug("listen:" + (string)channel + ": " + message);
          
          
        if (channel==RLVRS_CHANNEL)
            if (llList2String(lCommand, 1) == "clear")
        {
                clearBlind(sender);
            list tokens = llParseString2List(message, [","], []);
             else if (llGetListLength(lCommand) < 4)
             if (llGetListLength(tokens) != 3 && llList2Key(tokens, 1) != llGetOwner())
                 return;
                 return;
               
            integer i = findCoreKey(id);
           
            if (i == -1)
            {
                i = findCoreKey(NULL_KEY);
               
                if (i == -1)  return;
               
                processNewRequest(i, message, id);
            }
             else
             else
            {
                 setBlind(sender, llList2String(lCommand, 1), llList2String(lCommand, 2),
                 debug("Sending '" + message + "' on #" + (string)i);
                    llList2String(lCommand, 3), llList2String(lCommand, 4),
                llMessageLinked(i, NO_COMMAND, message, id);
                    llList2String(lCommand,5), llList2String(lCommand, 6));
            }
        }
          
          
        else if (channel == DIALOG_CHANNEL)
            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]);
             processDialogResponse(message);
             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)
         else if (channel == MENU_CHANNEL)
         {
         {
            llListenControl(MENU_HANDLE,FALSE);          // done with you, go deaf
             if (message == "CheckRelay")
           
             {
            if ( id != llGetOwner() )          return;
                integer cx = cores;
 
                key    corecheck;
             if     (message == mmOFF)          {  SetMode(MODE_OFF);
                vector  myPos = llGetPos();
                                                    releaseRestrictions ();
                integer isBusy = FALSE;
                                                }
                integer thisCore;
                                               
                //llOwnerSay(llList2CSV(["Busy Relay", coreKeys, g_priv]));
             else if (message == mmASK)          {   SetMode(MODE_ASK);
                                                }
           
            else if (message == mmAUTO)        {  SetMode(MODE_AUTO);
                                                }
           
            else if (message == mmHARDCORE1)    hardcore_confirm();
            else if (message == mmHARDCORE2)    hardcore_lockdown();
            else if (message == mmHCANCEL)      hardcore_cancel();
           
            else if (message == mmSAFE)        safeword();
            else if (message == mmSAFE1)        safeword1();
            else if (message == mmSAFE2)        safeword2();
            else if (message == mmSAFE3)        safeword3();
            else if (message == mmRELEASE)      safeword_release();
            else if (message == mmSCANCEL)      safeword_cancel();
           
            else if (message == mmSAFEOFF)      safewordOFF();
            else if (message == mmSWOCONFIRM)  safewordOFF_CONFIRM();
            else if (message == mmSWOCANCEL)    safewordOFF_CANCEL();
        } 
    }
 
    touch_start(integer num_detected)
    {
       
        if (mDEBUG) llWhisper(0,"Free Memory: " + (string) llGetFreeMemory());
       
        integer cx;
        key    corecheck;
        vector  myPos = llGetPos();
          
          
        for (cx = 0; cx < cores; ++cx)
                while (cx--)
        {
            corecheck = getControlObject(llList2Integer(corePrims, cx));
            if (corecheck != NULL_KEY)                                  // just on the off chance the object disappeared
            {                                                          // or we got away without getting released
                list l = llGetObjectDetails(corecheck, [ OBJECT_POS ]);  // check on the object
                float dist = 0.0;
               
                if(llGetListLength(l) > 0)                              // it's still around
                 {
                 {
                     // we checking to see if we are near the object
                     thisCore = (integer)llGetSubString(corePrims, cx, cx);
                     // compute the distance in 2D (no Z-axis)
                    integer mask = 1 << cx;
                     vector avatarPos = llList2Vector(l, 0);
                    integer t = cx * 36;
                     avatarPos.z = 0.0;
                     corecheck = llGetSubString(coreKeys, t, t + 35);
                    myPos.z = 0.0;
                     //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
                  
                  
                    dist = llVecDist(avatarPos, myPos);
                        if (llGetListLength(l) == 0)                               // ok, we're too far away, let me go
                }
                        {
               
                            if (!(g_priv & mask))
                if(llGetListLength(l) == 0 || dist > 96.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);
                    llWhisper(0, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because the device is out of range.");
                                
                                
                    llMessageLinked(llList2Integer(corePrims, cx), FREE_COMMAND, "free", NULL_KEY);
                            llMessageLinked(thisCore, CORE_CONTROL, "FreeCore", null_key);
                            coreKeys = replKey(coreKeys, NULL_KEY, cx);
                        }
                        else if (!(g_priv & (1 << cx)))
                            isBusy = TRUE;
                    }
                 }
                 }
                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")
            {
                askCount++;
                if (askCount == 1)
                    ask(sender);
            }
            else if (message == "SetSource")
            {
                integer core = llSubStringIndex(corePrims, (string)sender);
                coreKeys = replKey(coreKeys, (string)id, core);
                g_priv = g_priv | ~(1 << core);
            }
            else if (message == "IsCommitted")
            {
                integer core = llSubStringIndex(corePrims, (string)sender);
                integer mask = 1 << core;
                if ((string)id == "Grace")
                    g_priv = g_priv | mask;
                else
                    g_priv = g_priv & ~mask;
                if ((string)id == "Free")
                    coreKeys = replKey(coreKeys, NULL_KEY, core);
                //llOwnerSay(llList2CSV(["IsCommitted", core, mask, g_priv]));
             }
             }
         }
         }
       
    }
        list mmBUTTONS;
        string mmMENU;
    listen(integer channel, string name, key id, string message)
       
    {
         if (isBusy())        // ahhh.. we are locked down... but maybe can do something
         //llOwnerSay("listen:" + (string)channel + ": " + (string)id + ":" + message);
         if (channel==RLVRS_CHANNEL)
         {
         {
             if ( HARDCORE_ON && !SAFEWORD_ENABLE ) // HARDCORE and turned off SAFEWORD... sneer at them and leave
             if (mode == MODE_OFF)           // Check to see that the relay is on
             {
             {
                 llOwnerSay("You're the hardest of the HARDCORE, nothing you can do, no SAFEWORD remember?");
                 //llOwnerSay("deactivated - ignoring commands");
                return; // mode is 0 (off) => reject
            }
               
            //llOwnerSay("Root:RLV listen:" + message + " from " + name);
            integer p = llSubStringIndex(message, ",");
            if (p == -1)
                 return;
                 return;
             }
             string keyPart = llGetSubString(message, p + 1, p + 37);
             else if (!SAFEWORD_ENABLE)                       // well maybe not, foolish mortal......
             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("You turned off SAFEWORD, that door is closed.");
                 llOwnerSay("Detected another relay: " + name);
                 return;
                 return;
             }
             }
             else if (SAFEWORD_ENABLE)
              
            {               
            if (llSubStringIndex(message, ",") != -1)           // this is malformed and not for us
                mmBUTTONS = [mmSAFE];              // ok, they can safeword out..
                mmMENU = mmMENUTEX2;
            }
        }   
        else                // not locked down, so maybe they can do something
        {
               
            if (HARDCORE_ON)                     // HARDCORE, not locked down... nothing to see here, move along
            {
                llOwnerSay("HARDCORE: Controls Disabled");
                 return;
                 return;
             }
              
                     
            message = cmdID + "," + message;
             if (SAFEWORD_ENABLE)               // SAFEWORD still enabled, they can go HARDCORE, disable SAFEWORD
             //llOwnerSay("Root:id="+(string)id+":coreKeys="+coreKeys+":index="+(string)llSubStringIndex(coreKeys, (string)id));
             {                                   // or ON/OFF/AUTO
           
                 mmBUTTONS = [mmNULL, mmHARDCORE1, mmSAFEOFF, mmOFF, mmAUTO, mmASK];
            if ((p = llSubStringIndex(coreKeys, (string)id)) == -1)
                 mmMENU = mmMENUTEX1+mmMENUTEX1a+mmMENUTEX2a;
             {
            }
                //llOwnerSay("Free core index:" + (string)llSubStringIndex(coreKeys, NULL_KEY));
            if (!SAFEWORD_ENABLE)             // SAFEWORD disabled, they can go HARDCORE or ON/OFF/AUTO
                 if ((p = llSubStringIndex(coreKeys, NULL_KEY)) == -1)                    // no core is available
            {                                 
                 {
                 mmBUTTONS = [mmNULL, mmHARDCORE1, mmNULL, mmOFF, mmAUTO, mmASK];
                    if (!g_priv)
                 mmMENU = mmMENUTEX1+mmMENUTEX1a;
                        return;
                    // There's a core in a grace period, dare we use it?
                    return;
                }
       
                p = p / 36;
                 coreKeys = replKey(coreKeys, (string)id, p);
                 g_priv = g_priv & ~(1 << p);
             }
             }
            else
                p = p / 36;
            llMessageLinked((integer)llGetSubString(corePrims, p, p), RLVRS_CHANNEL, message, id);
            //llOwnerSay("Sent " + message + " to #" + llGetSubString(corePrims, p, p) + " (" + (string)p + ")");
         }
         }
          
          
         llListenControl(MENU_HANDLE,TRUE);                              // turn on the menu listener
         else if (channel == DIALOG_CHANNEL)
        llDialog(llGetOwner(), mmMENU, mmBUTTONS, MENU_CHANNEL );       // and see what they can do
            processDialogResponse(message);
     }
     }


     changed(integer change)
     changed(integer change)
     {
     {
         if (change & (CHANGED_OWNER | CHANGED_INVENTORY | CHANGED_ALLOWED_DROP)) \
         if (change & CHANGED_OWNER)
            llResetScript();
            init();
    }
   
    attach(key id)
    {
        askCount = 0;
        if (id != null_key)
            llWhisper(RLVRS_CHANNEL, RELAYTESTCMD  + "," + (string)llGetOwner() + "," + VERCMD);
     }
     }
      
      
     timer()
     timer()
     {
     {
         processDialogResponse("Deny");
         processDialogResponse(mmDENY);
     }
     }
}
}
</lsl>
</source>


===The SPU===
===The SPU===
Line 1,070: Line 752:
The SPU maintains all the restrictions of the object on the wearer, and generally the 'dirty work' of the relay function.
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 (090305.0) --[[User:Ilana Debevec|Ilana Debevec]] 18:24, 5 March 2009 (UTC)
version tk.RLV1100 SPU (120502.0) --[[User:Chloe1982 Constantine|Chloe1982 Constantine]] 09:08, 4 May 2012 (PDT)
<source lang="lsl2">
// tk.RLV1030 SPU (130121.0)
//
// ©left 2009 Think Kink (Think Kink is Ilana Debevec & Lyssa Daehlie)


<lsl>
// the Think Kink Restained Life PBA Relay (tkPBA for short) is released under a modified "CopyLeft" license.
// tk.RLV1030 SPU (090305.0)
//  
// The short form without legalese.  


// ©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.  
// 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
// It may be distributed in its full source code with this header and disclaimer and is not to be sold
// permission. Optionally it may be distributed in a 'no mod' form within Second Life™ PROVIDED that either a
// without permission. Optionally it may be distributed in a 'no mod' form within Second Life™ PROVIDED that  
// link to the full source IS provided (ie. this page or a .zip or .rar) within the object the code is contained
// either a link to the full source IS provided (ie. this page or a .zip or .rar) within the object the code is  
// AND/OR a off-world contact point (ie. email) that the source code may be requested from. This somewhat unusual
// contained AND/OR a off-world contact point (ie. email) that the source code may be requested from. This somewhat
// (no-mod) distribution is allowed to maintain the integrity and reliability of the creator reference of in-world
// unusual (no-mod) distribution is allowed to maintain the integrity and reliability of the creator reference of  
// objects (scripts). Changes and improvement to this code must be shared with the community so that we ALL benefit.
// 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  
 
// 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  
// 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,  
// gone before on the development of the Restraint Life Relay, including (but not limited to) Felis Darwin, Nano Siemens,
// Chorazin Allen, Azoth Amat, Gregor Mougin, Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow and she who
// Azoth Amat, Gregor Mougin, Nano Siemens, Cerdita Piek, Satomi Ahn, Marissa Mistwallow and Chorazin Allen, Marine Kelley.
// started it all ... Marine Kelley.  
 
// Major and mega thanks to Maike Short for her (all too brief) guidance and inspiration as keeper of the Relay Standards
// An extra special personal thanks to Chloe1982 Constantine who was able to help me put ideas into bits when I  
// We all wish you were still here, we won't forget you, ever.
// was buried whisker deep in both SL and RL issues.
 
// An extra special personal thanks to Chloe1982 Constantine without whom this would not have happend, She was able  
// In-world (no mod) copies of this relay may be picked up for free at THINK KINK in LaSalle
// 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 Think Kink in Zindra http://slurl.com/secondlife/Gilda%20Pointe/245/183/49
//  or thru the "Get tkPBA" in button in most Think Kink devices
 
// or email to Ilana.Debevec@gmail.com for full source.  
// or email to Ilana.Debevec@gmail.com for full source.  
 
// Ilana Debevec 25 Feb 2009
// Ilana Debevec 25 Feb 2009
 
// I couldn't have written this without both Ilana and Maike.
// I couldn't have written this without both Ilana and Maike.
// I wouldn't have written this without Jayne to whom this work is dedicated.
// Chloe 14 February 2009
// Chloe 14 February 2009
   
   
integer DEBUG = FALSE;
key    null_key    = NULL_KEY;
   
   
// ---------------------------------------------------
// ---------------------------------------------------
Line 1,112: Line 810:
// ---------------------------------------------------
// ---------------------------------------------------
   
   
integer RLVRS_PROTOCOL_VERSION = 1030; // version of the protocol, stated on the specification page
integer RLVRS_PROTOCOL_VERSION = 1100; // 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 RLVRS_IMPL_VERSION = ", satisfying the 1.1 and ORG standards."; // version of the implementation for debugging
   
string ORG_VERSIONS = "ORG=0001/who=001/vision=001/handover=001";
string PREFIX_RL_COMMAND = "@";
string myName = "tkPBA (unknown)";
string PREFIX_METACOMMAND = "!";
   
   
integer RLVRS_CHANNEL      = -1812221819;  // RLVRS in numbers
integer RLVRS_CHANNEL      = -1812221819;  // RLVRS in numbers
integer STATUS_OPEN_CHANNEL = -1373421300;
integer STATUS_OPEN_CHANNEL = -1373421300;
integer STATUS_SAY_CHANNEL  = -1373421301;
integer STATUS_SAY_CHANNEL  = -1373421301;
integer CORE_CONTROL        = -1383421304;
integer COMMAND_CHANNEL    = -1373421302;
integer GPU_CHANNEL        = -4360493;
integer GPU_CHANNEL        = -4360493;
integer GPU_PRIM;


integer NO_COMMAND          = 0;
integer HARMLESS_COMMAND    = 0;
integer FREE_COMMAND        = 1;
integer FORCE_COMMAND      = 1;
integer HOLD_COMMAND        = 2;
integer BINDING_COMMAND    = 2;
integer DOIT_COMMAND        = 3;
 
integer TEST_COMMAND        = 4;
integer MAX_TIME_AUTOACCEPT_AFTER_FORCESIT = 60; // seconds
   
   
integer MAX_OBJECT_DISTANCE = 100;    // 100m is llShout distance
integer LOGIN_DELAY_WAIT_FOR_PONG = 30;
integer LOGIN_DELAY_WAIT_FOR_PONG = 10;
integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60;
integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60;
float  reAskTime          = 300.0;
string RVL_COMMAND_START = "@this-is-a-script-generated-message-beyond-the-control-of-the-agent=n";
integer noPong              = FALSE;
string RVL_COMMAND_BLOCK = "@this-is-a-script-generated-message-beyond-the-control-of-the-agent";
 
integer ALLOW_CONTROL_BY_ATTACHMENTS = TRUE;


string  coreName   = "0";
integer coreName        = -1;
integer cmdMax          = 0;
string  savedCmds      = "";
integer isHardcore      = 0;
integer isComplexPerm  = 0;
integer listenHandle   = 0;
   
   
// ---------------------------------------------------
// ---------------------------------------------------
//                      Variables
//                      Variables
// ---------------------------------------------------
// ---------------------------------------------------
   
 
list restrictions; // restrictions currently applied (without the "=n" part)
string roots          = "";     // list of all commands enforced by the relay
key source;        // UUID of the object I'm commanded by, always equal to NULL_KEY if restrictions is empty, always set if not
key source;        // UUID of the object I'm commanded by, always equal to NULL_KEY if restrictions is empty, always set if not
 
string  objName        = "unknown object";
string  pendingMessage; // message of pending request
key    owner          = null_key;
integer pendingTime;
   
   
// used on login
// used on login
Line 1,154: Line 855:
integer loginWaitingForPong;
integer loginWaitingForPong;
integer loginPendingForceSit;
integer loginPendingForceSit;
key    lastForceSitDestination;
integer lastForceSitTime;


integer holding    = FALSE;
integer holding    = FALSE;
key    whoDoing    = NULL_KEY;
key    whoDoing    = null_key;
key    devOwner    = NULL_KEY;
key    devOwner    = null_key;
key    devQuery    = NULL_KEY;
key    devQuery    = null_key;
string  devOName    = "";
string  devOName    = "";
string  whoName    = "";
string  whoName    = "";
   
integer loginTimer = FALSE;
integer gracePeriod = FALSE;
// ---------------------------------------------------
integer unchecked  = TRUE;
//              Helper functions
integer blinded     = FALSE;
// ---------------------------------------------------
debug(string x)
{
     if (DEBUG)
    {
        llOwnerSay("DEBUG:Core" + coreName + ": " + x);
    }
}
   
   
   
   
Line 1,185: Line 879:
ack(string cmd_id, key id, string cmd, string ack)
ack(string cmd_id, key id, string cmd, string ack)
{
{
     llShout(RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack);
     llRegionSayTo(id, RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack);
    //llRegionSay(RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack);
    //llOwnerSay(llList2CSV(["ack", id, RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack]));
}
}
   
   
Line 1,191: Line 887:
sendRLCmd(string cmd)
sendRLCmd(string cmd)
{
{
     debug("Send: " + cmd);
     //llOwnerSay("Send: " + cmd);
     if (cmd != "")
     if (cmd != "")
         llOwnerSay(cmd);
         llOwnerSay(cmd);
}
}


// ---------------------------------------------------
setSource(key id)
//              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();
     //llOwnerSay(llList2CSV(["setSource", id]));
    list temp = llGetObjectDetails(id, ([OBJECT_POS]));
     objName = "unknown object";
     vector objPostition = llList2Vector(temp,0);
     devOName = "";
     float distance = llVecDist(objPostition, myPosition);
     devOwner = null_key;
    debug("Object distance: " + (string)distance);
    source = id;
     return distance <= MAX_OBJECT_DISTANCE;
     if (holding)
}
         llSetObjectDesc((string)id + "^" + (string)whoDoing);
// 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
     else
        llSetObjectDesc((string)id);
    if (source == null_key)
     {
     {
         pendingMessage = pendingMessage + "|" + llList2String(llParseString2List(message, [","], []), 2);
         whoDoing = null_key;
    }
        whoName = "";
}
         unchecked = TRUE;
         return;
// 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();
     }
     }
    objName = llKey2Name(source);
    devOwner = llGetOwnerKey(source);
    devQuery = llRequestAgentData(devOwner, DATA_NAME);
}
}
   
   
// ---------------------------------------------------
// ---------------------------------------------------
//              Executing of commands
//              Executing of commands
// ---------------------------------------------------
// ---------------------------------------------------
   
   
// execute a non-parsed message
// execute a non-parsed message
Line 1,254: Line 925:
execute(key id, string message)
execute(key id, string message)
{
{
     list tokens = llParseString2List(message, [","], []);
     //llOwnerSay(llList2CSV(["execute", id, message]));
    string cmd_id = llList2String(tokens, 0); // CheckAttach
     integer cp = llSubStringIndex(message, ",");
     list list_of_commands = llParseString2List(llList2String(tokens, 2), ["|"], []);
     string cmd_id = llGetSubString(message, 0, cp - 1);     // Whatever command id waa given
     if (llList2Key(tokens, 1) != llGetOwner())
     message = llGetSubString(message, cp + 1, -1);
        return;                                 // This shouldn't happen, but can't hurt
     integer len = llGetListLength (list_of_commands);
    integer i;
     string command;
     string command;
     string prefix;
     string prefix;
     for (i=0; i<len; ++i) // execute every command one by one
     //llOwnerSay("execute1:" + message);
    if (message == "!pong")
    {
        noPong = FALSE;
        loginWaitingForPong = FALSE;
    }
    else if (noPong)
     {
     {
         // a command is a RL command if it starts with '@' or a metacommand if it starts with '!'
         llOwnerSay("@clear");
         command = llList2String(list_of_commands, i);
        roots = "";
        noPong = FALSE;
        loginWaitingForPong = FALSE;
    }
   
    do
    {
        if ((cp = llSubStringIndex(message, "|")) == -1)
            command = message;
         else
        {
            if (cp == 0)
                command = "";
            else
                command = llGetSubString(message, 0, cp - 1);
            if (cp == (llStringLength(message) - 1))
                message = "";
            else
                message = llGetSubString(message, cp + 1, -1);
        }
        //llOwnerSay(llList2CSV(["split", cp, command, message]));
       
        //llOwnerSay("execute2:" + cmd_id + ":" + command);
         prefix = llGetSubString(command, 0, 0);
         prefix = llGetSubString(command, 0, 0);
         if (prefix == "@")                   // this is a RLV command
         if (prefix == PREFIX_RL_COMMAND) // this is a RLV command
        {
             executeRLVCommand(cmd_id, id, command);
             executeRLVCommand(cmd_id, id, command);
        }
         else if (prefix == "!")             // this is a metacommand, aimed at the relay itself
         else if (prefix == PREFIX_METACOMMAND) // this is a metacommand, aimed at the relay itself
            executeMetaCommand(cmd_id, id, command);
    }
    while (cp != -1);
   
    //llOwnerSay(llList2CSV(["going green", blinded, llStringLength(roots), roots]));
    if (blinded || llStringLength(roots))
    {
        llSetColor(<0.0, 1.0, 0.0>, ALL_SIDES); //  Green
        llSetAlpha(1.0, ALL_SIDES);
        if (gracePeriod)
         {
         {
             executeMetaCommand(cmd_id, id, command);
             //llOwnerSay("timer5: 0");
            llSetTimerEvent(0.0);
            gracePeriod = FALSE;
            llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "IsCommitted", "Busy");
         }
         }
        unchecked = FALSE;
     }
     }
}  
    else
    {
        //llOwnerSay("timer6: " + (string)reAskTime);
        llSetTimerEvent(reAskTime);
        gracePeriod = TRUE;
        llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "IsCommitted", "Grace");
        llSetColor(<0.0, 0.0, 0.0>,ALL_SIDES); //  Black
        llSetAlpha(1.0,ALL_SIDES);
    }
}


// is this a query?
// is this a query?
integer isQuery(string behav)
integer isNumeric(string behav)
{
{
     return ((llGetSubString(behav, 0, 3) == "@get") ||
     return ((llGetSubString(behav, 0, 3) == "@get") ||
Line 1,293: Line 1,008:
executeRLVCommand(string cmd_id, string id, string command)
executeRLVCommand(string cmd_id, string id, string command)
{
{
     command = llToLower(command);
     //llOwnerSay(llList2CSV(["eRLVCmd", cmd_id, id, command]));
    integer ep = llSubStringIndex(command = llToLower(command), "=");
     // we need to know whether whether is a rule or a simple command
     // we need to know whether whether is a rule or a simple command
     list tokens = llParseString2List(command, ["="], []);
     string behav = command;
     string behav = llList2String(tokens, 0); // @getattach:skull
    string param = "";
    string param = llList2String(tokens, 1); // 2222
    if (ep != -1)
     integer ind = llListFindList(restrictions, [behav]);
     {
        behav = llGetSubString(command, 0, ep - 1); // @getattach:skull
        param = llGetSubString(command, ep + 1, -1); // 2222
    }
     integer ind = inString(roots, behav, "|", TRUE, TRUE);
    string behavName = behav;
    string option = "";
   
   
     tokens = llParseString2List(behav, [":"], []); // @sit:<uuid>
     if ((ep = llSubStringIndex(behav, ":")) != -1)
    string behavName = llList2String (tokens, 0); // @sit
    {
        behavName = llGetSubString (behav, 0, ep - 1); // @sit
        option = llGetSubString (behav, ep + 1, -1);     // <uuid>
    }
   
   
     debug("executeRLVCommand: behav=!" + behav + "! param=!" + param + "!");
     //llOwnerSay(llList2CSV(["Checking", behavName, option]));
     if (isNumeric(behavName) && ((integer)param <= 0 || (integer)param == DEBUG_CHANNEL))
     if ((behavName == RVL_COMMAND_BLOCK && param != "n") ||
    {
         (isQuery(behavName) && (integer)param <= 0))
        ack(cmd_id, id, command, "ko");
         return;
    }
    else if (behavName == "@notify" && ((integer)option <= 0 || (integer)option == DEBUG_CHANNEL))
     {
     {
         ack(cmd_id, id, command, "ko");
         ack(cmd_id, id, command, "ko");
Line 1,313: Line 1,041:
     else if (param=="n" || param=="add") // add to restrictions
     else if (param=="n" || param=="add") // add to restrictions
     {
     {
        if (behavName == "@acceptpermission")
        {
            ack(cmd_id, id, command, "ko");
            return;
        }
         if (ind < 0)
         if (ind < 0)
         {
         {
             if (llGetListLength(restrictions) == 0)
             if (roots == "")
            {
                 roots = behav;
                 sendRLCmd(RVL_COMMAND_START);
             else
             }
                roots = roots + "|" + behav;
            restrictions += [behav];
         }
         }
        setSource(id); // we know that source is either NULL_KEY or id already
     }
     }
     else if (param == "y" || param == "rem") // remove from restrictions
     else if (param == "y" || param == "rem") // remove from restrictions
     {
     {
         if (ind > -1)  
         //llOwnerSay(llList2CSV(["releasing", param, behav, ind]));
         {
         if (behav == roots)
             restrictions = llDeleteSubList (restrictions, ind, ind);
             roots = "";
         }
         else if (ind > -1)
        if (llGetListLength(restrictions) == 0)
         {
         {
             setSource(NULL_KEY);
             integer bl = llStringLength(behav);
            if (ind + bl == llStringLength(roots))
                ind--;
            //llOwnerSay(llList2CSV(["deleting", roots, ind, ind + bl, llDeleteSubString(roots, ind, ind + bl)]));
            roots = llDeleteSubString(roots, ind, ind + bl);
         }
         }
        removeFromPendingList(behav);
     }
     }


     else if (param != "force" && ((integer) param == 0) && (behavName != "@clear"))
     else if (param != "force" && ((integer) param == 0) && (behavName != "@clear"))
     {
     {
        //llOwnerSay("rejecting" + (string)param);
         // this is either an unknown param (not "n", "add", "y", "rem", "force")
         // 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.
         // or a query which should be answered on the public chat channel 0.
Line 1,344: Line 1,078:
     }
     }
   
   
     workaroundForAtClear(command);
     // Note: @clear MUST NOT be used because the restrictions will be reapplied on next login
     sendRLCmd(command); // execute command
    // (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
    //llOwnerSay(llList2CSV(["executing", command, behavName, param]));
    if (command == "@clear")
        releaseRestrictions();
     else
    {
       
        // remembers the time and object if this command is a force sit
        // 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 (inString(roots, "@unsit", "|", TRUE, TRUE) == -1)
            {
                if (lastForceSitTime + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT < llGetUnixTime())
                {
                    //llOwnerSay("clearing lastForceSitDestination");
                    lastForceSitDestination = null_key;
                }
            }
        }
        if (param == "force" && behavName == "@sit")
        {
            lastForceSitDestination = (key) option;
            lastForceSitTime = llGetUnixTime();
            //llOwnerSay("remembered force sit");
        }
        sendRLCmd(command); // execute command
    }
     ack(cmd_id, id, command, "ok"); // acknowledge
     ack(cmd_id, id, command, "ok"); // acknowledge
}
}
   
   
// check for @clear
handleHandOver(string id, key targetObject, integer keepRestrictions)
// 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")
     //llOwnerSay("handover");
     {
     if (!keepRestrictions)
         releaseRestrictions();
         releaseRestrictions();
     }
       
     setSource(targetObject);
    pingWorldObjectIfUnderRestrictions();
}
}


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
// executes a meta command which is handled by the relay itself
executeMetaCommand(string cmd_id, string id, string command)
executeMetaCommand(string cmd_id, string id, string commandString)
{
{
     if (command == PREFIX_METACOMMAND + "version") // checking relay protocol version
    //llOwnerSay(llList2CSV(["Meta", cmd_id, id, commandString]));
    list tokens = llParseString2List(commandString, ["/"], []);
    string command = llList2String(tokens, 0);
   
     if (command == "!version") // checking relay protocol version
     {
     {
         ack(cmd_id, id, command, (string) RLVRS_PROTOCOL_VERSION);
         ack(cmd_id, id, command, (string) RLVRS_PROTOCOL_VERSION);
     }
     }
     else if (command == PREFIX_METACOMMAND + "implversion") // checking relay version
     else if (command == "!implversion") // checking relay version
     {
     {
         ack(cmd_id, id, command, RLVRS_IMPL_VERSION);
         ack(cmd_id, id, command, myName + RLVRS_IMPL_VERSION);
     }
     }
     else if (command == PREFIX_METACOMMAND + "release") // release all the restrictions (end session)
     else if (command == "!x-orgversions")
        ack(cmd_id, id, command, ORG_VERSIONS);
    else if (command == "!release") // release all the restrictions (end session)
     {
     {
        ack(cmd_id, id, command, "ok");
         releaseRestrictions();
         releaseRestrictions();
        ack(cmd_id, id, command, "ok");
     }
     }
     else if (command == PREFIX_METACOMMAND + "pong")
     else if (command == "!x-who")
     {
     {
        loginWaitingForPong = FALSE;
         key k = llList2Key(tokens, 1);
    }
    else if (llGetSubString(command, 0, 3) == PREFIX_METACOMMAND + "who")
    {
        list wCommands = llParseString2List(command, ["/"], []);
         key k = llList2Key(wCommands, 1);
         if (k)
         if (k)
         {
         {
Line 1,405: Line 1,156:
         }
         }
     }
     }
     else if (llGetSubString(command, 0, 6) == PREFIX_METACOMMAND + "vision")
     else if (command == "!x-vision")
     {
     {
         llMessageLinked(GPU_PRIM, GPU_CHANNEL, command, NULL_KEY);
         blinded = commandString != "!x-vision/clear";
        ack(cmd_id, id, command, "ok");
        llMessageLinked(LINK_ROOT, GPU_CHANNEL, commandString, null_key);
    }
         ack(cmd_id, id, commandString, "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;
            }
        }
     }
     }
     else if (command == "!x-handover")
     if (modified)
         handleHandOver(id, llList2Key(tokens, 1), llList2Integer(tokens, 2));
    {
    else
         if (llGetListLength(commands) > 0)
         ack(cmd_id, id, command, "ko");
        {
}  
            pendingMessage = llList2String(tokens, 0) + "," + llList2String(tokens, 1) + "," + llDumpList2String(commands, "|");
        }
        else
         {
            clearPendingMessages();
        }
    }
}
   
   
// 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)
Line 1,462: Line 1,172:
{
{
     holding = FALSE;
     holding = FALSE;
    whoDoing = NULL_KEY;
    whoName = "";
    devOName = "";
    devOwner = NULL_KEY;
      
      
     llSetColor(<0.0, 0.0, 0.0>,ALL_SIDES); //  Black
     llSetColor(<0.0, 0.0, 0.0>,ALL_SIDES); //  Black
     llSetAlpha(1.0,ALL_SIDES);
     llSetAlpha(1.0,ALL_SIDES);
     llMessageLinked(GPU_PRIM, GPU_CHANNEL, "!visionclear", NULL_KEY);
     llMessageLinked(LINK_ROOT, GPU_CHANNEL, "!x-vision/clear", null_key);
    blinded = 0;


     setSource(NULL_KEY);
     if (llStringLength(roots))
    integer i;
         llOwnerSay("@clear");
    integer len = llGetListLength (restrictions);
     lastForceSitDestination = null_key;
    for (i = 0; i < len; ++i)
     roots = "";
    {
         sendRLCmd(llList2String (restrictions, i) + "=y");
     }
     restrictions = [];
     loginPendingForceSit = FALSE;
     loginPendingForceSit = FALSE;
     clearPendingMessages();
     savedCmds = "";                                     // just in case we had any of these lurking
}
      
     loginTimer = FALSE;
// sends an !release,ok to the world object if we are in an active session
     if (source != null_key)
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);
         //llOwnerSay("Timer7: " + (string)reAskTime);
         return;
        llSetTimerEvent(reAskTime);
         gracePeriod = TRUE;
        llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "IsCommitted", "Grace");
     }
     }
    if (holding)
        tryToGluePendingCommands(id, message);
    else
        execute(id, message);
}
}
   
   
// ---------------------------------------------------
// ---------------------------------------------------
//            initialisation and login handling
//            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
// send a ping request and start a timer
pingWorldObjectIfUnderRestrictions()
pingWorldObjectIfUnderRestrictions()
{
{
    //llOwnerSay(llList2CSV(["pingWorldObjectIfUnderRestrictions", source, loginPendingForceSit]));
     loginWaitingForPong = FALSE;
     loginWaitingForPong = FALSE;
    loginTimer = FALSE;
    noPong = FALSE;
     if (source)
     if (source)
     {
     {
         ack("ping", source, "ping", "ping");
         ack("ping", source, "ping", "ping");
         timerTickCounter = 0;
         timerTickCounter = 0;
        //llOwnerSay("Timer8: 1");
         llSetTimerEvent(1.0);
         llSetTimerEvent(1.0);
         loginWaitingForPong = TRUE;
         loginWaitingForPong = TRUE;
        loginTimer = TRUE;
        noPong = TRUE;
     }
     }
}
}
 
sendForceSitDuringLogin()
// ---------------------------------------------------
{
//              Permission Handling
    debug("Force sit during login on " + (string) source);
// ---------------------------------------------------
    sendRLCmd ("@sit:" + (string) source + "=force");
 
}
integer inString(string source, string pattern, string follower, integer follow, integer endOK)
// processes a timer event
processTimer()
{
{
     timerTickCounter++;
     integer fp;
     debug("timer (" + (string) timerTickCounter + "): waiting for pong: " + (string) loginWaitingForPong + " pendingForceSit: " + (string) loginPendingForceSit);
     integer sl;
     if (loginWaitingForPong && (timerTickCounter >= LOGIN_DELAY_WAIT_FOR_PONG))
    integer pl = llStringLength(pattern);
     integer bonus = 0;
    while (sl = llStringLength(source))
     {
     {
         llMessageLinked(LINK_ROOT, STATUS_OPEN_CHANNEL, "Lucky Day: " + llKey2Name(llGetOwner()) + " is freed because " + llKey2Name(source) + " is not available.", NULL_KEY);
         integer pp = llSubStringIndex(source, pattern);
         loginWaitingForPong = FALSE;
   
        loginPendingForceSit = FALSE;
         if (pp == -1)                  // the easy case; it's not there
        releaseRestrictions();
            return -1;  
     }
      
        if (pp + pl == sl)             // it's OK for the pattern to terminate the source
    if (loginPendingForceSit)
    {
        integer agentInfo = llGetAgentInfo(llGetOwner());
        if (agentInfo & AGENT_SITTING)
         {
         {
             loginPendingForceSit = FALSE;
             if (endOK)
            debug("is sitting now");
                return pp + bonus;
        }
             else
        else if (timerTickCounter == LOGIN_DELAY_WAIT_FOR_FORCE_SIT)
                return -1;
        {
            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 the pattern is appropriately followed, then we're done
        fp = llSubStringIndex(source = llGetSubString(source, pp + pl, -1), follower);
        if (follow && (fp == 0))
            return pp + bonus;
        if (!follow && (fp != 0))
            return pp + bonus;
        bonus += pp + pl;
     }
     }
    return -1;
     if (!loginPendingForceSit && !loginWaitingForPong)
}   
 
integer check(string commands)
{
    integer cp = llSubStringIndex(commands, ",");
    if (cp == -1)
        return -1;
    commands = llGetSubString(commands, cp + 1, -1);
   
    // Do we know who is operating the toy?
    cp = -1;
     if ((cp = llSubStringIndex(commands, "!x-who/")) == -1)
     {
     {
         llSetTimerEvent(0.0);
         if ((cp = llSubStringIndex(commands, "!who/")) != -1)
            cp += 5;
     }
     }
    else
        cp += 7;
    key tempWho = (key)llGetSubString(commands, cp, cp + 35);
    if (tempWho)
        whoDoing = tempWho;
   
    // Do we need to worry?
    if (inString(commands, "=n", "|", TRUE, TRUE) != -1)
        return BINDING_COMMAND;
    if (inString(commands, "=add", "|", TRUE, TRUE) != -1)
        return BINDING_COMMAND;
   
    // What about ORG meta-commands?
    // The !vision check is deprecated and will be removed
    if (inString(commands, "!x-vision/", "clear", FALSE, FALSE) != -1)
        return BINDING_COMMAND;
    if (inString(commands, "!x-handover", "/", TRUE, FALSE) != -1)
        return FALSE;
       
    // Might we get embarrassed?
    if (inString(commands, "=force", "|", TRUE, TRUE) != -1)
        return FORCE_COMMAND;
   
    return HARMLESS_COMMAND;
}
}
integer checkPerm(key id)
{
    if (isHardcore)            // OK, in hardcore we will never check any more
        return TRUE;
       
    // Now check for land status
    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);
   
   
processCmdMessage(integer num, key id, string message)
    //llOwnerSay("owner= " + llKey2Name(parcel_owner) + " / " + llKey2Name(object_owner));
    //llOwnerSay("group= " + llKey2Name(parcel_group) + " / " + llKey2Name(object_group));
    return object_owner == owner                    // 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;            // OR its group is the same as the parcel I'm on
}
 
// ---------------------------------------------------
//              Pending Command Handling
// ---------------------------------------------------
 
gluePending(string message, key id)
{
{
     if (num == DOIT_COMMAND)
    integer l;
     if ((l = llStringLength(savedCmds)) > 4000)
     {
     {
         llSetColor(<0.0, 1.0, 0.0>,ALL_SIDES); //  Green
         llMessageLinked(LINK_ROOT, STATUS_OPEN_CHANNEL, llKey2Name(id) + " is flooding commands. Releasing restrictions.", null_key);
        llSetAlpha(1.0,ALL_SIDES);
         releaseRestrictions();
 
        holding = FALSE;
        if (id == NULL_KEY)
        {
            setSource(source);
            execute(source, pendingMessage);
         }
        else
        {
            setSource(id);
            execute(source, message);
        }
     }
     }
     else if (num == TEST_COMMAND)
     else if (l)
         execute(id, message);
         savedCmds = savedCmds + "," + message;
     else if (num == FREE_COMMAND && message == "free")
     else
         releaseRestrictions();
         savedCmds = message;
    else if (num == HOLD_COMMAND)
    {
        llSetColor(<1.0, 1.0, 0.0>,ALL_SIDES); //  Yellow
        llSetAlpha(1.0,ALL_SIDES);
          
          
         holding = TRUE;
    if (holding)
         return;
          
          
        list lCommands = llParseString2List(message, [",", "/", "|"], []);
    holding = TRUE;
        debug("First command: " + llList2String(lCommands, 2));
   
        if (llList2String(lCommands, 2) == "!who")
    integer p;
        {
    if ((p = llSubStringIndex(message, "!x-who/")) != -1)
            key k = llList2Key(lCommands, 3);
    {
            debug("key="+(string)k);
        if (whoDoing = llGetSubString(message, p + 7, p + 42))
             whoDoing = NULL_KEY;
             ;
             if (k)
        else
                whoDoing = k;
             whoDoing = null_key;
        }
       
        setSource(id);
        pendingMessage = message;
        pendingTime = llGetUnixTime();
     }
     }
}
    else if (p = llSubStringIndex(message, "!who/"))          // Compatibility with old toys
 
displayState()
{
    string message = "Idle";
    if (source != NULL_KEY)
     {
     {
         key owner = llGetOwner();
         if (whoDoing = llGetSubString(message, p + 5, p + 40))
        key devOwner = llGetOwnerKey(source);
             ;
       
         else
        string who = "";
             whoDoing = null_key;
        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);
   
    setSource(source);
    llSetColor(<1.0, 1.0, 0.0>,ALL_SIDES); //  Yellow
    llSetAlpha(1.0,ALL_SIDES);
     llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "AskThem", null_key);
}
}
   
   
Line 1,707: Line 1,362:
     state_entry()
     state_entry()
     {
     {
         init();
         integer p;
        string s;
       
        if ((p = llSubStringIndex(s = llGetObjectName(), ":")) == -1)
            return;
        coreName = (integer)llGetSubString(s, p + 1, -1);
   
        llSetColor(<0.0, 0.0, 0.0>, ALL_SIDES); //  Black
        llSetAlpha(1.0, ALL_SIDES);
       
        setSource(null_key);
       
        myName = llKey2Name(llGetLinkKey(LINK_ROOT));
        owner = llGetOwner();
 
        //llOwnerSay("Free Memory: " + (string) llGetFreeMemory());
     }
     }
   
   
     on_rez(integer start_param)
     on_rez(integer start_param)
     {
     {
        //llOwnerSay(llList2CSV(["on_rez", source, roots, inString(roots, "@unsit", "|", TRUE, 0)]));
         // relogging, we must refresh the viewer and ping the object if any
         // 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 is set to something, fire all the stored restrictions
         if (source != NULL_KEY)
   
        // sends the known restrictions (again) to the RL-viewer
         if (source != null_key && roots != "")
         {
         {
             reinforceKnownRestrictions();
             integer pp;
            string sr = roots;
 
            do
            {
                pp = llSubStringIndex(sr, "|");
                if (pp == -1)
                    sendRLCmd(sr + "=n");
                else
                {
                    sendRLCmd(llGetSubString(sr, 0, pp - 1) + "=n");
                    sr = llGetSubString(sr, pp + 1, -1);
                }
            }
            while (pp != -1);
           
            if (inString(roots, "@unsit", "|", TRUE, TRUE) != -1)
                loginPendingForceSit = TRUE;
             pingWorldObjectIfUnderRestrictions();
             pingWorldObjectIfUnderRestrictions();
         }
         }
        else
        {
            loginTimer = FALSE;
            if (gracePeriod)
            {
                //llOwnerSay("timer9: 0");
                llSetTimerEvent(0.0);
                unchecked = TRUE;
                gracePeriod = FALSE;
            }
            setSource(null_key);
        }
        //llOwnerSay("on_rez: " + (string)loginPendingForceSit);
     }
     }
   
   
     attach(key id)
     attach(key id)
     {
     {
         if (id == NULL_KEY)
         if (id == null_key)
         {
         {
             tellWorldObjectAboutCanceledSession();
             // Tell the object in world about this canceled session
            if (source != null_key)
                ack("release", source, "!release", "ok");
             releaseRestrictions();
             releaseRestrictions();
         }
         }
        else
            llRequestPermissions(owner = llGetOwner(), PERMISSION_TAKE_CONTROLS);
    }
   
    run_time_permissions(integer perm)
    {
        if (perm & PERMISSION_TAKE_CONTROLS)
            llTakeControls(CONTROL_FWD, TRUE, TRUE);
     }
     }
   
   
     timer()
     timer()
     {
     {
         processTimer();
         //llOwnerSay(llList2CSV(["timer", loginTimer, timerTickCounter, loginWaitingForPong, loginPendingForceSit, source, lastForceSitDestination]));
        if (loginTimer)
        {
            //llOwnerSay(llList2CSV(["timer", loginWaitingForPong, timerTickCounter, loginPendingForceSit, source, lastForceSitDestination]));
            timerTickCounter++;
            //llOwnerSay("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(owner) + " is freed because " + objName + " is not available.", null_key);
                loginWaitingForPong = FALSE;
                loginPendingForceSit = FALSE;
                releaseRestrictions();
            }
            if (loginPendingForceSit)
            {
                integer agentInfo = llGetAgentInfo(owner);
                if (agentInfo & AGENT_SITTING)
                {
                    loginPendingForceSit = FALSE;
                    //llOwnerSay("is sitting now");
                }
                else if (timerTickCounter == LOGIN_DELAY_WAIT_FOR_FORCE_SIT)
                {
                    llMessageLinked(LINK_ROOT, STATUS_OPEN_CHANNEL, "Lucky Day: " + llKey2Name(owner) + " is freed because sitting down again was not possible.", null_key);
                    loginPendingForceSit = FALSE;
                    releaseRestrictions();
                }
                else
                {
                    key sitTarget = source;
                    if (lastForceSitDestination)
                        sitTarget = lastForceSitDestination;
                    //llOwnerSay("Force sit during login on " + (string) sitTarget + " (source=" + (string) source + ",                            lastForceSitDestination=" + (string) lastForceSitDestination + ")");
                    sendRLCmd ("@sit:" + (string) sitTarget + "=force");
                }
            }
   
            if (!loginPendingForceSit && !loginWaitingForPong)
            {
                //llOwnerSay("Timer1: 0");
                llSetTimerEvent(0.0);
            }
        }
        else
        {
            //llOwnerSay("Timer2: 0");
            llSetTimerEvent(0.0);
            setSource(null_key);
            gracePeriod = FALSE;
            llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "IsCommitted", "Free");
        }
     }
     }
   
   
     link_message(integer sender, integer num, string message, key id)
     link_message(integer sender, integer num, string message, key id)
     {
     {
         debug("Link:" + (string)num + ": " + message);
         //llOwnerSay("Link:" + (string)num + ": " + message);
          
          
         if (num == NO_COMMAND)
         if (num == CORE_CONTROL)
             processRelayMessage(id, message);
        {
         else
             if (message == "RelayMax")
             processCmdMessage(num, id, message);
            {
                cmdMax = (integer)((string)id);
                unchecked = TRUE;
                isHardcore = (integer)llGetSubString(id, 2, 2);
            }
            else if (message == "FreeCore")
            {
                if (source != null_key)
                    ack("safeword", source, "!release", "ok");
                releaseRestrictions();
                //llOwnerSay("Timer3: 0");
                llSetTimerEvent(0.0);
                setSource(null_key);
                whoDoing = null_key;
                whoName = "";
                devOName = "";
                devOwner = null_key;
                unchecked = TRUE;
            }
            else if (message == "ExecuteSaved")
            {
                string cmdId;
                integer cp = llSubStringIndex(savedCmds, ",");
                unchecked = FALSE;
                //llOwnerSay(llList2CSV(["executeSaved", savedCmds]));
                do
                {
                    cmdId = llGetSubString(savedCmds, 0, cp - 1);
                    savedCmds = llGetSubString(savedCmds, cp + 1, -1);
                    if ((cp = llSubStringIndex(savedCmds, ",")) == -1)
                        execute(source, cmdId + "," + savedCmds);
                    else
                    {
                        execute(source, cmdId + "," + llGetSubString(savedCmds, 0, cp - 1));
                        savedCmds = llGetSubString(savedCmds, cp + 1, -1);
                        cp = llSubStringIndex(savedCmds, ",");
                    }
                }
                while (cp != -1);
                holding = FALSE;
            }
        }
         else if (num == RLVRS_CHANNEL)
        {
             //llOwnerSay(llList2CSV(["First", source, id, holding, unchecked]));
            if (source == null_key)
            {
                setSource(id);
                unchecked = TRUE;
            }
           
            if (holding)
            {
                gluePending(message, id);
                return;
            }
               
            if (unchecked)
            {
                integer thisLevel = check(message);            // We'll have to check
           
                //llOwnerSay(llList2CSV(["second", thisLevel, message, unchecked]));
           
                if (thisLevel != HARMLESS_COMMAND)
                {
                    integer untrusted = !checkPerm(id);
                    //llOwnerSay(llList2CSV(["third", untrusted, cmdMax, unchecked]));
               
                    if (isComplexPerm)
                    {
                        holding = TRUE;
                        savedCmds = message;
                        llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "CheckPerm" + (string)thisLevel+(string)(10 * cmdMax + untrusted), id);
                        return;
                    }
                   
                    if (untrusted || (thisLevel > cmdMax))
                    {
                        gluePending(message, id);
                        return;
                    }
                    else if (thisLevel == BINDING_COMMAND)
                        unchecked = FALSE;
                    //llOwnerSay(llList2CSV(["done", thisLevel, cmdMax, unchecked]));
                }
            }
            //llOwnerSay(llList2CSV(["execute", unchecked, cmdMax]));
            execute(id, message);
        }
     }
     }
      
      
Line 1,748: Line 1,600:
     {
     {
         // How this could be anything other than 1 I don't know
         // How this could be anything other than 1 I don't know
         displayState();
         string message = "Idle";
        if (source != null_key)
        {
            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 #" + (string)coreName + ": " +message, null_key);
     }
     }
   
   
Line 1,765: Line 1,640:
     }
     }
}
}
</lsl>
</source>
 
===The GPU===
 
The GPU is the 'bare metal' interface to the vision restrictions, actually changing the size, shape, texture, transparency, etc of the vision restriction prim. It maintains a stack of the restrictions from different objects can overaly each other. That way, multiple !vision requests can be additive as they come in and restored as they are lifted. There are other implementation options for the GPU, the important aspect of it is that it interprets the appropriate vision commands.
 
version tk.GPU (090305.0) --[[User:Ilana Debevec|Ilana Debevec]] 18:25, 5 March 2009 (UTC)
<lsl>
// tk.GPU (090305.0)
//
// ©left 2009 Think Kink (Think Kink is Ilana Debevec & Lyssa Daehlie)
 
// 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.
// I wouldn't have written this without Jayne to whom this work is dedicated.
// Chloe 14 February 2009
 
// if needed to check what is happening here
integer DEBUG  = FALSE;
 
integer VISION_COMMAND  = -4360493;        // identifies commands to the GPU
list    corePrims;                          // prims that have sent commands
list    coreAlphas;                        // transparency values
list    coreColors;                        // colors that have been set
list    coreTextures;                      // textures sent to the GPU
list    coreRepeats;                        // Repeats values
list    coreOffsets;                        // Lists of offsets
list    coreRotations;                      // Rotations in radians
 
debug(string x)
{
    if (DEBUG)
    {
        llOwnerSay("DEBUG:GPU: " + x);
    }
}
 
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)
{
    corePrims += [corePrim];
   
    float na;
    if (alpha == "*")
        na = llList2Float(coreAlphas, -1);
    else
    {
        na = (float)alpha;
        if (na < 0.0)
            na = 0.0;
        else if (na > 1.0 && na <= 100.0)
            na /= 100.0;
        else if (na > 100.0)
            na = 1.0;
    }
    coreAlphas += [na];
   
    vector nc;
    if (color == "*")
        nc = llList2Vector(coreColors, -1);
    else
    {
        nc = (vector)strReplace(color, "'", ",");
        if (nc.x > 1.0)
            nc = nc/255;
        if (invalidVector(nc, 0.0, 1.0))
            nc = llList2Vector(coreColors, 0);
    }
    coreColors += [nc];
   
    if (texture)
    {                  // stupid SL
    }
    else if ((string)texture == "*")
        texture = llList2Key(coreTextures, -1);
    else
        texture = llList2Key(coreTextures, 0);
    coreTextures += [texture];
   
    coreRepeats += [makeVector(repeats, 0.0, -1.0, coreRepeats)];
   
    coreOffsets += [makeVector(offsets, -1.0, 1.0, coreOffsets)];
   
    float nr;
    if (rot == "*")
        na = llList2Float(coreRotations, -1);
    else
    {
        nr = (float)rot;
        if (nr < 0.0)
            nr = 0.0;
    }
    coreRotations += [nr];
   
    integer p;
    p = llListFindList(corePrims, [corePrim]);
    if ((p+1) < llGetListLength(corePrims))
        clearBlind(corePrim);
}
 
vector makeVector(string str, float lv, float hv, list vecs)
{
    vector nr;
    if (str == "")
        return llList2Vector(vecs, 0);
    if (str == "*")
        return llList2Vector(vecs, -1);
    nr = (vector)("<"+strReplace(str, "'", ",")+",0.0>");
    if (invalidVector(nr, lv, hv))
        return llList2Vector(vecs, 0);
    return nr;
}   
 
clearBlind(integer corePrim)
{
    integer pos = llListFindList(corePrims, [corePrim]);
    if (pos == -1)
        return;
    corePrims = llDeleteSubList(corePrims, pos, pos);
    coreAlphas = llDeleteSubList(coreAlphas, pos, pos);
    coreColors = llDeleteSubList(coreColors, pos, pos);
    coreTextures = llDeleteSubList(coreTextures, pos, pos);
    coreRepeats = llDeleteSubList(coreRepeats, pos, pos);
    coreOffsets = llDeleteSubList(coreOffsets, pos, pos);
    coreRotations = llDeleteSubList(coreRotations, pos, pos);
}
 
integer allClear()
{
    return llGetListLength(corePrims) == 1;
}
 
float blindAlpha()
{
    integer i;
    integer n = llGetListLength(coreAlphas);
    float ra = llList2Float(coreAlphas, 0);
    float ta;
    for (i = 1; i < n; i++)
        if ((ta = llList2Float(coreAlphas, i)) < ra)
            ra = ta;
    return ra;
}
 
default
{
    state_entry ()
    {
        corePrims      = [-1];
        coreAlphas      = [1.0];
        coreColors      = [<1.0,1.0,1.0>];
        coreTextures    = [TEXTURE_BLANK];
        coreRepeats    = [<1.0,1.0,0.0>];
        coreOffsets    = [<0.0,0.0,0.0>];
        coreRotations  = [0.0];
       
        llSetScale(<0.01, 0.01, 0.01>);
        llSetColor(<1,1,1>,ALL_SIDES);
    } 
   
    on_rez (integer foo)
    {
        llResetScript();
    }
   
    link_message(integer sender_num, integer num, string command, key id)
    {
        debug("link:"+(string)num+": " + command);
        if (num != VISION_COMMAND)
            return;
        list lCommand = llParseString2List(command, ["/"], []);
        debug("#"+(string)llGetListLength(lCommand)+": "+(string)lCommand);
        string meta_command = llList2String(lCommand, 0);
       
        if (meta_command == "!vision")
        {
            if (llGetListLength(lCommand) < 4)
                return;
            setBlind(sender_num, llList2String(lCommand, 1), llList2String(lCommand, 2),
                llList2String(lCommand, 3), llList2String(lCommand, 4),
                llList2String(lCommand,5), llList2String(lCommand, 6));
        }
        else if (meta_command == "!visionclear")
        {
            clearBlind(sender_num);
        }
        else
            return;
       
        if (allClear())
        {
            llResetScript();
            return;
        }
       
        llSetPrimitiveParams([PRIM_COLOR, ALL_SIDES, llList2Vector(coreColors, -1),
            blindAlpha()]);
        llSetPrimitiveParams([PRIM_TEXTURE, ALL_SIDES, llList2String(coreTextures, -1),
            llList2Vector(coreRepeats, -1), llList2Vector(coreOffsets, -1),
            llList2Float(coreRotations, -1)]);
        llSetScale(<0.01, 10.0, 10.0>);
    }
}
</lsl>

Latest revision as of 17:25, 3 June 2021

DEM Relay HUD

OK, so this page is massively out of date - I'm starting to fix it

((current release of the Relay HUD is 200f))

The DEM Relay HUD is an implementation of a multi-object, multi-restriction relay for the Restrained Love viewer (RLV) 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 Relay and functions to provide vision restrictions without another attachment/hud. Note that these vision restrictions are becoming less necessary as RLVa, at least, has added new functionality (particularly the @setoverlay capability). The HUD implements some, but not all, of the features of the Open Relay Group. These extensions and scripts are released under the following license:

The Relay HUD 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 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.

Once based on Maike Short's 1030b relay, with thanks to Tahni Tarantal, RL Ansome, Nihal Quan and everyone else who helped with the shakedown and development of this device. Transitive thanks to everyone that has gone before on the development of the Restrained Life Relay, including (but not limited to) Felis Darwin, Nano Siemens, Azoth Amat, Gregor Mougin, Cerdita Piek, Satomi Ahn, Marissa Mistwallow, Chorazin Allen, and Marine Kelley.

The relay has changed a lot since it started as Maike's 1030b. Indeed it might now have changed beyond all recognition; it's been torn apart and rebuilt multiple times - there are still functions that look like the original, but they are few and far between. So don't blame Maike for any issues.

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. I really owe Maike and, also Satomi, a huge debt for everything, the original relay as well as suggestions for extensions.

A note about the script; I do all of my scripting using lslForge which has an import function which make it easy to share constants and commonly used functions. I've tried to minimize use of such functions, but I do use a couple of scripts that are not provided here as they contain script elements that I prefer not to share. The only one that really needs a replacement is the one that performs all the menu functions. There is liberal use of constants that are easily replaced with any set of unique numbers.

These are the pre-processed files; the post-processed ones are not something you want to see.

In-world (no mod) copies of this relay may be picked up for free at DEM Store🖈 or email to Chloe1982.Constantine@gmail.com for source.

Design Points

THIS PART IS MASSIVELY OUT OF DATE 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, mode (off/ask/auto/hardcore), 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 a logo to brand our products showing them RLV 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 of the logo. --Ilana Debevec 19:52, 27 February 2009 (UTC)

In reality, you will get a pack of logos; we use different colors of insert to represent state; the one shown above is for a relay that is on and in ASK mode, we also support Off, Play, Auto, Hardcore (with or without safeword) and remotely controlled (with or without safeword). --Chloe1982 Constantine 09:57, 4 May 2012 (PDT)

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

// tk.RLV110x DPU(130304.0) RLV Basic Relay 1.100 and ORG 1.0
//
// ©left 2009 Think Kink (Think Kink is Ilana Debevec & Lyssa Daehlie)

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

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

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

integer cores = 5;

string  corePrims           = "";               // prim numbers of the core indicators
string  coreKeys            = "";
string  coreAskKeys         = "";
integer coreInUse           = 0;
integer coreAsking          = 0;
key     source;                                 // which toy we're worrying about
integer g_priv              = 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);
}
 
// ---------------------------------------------------
//               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);
    coreKeys = NULL_KEY + NULL_KEY + NULL_KEY + NULL_KEY + NULL_KEY;
}
 
// ---------------------------------------------------
//            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);
    }
        
    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);
    }
    
    if ((askCount -= 1) < 0)
        askCount = 0;
    
    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)
{
    //llOwnerSay(llList2CSV(["ask", link, askCount]));
    string desc = (string)llGetLinkPrimitiveParams(link, [PRIM_DESC]);
    coreAskPrim = link;
    whoDoing = (key)llGetSubString(desc, 37, 72);
    source = (key)llGetSubString(desc, 0, 35);
    
    key myOwner = llGetOwner();
    key toyOwner = llGetOwnerKey(source);
        
    string text = llGetUsername(whoDoing);
    if (whoDoing == myOwner)
        text = "You";
    else if (text == "")
        text = "Someone";
    text += ", using ";
    
    string ownerName = llGetUsername(toyOwner);
    
    if (toyOwner == myOwner)
        ownerName = "your";
    else if (whoDoing == toyOwner)
        ownerName = "their";
    else if (ownerName == "")
        ownerName = "the";
    else
        ownerName += "'s";
        
    vector pos = llList2Vector(llGetObjectDetails(source, [OBJECT_POS]), 0);
    integer dist = (integer)llVecDist(pos, llGetRootPosition());
    //llOwnerSay(llList2CSV(["ask", source, pos, dist]));
    pos.x = (integer)pos.x;
    pos.y = (integer)pos.y;
    pos.z = (integer)pos.z;
    
    text += ownerName + " " + llKey2Name(source) +  "\nlocated at "+ llGetRegionName()
             + " " + (string)pos + " ("
             + (string)dist + "m)\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 ?";
    
        acceptOrDeny(text);         // 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
    
    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(llList2CSV(["root link:", sender, channel, message, id]));
        
        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;
                //llOwnerSay(llList2CSV(["Busy Relay", coreKeys, g_priv]));
        
                while (cx--)
                {
                    thisCore = (integer)llGetSubString(corePrims, cx, cx);
                    integer mask = 1 << cx;
                    integer t = cx * 36;
                    corecheck = llGetSubString(coreKeys, t, t + 35);
                    //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
                        {
                            if (!(g_priv & mask))
                                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);
                            coreKeys = replKey(coreKeys, NULL_KEY, cx);
                        }
                        else if (!(g_priv & (1 << cx)))
                            isBusy = TRUE;
                    }
                }
                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")
            {
                askCount++;
                if (askCount == 1)
                    ask(sender);
            }
            else if (message == "SetSource")
            {
                integer core = llSubStringIndex(corePrims, (string)sender);
                coreKeys = replKey(coreKeys, (string)id, core);
                g_priv = g_priv | ~(1 << core);
            }
            else if (message == "IsCommitted")
            {
                integer core = llSubStringIndex(corePrims, (string)sender);
                integer mask = 1 << core;
                if ((string)id == "Grace")
                    g_priv = g_priv | mask;
                else
                    g_priv = g_priv & ~mask;
                if ((string)id == "Free")
                    coreKeys = replKey(coreKeys, NULL_KEY, core);
                //llOwnerSay(llList2CSV(["IsCommitted", core, mask, g_priv]));
            }
        }
    }
 
    listen(integer channel, string name, key id, string message)
    {
        //llOwnerSay("listen:" + (string)channel + ": " + (string)id + ":" + message);
        if (channel==RLVRS_CHANNEL)
        {
            if (mode == MODE_OFF)           // Check to see that the relay is on
            {
                //llOwnerSay("deactivated - ignoring commands");
                return; // mode is 0 (off) => reject
            }
                
            //llOwnerSay("Root:RLV listen:" + message + " from " + name);
            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)
            {
                //llOwnerSay("Free core index:" + (string)llSubStringIndex(coreKeys, NULL_KEY));
                if ((p = llSubStringIndex(coreKeys, NULL_KEY)) == -1)                    // no core is available
                {
                    if (!g_priv)
                        return;
                    // There's a core in a grace period, dare we use it?
                    return;
                }
        
                p = p / 36;
                coreKeys = replKey(coreKeys, (string)id, p);
                g_priv = g_priv & ~(1 << p);
            }
            else
                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();
    }
    
    attach(key id)
    {
        askCount = 0;
        if (id != null_key)
            llWhisper(RLVRS_CHANNEL, RELAYTESTCMD  + "," + (string)llGetOwner() + "," + VERCMD);
    }
    
    timer()
    {
        processDialogResponse(mmDENY);
    }
}

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.RLV1100 SPU (120502.0) --Chloe1982 Constantine 09:08, 4 May 2012 (PDT)

// tk.RLV1030 SPU (130121.0)
//
// ©left 2009 Think Kink (Think Kink is Ilana Debevec & Lyssa Daehlie)

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

// 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 Think Kink in Zindra http://slurl.com/secondlife/Gilda%20Pointe/245/183/49
//   or thru the "Get tkPBA" in button in most Think Kink devices

// 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
 
key     null_key    = NULL_KEY;
 
// ---------------------------------------------------
//                     Constants
// ---------------------------------------------------
 
integer RLVRS_PROTOCOL_VERSION = 1100; // version of the protocol, stated on the specification page
string  RLVRS_IMPL_VERSION = ", satisfying the 1.1 and ORG standards."; // version of the implementation for debugging
string  ORG_VERSIONS = "ORG=0001/who=001/vision=001/handover=001";
string  myName = "tkPBA (unknown)";
 
integer RLVRS_CHANNEL       = -1812221819;  // RLVRS in numbers
integer STATUS_OPEN_CHANNEL = -1373421300;
integer STATUS_SAY_CHANNEL  = -1373421301;
integer CORE_CONTROL        = -1383421304;
integer COMMAND_CHANNEL     = -1373421302;
integer GPU_CHANNEL         = -4360493;

integer HARMLESS_COMMAND    = 0;
integer FORCE_COMMAND       = 1;
integer BINDING_COMMAND     = 2;

integer MAX_TIME_AUTOACCEPT_AFTER_FORCESIT = 60; // seconds
 
integer LOGIN_DELAY_WAIT_FOR_PONG = 30;
integer LOGIN_DELAY_WAIT_FOR_FORCE_SIT = 60;
float   reAskTime           = 300.0;
integer noPong              = FALSE;

integer ALLOW_CONTROL_BY_ATTACHMENTS = TRUE;

integer coreName        = -1;
integer cmdMax          = 0;
string  savedCmds       = "";
integer isHardcore      = 0;
integer isComplexPerm   = 0;
integer listenHandle    = 0;
 
// ---------------------------------------------------
//                      Variables
// ---------------------------------------------------

string  roots           = "";      // list of all commands enforced by the relay
key source;        // UUID of the object I'm commanded by, always equal to NULL_KEY if restrictions is empty, always set if not
string  objName         = "unknown object";
key     owner           = null_key;
 
// 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 holding     = FALSE;
key     whoDoing    = null_key;
key     devOwner    = null_key;
key     devQuery    = null_key;
string  devOName    = "";
string  whoName     = "";
integer loginTimer  = FALSE;
integer gracePeriod = FALSE;
integer unchecked   = TRUE;
integer blinded     = FALSE;
 
 
// ---------------------------------------------------
//               Low Level Communication
// ---------------------------------------------------
 
 
// acknowledge or reject
ack(string cmd_id, key id, string cmd, string ack)
{
    llRegionSayTo(id, RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack);
    //llRegionSay(RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack);
    //llOwnerSay(llList2CSV(["ack", id, RLVRS_CHANNEL, cmd_id + "," + (string)id + "," + cmd + "," + ack]));
}
 
// cmd begins with a '@' 
sendRLCmd(string cmd)
{
    //llOwnerSay("Send: " + cmd);
    if (cmd != "")
        llOwnerSay(cmd);
}

setSource(key id)
{
    //llOwnerSay(llList2CSV(["setSource", id]));
    objName = "unknown object";
    devOName = "";
    devOwner = null_key;
    source = id;
    if (holding)
        llSetObjectDesc((string)id + "^" + (string)whoDoing);
    else
        llSetObjectDesc((string)id);
    if (source == null_key)
    {
        whoDoing = null_key;
        whoName = "";
        unchecked = TRUE;
        return;
    }
    objName = llKey2Name(source);
    devOwner = llGetOwnerKey(source);
    devQuery = llRequestAgentData(devOwner, DATA_NAME);
}
 
// ---------------------------------------------------
//               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)
{
    //llOwnerSay(llList2CSV(["execute", id, message]));
    integer cp = llSubStringIndex(message, ",");
    string cmd_id = llGetSubString(message, 0, cp - 1);     // Whatever command id waa given
    message = llGetSubString(message, cp + 1, -1);
    string command;
    string prefix;
    //llOwnerSay("execute1:" + message);
    if (message == "!pong")
    {
        noPong = FALSE;
        loginWaitingForPong = FALSE;
    }
    else if (noPong)
    {
        llOwnerSay("@clear");
        roots = "";
        noPong = FALSE;
        loginWaitingForPong = FALSE;
    }
    
    do
    {
        if ((cp = llSubStringIndex(message, "|")) == -1)
            command = message;
        else
        {
            if (cp == 0)
                command = "";
            else
                command = llGetSubString(message, 0, cp - 1);
            if (cp == (llStringLength(message) - 1))
                message = "";
            else
                message = llGetSubString(message, cp + 1, -1);
        }
        //llOwnerSay(llList2CSV(["split", cp, command, message]));
        
        //llOwnerSay("execute2:" + cmd_id + ":" + command);
        prefix = llGetSubString(command, 0, 0);
        if (prefix == "@")                    // this is a RLV command
            executeRLVCommand(cmd_id, id, command);
        else if (prefix == "!")              // this is a metacommand, aimed at the relay itself
            executeMetaCommand(cmd_id, id, command);
    }
    while (cp != -1);
    
    //llOwnerSay(llList2CSV(["going green", blinded, llStringLength(roots), roots]));
    if (blinded || llStringLength(roots))
    {
        llSetColor(<0.0, 1.0, 0.0>, ALL_SIDES); //  Green
        llSetAlpha(1.0, ALL_SIDES);
        if (gracePeriod)
        {
            //llOwnerSay("timer5: 0");
            llSetTimerEvent(0.0);
            gracePeriod = FALSE;
            llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "IsCommitted", "Busy");
        }
        unchecked = FALSE;
    }
    else
    {
        //llOwnerSay("timer6: " + (string)reAskTime);
        llSetTimerEvent(reAskTime);
        gracePeriod = TRUE;
        llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "IsCommitted", "Grace");
        llSetColor(<0.0, 0.0, 0.0>,ALL_SIDES); //  Black
        llSetAlpha(1.0,ALL_SIDES);
    }
}

// is this a query?
integer isNumeric(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)
{
    //llOwnerSay(llList2CSV(["eRLVCmd", cmd_id, id, command]));
    integer ep = llSubStringIndex(command = llToLower(command), "=");
    // we need to know whether whether is a rule or a simple command
    string behav = command;
    string param = "";
    if (ep != -1)
    {
        behav = llGetSubString(command, 0, ep - 1);  // @getattach:skull
        param = llGetSubString(command, ep + 1, -1); // 2222
    }
    integer ind = inString(roots, behav, "|", TRUE, TRUE);
    string behavName = behav;
    string option = "";
 
    if ((ep = llSubStringIndex(behav, ":")) != -1)
    {
        behavName = llGetSubString (behav, 0, ep - 1);  // @sit
        option = llGetSubString (behav, ep + 1, -1);     // <uuid>
    }
 
    //llOwnerSay(llList2CSV(["Checking", behavName, option]));
    if (isNumeric(behavName) && ((integer)param <= 0 || (integer)param == DEBUG_CHANNEL))
    {
        ack(cmd_id, id, command, "ko");
        return;
    }
    else if (behavName == "@notify" && ((integer)option <= 0 || (integer)option == DEBUG_CHANNEL))
    {
        ack(cmd_id, id, command, "ko");
        return;
    }
    else if (param=="n" || param=="add") // add to restrictions
    {
        if (behavName == "@acceptpermission")
        {
            ack(cmd_id, id, command, "ko");
            return;
        }
        if (ind < 0)
        {
            if (roots == "")
                roots = behav;
            else
                roots = roots + "|" + behav;
        }
    }
    else if (param == "y" || param == "rem") // remove from restrictions
    {
        //llOwnerSay(llList2CSV(["releasing", param, behav, ind]));
        if (behav == roots)
            roots = "";
        else if (ind > -1)
        {
            integer bl = llStringLength(behav);
            if (ind + bl == llStringLength(roots))
                ind--;
            //llOwnerSay(llList2CSV(["deleting", roots, ind, ind + bl, llDeleteSubString(roots, ind, ind + bl)]));
            roots = llDeleteSubString(roots, ind, ind + bl);
        }
    }

    else if (param != "force" && ((integer) param == 0) && (behavName != "@clear"))
    {
        //llOwnerSay("rejecting" + (string)param);
        // 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;
    }
 
    // 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
    //llOwnerSay(llList2CSV(["executing", command, behavName, param]));
    if (command == "@clear")
        releaseRestrictions();
    else
    {
        
        // remembers the time and object if this command is a force sit
        // 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 (inString(roots, "@unsit", "|", TRUE, TRUE) == -1)
            {
                if (lastForceSitTime + MAX_TIME_AUTOACCEPT_AFTER_FORCESIT < llGetUnixTime())
                {
                    //llOwnerSay("clearing lastForceSitDestination");
                    lastForceSitDestination = null_key;
                }
            }
        }
 
        if (param == "force" && behavName == "@sit")
        {
            lastForceSitDestination = (key) option;
            lastForceSitTime = llGetUnixTime();
            //llOwnerSay("remembered force sit");
        }
        sendRLCmd(command); // execute command
    }
    ack(cmd_id, id, command, "ok"); // acknowledge
}
 
handleHandOver(string id, key targetObject, integer keepRestrictions)
{
    //llOwnerSay("handover");
    if (!keepRestrictions)
        releaseRestrictions();
        
    setSource(targetObject);
    pingWorldObjectIfUnderRestrictions();
}

 
// executes a meta command which is handled by the relay itself
executeMetaCommand(string cmd_id, string id, string commandString)
{
    //llOwnerSay(llList2CSV(["Meta", cmd_id, id, commandString]));
    list tokens = llParseString2List(commandString, ["/"], []);
    string command = llList2String(tokens, 0);
    
    if (command == "!version") // checking relay protocol version
    {
        ack(cmd_id, id, command, (string) RLVRS_PROTOCOL_VERSION);
    }
    else if (command == "!implversion") // checking relay version
    {
        ack(cmd_id, id, command, myName + RLVRS_IMPL_VERSION);
    }
    else if (command == "!x-orgversions")
        ack(cmd_id, id, command, ORG_VERSIONS);
    else if (command == "!release") // release all the restrictions (end session)
    {
        ack(cmd_id, id, command, "ok");
        releaseRestrictions();
    }
    else if (command == "!x-who")
    {
        key k = llList2Key(tokens, 1);
        if (k)
        {
            whoDoing = k;
            whoName = llKey2Name(k);
        }
    }
    else if (command == "!x-vision")
    {
        blinded = commandString != "!x-vision/clear";
        llMessageLinked(LINK_ROOT, GPU_CHANNEL, commandString, null_key);
        ack(cmd_id, id, commandString, "ok");
    }
    else if (command == "!x-handover")
        handleHandOver(id, llList2Key(tokens, 1), llList2Integer(tokens, 2));
    else
        ack(cmd_id, id, command, "ko");
} 
 
// lift all the restrictions (called by !release and by turning the relay off)
releaseRestrictions()
{
    holding = FALSE;
    
    llSetColor(<0.0, 0.0, 0.0>,ALL_SIDES); //  Black
    llSetAlpha(1.0,ALL_SIDES);
    llMessageLinked(LINK_ROOT, GPU_CHANNEL, "!x-vision/clear", null_key);
    blinded = 0;

    if (llStringLength(roots))
        llOwnerSay("@clear");
    lastForceSitDestination = null_key;
    roots = "";
    loginPendingForceSit = FALSE;
    savedCmds = "";                                     // just in case we had any of these lurking
    
    loginTimer = FALSE;
    if (source != null_key)
    {
        //llOwnerSay("Timer7: " + (string)reAskTime);
        llSetTimerEvent(reAskTime);
        gracePeriod = TRUE;
        llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "IsCommitted", "Grace");
    }
}
 
// ---------------------------------------------------
//            login handling
// ---------------------------------------------------
 
 
// send a ping request and start a timer
pingWorldObjectIfUnderRestrictions()
{
    //llOwnerSay(llList2CSV(["pingWorldObjectIfUnderRestrictions", source, loginPendingForceSit]));
    loginWaitingForPong = FALSE;
    loginTimer = FALSE;
    noPong = FALSE;
    if (source)
    {
        ack("ping", source, "ping", "ping");
        timerTickCounter = 0;
        //llOwnerSay("Timer8: 1");
        llSetTimerEvent(1.0);
        loginWaitingForPong = TRUE;
        loginTimer = TRUE;
        noPong = TRUE;
    }
}

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

integer inString(string source, string pattern, string follower, integer follow, integer endOK)
{
    integer fp;
    integer sl;
    integer pl = llStringLength(pattern);
    integer bonus = 0;
    while (sl = llStringLength(source))
    {
        integer pp = llSubStringIndex(source, pattern);
    
        if (pp == -1)                   // the easy case; it's not there
            return -1;    
    
        if (pp + pl == sl)              // it's OK for the pattern to terminate the source
        {
            if (endOK)
                return pp + bonus;
            else
                return -1;
        }
    
        // If the pattern is appropriately followed, then we're done
        fp = llSubStringIndex(source = llGetSubString(source, pp + pl, -1), follower);
        if (follow && (fp == 0))
            return pp + bonus;
        if (!follow && (fp != 0))
            return pp + bonus;
        bonus += pp + pl;
    }
    return -1;
}    

integer check(string commands)
{
    integer cp = llSubStringIndex(commands, ",");
    if (cp == -1)
        return -1;
    commands = llGetSubString(commands, cp + 1, -1);
    
    // Do we know who is operating the toy?
    cp = -1;
    if ((cp = llSubStringIndex(commands, "!x-who/")) == -1)
    {
        if ((cp = llSubStringIndex(commands, "!who/")) != -1)
            cp += 5;
    }
    else
        cp += 7;
    key tempWho = (key)llGetSubString(commands, cp, cp + 35);
    if (tempWho)
        whoDoing = tempWho;
    
    // Do we need to worry?
    if (inString(commands, "=n", "|", TRUE, TRUE) != -1)
        return BINDING_COMMAND;
    if (inString(commands, "=add", "|", TRUE, TRUE) != -1)
        return BINDING_COMMAND;
    
    // What about ORG meta-commands?
    // The !vision check is deprecated and will be removed
    if (inString(commands, "!x-vision/", "clear", FALSE, FALSE) != -1)
        return BINDING_COMMAND;
    if (inString(commands, "!x-handover", "/", TRUE, FALSE) != -1)
        return FALSE;
        
    // Might we get embarrassed?
    if (inString(commands, "=force", "|", TRUE, TRUE) != -1)
        return FORCE_COMMAND;
    
    return HARMLESS_COMMAND;
}

integer checkPerm(key id)
{
    if (isHardcore)             // OK, in hardcore we will never check any more
        return TRUE;
        
    // Now check for land status
    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);
 
    //llOwnerSay("owner= " + llKey2Name(parcel_owner) + " / " + llKey2Name(object_owner));
    //llOwnerSay("group= " + llKey2Name(parcel_group) + " / " + llKey2Name(object_group));
 
    return object_owner == owner                    // 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;            // OR its group is the same as the parcel I'm on
}

// ---------------------------------------------------
//               Pending Command Handling
// ---------------------------------------------------

gluePending(string message, key id)
{
    integer l;
    if ((l = llStringLength(savedCmds)) > 4000)
    {
        llMessageLinked(LINK_ROOT, STATUS_OPEN_CHANNEL, llKey2Name(id) + " is flooding commands. Releasing restrictions.", null_key);
        releaseRestrictions();
    }
    else if (l)
        savedCmds = savedCmds + "," + message;
    else
        savedCmds = message;
        
    if (holding)
        return;
        
    holding = TRUE;
    
    integer p;
    if ((p = llSubStringIndex(message, "!x-who/")) != -1)
    {
        if (whoDoing = llGetSubString(message, p + 7, p + 42))
            ;
        else
            whoDoing = null_key;
    }
    else if (p = llSubStringIndex(message, "!who/"))          // Compatibility with old toys
    {
        if (whoDoing = llGetSubString(message, p + 5, p + 40))
            ;
        else
            whoDoing = null_key;
    }
    
    setSource(source);
    llSetColor(<1.0, 1.0, 0.0>,ALL_SIDES); //  Yellow
    llSetAlpha(1.0,ALL_SIDES);
    llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "AskThem", null_key);
}
 
default
{
    state_entry()
    {
        integer p;
        string s;
        
        if ((p = llSubStringIndex(s = llGetObjectName(), ":")) == -1)
            return;
        coreName = (integer)llGetSubString(s, p + 1, -1);
    
        llSetColor(<0.0, 0.0, 0.0>, ALL_SIDES); //  Black
        llSetAlpha(1.0, ALL_SIDES);
        
        setSource(null_key);
        
        myName = llKey2Name(llGetLinkKey(LINK_ROOT));
        owner = llGetOwner();

        //llOwnerSay("Free Memory: " + (string) llGetFreeMemory());
    }
 
    on_rez(integer start_param)
    {
        //llOwnerSay(llList2CSV(["on_rez", source, roots, inString(roots, "@unsit", "|", TRUE, 0)]));
        // relogging, we must refresh the viewer and ping the object if any
        // if source is set to something, fire all the stored restrictions
    
        // sends the known restrictions (again) to the RL-viewer
        if (source != null_key && roots != "")
        {
            integer pp;
            string sr = roots;

            do
            {
                pp = llSubStringIndex(sr, "|");
                if (pp == -1)
                    sendRLCmd(sr + "=n");
                else
                {
                    sendRLCmd(llGetSubString(sr, 0, pp - 1) + "=n");
                    sr = llGetSubString(sr, pp + 1, -1);
                }
            }
            while (pp != -1);
            
            if (inString(roots, "@unsit", "|", TRUE, TRUE) != -1)
                loginPendingForceSit = TRUE;
            pingWorldObjectIfUnderRestrictions();
        }
        else
        {
            loginTimer = FALSE;
            if (gracePeriod)
            {
                //llOwnerSay("timer9: 0");
                llSetTimerEvent(0.0);
                unchecked = TRUE;
                gracePeriod = FALSE;
            }
            setSource(null_key);
        }
        //llOwnerSay("on_rez: " + (string)loginPendingForceSit);
    }
 
    attach(key id)
    {
        if (id == null_key)
        {
            // Tell the object in world about this canceled session
            if (source != null_key)
                ack("release", source, "!release", "ok");
            releaseRestrictions();
        }
        else
            llRequestPermissions(owner = llGetOwner(), PERMISSION_TAKE_CONTROLS);
    }
    
    run_time_permissions(integer perm)
    {
        if (perm & PERMISSION_TAKE_CONTROLS)
            llTakeControls(CONTROL_FWD, TRUE, TRUE);
    }
 
    timer()
    {
        //llOwnerSay(llList2CSV(["timer", loginTimer, timerTickCounter, loginWaitingForPong, loginPendingForceSit, source, lastForceSitDestination]));
        if (loginTimer)
        {
            //llOwnerSay(llList2CSV(["timer", loginWaitingForPong, timerTickCounter, loginPendingForceSit, source, lastForceSitDestination]));
            timerTickCounter++;
            //llOwnerSay("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(owner) + " is freed because " + objName + " is not available.", null_key);
                loginWaitingForPong = FALSE;
                loginPendingForceSit = FALSE;
                releaseRestrictions();
            }
 
            if (loginPendingForceSit)
            {
                integer agentInfo = llGetAgentInfo(owner);
                if (agentInfo & AGENT_SITTING)
                {
                    loginPendingForceSit = FALSE;
                    //llOwnerSay("is sitting now");
                }
                else if (timerTickCounter == LOGIN_DELAY_WAIT_FOR_FORCE_SIT)
                {
                    llMessageLinked(LINK_ROOT, STATUS_OPEN_CHANNEL, "Lucky Day: " + llKey2Name(owner) + " is freed because sitting down again was not possible.", null_key);
                    loginPendingForceSit = FALSE;
                    releaseRestrictions();
                }
                else
                {
                    key sitTarget = source;
                    if (lastForceSitDestination)
                        sitTarget = lastForceSitDestination;
                    //llOwnerSay("Force sit during login on " + (string) sitTarget + " (source=" + (string) source + ",                             lastForceSitDestination=" + (string) lastForceSitDestination + ")");
                    sendRLCmd ("@sit:" + (string) sitTarget + "=force");
                }
            }
    
            if (!loginPendingForceSit && !loginWaitingForPong)
            {
                //llOwnerSay("Timer1: 0");
                llSetTimerEvent(0.0);
            }
        }
        else
        {
            //llOwnerSay("Timer2: 0");
            llSetTimerEvent(0.0);
            setSource(null_key);
            gracePeriod = FALSE;
            llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "IsCommitted", "Free");
        }
    }
 
    link_message(integer sender, integer num, string message, key id)
    {
        //llOwnerSay("Link:" + (string)num + ": " + message);
        
        if (num == CORE_CONTROL)
        {
            if (message == "RelayMax")
            {
                cmdMax = (integer)((string)id);
                unchecked = TRUE;
                isHardcore = (integer)llGetSubString(id, 2, 2);
            }
            else if (message == "FreeCore")
            {
                if (source != null_key)
                    ack("safeword", source, "!release", "ok");
                releaseRestrictions();
                //llOwnerSay("Timer3: 0");
                llSetTimerEvent(0.0);
                setSource(null_key);
                whoDoing = null_key;
                whoName = "";
                devOName = "";
                devOwner = null_key;
                unchecked = TRUE;
            }
            else if (message == "ExecuteSaved")
            {
                string cmdId;
                integer cp = llSubStringIndex(savedCmds, ",");
                unchecked = FALSE;
                //llOwnerSay(llList2CSV(["executeSaved", savedCmds]));
                do
                {
                    cmdId = llGetSubString(savedCmds, 0, cp - 1);
                    savedCmds = llGetSubString(savedCmds, cp + 1, -1);
                    if ((cp = llSubStringIndex(savedCmds, ",")) == -1)
                        execute(source, cmdId + "," + savedCmds);
                    else
                    {
                        execute(source, cmdId + "," + llGetSubString(savedCmds, 0, cp - 1));
                        savedCmds = llGetSubString(savedCmds, cp + 1, -1);
                        cp = llSubStringIndex(savedCmds, ",");
                    }
                }
                while (cp != -1);
                holding = FALSE;
            }
        }
        else if (num == RLVRS_CHANNEL)
        {
            //llOwnerSay(llList2CSV(["First", source, id, holding, unchecked]));
            if (source == null_key)
            {
                setSource(id);
                unchecked = TRUE;
            }
            
            if (holding)
            {
                gluePending(message, id);
                return;
            }
                
            if (unchecked)
            {
                integer thisLevel = check(message);             // We'll have to check 
            
                //llOwnerSay(llList2CSV(["second", thisLevel, message, unchecked]));
            
                if (thisLevel != HARMLESS_COMMAND)
                {
                    integer untrusted = !checkPerm(id);
                    //llOwnerSay(llList2CSV(["third", untrusted, cmdMax, unchecked]));
                
                    if (isComplexPerm)
                    {
                        holding = TRUE;
                        savedCmds = message;
                        llMessageLinked(LINK_ROOT, COMMAND_CHANNEL, "CheckPerm" + (string)thisLevel+(string)(10 * cmdMax + untrusted), id);
                        return;
                    }
                    
                    if (untrusted || (thisLevel > cmdMax))
                    {
                        gluePending(message, id);
                        return;
                    }
                    else if (thisLevel == BINDING_COMMAND)
                        unchecked = FALSE;
                    //llOwnerSay(llList2CSV(["done", thisLevel, cmdMax, unchecked]));
                }
            }
            //llOwnerSay(llList2CSV(["execute", unchecked, cmdMax]));
            execute(id, message);
        }
    }
    
    touch_start(integer num)
    {
        // How this could be anything other than 1 I don't know
        string message = "Idle";
        if (source != null_key)
        {
            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 #" + (string)coreName + ": " +message, null_key);
    }
 
    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;
    }
}