Combined LockMeister Cuff script

From Second Life Wiki
Jump to: navigation, search

LockMeister+ Cuffs

Overview

The LockMeister system protocol for cuffs, collars and leash handles has been around from many years and in that time a number of extensions to the protocol have been added and have been documented.

LockMeister+ is an effort to bring together those extensions and define a set of expected behaviours to allow greater interoperability and functionality between devices that use the extended protocol. The aims of LockMeister+ are:

  • To gather up the existing extensions into a new extended protocol.
  • To add functionality that can be gained from combining those extensions.
  • To define the expected behaviour for a number if classes of devices.
  • To be 100% backwards compatible with what has gone before.

The LockMeister+ Cuff reference script

The LockMeister+ cuff:

  • MUST implement the basic LockMeister query/response.
  • MUST implement the Sensations extensions to allow particle chains between cuffs ("target", "texture", "age", "gravity" commands).
  • MUST implement the new "reset" command for stopping particle chains and setting their parameters back to the default for the device.
  • MUST implement the "here" and "gone" extensions.
  • MAY implement the "col" command if needed (the reference script does not).

In addition the LockMeister+ cuff's behaviour:

  • MUST monitor "here" messages in order to connect to a target should it appear or move the particle chain to a more preferable target should it become available.
  • MUST monitor the "gone" event to remove a particle chain should the target disappear.
  • MUST send out new target requests should the current target disappear.
  • SHOULD reconnect all particle chains when the AV logs on.

Implementation Notes

This script will only run correctly in the ROOT PRIM of and object. This is because it uses an "attach" event which are not correctly handled when is child prims. On detach the child prim's "attach" event is usually not called and instead is executed when the object is reattached. To this end the "gone" command is allowed to come from a prim that is not the target prim and so should be handled by LockMeister name rather than UUID. This allows the root prim of an object to handle "attach" events on behalf of child target prims.


///////////////////////////////////////////////////////////////////////
//
// Comprehensive Lockmeister Cuff Script
// (LockMeister+ reference script)
//
// Tianna Violet
// 20 Jan 2010
//
// This is a Lockmeister cuff script that combines many of the extensions
// to the lockmeister protocol at defined here:
// http://wiki.secondlife.com/wiki/LSL_Protocol/LockMeister_System
// To the basic query response is added Amethyst Rosencrans' Sensations
// extensions for allowing particle links between cuffs and
// Henri Beauchamp's "here" and "gone" extensions which notify when a cuff
// is attached and detached.  Combining these allows a few extra features:
// * Cuffs can delete or reassign particle chains if their target object
// disappears.
// * Cuffs can attach if the target point appears.
// * Cuffs can reattach when an AV logs in.
//
// In addition is added a new command, "reset" defined as stopping the
// particle chain and resetting the Age, Gravity, and Texture back to
// their defaults.
//
// Although this script is new, reference was made to Amethyst Rosencrans'
// "Free Amethyst cuff script v2" (which is a little out of date) and
// from spying on the Lockmeister messages coming from the Amethyst cuffs
// set.
//
// Note that this script will only work in the ROOT PRIM ONLY as it
// uses an "attach" event which aren't handled properly in child prims.
//
// Note that the "gone" message is allowed to come from a prim other
// than the target prim to allow an object root prim to handle "attach"
// events on behalf of child targets.
///////////////////////////////////////////////////////////////////////
// The lockmeister name of the cuff.  See the above link for details
string LOCKMEISTER_NAME = "lcuff";
///////////////////////////////////////////////////////////////////////
string gCuffMessage;
string gRequestedTargets;
string gWorkingTargets;
key gCurrentTarget;
string gCurrentTargetName;
float gAge;
float gGravity;
string gTexture;
 
// Start or update the particle stream
updateParticles()
{
    llParticleSystem( [PSYS_PART_START_SCALE, <0.1, 0.1, 0>, PSYS_PART_END_SCALE, <1, 1, 0>,
    PSYS_PART_START_COLOR, <1, 1, 1>, PSYS_PART_END_COLOR, <1, 1, 1>,
    PSYS_PART_START_ALPHA, 1.0, PSYS_PART_END_ALPHA, 1.0, PSYS_SRC_TEXTURE, gTexture,
    PSYS_SRC_BURST_PART_COUNT, 1, PSYS_SRC_BURST_RATE, 0.0, PSYS_PART_MAX_AGE, gAge,
    PSYS_SRC_MAX_AGE, 0.0, PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_DROP, PSYS_SRC_BURST_RADIUS, 0.5,
    PSYS_SRC_INNERANGLE, 0.0, PSYS_SRC_OUTERANGLE, 0.0, PSYS_SRC_OMEGA, <0, 0, 0>,
    PSYS_SRC_ACCEL, <0, 0, -gGravity>, PSYS_SRC_BURST_SPEED_MIN, 1000.0,
    PSYS_SRC_BURST_SPEED_MAX, 1000.0, PSYS_SRC_TARGET_KEY, gCurrentTarget,
    PSYS_PART_FLAGS, PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_FOLLOW_SRC_MASK | PSYS_PART_TARGET_POS_MASK]);
}
 
// ping the cuffs that can be used as possible targets, if any
pingCuffs()
{
    list cuffs;
    integer a;
    integer len;
 
    gWorkingTargets = gRequestedTargets;
    if (gWorkingTargets != "")
    {
        cuffs = llParseString2List(gWorkingTargets, ["-"], []);
        len = llGetListLength(cuffs);
        for (a = 0; a < len; a++)
        {
            llWhisper(-8888, (string) llGetOwner() + llList2String(cuffs, a));
        }
    }
}
 
default
{
    state_entry()
    {
        gCuffMessage = (string) llGetOwner() + LOCKMEISTER_NAME;
        gRequestedTargets = "";
        gWorkingTargets = "";
        gCurrentTarget = NULL_KEY;
        gCurrentTargetName = "";
        gAge = 2;
        gGravity = 0.7;
        gTexture = "1ffb37fa-2fc1-dbec-d8ea-0607583a03c6";
        llListen(-8888, "", NULL_KEY, "");        
    }
 
    on_rez(integer start_param)
    {
        llParticleSystem([]);
        gCuffMessage = (string) llGetOwner() + LOCKMEISTER_NAME;
    }
 
    attach(key id)
    {
        string message;
 
        message = " gone";
        if (id)
        {
            gCuffMessage = (string) llGetOwner() + LOCKMEISTER_NAME;
            message = " here";
            pingCuffs();
        }
        llSay(-8888, gCuffMessage + message);    // send the "here" and "gone" message
    }
 
    listen(integer channel, string name, key id, string message)
    {
        list commands = ["gravity", "age", "texture", "target", "reset"];
        list responses = ["ok", "here", "gone"];
        list data;
        list cuffs;
        integer index;
        integer i;
        integer atThis;
        float tempf;
        string temps;
 
        if (channel == -8888)
        {
            if (message == gCuffMessage)    // quick early out for the basic response
            {
                llSay(-8888, gCuffMessage + " ok");
                return;
            }
            if (((key) llGetSubString(message, 0, 35)) == llGetOwner())
            {
                data = llParseStringKeepNulls(llGetSubString(message, 36, -1), ["|"], []);
                index = llListFindList(commands, llList2List(data, 0, 0));
                if (~index)
                {
                    if (index < 3)    // the 3 particle params that can change
                    {
                        if (index < 2)
                        {
                            tempf = (float) llList2String(data, 1);
                            if (tempf)
                            {
                                if (index)    // "age"
                                {
                                    gAge = tempf;
                                } else {        // "gravity"
                                    gGravity = tempf;
                                }
                            }
                        } else {    // "texture"
                            temps = llList2String(data, 1);
                            if (((key) temps) != NULL_KEY)
                            {
                                gTexture = temps;
                            }
                        }
                        if (gCurrentTarget)
                        {
                            updateParticles();
                        }
                    }
                    if (index == 3)    // "target"
                    {
                        temps = "";
                        atThis = TRUE;
                        if (llGetListLength(data) > 1)
                        {
                            i = llListFindList(llList2ListStrided(llList2List(data, 1, -1), 0, -1, 2), [LOCKMEISTER_NAME]);
                            if (~i)
                            {
                                temps = llList2String(data, (i + 1) << 1);
                            } else {
                                atThis = FALSE;
                            }
                        }
                        if (atThis)
                        {
                            gCurrentTarget = NULL_KEY;
                            gCurrentTargetName = "";
                            gRequestedTargets = temps;
                            pingCuffs();
                            llParticleSystem([]);
                        }
                    }
                    if (index == 4) // "reset"
                    {
                        llParticleSystem([]);
                        llResetScript();
                    }
                } else { 
                    data = llParseString2List(llList2String(data, 0), [" "], []);
                    index = llListFindList(responses, llList2List(data, 1, 1));
                    if (~index) // "ok", "here" and "gone" messages from others
                    {
                        temps = llList2String(data, 0);
                        if (index < 2) // "ok" and "here" treated the same
                        {
                            if (gWorkingTargets != "")
                            {
                                cuffs = llParseString2List(gWorkingTargets, ["-"], []);
                                i = llListFindList(cuffs, [temps]);
                                if (~i)
                                {
                                    gWorkingTargets = llDumpList2String(llList2List(cuffs, i + 1, -1), "-");
                                    gCurrentTarget = id;
                                    gCurrentTargetName = temps;
                                    updateParticles();
                                }
                            }
                        } else { // "gone"
                            if (gCurrentTargetName == temps) // test on name as the "gone" may not come
                            {                                // from out target prim
                                gCurrentTarget = NULL_KEY;
                                gCurrentTargetName = "";
                                llParticleSystem([]);
                                pingCuffs();
                            }
                        }
                    }
                }
            }
        }
    }
}