Difference between revisions of "MLPV2 LockGuard plugin"

From Second Life Wiki
Jump to navigation Jump to search
(<lsl> tag to <source>)
 
(5 intermediate revisions by 3 users not shown)
Line 2: Line 2:
* [[MLPV2]]
* [[MLPV2]]


Basically this is an alternate way of adding particle chain support to MLP2 using the LockGuard protocol.  When the object is rezzed in world the script makes a list of the prim names and keys for later.  It then reads the note card with the linking information each time inventory changes.  When MLP2 gives pose info the script determines if it pertains to it's list and if so notifies lockguard enabled attachments of what to do and what prim keys to do it to.
Basically this is an alternative way of adding particle chain support to MLP2 using the LockGuard protocol.  When the object is rezzed in world the script makes a list of the prim names and keys for later.  It then reads the note card with the linking information each time inventory changes.  When MLP2 gives pose info the script determines if it pertains to it's list and if so notifies lockguard enabled attachments of what to do and what prim keys to do it to.
 
Ed: Note that this script requires a build which has linked, at the point when the script starts up, the prim points (e.g. posts, etc) to which the chains from the person's cuffs etc will attach themselves.


So what do you need to do?
So what do you need to do?
Line 12: Line 14:
Add the following script to your furniture.  It may complain, but you can ignore that for now.
Add the following script to your furniture.  It may complain, but you can ignore that for now.


<lsl>
<source lang="lsl2">
// ----------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------
// MLPV2 Plugin for LockGuard Partical Chaining v1.2
// MLPV2 Plugin for LockGuard Particle Chaining v1.3
// ----------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------


Line 21: Line 23:
// ----------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------
// Notecard .CHAINDATA format:
// Notecard .CHAINDATA format:
// menulable | ballnumber | anchorprimname | LGattachpoint optionalparams
// menulabel | ballnumber | anchorprimname | LGattachpoint optionalparams
//
//
// Example data:
// Example data:
Line 62: Line 64:
// Changes:
// Changes:
// ----------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------
// Version 1.3
// - Fixed an error that was causing multiple avatar chain sets from working
// - Fixed chainData not properly clearing on a reload
// - Changed the order of operations in the changed state for better tracking and simplified
//
// Version 1.2
// Version 1.2
// - Large overhaul of how the data on the cards is stored.  Original format was made
// - Large overhaul of how the data on the cards is stored.  Original format was made
Line 67: Line 74:
//  swap function in a chained context, so why complicate the notecard files?  Plus
//  swap function in a chained context, so why complicate the notecard files?  Plus
//  new format is more memory friendly.
//  new format is more memory friendly.
// - Notecard seperator changed from ; to | to match favorite RLV plugin format
// - Note card separator changed from ; to | to match favorite RLV plugin format
// - Changed the need for the animation name to just the ball number in the notecard
// - Changed the need for the animation name to just the ball number in the note card
// - Removed some unused variables
// - Removed some unused variables
// - Note card renamed to .CHAINDATA to follow MLP2 naming conventions
// - Note card renamed to .CHAINDATA to follow MLP2 naming conventions
Line 100: Line 107:


string  Pose;                  // Pose name
string  Pose;                  // Pose name
//string  Avname;                // Av Name
key    Avkey;                  // Avatar Key
key    Avkey;                  // Avatar Key


string CurrentSet = "stand";    // stand being the menu default
string CurrentSet = "stand";    // stand being the menu default


list chainData = [];            // The list of all data from the notecard [lable,ballnum,attachpoint,params]
list chainData = [];            // The list of all data from the notecard [label,ballnum,attachpoint,params]
integer chainCount = 0;        // How many entries in the list
integer chainCount = 0;        // How many entries in the list
integer chainStride = 4;        // The stride count for the list
integer chainStride = 4;        // The stride count for the list


list primKeys = [];            // The list of prims in the object and thier keys [primname,key]
list primKeys = [];            // The list of prims in the object and their keys [primname,key]
integer primKeysCount = 0;      // How many entries in the list
integer primKeysCount = 0;      // How many entries in the list
integer primKeysStride = 2;    // The stride count for the list
integer primKeysStride = 2;    // The stride count for the list
Line 135: Line 141:
     ConfigCardIndex++;
     ConfigCardIndex++;
     ConfigQueryId = llGetNotecardLine(ConfigCardName, ConfigLineIndex);
     ConfigQueryId = llGetNotecardLine(ConfigCardName, ConfigLineIndex);
     debug("[?] Reading file: " + ConfigCardName);
     debug("====================\n[?] Reading file: " + ConfigCardName);
     return (TRUE);
     return (TRUE);
}
}


// add chain data to the list...
// add chain data to the list...
add_cd(string lable, string ballnum, string target, string params) {
add_cd(string label, string ballnum, string target, string params) {
     chainData = (chainData=[]) + chainData + (list)lable;
     chainData = (chainData=[]) + chainData + (list)label;
     chainData = (chainData=[]) + chainData + (list)ballnum;
     chainData = (chainData=[]) + chainData + (list)ballnum;
     chainData = (chainData=[]) + chainData + (list)target;
     chainData = (chainData=[]) + chainData + (list)target;
Line 207: Line 213:
state load  {
state load  {
     state_entry() {
     state_entry() {
        chainData = []; // reset chainData in case it's a soft reload of info
        avData = []; // reset avData for soft reload.  PLEASE do not be sitting while editing chain data.     
         string item;
         string item;
         ConfigCards = [];
         ConfigCards = [];
Line 234: Line 242:
         if (llGetSubString(data,0,0) != "/" && llStringLength(data)) {              // Skip comments and blank lines
         if (llGetSubString(data,0,0) != "/" && llStringLength(data)) {              // Skip comments and blank lines
             list ldata = llParseStringKeepNulls(data, ["|"], []);                  // Get the whole line for processing...
             list ldata = llParseStringKeepNulls(data, ["|"], []);                  // Get the whole line for processing...
             string lable = llStringTrim(llList2String(ldata, 0), STRING_TRIM);      //  Extract lable
             string label = llStringTrim(llList2String(ldata, 0), STRING_TRIM);      //  Extract label
             string ballnum = llStringTrim(llList2String(ldata, 1), STRING_TRIM);    //  Extract ball number
             string ballnum = llStringTrim(llList2String(ldata, 1), STRING_TRIM);    //  Extract ball number
             string target = llStringTrim(llList2String(ldata, 2), STRING_TRIM);    //  Extract target point
             string target = llStringTrim(llList2String(ldata, 2), STRING_TRIM);    //  Extract target point
             string param = llStringTrim(llList2String(ldata, 3), STRING_TRIM);      //  Extract params
             string param = llStringTrim(llList2String(ldata, 3), STRING_TRIM);      //  Extract params
             add_cd(lable,ballnum,target,param);                                    // Add all that to chainData
             add_cd(label,ballnum,target,param);                                    // Add all that to chainData
         }
         }
          
          
         ++ConfigLineIndex;                                                  // Incrament counter
         ++ConfigLineIndex;                                                  // Increment counter
         ConfigQueryId = llGetNotecardLine(ConfigCardName, ConfigLineIndex); // Read next line of data notecard...
         ConfigQueryId = llGetNotecardLine(ConfigCardName, ConfigLineIndex); // Read next line of data notecard...
     }
     }
Line 248: Line 256:


state on {
state on {
     state_entry()  {   }
     state_entry()  {
            debug("[!] Ready!\n====================");
        }
     on_rez(integer arg) { llResetScript(); }        // Object was rezed in world, reset script for freshness
     on_rez(integer arg) { llResetScript(); }        // Object was rezed in world, reset script for freshness
     changed(integer change) {
     changed(integer change) {
Line 263: Line 273:
             return;                    // Nothing more for us, so return
             return;                    // Nothing more for us, so return
         }
         }
        debug("========== START");


         list linedata = llParseStringKeepNulls(message,["|"],[]);  // It's a generic sit/unsit/change update, so break it down
         list linedata = llParseStringKeepNulls(message,["|"],[]);  // It's a generic sit/unsit/change update, so break it down
Line 271: Line 280:
         integer findSet = llListFindList(chainData, (list)CurrentSet);  // Check chainData for this set name...         
         integer findSet = llListFindList(chainData, (list)CurrentSet);  // Check chainData for this set name...         
         integer findAv = llListFindList(avData, [id]);                  // Check avData for the person sitting...
         integer findAv = llListFindList(avData, [id]);                  // Check avData for the person sitting...
       
        debug("==========\n= SET: " + CurrentSet + "  BALL: " + param1 + "  ANIMATION: " + param2);


         if (findSet == -1) {                                            // There is no chain data for this set, so...
         if (findSet == -1) {                                            // There is no chain data for this set, so...
Line 286: Line 297:
         } else debug("[!] Chain data found");
         } else debug("[!] Chain data found");


        if (llList2String(chainData,findSet + 1) != param1) {  return;  }    // We have data for this set, but not this ball.  Returning.


         list chainLog = [];                                                    // set up the list of commands [attachpoint, params]
         list chainLog = [];                                                    // set up the list of commands [attachpoint, params]
         integer ii;                                                            // initialize counter var
         integer ii;                                                            // initialize counter var
         for(ii = 0; ii < llGetListLength(chainData); ii = ii + chainStride) {  // Run through the chainData list... [lable,ballnum,attachpoint,params]
         for(ii = 0; ii < llGetListLength(chainData); ii = ii + chainStride) {  // Run through the chainData list... [label,ballnum,attachpoint,params]
             if (llList2String(chainData, ii) == CurrentSet) {                  //  if the set has a match...
             if (llList2String(chainData, ii) == CurrentSet) {                  //  if the set has a match...
                 chainLog = llList2List(chainData, ii + 2, ii + 3) + chainLog;  //  add attachpoint and params to chainLog
                 if (llList2String(chainData, ii + 1) == param1) {              //  if the matching set also matches ball...
             }                                                                  // End if
                    chainLog = llList2List(chainData, ii + 2, ii + 3) + chainLog;  //  add attachpoint and params to chainLog
                }                                                              // End ball if
             }                                                                  // End set if
         }                                                                      // End for
         }                                                                      // End for
       
        if (chainLog == []) {  return;  }    // We have data for this set, but not this ball.  Returning.


         // TYPE: sit
         // TYPE: sit
Line 339: Line 353:
         if (num == -11002) {
         if (num == -11002) {
             debug("[!] Pose change triggered");
             debug("[!] Pose change triggered");
            list itemData = [];                                                                        // Set up itemData
                       
             if (findAv != -1) {                                                                        // Are we already doing something to av?
             if (findAv != -1) {                                                                        // If avatar exists in avData...
                 itemData = llParseStringKeepNulls(llList2String(avData,findAv + 1),["|"],[]);           // if we are, get that data
                 list itemData = llParseStringKeepNulls(llList2String(avData,findAv + 1),["|"],[]);     // Get the chains we have active on av
                 itemData = UnAliasChainPoints(itemData);                                               // unalias the data for multiple points
                integer yx;                                                                            // Initialize counter var
             }                                                                                          // end if for av finding
                 for(yx = 0; yx < llGetListLength(itemData); yx++) {                                    // Step through list of chain data
 
                    llWhisper(LGChannel,"lockguard " + (string)id + " " + llList2String(itemData,yx) + " unlink");  // Tell LG items to let go
             integer ix;                                                                                // Initialize counter var
                    debug("[-] " + llList2String(itemData,yx));
             string tempPoints = "";                                                                    // Set up a holding var
                }                                                                                      // End for
             for(ix = 0; ix < llGetListLength(chainLog); ix = ix + 2) {                                  // Step through chainLog
                avData = llDeleteSubList(avData, findAv, findAv + 1);                                  // Remove data from avData
                 integer findKey = llListFindList(primKeys, (list)llList2String(chainLog, ix));          // find location of attach point in primKeys
             }                                                                                          // End if for av found in list       
              
             string tempPoints = "";                                                                    // set up tempPoints
            integer zx;                                                                                // initalize counter
             for(zx = 0; zx < llGetListLength(chainLog); zx = zx + 2) {                                  // step through chainLog
                 integer findKey = llListFindList(primKeys, (list)llList2String(chainLog, zx));          // find the target point in the key list
                  
                  
                 if (findKey == -1) {                                                                    // if point does not exist..
                 if (findKey == -1) {                                                                    // if point does not exist..
                     llWhisper(0,"[!] " + llList2String(chainLog, ix) + " does not exist!");            // warn user
                     llWhisper(0,"[!] " + llList2String(chainLog, zx) + " does not exist!");            // warn user
                 }                                                                                      // end if
                 }                                                                                      // end if
                  
                  
                 llWhisper(LGChannel,"lockguard " + (string)id + " " + llList2String(chainLog,ix + 1) +  // Tell LG attachments to draw the chains
                 llWhisper(LGChannel,"lockguard " + (string)id + " " + llList2String(chainLog,zx + 1) +  // sent link command to LG items
                             " link " + llList2String(primKeys, findKey + 1));
                             " link " + llList2String(primKeys, findKey + 1));
                 debug("[+] " + llList2String(chainLog,ix + 1));
                 debug("[+] " + llList2String(chainLog,zx + 1));
 
                 tempPoints = tempPoints +  llList2String(chainLog,zx + 1) + "|";                       // update tempPoints var
                 // cut down the command line to just the attach point for compairison
                string stripPoint = llList2String(llParseStringKeepNulls(llList2String(chainLog,ix + 1),[" "],[]), 0);
                tempPoints = tempPoints + stripPoint + "|";                                             // update tempPoints
            }
 
            tempPoints = llGetSubString(tempPoints, 0, -2);                                            // trim the final char "|"
           
            if (findAv != -1) { avData = llDeleteSubList(avData, findAv, findAv + 1);  }              // clear old av data if it exists
            avData = (avData=[]) + avData + [id];                                                      // add new av key
            avData = (avData=[]) + avData + [tempPoints];                                              // add new attach point data
           
            list tempData = llParseStringKeepNulls(llList2String(avData,findAv + 1),["|"],[]);          // Grab the list of chained points
            tempData = UnAliasChainPoints(tempData);                                                    // Unalias the data if it has groups
            // compaire the two data lists, remove dumplicates
            integer yx;                                                                                // Initialize counter var
            for(yx = 0; yx < llGetListLength(tempData); yx++) {                                        // step through tempData
                integer overlapWhere = llListFindList(itemData, llList2List(tempData, yx, yx));        // look for duplicate entries
                    if (overlapWhere != -1) {                                                          // if we find a duplicate...
                        itemData = llDeleteSubList(itemData, overlapWhere, overlapWhere);              //  remove it from the list
                    }                                                                                  // end duplication check if
             }                                                                                          // end for
             }                                                                                          // end for
              
              
             if ((findAv != -1) && ( llGetListLength(itemData) > 0)) {                                  // Do we have left over points for av?
             tempPoints = llGetSubString(tempPoints, 0, -2);                                            // trim the final char since we're done
                for(ix = 0; ix < llGetListLength(itemData); ix++) {                                    // Step through itemData
            avData = (avData=[]) + avData + [id];                                                       // add new key to avData [avkey,currentchains]
                    llWhisper(LGChannel,"lockguard " + (string)id + " " + llList2String(itemData,ix) + " unlink"); // tell LG to unlink
            avData = (avData=[]) + avData + [tempPoints];                                               // add new chained points to avData
                debug("[-] " + llList2String(itemData,ix));
                }                                                                                      // end for
            }                                                                                          // end leftover if
         }                                                                                              // end change if
         }                                                                                              // end change if
     }                                                                                                  // end link_message
     }                                                                                                  // end link_message
}                                                                                                      // end state on
}                                                                                                      // end state on                                                                                // end state on
</lsl>
</source>


'''Step 3:'''
'''Step 3:'''
Create the data note card.  This card needs to be named .chains, but can have a further name past that if you need to use more than one card.  The card will look something like this:
Create the data note card.  This card needs to be named .CHAINDATA but can have a further name past that if you need to use more than one card.  The card will look something like this:


  //////////////////////////////////////////////////
  //////////////////////////////////////////////////
  // Chain control
  // Chain control
  //////////////////////////////////////////////////
  //////////////////////////////////////////////////
  //  Part 1: menulable
  //  Part 1: menulabel
  //  Part 2: ball chain is linked to
  //  Part 2: ball chain is linked to
  //  Part 3: prim name to link to
  //  Part 3: prim name to link to
Line 416: Line 412:
  Dangled | 0 | point1 | ankles gravity 0.0 life 0.75
  Dangled | 0 | point1 | ankles gravity 0.0 life 0.75
  Captured | 0 | point3 | wrists
  Captured | 0 | point3 | wrists
[[Category:MLPV2]]

Latest revision as of 00:17, 7 June 2016

Basically this is an alternative way of adding particle chain support to MLP2 using the LockGuard protocol. When the object is rezzed in world the script makes a list of the prim names and keys for later. It then reads the note card with the linking information each time inventory changes. When MLP2 gives pose info the script determines if it pertains to it's list and if so notifies lockguard enabled attachments of what to do and what prim keys to do it to.

Ed: Note that this script requires a build which has linked, at the point when the script starts up, the prim points (e.g. posts, etc) to which the chains from the person's cuffs etc will attach themselves.

So what do you need to do?

Step 1: Figure out what prims you want to use as anchor points, and give each a unique name. Personally I recommend the boring yet easy to remember format of point1, point2, point3, etc. Remember these names for later.

Step 2: Add the following script to your furniture. It may complain, but you can ignore that for now.

// ----------------------------------------------------------------------------------
// MLPV2 Plugin for LockGuard Particle Chaining v1.3
// ----------------------------------------------------------------------------------

// ----------------------------------------------------------------------------------
// Notecard Format
// ----------------------------------------------------------------------------------
// Notecard .CHAINDATA format:
// menulabel | ballnumber | anchorprimname | LGattachpoint optionalparams
//
// Example data:
// Bound | 0 | point2 | rightwrist gravity 0.2 life 0.75
// Bound | 0 | point4 | leftwrist gravity 0.2 life 0.75
// Bound | 0 | point3 | collarfrontloop gravity 1.75 life 1.5
// Displayed | 0 | point1 | wrists gravity 0.0 life 2.0
// Hung | 0 | point1 | wrists gravity 0.0 life 0.75
// Dangled | 0 | point1 | ankles gravity 0.0 life 0.75
// Captured | 0 | point3 | wrists

// ----------------------------------------------------------------------------------
// Copyright 2011, Grey Mars. All rights reserved.
// ----------------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//   1. Redistributions of source code must retain the above copyright notice, this list of
//      conditions and the following disclaimer.
//
//   2. Redistributions in binary form must reproduce the above copyright notice, this list
//      of conditions and the following disclaimer in the documentation and/or other materials
//      provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY GREY MARS ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those of the
// authors and should not be interpreted as representing official policies, either expressed
// or implied, of Grey Mars.

// ----------------------------------------------------------------------------------
// Changes:
// ----------------------------------------------------------------------------------
// Version 1.3
// - Fixed an error that was causing multiple avatar chain sets from working
// - Fixed chainData not properly clearing on a reload
// - Changed the order of operations in the changed state for better tracking and simplified
//
// Version 1.2
// - Large overhaul of how the data on the cards is stored.  Original format was made
//   to preserve the working of the MLP2 swap function.  However no one ever uses the
//   swap function in a chained context, so why complicate the notecard files?  Plus
//   new format is more memory friendly.
// - Note card separator changed from ; to | to match favorite RLV plugin format
// - Changed the need for the animation name to just the ball number in the note card
// - Removed some unused variables
// - Note card renamed to .CHAINDATA to follow MLP2 naming conventions
// - Removed sim ratings data poll.
// - Added warning if the desired target point does not exist in the linkset
// - Added more comments because comments are good.
//
// Version 1.1
// - Added a data reload on inventory change
//
// Version 1.0
// - Changed script name from ~chaincontrol
// - Cleaned up old comments and made it more readable
// - First post to lsl wiki ( https://wiki.secondlife.com/wiki/MLPV2_Addons )

// ----------------------------------------------------------------------------------
// Globals
// ----------------------------------------------------------------------------------

integer VERBOSE = FALSE;        // Give extra info

integer LGChannel = -9119;      // The LG listen channel
integer LGHandle;               // Handle ID for LG messages

// Globals for reading card config
integer ConfigLineIndex;
list    ConfigCards;            // list of names of config cards
string  ConfigCardName;         // name of card being read
integer ConfigCardIndex;        // index of next card to read
key     ConfigQueryId;          // The dataserver ID for the query sent

string  Pose;                   // Pose name
key     Avkey;                  // Avatar Key

string CurrentSet = "stand";    // stand being the menu default

list chainData = [];            // The list of all data from the notecard [label,ballnum,attachpoint,params]
integer chainCount = 0;         // How many entries in the list
integer chainStride = 4;        // The stride count for the list

list primKeys = [];             // The list of prims in the object and their keys [primname,key]
integer primKeysCount = 0;      // How many entries in the list
integer primKeysStride = 2;     // The stride count for the list

list avData = [];               // The list of currently chained avs [avkey,currentchains]
integer avDataCount = 0;        // How many entries in the list
integer avDataStride = 2;       // The stride count for the list

// ----------------------------------------------------------------------------------
// Support functions
// ----------------------------------------------------------------------------------

// Debugging outputs
debug(string text) {   if (VERBOSE) llOwnerSay(text);   }

// get the next card in case there is more than one
integer next_card() {
    if (ConfigCardIndex >= llGetListLength(ConfigCards)) {
        ConfigCards = [];
        return (FALSE);
    }
    
    ConfigLineIndex = 0;
    ConfigCardName = llList2String(ConfigCards, ConfigCardIndex);
    ConfigCardIndex++;
    ConfigQueryId = llGetNotecardLine(ConfigCardName, ConfigLineIndex);
    debug("====================\n[?] Reading file: " + ConfigCardName);
    return (TRUE);
}

// add chain data to the list...
add_cd(string label, string ballnum, string target, string params) {
    chainData = (chainData=[]) + chainData + (list)label;
    chainData = (chainData=[]) + chainData + (list)ballnum;
    chainData = (chainData=[]) + chainData + (list)target;
    chainData = (chainData=[]) + chainData + (list)params;
    ++chainCount;
}

// Does this point actually referance a set of points?
list UnAliasChainPoints(list originalData) {
// wrists   ->  leftwrist rightwrist
// ankles   ->  leftankle rightankle
// allfour  ->  leftwrist rightwrist leftankle rightankle
// nipples  ->  leftnipplering rightnipplering 
// arms     ->  leftupperarm rightupperarm
// thighs   ->  leftupperthigh rightupperthigh
// knees    ->  leftknee rightknee

    list modData = [];
    integer xx;
    
    // Hacky.  Feel free to optimize.
    for(xx = 0; xx < llGetListLength(originalData); xx++) {
        if (llToLower(llList2String(originalData,xx)) == "wrists") {
            modData = modData + ["leftwrist", "rightwrist"];
        } else if (llToLower(llList2String(originalData,xx)) == "ankles") {
            modData = modData + ["leftankle", "rightankle"];
        } else if (llToLower(llList2String(originalData,xx)) == "allfour") {
            modData = modData + ["leftankle", "rightankle", "leftwrist", "rightwrist"];
        } else if (llToLower(llList2String(originalData,xx)) == "nipples") {
            modData = modData + ["leftnipplering", "rightnipplering"];
        } else if (llToLower(llList2String(originalData,xx)) == "arms") {
            modData = modData + ["leftupperarm", "rightupperarm"];
        } else if (llToLower(llList2String(originalData,xx)) == "thighs") {
            modData = modData + ["leftupperthigh", "rightupperthigh"];
        } else if (llToLower(llList2String(originalData,xx)) == "knees") {
            modData = modData + ["leftknee", "rightknee"];
        } else {    modData = modData + llList2List(originalData,xx,xx);     }
    }
    
    return modData;   
}

// ----------------------------------------------------------------------------------
// Main Script
// ----------------------------------------------------------------------------------

default {
    state_entry() {
        
        // Now we build the prim key list...
        integer n;                                                      // Initialize the counter
        integer linkcount = llGetNumberOfPrims();                       // How many items in the link set?
        for (n = 2; n <= linkcount; n++)    {                           // Cycle through, skipping root
            string thiselement = llGetLinkName(n);                      // Get the prim name
            if (thiselement != "Object") {                              // If the name is something besides the default...
                primKeys = primKeys + [thiselement, llGetLinkKey(n)];   // add it to primKeys
                primKeysCount++;                                        // increment counter
            }                                                           // end if
        }                                                               // end for loop
        
        state load;                                                     // Go load the data...
    }
}

state load  {
    state_entry() {
        chainData = []; // reset chainData in case it's a soft reload of info
        avData = []; // reset avData for soft reload.  PLEASE do not be sitting while editing chain data.       
        string item;
        ConfigCards = [];
        integer n = llGetInventoryNumber(INVENTORY_NOTECARD);
        while (n-- > 0) { // get the data off cards with the right name
            item = llGetInventoryName(INVENTORY_NOTECARD, n);
            if (llSubStringIndex(item, ".CHAINDATA") != -1) {
                ConfigCards = (ConfigCards=[]) + ConfigCards + (list) item;
            }
        }

        ConfigCardIndex = 0;
        ConfigCards = llListSort(ConfigCards, 1, TRUE);
        next_card();
    }
    
    dataserver(key query_id, string data) {
        
        if (query_id != ConfigQueryId) {    return;     }   // Make sure this is the data event we asked for
        
        if (data == EOF) {                      // Finished with this card...
            if ( next_card() ) { return;  }     // Are there more cards to process?
            state on;                           // All cards done, go to the on state
        }
        
        data = llStringTrim(data, STRING_TRIM);                                     // Cut off white space from the front and end...
        if (llGetSubString(data,0,0) != "/" && llStringLength(data)) {              // Skip comments and blank lines
            list ldata = llParseStringKeepNulls(data, ["|"], []);                   // Get the whole line for processing...
            string label = llStringTrim(llList2String(ldata, 0), STRING_TRIM);      //   Extract label
            string ballnum = llStringTrim(llList2String(ldata, 1), STRING_TRIM);    //   Extract ball number
            string target = llStringTrim(llList2String(ldata, 2), STRING_TRIM);     //   Extract target point
            string param = llStringTrim(llList2String(ldata, 3), STRING_TRIM);      //   Extract params
            add_cd(label,ballnum,target,param);                                     // Add all that to chainData
        }
        
        ++ConfigLineIndex;                                                  // Increment counter
        ConfigQueryId = llGetNotecardLine(ConfigCardName, ConfigLineIndex); // Read next line of data notecard...
    }
}


state on {
    state_entry()   {
            debug("[!] Ready!\n====================");
        }
    on_rez(integer arg) { llResetScript(); }        // Object was rezed in world, reset script for freshness
    changed(integer change) {
        if (change & CHANGED_INVENTORY) state load; // Inventory changed, so reload the card data to be safe
    }

    link_message(integer from, integer num, string message, key id) {
        
        // Do we care about this link message?  If not, just return
        if (!((num == -11000) || (num == -11001) || (num == -11002) || (message == "POSEB"))) { return; }
    
        if (message == "POSEB") {       // Is it a set change?
            CurrentSet = (string)id;    // Save the set name...
            return;                     // Nothing more for us, so return
        }

        list linedata = llParseStringKeepNulls(message,["|"],[]);   // It's a generic sit/unsit/change update, so break it down
        string param1 = llList2String(linedata, 0);                 //    Extract ball number
        string param2 = llList2String(linedata, 1);                 //    Extract animation

        integer findSet = llListFindList(chainData, (list)CurrentSet);  // Check chainData for this set name...        
        integer findAv = llListFindList(avData, [id]);                  // Check avData for the person sitting...
        
        debug("==========\n= SET: " + CurrentSet + "   BALL: " + param1 + "   ANIMATION: " + param2);

        if (findSet == -1) {                                            // There is no chain data for this set, so...
            debug("[!] No chain data found");
            if (findAv != -1) {                                         // Do we still have chain data for this av, and need to clear it?
                list itemData = llParseStringKeepNulls(llList2String(avData,findAv + 1),["|"],[]);  // find the stored data
                integer ix;                                                                         // initialize counter
                for(ix = 0; ix < llGetListLength(itemData); ix++) {                                 // Step through the stored data
                    llWhisper(LGChannel,"lockguard " + (string)id + " " + llList2String(itemData,ix) + " unlink");  // unlink from points
                    debug("[-] " + llList2String(itemData,ix));
                }                                                                                   // end for
                avData = llDeleteSubList(avData, findAv, findAv + 1);                               // finally delete the stored data
            }                                                                                       // end findAv if
            return;                                                                                 // No further processing for this event needed
        } else debug("[!] Chain data found");


        list chainLog = [];                                                     // set up the list of commands [attachpoint, params]
        integer ii;                                                             // initialize counter var
        for(ii = 0; ii < llGetListLength(chainData); ii = ii + chainStride) {   // Run through the chainData list... [label,ballnum,attachpoint,params]
            if (llList2String(chainData, ii) == CurrentSet) {                   //   if the set has a match...
                if (llList2String(chainData, ii + 1) == param1) {               //   if the matching set also matches ball...
                    chainLog = llList2List(chainData, ii + 2, ii + 3) + chainLog;   //   add attachpoint and params to chainLog
                }                                                               // End ball if
            }                                                                   // End set if
        }                                                                       // End for
        
        if (chainLog == []) {   return;   }     // We have data for this set, but not this ball.  Returning.

        // TYPE: sit
        // FORMAT: llMessageLinked(LINK_SET, -11000, (string)BallNum + "|" + animation, avatar);
        if (num == -11000) {
            debug("[!] Sit triggered");
            string tempPoints = "";                                                                     // set up tempPoints
            integer ix;                                                                                 // initalize counter
            for(ix = 0; ix < llGetListLength(chainLog); ix = ix + 2) {                                  // step through chainLog
                integer findKey = llListFindList(primKeys, (list)llList2String(chainLog, ix));          // find the target point in the key list
                
                if (findKey == -1) {                                                                    // if point does not exist..
                    llWhisper(0,"[!] " + llList2String(chainLog, ix) + " does not exist!");             // warn user
                }                                                                                       // end if
                
                llWhisper(LGChannel,"lockguard " + (string)id + " " + llList2String(chainLog,ix + 1) +  // sent link command to LG items
                            " link " + llList2String(primKeys, findKey + 1));
                debug("[+] " + llList2String(chainLog,ix + 1));
                tempPoints = tempPoints +  llList2String(chainLog,ix + 1) + "|";                        // update tempPoints var
            }                                                                                           // end for
            
            tempPoints = llGetSubString(tempPoints, 0, -2);                                             // trim the final char since we're done
            avData = (avData=[]) + avData + [id];                                                       // add new key to avData [avkey,currentchains]
            avData = (avData=[]) + avData + [tempPoints];                                               // add new chained points to avData
        }                                                                                               // End if for sit

        // TYPE: unsit
        // FORMAT:  llMessageLinked(LINK_SET, -11001, (string)BallNum, llGetPermissionsKey());
        if (num == -11001) {
            debug("[!] Unsit triggered"); 
            if (findAv != -1) {                                                                         // If avatar exists in avData...
                list itemData = llParseStringKeepNulls(llList2String(avData,findAv + 1),["|"],[]);      // Get the chains we have active on av
                integer ix;                                                                             // Initialize counter var
                for(ix = 0; ix < llGetListLength(itemData); ix++) {                                     // Step through list of chain data
                    llWhisper(LGChannel,"lockguard " + (string)id + " " + llList2String(itemData,ix) + " unlink");  // Tell LG items to let go
                    debug("[-] " + llList2String(itemData,ix));
                }                                                                                       // End for
                avData = llDeleteSubList(avData, findAv, findAv + 1);                                   // Remove data from avData
            }                                                                                           // End if for av found in list
        }                                                                                               // End if for unsit
        
        // TYPE: change
        // FORMAT: llMessageLinked(LINK_SET, -11002, (string)BallNum + "|" + animation, avatar);
        if (num == -11002) {
            debug("[!] Pose change triggered");
                        
            if (findAv != -1) {                                                                         // If avatar exists in avData...
                list itemData = llParseStringKeepNulls(llList2String(avData,findAv + 1),["|"],[]);      // Get the chains we have active on av
                integer yx;                                                                             // Initialize counter var
                for(yx = 0; yx < llGetListLength(itemData); yx++) {                                     // Step through list of chain data
                    llWhisper(LGChannel,"lockguard " + (string)id + " " + llList2String(itemData,yx) + " unlink");  // Tell LG items to let go
                    debug("[-] " + llList2String(itemData,yx));
                }                                                                                       // End for
                avData = llDeleteSubList(avData, findAv, findAv + 1);                                   // Remove data from avData
            }                                                                                           // End if for av found in list        
            
            string tempPoints = "";                                                                     // set up tempPoints
            integer zx;                                                                                 // initalize counter
            for(zx = 0; zx < llGetListLength(chainLog); zx = zx + 2) {                                  // step through chainLog
                integer findKey = llListFindList(primKeys, (list)llList2String(chainLog, zx));          // find the target point in the key list
                
                if (findKey == -1) {                                                                    // if point does not exist..
                    llWhisper(0,"[!] " + llList2String(chainLog, zx) + " does not exist!");             // warn user
                }                                                                                       // end if
                
                llWhisper(LGChannel,"lockguard " + (string)id + " " + llList2String(chainLog,zx + 1) +  // sent link command to LG items
                            " link " + llList2String(primKeys, findKey + 1));
                debug("[+] " + llList2String(chainLog,zx + 1));
                tempPoints = tempPoints +  llList2String(chainLog,zx + 1) + "|";                        // update tempPoints var
            }                                                                                           // end for
            
            tempPoints = llGetSubString(tempPoints, 0, -2);                                             // trim the final char since we're done
            avData = (avData=[]) + avData + [id];                                                       // add new key to avData [avkey,currentchains]
            avData = (avData=[]) + avData + [tempPoints];                                               // add new chained points to avData
        }                                                                                               // end change if
    }                                                                                                   // end link_message
}                                                                                                       // end state on                                                                                // end state on

Step 3: Create the data note card. This card needs to be named .CHAINDATA but can have a further name past that if you need to use more than one card. The card will look something like this:

//////////////////////////////////////////////////
// Chain control
//////////////////////////////////////////////////
//  Part 1: menulabel
//  Part 2: ball chain is linked to
//  Part 3: prim name to link to
//  Part 4: LG attach point
//  Part 4 optional commands:
//      texture key
//      size # #
//      life #
//      gravity #
//      speed #
//      color # # #
//////////////////////////////////////////////////
Bound | 0 | point2 | rightwrist gravity 0.2 life 0.75
Bound | 0 | point4 | leftwrist gravity 0.2 life 0.75
Bound | 0 | point3 | collarfrontloop gravity 1.75 life 1.5
Displayed | 0 | point1 | wrists gravity 0.0 life 2.0
Hung | 0 | point1 | wrists gravity 0.0 life 0.75
Dangled | 0 | point1 | ankles gravity 0.0 life 0.75
Captured | 0 | point3 | wrists