Difference between revisions of "LSL Protocol/LockMeister System"

From Second Life Wiki
Jump to navigation Jump to search
m (updated to use <syntaxhighlight> blocks.)
 
(11 intermediate revisions by 3 users not shown)
Line 24: Line 24:


''''An item will send the message using this format:'''<br />
''''An item will send the message using this format:'''<br />
message shape: <avatar key><command> example: "748bb591-0d9d-4907-8287-dc27b8267e24lcuff" (we ping the left wrist cuff attachment of the avatar whose key is positioned before the name of the attachment point, or command)<br />
message shape: <code><avatar key><command></code> example: <code>"748bb591-0d9d-4907-8287-dc27b8267e24lcuff"</code> (we ping the left wrist cuff attachment of the avatar whose key is positioned before the name of the attachment point, or command)<br />
'''The concerned attachment will answer:'''<br />
'''The concerned attachment will answer:'''<br />
message shape: <avatar key><command>" ok" example: "748bb591-0d9d-4907-8287-dc27b8267e24lcuff ok" (the concerned object answer on the channel -8888)<br />
message shape: <code><avatar key><command>" ok"</code> example: <code>"748bb591-0d9d-4907-8287-dc27b8267e24lcuff ok"</code> (the concerned object answer on the channel -8888)<br />


In order to draw a chain of particles we need the key of the object.  When we used the listener event to get the answer message, the [[listen]] event returned us the key of the object that answered in it's key id field.  This is the key of the speaking prim, which should be the prim we need to attach the chain to.
In order to draw a chain of particles we need the key of the object.  When we used the listener event to get the answer message, the [[listen]] event returned us the key of the object that answered in it's key id field.  This is the key of the speaking prim, which should be the prim we need to attach the chain to.
Line 36: Line 36:


''''An item will send the message using this format (same as V1):'''<br />
''''An item will send the message using this format (same as V1):'''<br />
message shape: <avatar key><command> <br />
message shape: <code><avatar key><command></code> <br />
example: "748bb591-0d9d-4907-8287-dc27b8267e24lcuff" <br />
example: <code>"748bb591-0d9d-4907-8287-dc27b8267e24lcuff"</code> <br />
(we ping the left wrist cuff attachment of the avatar whose key is positioned before the name of the attachment point, or command)<br />
(we ping the left wrist cuff attachment of the avatar whose key is positioned before the name of the attachment point, or command)<br />
<br />
<br />
'''The concerned attachment will answer (same as V1):'''<br />
'''The concerned attachment will answer (same as V1):'''<br />
message shape: <avatar key><command>" ok" <br />
message shape: <code><avatar key><command>" ok"</code> <br />
example: "748bb591-0d9d-4907-8287-dc27b8267e24lcuff ok" (the concerned object answer on the channel -8888)<br />
example: <code>"748bb591-0d9d-4907-8287-dc27b8267e24lcuff ok"</code> (the concerned object answer on the channel -8888)<br />
<br />
<br />
We now have in the listen event 'id' parameter a key, we should start using it as a particle target, if the object is only LMV1 it will be the proper target id, but if the object is LMV2, this will most likely be the root prim of the object.
We now have in the listen event 'id' parameter a key, we should start using it as a particle target, if the object is only LMV1 it will be the proper target id, but if the object is LMV2, this will most likely be the root prim of the object.
now we can use this id to send the lmv2 messages using llRegionSayTo()<br />
now we can use this id to send the lmv2 messages using <code>llRegionSayTo()</code><br />
<br />
<br />
LMV2 MESSAGES SHOULD ALWAYS USE llRegionSayTo() !!<br />
LMV2 MESSAGES SHOULD ALWAYS USE <code>llRegionSayTo()</code> !!<br />
<br />
<br />
''''The item can now send an LMV2 message using this format:'''<br />
''''The item can now send an LMV2 message using this format:'''<br />
message shape: <avatar key>|LMV2|RequestPoint|<anchor point> <br />
message shape: <code><avatar key>|LMV2|RequestPoint|<anchor point></code> <br />
example: "748bb591-0d9d-4907-8287-dc27b8267e24|LMV2|RequestPoint|lcuff" <br />
example: <code>"748bb591-0d9d-4907-8287-dc27b8267e24|LMV2|RequestPoint|lcuff"</code> <br />
<br />
<br />
'''The concerned attachment will answer (if it is LMV2 compatible):'''<br />
'''The concerned attachment will answer (if it is LMV2 compatible):'''<br />
message shape: <avatar key>|LMV2|ReplyPoint|<anchor point>|<anchor point key><br />
message shape: <code><avatar key>|LMV2|ReplyPoint|<anchor point>|<anchor point key></code><br />
example: "748bb591-0d9d-4907-8287-dc27b8267e24|LMV2|ReplyPoint|lcuff|636bbccc-0d9d-4907-8287-dc27b8267e24" <br />
example: <code>"748bb591-0d9d-4907-8287-dc27b8267e24|LMV2|ReplyPoint|lcuff|636bbccc-0d9d-4907-8287-dc27b8267e24"</code> <br />


If the target object doesn't support LMV2, we won't receive any answer, so it's important to start drawing the particles before the LMV2 reply comes.
If the target object doesn't support LMV2, we won't receive any answer, so it's important to start drawing the particles before the LMV2 reply comes.
Line 92: Line 92:
|-
|-
|| 11 || rtigh || right upper leg cuff
|| 11 || rtigh || right upper leg cuff
|-
||    || ritigh || right upper leg cuff (inner thigh)
|-
|-
|| 12 || ltigh || left upper leg cuff
|| 12 || ltigh || left upper leg cuff
|-
||    || litigh || left upper leg cuff (inner thigh)
|-
|-
|| 13 || rlcuff || right ankle cuff
|| 13 || rlcuff || right ankle cuff
Line 134: Line 138:
|-
|-
|| 32 || lbelt || left side of the belt
|| 32 || lbelt || left side of the belt
|-
|| 33 || chest || center of the chest, on the solar plexus (not currently in the picture)
|}
|}
|[[Image:lockmeister-mooring-pts.jpg]]
|[[Image:lockmeister-mooring-pts.jpg]]
Line 144: Line 150:


===Special Commands===
===Special Commands===
<avatar key>col<vector> - order a color change (used in LockMeister collars to tint the cuffs like the collar) OPTIONAL<br />
<code><avatar key>col<vector></code> - order a color change (used in LockMeister collars to tint the cuffs like the collar) OPTIONAL<br />
ex: "748bb591-0d9d-4907-8287-dc27b8267e24col<1.0,1.0,1.0>" will say to an item able to understand it (color yourself in white)
ex: <code>"748bb591-0d9d-4907-8287-dc27b8267e24col<1.0,1.0,1.0>"</code> will say to an item able to understand it (color yourself in white)


<avatar key>booton/bootoff = commands sent by collar objects, asking that the shoes animation overrider be started or stopped<br />
<code><avatar key>booton/bootoff</code> = commands sent by collar objects, asking that the shoes animation overrider be started or stopped<br />
ex: "748bb591-0d9d-4907-8287-dc27b8267e24booton" activate the animation overrider<br />
ex: <code>"748bb591-0d9d-4907-8287-dc27b8267e24booton"</code> activate the animation overrider<br />
ex: "748bb591-0d9d-4907-8287-dc27b8267e24bootoff" deactivate the animation overrider
ex: <code>"748bb591-0d9d-4907-8287-dc27b8267e24bootoff"</code> deactivate the animation overrider


===Amethyst extension commands (LMV1)===
===Amethyst extension commands (LMV1)===
Line 155: Line 161:
The following commands are not part of the main lockmeister protocol, but additions Amethyst Rosencrans made in her sold and free cuff scripts.  They are here to document the extensions so others can utilize them.
The following commands are not part of the main lockmeister protocol, but additions Amethyst Rosencrans made in her sold and free cuff scripts.  They are here to document the extensions so others can utilize them.


<avatar key>target|point1|point2 = Connect a chain from mooring point 1 to mooring point 2
<code><avatar key>target|point1|point2</code> = Connect a chain from mooring point 1 to mooring point 2
ex: "748bb591-0d9d-4907-8287-dc27b8267e24target|lcuff|rcuff" will create a chain from the left cuff to the right cuff
ex: <code>"748bb591-0d9d-4907-8287-dc27b8267e24target|lcuff|rcuff"</code> will create a chain from the left cuff to the right cuff


<avatar key>target|point = Remove a chain from mooring point
<code><avatar key>target|point</code> = Remove a chain from mooring point
ex: "748bb591-0d9d-4907-8287-dc27b8267e24target|lcuff" will remove a chain from the left cuff
ex: <code>"748bb591-0d9d-4907-8287-dc27b8267e24target|lcuff"</code> will remove a chain from the left cuff


<avatar key>texture|<texture key> = Change the texture of the chains used by the target command
<code><avatar key>texture|<texture key></code> = Change the texture of the chains used by the target command
ex: "748bb591-0d9d-4907-8287-dc27b8267e24texture|1ffb37fa-2fc1-dbec-d8ea-0607583a03c6" will change the chain texture
ex: <code>"748bb591-0d9d-4907-8287-dc27b8267e24texture|1ffb37fa-2fc1-dbec-d8ea-0607583a03c6"</code> will change the chain texture


<avatar key>gravity|<downward acceleration float> = Change the rate at which the chain particles move down
<code><avatar key>gravity|<downward acceleration float></code> = Change the rate at which the chain particles move down
ex: "748bb591-0d9d-4907-8287-dc27b8267e24gravity|0.5" will change the chain texture to move down half a meter a second
ex: <code>"748bb591-0d9d-4907-8287-dc27b8267e24gravity|0.5"</code> will change the chain texture to move down half a meter a second


<avatar key>age|<age float> = Change how fast the chain particles move from point 1 to point 2
<code><avatar key>age|<age float></code> = Change how fast the chain particles move from point 1 to point 2
ex: "748bb591-0d9d-4907-8287-dc27b8267e24age|2.0" will change the chain texture to move the distance in 2 seconds
ex: <code>"748bb591-0d9d-4907-8287-dc27b8267e24age|2.0"</code> will change the chain texture to move the distance in 2 seconds


===Cool Products extension commands (LMV1)===
===Cool Products extension commands (LMV1)===
Line 174: Line 180:
The following commands are not part of the main lockmeister protocol, but additions Henri Beauchamp made for use by his Cool Products (such as the Cool Collar).  They are here to document the extensions so others can utilize them.
The following commands are not part of the main lockmeister protocol, but additions Henri Beauchamp made for use by his Cool Products (such as the Cool Collar).  They are here to document the extensions so others can utilize them.


<avatar key>point here = Signals that anchoring point "point" just appeared.
<code><avatar key>point here</code> = Signals that anchoring point "point" just appeared.
ex: "748bb591-0d9d-4907-8287-dc27b8267e24handle here" could be emitted by the on_rez event of a leash handle to signal the collars connected to the owner of the leash handle that they should connect to it instead of staying connected to the center of the avatar.
ex: <code>"748bb591-0d9d-4907-8287-dc27b8267e24handle here"</code> could be emitted by the on_rez event of a leash handle to signal the collars connected to the owner of the leash handle that they should connect to it instead of staying connected to the center of the avatar.


<avatar key>point gone = Signals that anchoring point "point" just disappeared.
<code><avatar key>point gone</code> = Signals that anchoring point "point" just disappeared.
ex: "748bb591-0d9d-4907-8287-dc27b8267e24handle gone" could be emitted by the a leash handle when it is detached to signal the collars connected to it that they should unleash.
ex: <code>"748bb591-0d9d-4907-8287-dc27b8267e24handle gone"</code> could be emitted by the a leash handle when it is detached to signal the collars connected to it that they should unleash.


===(unknown) extension commands (LMV1)===
===(unknown) extension commands (LMV1)===


<toucher key>post ok = Signals that <toucher key> has touched an object. The toy will then issue an "<object key>handle" command to the object, which will return "<object key>handle ok" to activate the leash.
<code><toucher key>post ok</code> = Signals that <code><toucher key></code> has touched an object. The toy will then issue an <code>"<object key>handle"</code> command to the object, which will return <code>"<object key>handle ok"</code> to activate the leash.


===Note about message sending===
===Note about message sending===
* It is recommended to send the LMV1 "ping" messages through llWhisper so only close proximity objects will receive them.
* It is recommended to send the LMV1 "ping" messages through llWhisper so only close proximity objects will receive them.
* Now that llRegionSayTo() is available, it is strongly recommended to send "pong" messages through llRegionSayTo(), because of this, it is NOT recommended to have objects rely on "message snooping", because they won't be able to see message replies with llRegionSayTo.
* Now that <code>llRegionSayTo()</code> is available, it is strongly recommended to send "pong" messages through <code>llRegionSayTo()</code>, because of this, it is NOT recommended to have objects rely on "message snooping", because they won't be able to see message replies with <code>llRegionSayTo()</code>.
* LMV2 Messages should ALWAYS be send using llRegionSayTo().
* LMV2 Messages should ALWAYS be send using <code>llRegionSayTo()</code>.
 
===Seal Of Approval (optional)===
The seal of aproval program is currently discontinued.


===Example Scripts===
===Example Scripts===


====Example Attachment Script====
====Example Attachment Script====
<lsl>//KDC sample LockMeister attachment version 1.1a
<syntaxhighlight lang="lsl2">
//KDC sample LockMeister attachment version 1.1a
//
//
//NOTE: This script must be in the root prim of the object for working properly
//NOTE: This script must be in the root prim of the object for working properly
Line 204: Line 208:
//
//
//KDC, kyrah design concept
//KDC, kyrah design concept
 
string mooring_point = "lcuff";
string mooring_point = "lcuff";
//write here the mooring point you want to use
//write here the mooring point you want to use
 
string anchor_name  = "";
string anchor_name  = "";
//write here the name of the child prim that will act as the anchor point, leave blank to use the root.
//write here the name of the child prim that will act as the anchor point, leave blank to use the root.
 
default
default
{
{
Line 227: Line 231:
         if( !llGetAttached() )
         if( !llGetAttached() )
             return;
             return;
 
         if (message == (string)llGetOwner()+ mooring_point)
         if (message == (string)llGetOwner()+ mooring_point)
         {  //This part reply to Lockmeister v1 messages
         {  //This part reply to Lockmeister v1 messages
             //message structure:  llGetOwner()+mooring_point ( without the '+' )
             //message structure:  llGetOwner()+mooring_point ( without the '+' )
 
             llWhisper(-8888, (string)llGetOwner()+ mooring_point+" ok");//answering it
             llWhisper(-8888, (string)llGetOwner()+ mooring_point+" ok");//answering it
             //message structure:  llGetOwner()+mooring_point+" ok" ( without the '+' )
             //message structure:  llGetOwner()+mooring_point+" ok" ( without the '+' )
         }
         }
 
         //we parse the message into a list and recover each element.
         //we parse the message into a list and recover each element.
         list params = llParseString2List( message, ["|"], [] );
         list params = llParseString2List( message, ["|"], [] );
 
         if( llList2List(params,0,3) == [llGetOwner(), "LMV2", "RequestPoint", mooring_point] )    
        //NOTE: You can check all of these in a single statement, this is just for the sake of clarity.
         if(llList2String(params,0) != (string)llGetOwner())
            return;
        if(llList2String(params,1) != "LMV2")
            return;
        if(llList2String(params,2) != "RequestPoint")
            return;
        if(llList2String(params,3) != mooring_point)
            return;
       
        //this message is for us, it's claiming to be an LMV2 message, it's a "Request" message, and concerns the mooring_point we specified
        //message structure:  llGetOwner()|LMV2|RequestPoint|anchor_name
 
        //Now that we are certain that the message concerns us, we look for the prim key to insert in our reply.
 
        if(anchor_name  == "")
         {
         {
             //this message is for us, it's claiming to be an LMV2 message, it's a "Request" message, and concerns the mooring_point we specified
             //If there is no anchor set, we assume root prim.
            //message structure:  llGetOwner()|LMV2|RequestPoint|anchor_name
            llRegionSayTo( id, -8888, llDumpList2String( [llGetOwner(), "LMV2", "ReplyPoint", mooring_point, llGetKey()], "|" ) );
        }
             //Now that we are certain that the message concerns us, we look for the prim key to insert in our reply.
        else
              
        {
             if(anchor_name  == "")
             //Otherwise, we loop through the link set looking for a match.
             integer i;
             for( i = 1; i <= llGetNumberOfPrims(); i++)
             {
             {
                 //If there is no anchor set, we assume root prim.
                 if( llGetLinkName(i) == anchor_name )
                llRegionSayTo( id, -8888, llDumpList2String( [llGetOwner(), "LMV2", "ReplyPoint", mooring_point, llGetKey()], "|" ) );
                {  //If this is our anchor prim, we reply
            }
                    //pattern sent:  llGetOwner()|LMV2|ReplyPoint|anchor_name|anchor_key
            else
                    llRegionSayTo( id, -8888, llDumpList2String( [llGetOwner(), "LMV2", "ReplyPoint", mooring_point, llGetLinkKey(i)], "|" ) );
            {
                    return;
                //Otherwise, we loop through the link set looking for a match.
                integer i;
                for( i = 1; i <= llGetNumberOfPrims(); i++)
                {
                    if( llGetLinkName(i) == anchor_name )
                    {  //If this is our anchor prim, we reply
                        //pattern sent:  llGetOwner()|LMV2|ReplyPoint|anchor_name|anchor_key
                        llRegionSayTo( id, -8888, llDumpList2String( [llGetOwner(), "LMV2", "ReplyPoint", mooring_point, llGetLinkKey(i)], "|" ) );
                        return;
                    }
                 }
                 }
             }
             }
         }
         }
     }
     }
}</lsl>
}</syntaxhighlight>


====Example Furniture Script====
====Basic Chaining Script====
<lsl>
<syntaxhighlight lang="lsl2">
// Lockmeister V2 Bondage Cross Example.
//////////////////////////////////////////////////////////////////////////
// by Innula Zenovka
//KDC basic LockMeister chaining script
//http://wiki.secondlife.com/wiki/LSL_Protocol/LockMeister_System
//
//
// This script shows a typical usage for a Bondage Cross.
// This is a 'minimal' example to demonstrate locating and chaining to a
// The script expects to be placed in the root of a linkset with
// specific LockMeister target point.
// four chain points included.
//////////////////////////////////////////////////////////////////////////
// The script scans the linkset and identifies the four chain points
// by their description field:
// "top left", "top right", "bottom left", "bottom right" (without quotes, specified below)
// When someone sits on the cross, the animation found inside of the root prim
// will be played and 4 chains corresponding to the chain points specified will be started.
// The animation is removed and the chains turned off when the avatar unsits.


integer LM;
integer LM_CHANNEL = -8888;
string  LM_TARGET = "collar";


integer handle;
key user_id = NULL_KEY;
integer lm_channel = -8888;
integer target_found;
integer i;
integer max;
integer n;
integer number_of_prims;


key victim;
vector RED    = <1,0,0>;
list emitters;
vector ORANGE = <1,.5,0>;
list particles;
vector YELLOW = <1,1,0>;
vector GREEN  = <0,1,0>;


string LM2_request = "|LMV2|RequestPoint|";
DrawChain(key id) //Creates/Updates the particle chain
string anim;
{
    llParticleSystem(
        [
            PSYS_SRC_PATTERN,PSYS_SRC_PATTERN_DROP,
            PSYS_SRC_TARGET_KEY,id,
            PSYS_PART_START_SCALE,<0.05,1,0>,
            PSYS_PART_START_COLOR,<0,0,0>,
            PSYS_SRC_TEXTURE,"5748decc-f629-461c-9a36-a35a221fe21f",
            PSYS_PART_MAX_AGE,2.0,
            PSYS_SRC_BURST_RATE,0.01,
            PSYS_PART_FLAGS, PSYS_PART_FOLLOW_SRC_MASK | PSYS_PART_TARGET_POS_MASK | PSYS_PART_RIBBON_MASK,
            PSYS_SRC_ACCEL,<0,0,-1>
        ]);
}


string str_top_left ="top left"; //put in the description fields of the emitter prims
default
string str_top_right ="top right";
{
string str_bottom_left ="bottom left";
    state_entry()
string str_bottom_right="bottom right";
    {
        LM = llListen(LM_CHANNEL,"","",""); //Creates the LockMeister listener
        llListenControl(LM,FALSE);


        llSetColor(RED,ALL_SIDES);
    }
    touch_start(integer num_touches)
    {
        key clicker_id = llDetectedKey(0);


string left_wrist = "lcuff"; //lockmeister names
        if(user_id)
string right_wrist = "rcuff";
        {  //Remove the chain, user and disable the listener.
string right_ankle = "rlcuff";
            llParticleSystem([]);
string left_ankle = "llcuff";
            user_id = NULL_KEY;
            target_found = FALSE;
            llListenControl(LM,FALSE);


string request;
            llSetColor(RED,ALL_SIDES); //RED: No chain is being drawn.
        }
        else
        {  //Register user, enable listener and send a LMV1 'ping'.
            user_id = clicker_id;
            llListenControl(LM,TRUE);
            llRegionSayTo(user_id, LM_CHANNEL, (string)user_id+LM_TARGET);


            llSetColor(ORANGE,ALL_SIDES); //ORANGE: Waiting for LMV1 reply.
        }
    }
    listen(integer channel,string name, key id, string message)
    {
    llOwnerSay(llList2CSV([channel,name,id,message]));


init(){
        if(llGetOwnerKey(id) != user_id) return; //Wrong object owner? no processing.
number_of_prims = llGetObjectPrimCount(llGetKey());
anim = llGetInventoryName(INVENTORY_ANIMATION,0);
emitters = [str_top_left,left_wrist,str_top_right,right_wrist,str_bottom_left,left_ankle,str_bottom_right,right_ankle];
//descriptions of our points, and the LM points we want to use as targets for our chains
max = llGetNumberOfPrims()+1;
while (max--){//loop through the linkset
string s = llToLower(llStringTrim((string)llGetLinkPrimitiveParams(max,[PRIM_DESC]),STRING_TRIM));
n = llListFindList(emitters,[s]); //check if it's an emitter prim
if(~n){// if it is
emitters = llListInsertList(emitters,[max],(n+2));
//insert it into the list after the LM attachment point name
}
}
max = llGetListLength(emitters);//now get rid of the link descriptions from the list, cos we're not using them again
while (max>-1){
emitters = llDeleteSubList(emitters,max,max);
max-=3;
}
max = llGetListLength(emitters);
// llOwnerSay(llList2CSV(emitters));


particles = [  // start of particle settings
        if(message == ((string)user_id+LM_TARGET+" ok")) //It's a LMV1 message and it is for the lm point we are looking for!
// Texture Parameters:
        {
PSYS_SRC_TEXTURE, llGetInventoryKey(llGetInventoryName(INVENTORY_TEXTURE, 0)),//nb -- only works for full perms textures,
            target_found = TRUE;
// otherwise hard code the texture's uuid
            DrawChain(id);
PSYS_PART_START_SCALE, <0.1, 0.1, FALSE>, PSYS_PART_END_SCALE, <0.1, 0.1, FALSE>,
            llRegionSayTo(id,LM_CHANNEL,(string)user_id+"|LMV2|RequestPoint|"+LM_TARGET); //Lets try to obtain a more precise chain target (USERID|LMV2|RequestPoint|LMCODE).
PSYS_PART_START_COLOR, <1.00,1.00,1.00>,    PSYS_PART_END_COLOR, <1.00,1.00,1.00>,
PSYS_PART_START_ALPHA, (float) 1.0,         PSYS_PART_END_ALPHA, (float) 1.0,


// Production Parameters:
            llSetColor(YELLOW,ALL_SIDES); //YELLOW: LMV1 OK, Waiting for a (possible) LMV2 reply.
PSYS_SRC_BURST_PART_COUNT, (integer) 2,
            return;
PSYS_SRC_BURST_RATE,         (float) 0.01,
         }
PSYS_PART_MAX_AGE,          (float) 2.0,
        else if(!target_found) return; //if we haven't received a LMV1 message first, there is no need to try to get an LMV2 message.  
// PSYS_SRC_MAX_AGE,           (float)  0.00,


// Placement Parameters:
        //LMV2 Message checking.
PSYS_SRC_PATTERN, (integer) 1, // 1=DROP, 2=EXPLODE, 4=ANGLE, 8=CONE,
        list data = llParseString2List(message,["|"],[""]);
        if( llList2Key(data,0)   != user_id )      return; //data,0 is the avatar uuid, it has to match our user.
        if(llList2String(data,1) != "LMV2")        return; //data,1 is the protocal name: has to be LMV2
        if(llList2String(data,2) != "ReplyPoint")  return; //data,2 is the command: has to be 'ReplyPoint'
        if(llList2String(data,3) != LM_TARGET)      return; //data,3 is the lm target: it has to match the lm target we are looking for.


// Placement Parameters (for any non-DROP pattern):
        DrawChain(llList2String(data,4));    //Update the particle chain to its real target (data,4)
//  PSYS_SRC_BURST_SPEED_MIN, (float) 00.0,   PSYS_SRC_BURST_SPEED_MAX, (float) 00.0,
        llListenControl(LM,FALSE)//We don't need to listen for commands anymore.
// PSYS_SRC_BURST_RADIUS, (float) 00.0,


// Placement Parameters (only for ANGLE & CONE patterns):
        llSetColor(GREEN,ALL_SIDES); //GREEN: LMV2 OK, we are done.
// PSYS_SRC_ANGLE_BEGIN, (float) 0.50 * PI,  PSYS_SRC_ANGLE_END, (float) 0.50 * PI,
    }
//  PSYS_SRC_OMEGA, <00.00, 00.00, 01.00>,
}
</syntaxhighlight>


// After-Effect & Influence Parameters:
====Advanced Furniture Script====
PSYS_SRC_ACCEL, < 00.00, 00.00, -00.1>,
<syntaxhighlight lang="lsl2">
// PSYS_SRC_TARGET_KEY, (key) llGetLinkKey(llGetLinkNum() + 1),
//////////////////////////////////////////////////////////////////////////
 
//Copyright (c) 2017 KDC: Kyrah Design Concept
PSYS_PART_FLAGS, (integer) ( 0                  // Texture Options:
//Lockmeister sample furniture code
| PSYS_PART_INTERP_COLOR_MASK
//http://wiki.secondlife.com/wiki/LSL_Protocol/LockMeister_System
| PSYS_PART_INTERP_SCALE_MASK
//
| PSYS_PART_EMISSIVE_MASK
//This is free software: you can redistribute it and/or modify
| PSYS_PART_FOLLOW_VELOCITY_MASK
//it under the terms of the GNU Affero General Public License as
// After-effect & Influence Options:
//published by the Free Software Foundation, either version 3 of the
//   | PSYS_PART_WIND_MASK
//License, or (at your option) any later version.
// | PSYS_PART_BOUNCE_MASK
//
// | PSYS_PART_FOLLOW_SRC_MASK
//This program is distributed in the hope that it will be useful,
| PSYS_PART_TARGET_POS_MASK
//but WITHOUT ANY WARRANTY; without even the implied warranty of
// | PSYS_PART_TARGET_LINEAR_MASK
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
)
//GNU Affero General Public License for more details.
//end of particle settings
//
];
//You should have received a copy of the GNU Affero General Public License
//along with this program. If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////////////




}
//Configuration
list    LM_TARGETS      = ["lcuff","rcuff"];    //the lm targets you are looking to bind to (the target child prim will have to use the same name).
key    SIT_ANIMATION  = "";                  //put your sit animation name here.
vector  SIT_OFFSET      = <0,0,0.1>;            //change the sitting offset, please note that it cannot be 0,0,0 or sl disable sit targets.
vector  SIT_ROTATION    = <0,0,0>;              //the rotation of the avatar, in degrees, for your convenience.
key    CHAIN_TEXTURE  = TEXTURE_BLANK;        //the chain texture.


integer LM_CHANNEL = -8888;
list    lm_target_states;
integer lm_handle;
key sitting_avatar;


default
Reset_lm_state()
{
{
state_entry()
lm_target_states = [];
integer max = llGetListLength(LM_TARGETS);
while(max--)
{
{
init();
    lm_target_states += 0;
llSitTarget(<-0.19225, 0.00489, -0.42793>, <-0.06705, -0.27499, 0.95891, 0.01923>);
    SetParticles(NULL_KEY,llList2String(LM_TARGETS,max));
}
}
}


on_rez(integer start_param)
SetParticles(key target, string lm_code)
{
integer max = llGetNumberOfPrims() + 1;
while(max--)
{
{
init();
        if(llGetLinkName(max) == lm_code)
}
        {
            if(target == NULL_KEY)
                llLinkParticleSystem(max,[]);
            else
            {
                llLinkParticleSystem(max,[
                                            PSYS_PART_FLAGS,PSYS_PART_FOLLOW_SRC_MASK|PSYS_PART_FOLLOW_VELOCITY_MASK|PSYS_PART_TARGET_POS_MASK,
                                            PSYS_SRC_PATTERN,PSYS_SRC_PATTERN_DROP,
                                            PSYS_PART_START_SCALE, < 0.05, 0.05, 0.0 >,
                                            PSYS_PART_MAX_AGE, 3.0,
                                            PSYS_SRC_BURST_RATE, 0.001,
                                            PSYS_SRC_TEXTURE, CHAIN_TEXTURE,
                                            PSYS_SRC_BURST_SPEED_MIN,0.1,
                                            PSYS_SRC_BURST_SPEED_MAX,0.1,
                                            PSYS_SRC_TARGET_KEY, target,
                                            PSYS_SRC_BURST_PART_COUNT, 1,
                                            PSYS_SRC_ACCEL, < 0, 0, -0.01 >
                                            ]);
            }
        }
    }
}
default
{
    state_entry()
    {
        Reset_lm_state();
        llSitTarget(SIT_OFFSET,llEuler2Rot(SIT_ROTATION*DEG_TO_RAD));
 
        lm_handle = llListen(LM_CHANNEL,"","","");
        llListenControl(lm_handle,FALSE);
    }
    changed(integer change)
    {
        if(!(change & CHANGED_LINK))
            return;
        if(sitting_avatar == llAvatarOnSitTarget())
            return;
 
        sitting_avatar = llAvatarOnSitTarget();
        if(sitting_avatar != NULL_KEY)
        {
            if(SIT_ANIMATION != "")  //If we have an animation, request animation permission.
                llRequestPermissions(sitting_avatar,PERMISSION_TRIGGER_ANIMATION);
 
            llListenControl(lm_handle,TRUE);    //open listener.
            llSetTimerEvent(10.0);
 
            integer max = llGetListLength(LM_TARGETS);


changed(integer change)
            while(max--)           //call all the anchors, LMV1 style.
{
                llRegionSayTo(sitting_avatar,LM_CHANNEL,(string)sitting_avatar + llList2String(LM_TARGETS,max) );
if(change & CHANGED_LINK){
        }
victim = llAvatarOnSitTarget();
        else
if(victim){
        {
llRequestPermissions(victim,PERMISSION_TRIGGER_ANIMATION);
            if(llGetPermissions()) //If we have been granted permissions, stop animating.
}
                llStopAnimation(SIT_ANIMATION);
else{
llListenRemove(handle);
llLinkParticleSystem(LINK_SET,[]);
if(llGetObjectPrimCount(llGetKey())!=number_of_prims){
//some relinking has taken place, so read the link numbers again
init();
}
}
}
else if (change & CHANGED_INVENTORY){
init();
}
}


run_time_permissions(integer permissions)
            llListenControl(lm_handle,FALSE);
{
            llSetTimerEvent(0.0);
if(permissions & PERMISSION_TRIGGER_ANIMATION){
            Reset_lm_state();
if(anim){
        }
llStopAnimation("sit");
    }
llStartAnimation(anim);
    run_time_permissions(integer perms)
}
    {
handle = llListen(lm_channel,"","","");
        if( (perms & PERMISSION_TRIGGER_ANIMATION) && (SIT_ANIMATION != "") )
n=0;
        {
while (n<max){//run though the list of emitters, asking for the uuids of the target points
            llStopAnimation("Sit");    //Some poses
request = llList2String(emitters,n);
            llStartAnimation("Stand"); //Work better with these 2 lines
llRegionSayTo(victim,lm_channel,(string)victim+request);//v1 style LM message
            llStartAnimation(SIT_ANIMATION);
n+=2;
        }
}
    }
}
    listen(integer channel,string name,key id,string message)
}
    {
        if(llGetSubString(message,0,35) != sitting_avatar) //this ensures that the message is for us LMV1 or LMV2 wise.
            return;
        if(llGetOwnerKey(id) != sitting_avatar) //reject if the message doesn't come from an attachment owned by the avatar
            return;


listen(integer channel, string name, key id, string message){
        if(llGetSubString(message,-3,-1) == " ok")//lmv1 ' ok' received
        {
            string lmv1_anchor = llGetSubString(message,36,-4);//strip the 'ok' and the 'target key'.
            integer lmv1_index = llListFindList(LM_TARGETS,[lmv1_anchor]);


if(llGetSubString(message,0,35)==(string)victim){// it's for us
            if( (lmv1_index != -1) && (llList2Integer(lm_target_states,lmv1_index) == 0) )  //This is for us (LMV1)
if(llGetSubString(message,-2,-1)=="ok"){//it's an old style v1 LM reply
            {
string s = llGetSubString(message,36,-4);//strip out the victim's key and the " ok" tag
                SetParticles(id, lmv1_anchor);
i = llListFindList(emitters,[s]);
                lm_target_states = llListReplaceList(lm_target_states,[1],lmv1_index,lmv1_index);
                llRegionSayTo(sitting_avatar,LM_CHANNEL,llDumpList2String([sitting_avatar,"LMV2","RequestPoint",lmv1_anchor],"|"));
            }
        }
        else    //lets see if it's an LMV2 message then.
        {
            list commands    = llParseString2List(message,["|"],[]);
            string target    = llList2String(    commands, 0);
            string protocol  = llList2String(    commands, 1); //for us it should be "lmv2" as in "lockmeister version 2"
            string command    = llList2String(    commands, 2); //"request" or "reply"
            string lm_anchor  = llList2String(   commands, 3); //the lm point
            string target_key = llList2String(   commands, 4); //the actual key we should latch to.


if(~i){//check that it's a point on the list
            if( target != sitting_avatar || protocol != "LMV2" || command != "ReplyPoint")
i+=1;//the emitter number is the next item
                return; //Early bailing out if the command isn't formatted properly.
//draw a chain, using the uuid of the sender as the target
llLinkParticleSystem(llList2Integer(emitters,i),particles+[PSYS_SRC_TARGET_KEY,id]);


//now send a v2 style LM message, because if the target attachment is using v2 style messages,
            integer lmv2_index = llListFindList(LM_TARGETS,[lm_anchor]);
// then the chains will be better targetted
            if( (lmv2_index != -1) && (llList2Integer(lm_target_states,lmv2_index) == 1) )  //This is for us (LMV2)
request = (string)victim+LM2_request+s;
            {
llRegionSayTo(victim,lm_channel,request);
                SetParticles(target_key, lm_anchor);
}
                lm_target_states = llListReplaceList(lm_target_states,[2],lmv2_index,lmv2_index);
            }


}
            //this section is not strictly necessary but we can close the listener early
else{//v2 style LM reply
            //if all the anchors are in the lmv2 state.
// is it a v2 style LM reply?
            integer close_listener;
list temp = llParseString2List(message,["|"],[""]);
            integer max = llGetListLength(lm_target_states);
if(llList2String(temp,1)=="LMV2" && llList2String(temp,2)=="ReplyPoint"){    //looks like it is
            while(max--)
temp = llDeleteSubList(temp,0,2);//get rid of the victim's key and the "LMV2", "ReplyPoint" bits
            {
i=llListFindList(emitters,llList2List(temp,0,0));//check it's a point on the list
                if(llList2Integer(lm_target_states,max) == 2)
if(~i){
                    close_listener++;
i+=1;//the emitter number is the next item
            }
//draw a chain, this time using the uuid supplied in the message (cast as a key) as the target
llLinkParticleSystem(llList2Integer(emitters,i),particles+[PSYS_SRC_TARGET_KEY,(key)llList2String(temp,1)]);
}
}
}
}
}


            if(close_listener == llGetListLength(lm_target_states))
            {
                llListenControl(lm_handle,FALSE);
                llSetTimerEvent(0.0);
            }
        }
    }
    timer()
    {
        //Close the listener after a little while.
        llListenControl(lm_handle,FALSE);
        llSetTimerEvent(0.0);
    }
}
}
</lsl>
</syntaxhighlight>
[[Category:LockMeister]]
[[Category:LockMeister]]

Latest revision as of 11:29, 18 January 2023

LockMeister

The LockMeister System is a keyring of commands used for enhanced functions in bondage toys, its main function is to get the keys needed for particle chains, but as the protocol grow more and more commands are added. This protocol is maintained and designed by Kyrah Abattoir, IM her inworld if you have suggestions.

Return to protocol exchange

Features

  • Simple to use
  • Flexible
  • Fully backward compatible
  • Sensorless key grabbing (possible on child prims too) for particle chains (several mooring points)
  • Boot/shoes animation overrider controlling (on/off)
  • Color propagation in a lockmeister item set

Legacy Functions (LMV1)

Basically, all LockMeister items listen on channel -8888
Its strongly recommended to use only the llWhisper function for sending the commands

When someone sits, click (the interaction model is left to the imagination of the scripter) on a LockMeister compliant item, this one will sequentially send the ping messages to each of the mooring points it needs, for example for a St. Andree Cross we will ping the wrist and ankle cuffs, in our example we see we will have to send the messages lcuff,rcuff,llcuff,rlcuff (which are the ones for these points).
When a LockMeister attachment detects the message corresponding to itself in the channel -8888 it will answer by the same message completed by "ok"

'An item will send the message using this format:
message shape: <avatar key><command> example: "748bb591-0d9d-4907-8287-dc27b8267e24lcuff" (we ping the left wrist cuff attachment of the avatar whose key is positioned before the name of the attachment point, or command)
The concerned attachment will answer:
message shape: <avatar key><command>" ok" example: "748bb591-0d9d-4907-8287-dc27b8267e24lcuff ok" (the concerned object answer on the channel -8888)

In order to draw a chain of particles we need the key of the object. When we used the listener event to get the answer message, the listen event returned us the key of the object that answered in it's key id field. This is the key of the speaking prim, which should be the prim we need to attach the chain to.

The New Extension (LMV2)

The LockMeister protocol version 2 (LMV2) is built over the initial protocol (LMV1) and is designed to allow scripters to create furnitures and attachments without needing a slave script in every anchor prims. Linden Labs declared that they wouldn't be supporting any method for sending chat from a root prim and making it come from a child prim ( no "llLinkSay(integer link_number,integer channel,string message )" for example. ), the LMV2 protocol adds an extra set of commands to get around this problem.

'An item will send the message using this format (same as V1):
message shape: <avatar key><command>
example: "748bb591-0d9d-4907-8287-dc27b8267e24lcuff"
(we ping the left wrist cuff attachment of the avatar whose key is positioned before the name of the attachment point, or command)

The concerned attachment will answer (same as V1):
message shape: <avatar key><command>" ok"
example: "748bb591-0d9d-4907-8287-dc27b8267e24lcuff ok" (the concerned object answer on the channel -8888)

We now have in the listen event 'id' parameter a key, we should start using it as a particle target, if the object is only LMV1 it will be the proper target id, but if the object is LMV2, this will most likely be the root prim of the object. now we can use this id to send the lmv2 messages using llRegionSayTo()

LMV2 MESSAGES SHOULD ALWAYS USE llRegionSayTo() !!

'The item can now send an LMV2 message using this format:
message shape: <avatar key>|LMV2|RequestPoint|<anchor point>
example: "748bb591-0d9d-4907-8287-dc27b8267e24|LMV2|RequestPoint|lcuff"

The concerned attachment will answer (if it is LMV2 compatible):
message shape: <avatar key>|LMV2|ReplyPoint|<anchor point>|<anchor point key>
example: "748bb591-0d9d-4907-8287-dc27b8267e24|LMV2|ReplyPoint|lcuff|636bbccc-0d9d-4907-8287-dc27b8267e24"

If the target object doesn't support LMV2, we won't receive any answer, so it's important to start drawing the particles before the LMV2 reply comes. If the reply DOES arrive, we can grab the new target key and synchronise our particle system.

Complete List of Mooring Points

# name Description
1 rcuff right wrist cuff
2 rbiceps right upperarm cuff
3 lbiceps left upper arm cuff
4 lcuff left wrist cuff
5 lblade left shoulder blade
6 rblade right shoulder blade
7 rnipple right nipple point
8 lnipple left nipple point
9 rfbelt right front of the belt
10 lfbelt left front of the belt
11 rtigh right upper leg cuff
ritigh right upper leg cuff (inner thigh)
12 ltigh left upper leg cuff
litigh left upper leg cuff (inner thigh)
13 rlcuff right ankle cuff
14 llcuff left ankle cuff
15 lbbelt left back of the belt
16 rbbelt right back of the belt
17 pelvis lower front of the pelvis
18 fbelt front of the belt
19 bbelt back of the belt
20 rcollar right of the collar
21 lcollar left of the collar
22 thead top of the head
23 collar front of the collar
24 lbit left corner of mouth/cheek (for pony bits)
25 rbit right corner of mouth/cheek (for pony bits)
26 nose nose
27 bcollar back of the collar
28 back middle of the back
29 lhand left hand (e.g. for mittens)
30 rhand right hand (e.g. for mittens)
31 rbelt right side of the belt
32 lbelt left side of the belt
33 chest center of the chest, on the solar plexus (not currently in the picture)
Lockmeister-mooring-pts.jpg

NOTE: ltigh/rtigh were typos introduced during LM1 so for compatibility reasons we are keeping them as is.

Others

  • handle - type "leash handle" , this is useful for leashing to a leash handle.
  • tether - type "tether point" , this is useful for leashing to some fixed anchor.

Special Commands

<avatar key>col<vector> - order a color change (used in LockMeister collars to tint the cuffs like the collar) OPTIONAL
ex: "748bb591-0d9d-4907-8287-dc27b8267e24col<1.0,1.0,1.0>" will say to an item able to understand it (color yourself in white)

<avatar key>booton/bootoff = commands sent by collar objects, asking that the shoes animation overrider be started or stopped
ex: "748bb591-0d9d-4907-8287-dc27b8267e24booton" activate the animation overrider
ex: "748bb591-0d9d-4907-8287-dc27b8267e24bootoff" deactivate the animation overrider

Amethyst extension commands (LMV1)

The following commands are not part of the main lockmeister protocol, but additions Amethyst Rosencrans made in her sold and free cuff scripts. They are here to document the extensions so others can utilize them.

<avatar key>target|point1|point2 = Connect a chain from mooring point 1 to mooring point 2 ex: "748bb591-0d9d-4907-8287-dc27b8267e24target|lcuff|rcuff" will create a chain from the left cuff to the right cuff

<avatar key>target|point = Remove a chain from mooring point ex: "748bb591-0d9d-4907-8287-dc27b8267e24target|lcuff" will remove a chain from the left cuff

<avatar key>texture|<texture key> = Change the texture of the chains used by the target command ex: "748bb591-0d9d-4907-8287-dc27b8267e24texture|1ffb37fa-2fc1-dbec-d8ea-0607583a03c6" will change the chain texture

<avatar key>gravity|<downward acceleration float> = Change the rate at which the chain particles move down ex: "748bb591-0d9d-4907-8287-dc27b8267e24gravity|0.5" will change the chain texture to move down half a meter a second

<avatar key>age|<age float> = Change how fast the chain particles move from point 1 to point 2 ex: "748bb591-0d9d-4907-8287-dc27b8267e24age|2.0" will change the chain texture to move the distance in 2 seconds

Cool Products extension commands (LMV1)

The following commands are not part of the main lockmeister protocol, but additions Henri Beauchamp made for use by his Cool Products (such as the Cool Collar). They are here to document the extensions so others can utilize them.

<avatar key>point here = Signals that anchoring point "point" just appeared. ex: "748bb591-0d9d-4907-8287-dc27b8267e24handle here" could be emitted by the on_rez event of a leash handle to signal the collars connected to the owner of the leash handle that they should connect to it instead of staying connected to the center of the avatar.

<avatar key>point gone = Signals that anchoring point "point" just disappeared. ex: "748bb591-0d9d-4907-8287-dc27b8267e24handle gone" could be emitted by the a leash handle when it is detached to signal the collars connected to it that they should unleash.

(unknown) extension commands (LMV1)

<toucher key>post ok = Signals that <toucher key> has touched an object. The toy will then issue an "<object key>handle" command to the object, which will return "<object key>handle ok" to activate the leash.

Note about message sending

  • It is recommended to send the LMV1 "ping" messages through llWhisper so only close proximity objects will receive them.
  • Now that llRegionSayTo() is available, it is strongly recommended to send "pong" messages through llRegionSayTo(), because of this, it is NOT recommended to have objects rely on "message snooping", because they won't be able to see message replies with llRegionSayTo().
  • LMV2 Messages should ALWAYS be send using llRegionSayTo().

Example Scripts

Example Attachment Script

//KDC sample LockMeister attachment version 1.1a
//
//NOTE: This script must be in the root prim of the object for working properly
//      Please ensure that you named the anchor prim properly too, this is the prim the script will look for
//      when sending LMV2 replies.
//      ...
//      in a general way this script is here as an explanation to the LockMeister System so ... WRITE YOUR OWN CODE!!
//
//KDC, kyrah design concept

string mooring_point = "lcuff";
//write here the mooring point you want to use

string anchor_name   = "";
//write here the name of the child prim that will act as the anchor point, leave blank to use the root.

default
{
    attach(key id)
    {
        if(id != NULL_KEY)
            llResetScript();
    }
    state_entry()
    {
        llListen(-8888,"","","");//open the lockmeister channel
    }
    listen(integer channel,string name,key id,string message)
    {
        //This will ensure that we only answer to LockMeister messages when we are attached to an avatar.
        if( !llGetAttached() )
            return;

        if (message == (string)llGetOwner()+ mooring_point)
        {   //This part reply to Lockmeister v1 messages
            //message structure:   llGetOwner()+mooring_point ( without the '+' )

            llWhisper(-8888, (string)llGetOwner()+ mooring_point+" ok");//answering it
            //message structure:   llGetOwner()+mooring_point+" ok" ( without the '+' )
        }

        //we parse the message into a list and recover each element.
        list params = llParseString2List( message, ["|"], [] );

        //NOTE: You can check all of these in a single statement, this is just for the sake of clarity.
        if(llList2String(params,0) != (string)llGetOwner())
            return;
        if(llList2String(params,1) != "LMV2")
            return;
        if(llList2String(params,2) != "RequestPoint")
            return;
        if(llList2String(params,3) != mooring_point)
            return;
        
        //this message is for us, it's claiming to be an LMV2 message, it's a "Request" message, and concerns the mooring_point we specified
        //message structure:   llGetOwner()|LMV2|RequestPoint|anchor_name

        //Now that we are certain that the message concerns us, we look for the prim key to insert in our reply.

        if(anchor_name   == "")
        {
            //If there is no anchor set, we assume root prim.
            llRegionSayTo( id, -8888, llDumpList2String( [llGetOwner(), "LMV2", "ReplyPoint", mooring_point, llGetKey()], "|" ) );
        }
        else
        {
            //Otherwise, we loop through the link set looking for a match.
            integer i;
            for( i = 1; i <= llGetNumberOfPrims(); i++)
            {
                if( llGetLinkName(i) == anchor_name )
                {   //If this is our anchor prim, we reply
                    //pattern sent:   llGetOwner()|LMV2|ReplyPoint|anchor_name|anchor_key
                    llRegionSayTo( id, -8888, llDumpList2String( [llGetOwner(), "LMV2", "ReplyPoint", mooring_point, llGetLinkKey(i)], "|" ) );
                    return;
                }
            }
        }
    }
}

Basic Chaining Script

//////////////////////////////////////////////////////////////////////////
//KDC basic LockMeister chaining script
//http://wiki.secondlife.com/wiki/LSL_Protocol/LockMeister_System
//
// This is a 'minimal' example to demonstrate locating and chaining to a
// specific LockMeister target point.
//////////////////////////////////////////////////////////////////////////

integer LM;
integer LM_CHANNEL = -8888;
string  LM_TARGET = "collar";

key user_id = NULL_KEY;
integer target_found;

vector RED    = <1,0,0>;
vector ORANGE = <1,.5,0>;
vector YELLOW = <1,1,0>;
vector GREEN  = <0,1,0>;

DrawChain(key id) //Creates/Updates the particle chain
{
    llParticleSystem(
        [
            PSYS_SRC_PATTERN,PSYS_SRC_PATTERN_DROP,
            PSYS_SRC_TARGET_KEY,id,
            PSYS_PART_START_SCALE,<0.05,1,0>,
            PSYS_PART_START_COLOR,<0,0,0>,
            PSYS_SRC_TEXTURE,"5748decc-f629-461c-9a36-a35a221fe21f",
            PSYS_PART_MAX_AGE,2.0,
            PSYS_SRC_BURST_RATE,0.01,
            PSYS_PART_FLAGS, PSYS_PART_FOLLOW_SRC_MASK | PSYS_PART_TARGET_POS_MASK | PSYS_PART_RIBBON_MASK,
            PSYS_SRC_ACCEL,<0,0,-1>
        ]);
}

default
{
    state_entry()
    {
        LM = llListen(LM_CHANNEL,"","",""); //Creates the LockMeister listener
        llListenControl(LM,FALSE);

        llSetColor(RED,ALL_SIDES);
    }
    touch_start(integer num_touches)
    {
        key clicker_id = llDetectedKey(0);

        if(user_id)
        {   //Remove the chain, user and disable the listener.
            llParticleSystem([]);
            user_id = NULL_KEY;
            target_found = FALSE;
            llListenControl(LM,FALSE);

            llSetColor(RED,ALL_SIDES); //RED: No chain is being drawn.
        }
        else
        {   //Register user, enable listener and send a LMV1 'ping'.
            user_id = clicker_id;
            llListenControl(LM,TRUE);
            llRegionSayTo(user_id, LM_CHANNEL, (string)user_id+LM_TARGET);

            llSetColor(ORANGE,ALL_SIDES); //ORANGE: Waiting for LMV1 reply.
        }
    }
    listen(integer channel,string name, key id, string message)
    {
    	llOwnerSay(llList2CSV([channel,name,id,message]));

        if(llGetOwnerKey(id) != user_id) return; //Wrong object owner? no processing.

        if(message == ((string)user_id+LM_TARGET+" ok")) //It's a LMV1 message and it is for the lm point we are looking for!
        {
            target_found = TRUE;
            DrawChain(id);
            llRegionSayTo(id,LM_CHANNEL,(string)user_id+"|LMV2|RequestPoint|"+LM_TARGET); //Lets try to obtain a more precise chain target (USERID|LMV2|RequestPoint|LMCODE).

            llSetColor(YELLOW,ALL_SIDES); //YELLOW: LMV1 OK, Waiting for a (possible) LMV2 reply.
            return;
        }
        else if(!target_found) return; //if we haven't received a LMV1 message first, there is no need to try to get an LMV2 message. 

        //LMV2 Message checking.
        list data = llParseString2List(message,["|"],[""]);
        if( llList2Key(data,0)   != user_id )       return; //data,0 is the avatar uuid, it has to match our user.
        if(llList2String(data,1) != "LMV2")         return; //data,1 is the protocal name: has to be LMV2
        if(llList2String(data,2) != "ReplyPoint")   return; //data,2 is the command: has to be 'ReplyPoint'
        if(llList2String(data,3) != LM_TARGET)      return; //data,3 is the lm target: it has to match the lm target we are looking for.

        DrawChain(llList2String(data,4));    //Update the particle chain to its real target (data,4)
        llListenControl(LM,FALSE);  //We don't need to listen for commands anymore.

        llSetColor(GREEN,ALL_SIDES); //GREEN: LMV2 OK, we are done.
    }
}

Advanced Furniture Script

//////////////////////////////////////////////////////////////////////////
//Copyright (c) 2017 KDC: Kyrah Design Concept
//Lockmeister sample furniture code
//http://wiki.secondlife.com/wiki/LSL_Protocol/LockMeister_System
//
//This is free software: you can redistribute it and/or modify
//it under the terms of the GNU Affero General Public License as
//published by the Free Software Foundation, either version 3 of the
//License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU Affero General Public License for more details.
//
//You should have received a copy of the GNU Affero General Public License
//along with this program. If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////////////


//Configuration
list    LM_TARGETS      = ["lcuff","rcuff"];    //the lm targets you are looking to bind to (the target child prim will have to use the same name).
key     SIT_ANIMATION   = "";                   //put your sit animation name here.
vector  SIT_OFFSET      = <0,0,0.1>;            //change the sitting offset, please note that it cannot be 0,0,0 or sl disable sit targets.
vector  SIT_ROTATION    = <0,0,0>;              //the rotation of the avatar, in degrees, for your convenience.
key     CHAIN_TEXTURE   = TEXTURE_BLANK;        //the chain texture.

integer LM_CHANNEL = -8888;
list    lm_target_states;
integer lm_handle;
key sitting_avatar;

Reset_lm_state()
{
	lm_target_states = [];
	integer max = llGetListLength(LM_TARGETS);
	while(max--)
	{
	    lm_target_states += 0;
	    SetParticles(NULL_KEY,llList2String(LM_TARGETS,max));
	}
}

SetParticles(key target, string lm_code)
{
	integer max = llGetNumberOfPrims() + 1;
	while(max--)
	{
        if(llGetLinkName(max) == lm_code)
        {
            if(target == NULL_KEY)
                llLinkParticleSystem(max,[]);
            else
            {
                llLinkParticleSystem(max,[
                                            PSYS_PART_FLAGS,PSYS_PART_FOLLOW_SRC_MASK|PSYS_PART_FOLLOW_VELOCITY_MASK|PSYS_PART_TARGET_POS_MASK,
                                            PSYS_SRC_PATTERN,PSYS_SRC_PATTERN_DROP,
                                            PSYS_PART_START_SCALE, < 0.05, 0.05, 0.0 >,
                                            PSYS_PART_MAX_AGE, 3.0,
                                            PSYS_SRC_BURST_RATE, 0.001,
                                            PSYS_SRC_TEXTURE, CHAIN_TEXTURE,
                                            PSYS_SRC_BURST_SPEED_MIN,0.1,
                                            PSYS_SRC_BURST_SPEED_MAX,0.1,
                                            PSYS_SRC_TARGET_KEY, target,
                                            PSYS_SRC_BURST_PART_COUNT, 1,
                                            PSYS_SRC_ACCEL, < 0, 0, -0.01 >
                                            ]);
            }
        }
    }
}
default
{
    state_entry()
    {
        Reset_lm_state();
        llSitTarget(SIT_OFFSET,llEuler2Rot(SIT_ROTATION*DEG_TO_RAD));

        lm_handle = llListen(LM_CHANNEL,"","","");
        llListenControl(lm_handle,FALSE);
    }
    changed(integer change)
    {
        if(!(change & CHANGED_LINK))
            return;
        if(sitting_avatar == llAvatarOnSitTarget())
            return;

        sitting_avatar = llAvatarOnSitTarget();
        if(sitting_avatar != NULL_KEY)
        {
            if(SIT_ANIMATION != "")   //If we have an animation, request animation permission.
                llRequestPermissions(sitting_avatar,PERMISSION_TRIGGER_ANIMATION);

            llListenControl(lm_handle,TRUE);    //open listener.
            llSetTimerEvent(10.0);

            integer max = llGetListLength(LM_TARGETS);

            while(max--)            //call all the anchors, LMV1 style.
                llRegionSayTo(sitting_avatar,LM_CHANNEL,(string)sitting_avatar + llList2String(LM_TARGETS,max) );
        }
        else
        {
            if(llGetPermissions()) //If we have been granted permissions, stop animating.
                llStopAnimation(SIT_ANIMATION);

            llListenControl(lm_handle,FALSE);
            llSetTimerEvent(0.0);
            Reset_lm_state();
        }
    }
    run_time_permissions(integer perms)
    {
        if( (perms & PERMISSION_TRIGGER_ANIMATION) && (SIT_ANIMATION != "") )
        {
            llStopAnimation("Sit");     //Some poses
            llStartAnimation("Stand");  //Work better with these 2 lines
            llStartAnimation(SIT_ANIMATION);
        }
    }
    listen(integer channel,string name,key id,string message)
    {
        if(llGetSubString(message,0,35) != sitting_avatar) //this ensures that the message is for us LMV1 or LMV2 wise.
            return;
        if(llGetOwnerKey(id) != sitting_avatar) //reject if the message doesn't come from an attachment owned by the avatar
            return;

        if(llGetSubString(message,-3,-1) == " ok")//lmv1 ' ok' received
        {
            string  lmv1_anchor = llGetSubString(message,36,-4);//strip the 'ok' and the 'target key'.
            integer lmv1_index = llListFindList(LM_TARGETS,[lmv1_anchor]);

            if( (lmv1_index != -1) && (llList2Integer(lm_target_states,lmv1_index) == 0) )  //This is for us (LMV1)
            {
                SetParticles(id, lmv1_anchor);
                lm_target_states = llListReplaceList(lm_target_states,[1],lmv1_index,lmv1_index);
                llRegionSayTo(sitting_avatar,LM_CHANNEL,llDumpList2String([sitting_avatar,"LMV2","RequestPoint",lmv1_anchor],"|"));
            }
        }
        else    //lets see if it's an LMV2 message then.
        {
            list commands     = llParseString2List(message,["|"],[]);
            string target     = llList2String(    commands, 0);
            string protocol   = llList2String(    commands, 1); //for us it should be "lmv2" as in "lockmeister version 2"
            string command    = llList2String(    commands, 2); //"request" or "reply"
            string lm_anchor  = llList2String(    commands, 3); //the lm point 
            string target_key = llList2String(    commands, 4); //the actual key we should latch to.

            if( target != sitting_avatar || protocol != "LMV2" || command != "ReplyPoint")
                return; //Early bailing out if the command isn't formatted properly.

            integer lmv2_index = llListFindList(LM_TARGETS,[lm_anchor]);
            if( (lmv2_index != -1) && (llList2Integer(lm_target_states,lmv2_index) == 1) )  //This is for us (LMV2)
            {
                SetParticles(target_key, lm_anchor);
                lm_target_states = llListReplaceList(lm_target_states,[2],lmv2_index,lmv2_index);
            }

            //this section is not strictly necessary but we can close the listener early
            //if all the anchors are in the lmv2 state.
            integer close_listener;
            integer max = llGetListLength(lm_target_states);
            while(max--)
            {
                if(llList2Integer(lm_target_states,max) == 2)
                    close_listener++;
            }

            if(close_listener == llGetListLength(lm_target_states))
            {
                llListenControl(lm_handle,FALSE);
                llSetTimerEvent(0.0);
            }
        }
    }
    timer()
    {
        //Close the listener after a little while.
        llListenControl(lm_handle,FALSE);
        llSetTimerEvent(0.0);
    }
}