Difference between revisions of "Builders Buddy v1"

From Second Life Wiki
Jump to navigation Jump to search
(→‎The Base Script: updated to version 2.0)
m (Replaced <source> with <syntaxhighlight>)
 
(16 intermediate revisions by 6 users not shown)
Line 1: Line 1:
= Builder's Buddy =
{{LSL Header}}
This is a repost of the current Builder's Buddy scripts, as originally released on the prior LSL wiki.  There are two scripts; One goes in a "base" prim, which is the piece that is moved/rotated/etc.  The component script goes into each linked set that makes up the rest of the large build.  In short, only one base script, many component scripts. Got it?
{{RightToc}}
This is a repost of the current Builder's Buddy scripts, as originally released on the prior LSL wiki.  There are two scripts; One goes in a "base" prim, which is the piece that is moved/rotated/etc.  The component script goes into each linked set that makes up the rest of the large build.  In short, only one base script, many component scripts.


== Note to Sellers! ==
'''The most current "official" version of these scripts is 1.10.'''
Due to a number of people confused by the copyright wording, here's a recap: '''Use this script any way you want. SELL IT EVEN!  Just make sure I am given credit as the creator.''' That's the only restriction.


== The Base Script ==
 
  ///////////////////////////////////////////////////////////////////////////////
== Help Documentation ==
  // Builders' Buddy 2.0 (Base Script)
 
  // by Newfie Pendragon, March 2006
Complete Step-by-Step help is posted here: [[Builder's Buddy Help Document]] . The help document also includes a short quick end-user guide for you to distribute to end-users of your products.
  //
 
  // This script is distributed with permission that it may be used in
 
  // any way, or may be further modified/included in resale items.
== Obligatory Copyright Notice ==
  // HOWEVER, if this script is used as part of a for-sale product,
There are only a few points here:
  // it is required that appropriate credit be given to Newfie for
* Use this script as you wish, to modify, sell, etc.
  // the script (or portions used in derivatives).  That's a fair price
* If you use this script in a for-sale product, please give credit to Newfie Pendragon as the creator of the script.
  // in exchange for unlimited use of this script, dontcha think?
* If you wish to modify this script and release the changes for public use, please submit changes to Newfie Pendragon.  This is to ensure a consistent version numbering on releases, and to ensure changes are not wiped out in future releases.
  //
 
  // SL Forum thread and new versions found here:
== The Base Script ==
  // http://forums.secondlife.com/showthread.php?t=96792
<syntaxhighlight lang="lsl2">
  ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
  //
// Builders' Buddy 1.10 (Base Script)
  // Script Purpose & Use
// by Newfie Pendragon, 2006-2008
  //
///////////////////////////////////////////////////////////////////////////////
  //  Functions are dependent on the "component script"
//
  //
// Script Purpose & Use
  // QUICK USE:
// Functions are dependent on the "component script"
  // - Drop this script in the Base.
//
  // - Drop the "Component" Script in each building part.
// QUICK USE:
  // - Touch your Base, and choose RECORD
// - Drop this script in the Base.
  // - Take all building parts into inventory
// - Drop the "Component" Script in each building part.
  // - Drag building parts from inventory into Base Prim
// - Touch your Base, and choose RECORD
  // - Touch your base and choose BUILD
// - Take all building parts into inventory
  //
// - Drag building parts from inventory into Base Prim
  // OTHER COMMANDS from the Touch menu
// - Touch your base and choose BUILD
  // - To reposition, move/rotate Base Prim choose POSITION
//
  // - To lock into position (removes scripts) choose DONE
// OTHER COMMANDS from the Touch menu
  // - To delete building pieces: choose CLEAN
// - To reposition, move/rotate Base Prim choose POSITION
  //
// - To lock into position (removes scripts) choose DONE
  ///////////////////////////////////////////////////////////////////////////////
// - To delete building pieces: choose CLEAN
  //
///////////////////////////////////////////////////////////////////////////////
  //  History
// This script is copyrighted material, and has a few (minor) restrictions.
  //
// For complete details, including a revision history, please see
  // v1.0 - 20060328 - Newfie Pendragon
//  http://wiki.secondlife.com/wiki/Builders_Buddy
  //     - Original Version
///////////////////////////////////////////////////////////////////////////////
  // v1.1 - 20060331 - Kalidor Lazarno
 
  //     - Added a Dialog Engine to the base script
// Channel used by Base Prim to talk to Component Prims
  // v1.5 - 20060612 - Androclese Antonelli
// This channel must be the same one in the component script
  //     - Added a random number generator to the dialog engine to elimintate
// A negative channel is used because it elimited accidental activations
  //       problems with multiple BB boxes cross-talking
// by an Avatar talking on obscure channels
  //     - Added a timer to the listen command to put it asleep after 10sec.
integer DefaultPRIMCHAN = -192567;    // Default channel to use
  //     - Added a Menu Description
integer PRIMCHAN = DefaultPRIMCHAN;    // Channel used by Base Prim to talk to Component Prims;
  //     - Added n "creator" flag so the owner could use the same object with full
                                      // ***THIS MUST MATCH IN BOTH SCRIPTS!***
  //       menu options and only a single flag change
 
  //     - Added an "ingroup" flag to enable/disable the same group use function
//The UUID of the creator of the object
  //     - Non-Admin usage cleans the inventory items as they spawn
//Leave this as "" unless SL displays wrong name in object properties
  // v1.6 - 20060624 - Newfie Pendragon
key creatorUUID = "";
  //     - Added active repositioning (building moves as the base piece moves)
 
  //     - Added "Reset" Option to unlink parts from base temporarily
// Set to TRUE to allow group members to use the dialog menu
  //     - Modified creator flag to automatically set based if owner is creator
// Set to FALSE to disallow group members from using the dialog menu
  //     - Minor changes to improve code readability (for those learning LSL)
integer ingroup = TRUE;
  // v1.8 - 20070429 - Newfie Pendragon
 
  //     - Added a variable to allow a user to tweak how long a listener is open,
// Set to TRUE to delete piece from inventory when rezzed
  //       and changed the default to 30 seconds.
// (WARNING) If set to FALSE, user will be able to rez multiple copies
  // v1.9 - 20070630 - Newfie Pendragon
integer deleteOnRez = FALSE;
  //     - Changed to use llRegionSay - no more 96m max distance (same sim)
 
  //     - Changed rez sequence to be less affected by lag/gray goo fence
// Allow non-creator to use CLEAN command?
  //     - Timer always on, less code/more reliable
// (WARNING) If set to TRUE, it is recommended to set
  // v2.0 - 20070829 - Huney Jewell
// deleteOnRez to FALSE, or user could lose entire building
  //     - Added configurable constant to determine, which Menu Options will be diplayed
integer allowClean = TRUE;
  //     - Menu Option 'Clean' now also deletes the prim which contains this script
 
  ///////////////////////////////////////////////////////////////////////////////
//When user selects CLEAN, delete the base prim too?
 
integer dieOnClean = FALSE;
  ///////////////////////////////////////////////////////////////////////////////
 
  // Configurable Settings
// Set to TRUE to record piece's location based on sim
 
// coordinates instead of relationship to base prim
 
integer recordSimLocation = FALSE;
  // Channel used by Base Prim to talk to Component Prims
 
  // This channel must be the same one in the component script
// Set to TRUE to rez all building pieces before positioning,
  // A negative channel is used because it elimited accidental activations
// or FALSE to do (slower?) one at a time
  // by an Avatar talking on obscure channels
integer bulkBuild = TRUE;
  integer PRIMCHAN = -19730611;    // Channel used by Base Prim to talk to Component Prims;
 
                                  // ***THIS MUST MATCH IN BOTH SCRIPTS!***
//Set to FALSE if you dont want the script to say anything while 'working'
 
integer chatty = TRUE;
  // Set to TRUE to allow group members to use the dialog menu
 
  // Set to FALSE to disallow group members from using the dialog menu
//How long to listen for a menu response before shutting down the listener
  integer ingroup = TRUE;
float fListenTime = 30.0;
 
 
  //Set to FALSE if you dont want the script to say anything while 'working'
//How often (in seconds) to perform any timed checks
  integer chatty = TRUE;
float fTimerRate = 0.25;
 
 
  //How long to listen for a menu response before shutting down the listener
//How long to sit still before exiting active mode
  float fListenTime = 30.0;
float fStoppedTime = 30.0;
 
 
  //How often (in seconds) to perform any timed checks
//SL sometimes blocks rezzing to prevent "gray goo" attacks
  float fTimerRate = 0.25;
//How long we wait (seconds) before we assume SL blocked our rez attempt
 
integer iRezWait = 10;
  //How long to sit still before exiting active mode
 
  float fStoppedTime = 30.0;
//Specify which Menu Options will be displayed
 
//FALSE will restrict full options to creator
  //SL sometimes blocks rezzing to prevent "gray goo" attacks
//TRUE will offer full options to anyone
  //How long we wait (seconds) before we assume SL blocked our rez attempt
integer fullOptions = FALSE;
  integer iRezWait = 3;
 
 
//Set to TRUE if you want ShapeGen channel support
  //Specify which Menu Options will be displayed
// (Last 4 digits of channel affected)
  //FALSE will restrict full options to creator
integer SGCompatible = FALSE;
  //TRUE will offer full options to anyone
 
  integer fullOptions = TRUE;
 
 
///////////////////////////////////////////////////////////////////////////////
 
//Part of KEYPAD CODE BY Andromeda Quonset....More added below in seevral places
  ///////////////////////////////////////////////////////////////////////////////
list Menu2 = [ "-", "0","enter","7","8","9","4","5","6","1","2","3"];
  // DO NOT EDIT BELOW THIS LINE.... NO.. NOT EVEN THEN
string Input = "";
  ///////////////////////////////////////////////////////////////////////////////
string Sign = "+";
 
string SignInput = " ";
  //Name each option-these names will be your button names.
string Caption = "Enter a number, include any leading 0's: ";
  string optRecord = "Record";
 
  string optReset = "Reset";
///////////////////////////////////////////////////////////////////////////////
  string optBuild = "Build";
// DO NOT EDIT BELOW THIS LINE.... NO.. NOT EVEN THEN
  string optPos = "Position";
///////////////////////////////////////////////////////////////////////////////
  string optClean = "Clean";
 
  string optDone = "Done";
//Name each option-these names will be your button names.
 
string optRecord = "Record";
  //Menu option descriptions
string optReset = "Reset";
  string descRecord = ": Record the position of all parts\n";
string optBuild = "Build";
  string descReset = ": Forgets the position of all parts\n";
string optPos = "Position";
  string descBuild = ": Rez inv. items and position them\n";
string optClean = "Clean";
  string descPos = ": Reposition the parts to a new location\n";
string optDone = "Done";
  string descClean = ": De-Rez all pieces\n";
string optChannel = "Channel";
  string descDone = ": Remove all BB scripts and freeze parts in place.\n";
 
 
//Menu option descriptions
  integer MENU_CHANNEL;
string descRecord = ": Record the position of all parts\n";
  integer MENU_HANDLE;
string descReset = ": Forgets the position of all parts\n";
  key agent;
string descBuild = ": Rez inv. items and position them\n";
  key objectowner;
string descPos = ": Reposition the parts to a new location\n";
  integer group;
string descClean = ": De-Rez all pieces\n";
  string title = "";
string descDone = ": Remove all BB scripts and freeze parts in place.\n";
  list optionlist = [];
string descChannel = ": Change Channel used on base and parts.\n";
  integer bMoving;
 
  vector vLastPos;
integer MENU_CHANNEL;
  rotation rLastRot;
integer MENU2_CHANNEL;
  integer bRezzing;
integer MENU_HANDLE;
  integer iListenTimeout = 0;
integer MENU2_HANDLE;
  integer iLastRez = 0;
key agent;
  integer iRezIndex;
key objectowner;
  //integer bTimerOn = FALSE;
integer group;
 
string title = "";
  //To avoid flooding the sim with a high rate of movements
list optionlist = [];
  //(and the resulting mass updates it will bring), we used
integer bMoving;
  // a short throttle to limit ourselves
vector vLastPos;
  announce_moved()
rotation rLastRot;
  {
integer bRezzing;
      llRegionSay(PRIMCHAN, "MOVE " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
integer iListenTimeout = 0;
      llResetTime();        //Reset our throttle
integer iLastRez = 0;
      vLastPos = llGetPos();
integer iRezIndex;
      rLastRot = llGetRot();
 
      return;
 
  }
InvertSign()
 
{
 
    if(Sign == "+")
  rez_object()
        Sign = "-";
  {
    else
      //Rez the object indicated by iRezIndex
        Sign = "+";
      llRezObject(llGetInventoryName(INVENTORY_OBJECT, iRezIndex), llGetPos(), ZERO_VECTOR, llGetRot(), PRIMCHAN);
}
      iLastRez = llGetUnixTime();
 
 
//To avoid flooding the sim with a high rate of movements
      if(!bRezzing) {
//(and the resulting mass updates it will bring), we used
          bRezzing = TRUE;
// a short throttle to limit ourselves
          //timer_on();
announce_moved()
      }
{
  }
    llRegionSay(PRIMCHAN, "MOVE " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
 
    llResetTime();        //Reset our throttle
  post_rez_object()
    vLastPos = llGetPos();
  {
    rLastRot = llGetRot();
      if ( llGetCreator() != llGetOwner() )
    return;
          llRemoveInventory(llGetInventoryName(INVENTORY_OBJECT, iRezIndex));
}
  }
 
 
 
  ///////////////////////////////////////////////////////////////////////////////
rez_object()
  ///////////////////////////////////////////////////////////////////////////////
{
  ///////////////////////////////////////////////////////////////////////////////
    //Rez the object indicated by iRezIndex
  default {
    llRezObject(llGetInventoryName(INVENTORY_OBJECT, iRezIndex), llGetPos(), ZERO_VECTOR, llGetRot(), PRIMCHAN);
      ///////////////////////////////////////////////////////////////////////////////
    iLastRez = llGetUnixTime();
      changed(integer change) {
 
          if(change & CHANGED_OWNER)
    if(!bRezzing) {
          llResetScript();
        bRezzing = TRUE;
      }
        //timer_on();
 
    }
      ///////////////////////////////////////////////////////////////////////////////
}
      state_entry () {
 
          //Use which menu?
post_rez_object()
          if ( llGetCreator() == llGetOwner() || fullOptions) {
{
              //Display all options
    if ( creatorUUID != llGetOwner() ) {
              optionlist = [optPos, optClean, optDone, optRecord, optReset, optBuild];
        if(deleteOnRez) llRemoveInventory(llGetInventoryName(INVENTORY_OBJECT, iRezIndex));
              title = optRecord + descRecord;
    }
              title += optReset + descReset;
}
              title += optBuild + descBuild;
 
              title += optPos + descPos;
heard(integer channel, string name, key id, string message)
              title += optClean + descClean;
{
              title += optDone + descDone;
    if( channel == PRIMCHAN ) {
 
        if( message == "READYTOPOS" ) {
          } else {
            //New prim ready to be positioned
              //Display limited options
            vector vThisPos = llGetPos();
              optionlist = [optBuild, optPos, optDone];
            rotation rThisRot = llGetRot();
              title = optBuild + descBuild;
            llRegionSay(PRIMCHAN, "MOVESINGLE " + llDumpList2String([ vThisPos, rThisRot ], "|"));
              title += optPos + descPos;
 
              title += optDone + descDone;
        } else if( message == "ATDEST" ) {
          }
            //Rez the next in the sequence (if any)
 
            iRezIndex--;
          //Record our position
            if(iRezIndex >= 0) {
          vLastPos = llGetPos();
                //Attempt to rez it
          rLastRot = llGetRot();
                rez_object();
 
            } else {
          llSetTimerEvent(fTimerRate);
                //We are done building, reset our listeners
      }
                iLastRez = 0;
 
                bRezzing = FALSE;
      ///////////////////////////////////////////////////////////////////////////////
                state reset_listeners;
      touch_start (integer total_number) {
            }
          group = llDetectedGroup(0); // Is the Agent in the objowners group?
        }
          agent = llDetectedKey(0); // Agent's key
        return;
          objectowner = llGetOwner(); // objowners key
 
          // is the Agent = the owner OR is the agent in the owners group
    } else if( channel == MENU_CHANNEL ) {  //Process input from original menu
          if ( (objectowner == agent) || ( group && ingroup )  )  {
        if ( message == optRecord ) {
              iListenTimeout = llGetUnixTime() + llFloor(fListenTime);
            PRIMCHAN = DefaultPRIMCHAN;
              MENU_CHANNEL = llFloor(llFrand(-99999.0 - -100));
            llOwnerSay("Recording positions...");
              MENU_HANDLE = llListen(MENU_CHANNEL,"","","");
            if(recordSimLocation) {
              llDialog(agent, title, optionlist, MENU_CHANNEL);
                //Location in sim
              //timer_on();
                llRegionSay(PRIMCHAN, "RECORDABS " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
          }
            } else {
      }
                //Location relative to base
 
                llRegionSay(PRIMCHAN, "RECORD " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
      ///////////////////////////////////////////////////////////////////////////////
            }
      listen(integer channel, string name, key id, string message) {
            return;
          if ( message == optRecord ) {
        }
              llOwnerSay("Recording positions...");
        if( message == optReset ) {
              llRegionSay(PRIMCHAN, "RECORD " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
            llOwnerSay("Forgetting positions...");
              return;
            llShout(PRIMCHAN, "RESET");
          }
            return;
          if( message == optReset ) {
        }
              llOwnerSay("Forgetting positions...");
        if ( message == optBuild ) {
              llShout(PRIMCHAN, "RESET");
            if(chatty) llOwnerSay("Rezzing build pieces...");
              return;
 
          }
            //If rezzing/positioning one at a time, we need
          if ( message == optBuild ) {
            // to listen for when they've reached their dest
              if(chatty) llOwnerSay("Rezzing build pieces...");
            if(!bulkBuild) {
              iRezIndex = llGetInventoryNumber(INVENTORY_OBJECT) - 1;
                llListen(PRIMCHAN, "", NULL_KEY, "READYTOPOS");
              rez_object();
                llListen(PRIMCHAN, "", NULL_KEY, "ATDEST");
              return;
            }
          }
 
          if ( message == optPos ) {
            //Start rezzing, last piece first
              if(chatty) llOwnerSay("Positioning");
            iRezIndex = llGetInventoryNumber(INVENTORY_OBJECT) - 1;
              vector vThisPos = llGetPos();
            rez_object();
              rotation rThisRot = llGetRot();
            return;
              llRegionSay(PRIMCHAN, "MOVE " + llDumpList2String([ vThisPos, rThisRot ], "|"));
        }
              return;
        if ( message == optPos ) {
          }
            if(chatty) llOwnerSay("Positioning");
          if ( message == optClean ) {
            vector vThisPos = llGetPos();
              llRegionSay(PRIMCHAN, "CLEAN");
            rotation rThisRot = llGetRot();
              llDie();
            llRegionSay(PRIMCHAN, "MOVE " + llDumpList2String([ vThisPos, rThisRot ], "|"));
              return;
            return;
          }
        }
          if ( message == optDone ) {
        if ( message == optClean ) {
              llRegionSay(PRIMCHAN, "DONE");
            llRegionSay(PRIMCHAN, "CLEAN");
              if(chatty) llOwnerSay("Removing Builder's Buddy scripts.");
            if(dieOnClean) llDie();
              return;
            return;
          }
        }
      }
        if ( message == optDone ) {
 
            llRegionSay(PRIMCHAN, "DONE");
      ///////////////////////////////////////////////////////////////////////////////
            if(chatty) llOwnerSay("Removing Builder's Buddy scripts.");
      moving_start()
            return;
      {
        }
          if( !bMoving )
        if ( message == optChannel ) {
          {
            Sign = "+"; //default is a positive number
              bMoving = TRUE;
            Input = "";
              //timer_on();
            llDialog( agent, Caption, Menu2, MENU2_CHANNEL );
              announce_moved();
        }
          }
 
      }
    } else if ( channel == MENU2_CHANNEL ) {    //process input from MENU2
 
        // if a valid choice was made, implement that choice if possible.
      ///////////////////////////////////////////////////////////////////////////////
        // (llListFindList returns -1 if Choice is not in the menu list.)
      object_rez(key id) {
        if ( llListFindList( Menu2, [ message ]) != -1 ) {
          //The object rezzed, perform any post-rez processing
            if( llListFindList(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], [message]) != -1) {
          post_rez_object();
                Input += message;
 
                SignInput = Sign + Input;
          //Move on to the next object
                llDialog( agent, Caption + SignInput, Menu2, MENU2_CHANNEL );
          //Loop through backwards (safety precaution in case of inventory change)
 
          iRezIndex--;
            } else if( message == "-" ) {
          if(iRezIndex >= 0) {
                InvertSign();
              //Attempt to rez it
                SignInput = Sign + Input;
              rez_object();
                llDialog( agent, Caption + SignInput, Menu2, MENU2_CHANNEL );
 
 
          } else {
            } else if( message == "enter" ) {    //terminate input from menu2
              //Rezzing complete, now positioning
                string CalcChan = Input;
              iLastRez = 0;
 
              bRezzing = FALSE;
            //Apply ShapeGen compatibility?
              if(chatty) llOwnerSay("Positioning");
            if(SGCompatible) {
              llRegionSay(PRIMCHAN, "MOVE " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
                    //new assign channel number, forcing last 4 digits to 0000
          }
                integer ChanSize = llStringLength(Input); //determine number of digits (chars)
      }
                if(ChanSize > 5) {
 
                    CalcChan = llGetSubString(Input, 0, 4);   //Shorten to 5 digits
      ///////////////////////////////////////////////////////////////////////////////
                }
      timer() {
                CalcChan += "0000"; //append 0000
          //Did we change position/rotation?
                    if(Sign == "-")
          if( (llGetRot() != rLastRot) || (llGetPos() != vLastPos) )
                    CalcChan = Sign + CalcChan;
          {
            }
              if( llGetTime() > fTimerRate ) {
            PRIMCHAN = (integer)CalcChan; //assign channel number
                  announce_moved();
            llOwnerSay("Channel set to " + (string)PRIMCHAN + ".");
              }
            }
          }
 
 
        } else {
          //Are we rezzing?
            llDialog( agent, Caption, Menu2, MENU2_CHANNEL );
          if(bRezzing) {
        }
              //Did the last one take too long?
    }
              if((llGetUnixTime() - iLastRez) >= iRezWait) {
}
                  //Yes, retry it
 
                  if(chatty) llOwnerSay("Reattempting rez of most recent piece");
 
                  rez_object();
///////////////////////////////////////////////////////////////////////////////
              }
///////////////////////////////////////////////////////////////////////////////
          }
///////////////////////////////////////////////////////////////////////////////
 
default {
          //Open listener?
    ///////////////////////////////////////////////////////////////////////////////
          if( iListenTimeout != 0 )
    changed(integer change) {
          {
        if(change & CHANGED_OWNER)
              //Past our close timeout?
        llResetScript();
              if( iListenTimeout <= llGetUnixTime() )
    }
              {
 
                  iListenTimeout = 0;
    ///////////////////////////////////////////////////////////////////////////////
                  llListenRemove(MENU_HANDLE);
    state_entry () {
              }
        //Determine the creator UUID
          }
        if(creatorUUID == "") creatorUUID = llGetCreator();
      }
 
 
        //Use which menu?
      ///////////////////////////////////////////////////////////////////////////////
        if (creatorUUID == llGetOwner() || fullOptions) {
      on_rez(integer iStart)
            //Display all options
      {
            optionlist = [optPos, optClean, optDone, optRecord, optReset, optBuild, optChannel];
          //Reset ourselves
            title = optRecord + descRecord;
          llResetScript();
            title += optReset + descReset;
      }
            title += optBuild + descBuild;
  }
            title += optPos + descPos;
            title += optClean + descClean;
            title += optDone + descDone;
            title += optChannel + descChannel;
 
        } else {
            //Display limited options
            if(allowClean) {
                optionlist = [optBuild, optPos, optClean, optDone];
                title = optBuild + descBuild;
                title += optPos + descPos;
                title += optClean + descClean;
                title += optDone + descDone;
            } else {
                optionlist = [optBuild, optPos, optDone];
                title = optBuild + descBuild;
                title += optPos + descPos;
                title += optDone + descDone;
            }
        }
 
        //Record our position
        vLastPos = llGetPos();
        rLastRot = llGetRot();
 
        llSetTimerEvent(fTimerRate);
    }
 
    ///////////////////////////////////////////////////////////////////////////////
    touch_start (integer total_number) {
        group = llDetectedGroup(0); // Is the Agent in the objowners group?
        agent = llDetectedKey(0); // Agent's key
        objectowner = llGetOwner(); // objowners key
        // is the Agent = the owner OR is the agent in the owners group
        if ( (objectowner == agent) || ( group && ingroup )  )  {
            iListenTimeout = llGetUnixTime() + llFloor(fListenTime);
            MENU_CHANNEL = llFloor(llFrand(-99999.0 - -100));
            MENU2_CHANNEL = MENU_CHANNEL + 1;
            MENU_HANDLE = llListen(MENU_CHANNEL,"","","");
            MENU2_HANDLE = llListen(MENU2_CHANNEL,"","","");
            if ( creatorUUID == llGetOwner() || fullOptions) {
                llDialog(agent,title + "Now on Channel " + (string)PRIMCHAN, optionlist, MENU_CHANNEL); //display channel number if authorized
            } else {
                llDialog(agent, title, optionlist, MENU_CHANNEL);
            }
            //timer_on();
        }
    }
 
    ///////////////////////////////////////////////////////////////////////////////
    listen(integer channel, string name, key id, string message) {
    heard(channel, name, id, message);
    return;
    }
 
    ///////////////////////////////////////////////////////////////////////////////
    moving_start()
    {
        if( !bMoving )
        {
            bMoving = TRUE;
            //timer_on();
            announce_moved();
        }
    }
 
    ///////////////////////////////////////////////////////////////////////////////
    object_rez(key id) {
        //The object rezzed, perform any post-rez processing
        post_rez_object();
 
        //Rezzing it all before moving?
        if(bulkBuild) {
            //Move on to the next object
            //Loop through backwards (safety precaution in case of inventory change)
            iRezIndex--;
            if(iRezIndex >= 0) {
                //Attempt to rez it
                rez_object();
 
            } else {
                //Rezzing complete, now positioning
                iLastRez = 0;
                bRezzing = FALSE;
                if(chatty) llOwnerSay("Positioning");
                llRegionSay(PRIMCHAN, "MOVE " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
            }
        }
    }
 
    ///////////////////////////////////////////////////////////////////////////////
    timer() {
        //Did we change position/rotation?
        if( (llGetRot() != rLastRot) || (llGetPos() != vLastPos) )
        {
            if( llGetTime() > fTimerRate ) {
                announce_moved();
            }
        }
 
        //Are we rezzing?
        if(bRezzing) {
            //Did the last one take too long?
            if((llGetUnixTime() - iLastRez) >= iRezWait) {
                //Yes, retry it
                if(chatty) llOwnerSay("Reattempting rez of most recent piece");
                rez_object();
            }
        }
 
        //Open listener?
        if( iListenTimeout != 0 )
        {
            //Past our close timeout?
            if( iListenTimeout <= llGetUnixTime() )
            {
                iListenTimeout = 0;
                llListenRemove(MENU_HANDLE);
            }
        }
    }
 
    ///////////////////////////////////////////////////////////////////////////////
    on_rez(integer iStart)
    {
        //Reset ourselves
        llResetScript();
    }
}
 
 
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
state reset_listeners
{
    //////////////////////////////////////////////////////////////////////////////////////////
    state_entry()
    {
        state default;
    }
}
</syntaxhighlight>


== The Component Script ==
== The Component Script ==
  ///////////////////////////////////////////////////////////////////////////////
<syntaxhighlight lang="lsl2">
  // Builders' Buddy 1.9 (Component Pieces)
///////////////////////////////////////////////////////////////////////////////
  // by Newfie Pendragon, March 2006
// Builders' Buddy 1.10 (Component Script)
  //
// by Newfie Pendragon, 2006-2008
  // This script is distributed with permission that it may be used in
///////////////////////////////////////////////////////////////////////////////
  // any way, or may be further modified/included in resale items.
//
  // HOWEVER, if this script is used as part of a for-sale product,
// Script Purpose & Use
  // it is required that appropriate credit be given to Newfie for
// Functions are dependent on the "component script"
  // the script (or portions used in derivatives).  That's a fair price
//
  // in exchange for unlimited use of this script, dontcha think?
// QUICK USE:
  //
// - Drop this script in the Base.
  // SL Forum thread and new versions found here:
// - Drop the "Component" Script in each building part.
  // http://forums.secondlife.com/showthread.php?t=96792
// - Touch your Base, and choose RECORD
  ///////////////////////////////////////////////////////////////////////////////
// - Take all building parts into inventory
  // INSTRUCTIONS
// - Drag building parts from inventory into Base Prim
  // This is the *Component Piece* half of the Builders' Buddy system.
// - Touch your base and choose BUILD
  // Drop it into each 'piece' of the building.  Drop the Base Prim Script
//
  // into the prim  that will be the container/box that will be used to
// OTHER COMMANDS from the Touch menu
  // store the building once completed.  It can be in each individual
// - To reposition, move/rotate Base Prim choose POSITION
  // prim, but if you link as much as possible (and put the script in the link
// - To lock into position (removes scripts) choose DONE
  // set), it'll be much more neighbourly and less strain on the sim.
// - To delete building pieces: choose CLEAN
  //
///////////////////////////////////////////////////////////////////////////////
  // QUICK USE:
// This script is copyrighted material, and has a few (minor) restrictions.
  // - Drop this script in the Base.
// For complete details, including a revision history, please see
  // - Drop the "Component" Script in each building part.
//  http://wiki.secondlife.com/wiki/Builders_Buddy
  // - Touch  your Base, and choose RECORD
///////////////////////////////////////////////////////////////////////////////
  // - Take all building parts into inventory
 
  // - Drag building parts from inventory into Base Prim
 
  //  - Touch your base and choose BUILD
//////////////////////////////////////////////////////////////////////////////////////////
  //
// Configurable Settings
  ///////////////////////////////////////////////////////////////////////////////
float fTimerInterval = 0.25;        // Time in seconds between movement 'ticks'
  // History
integer DefaultChannel = -192567; // Andromeda Quonset's default channel
  //
integer PRIMCHAN = DefaultChannel;  // Channel used by Base Prim to talk to Component Prims;
  // v1.0 - 20060328 - Newfie Pendragon - Original Version
                                    // ***THIS MUST MATCH IN BOTH SCRIPTS!***
  // v1.5 - 20060612 - Androclese Antonelli
 
  //       - (See base script for details)
//////////////////////////////////////////////////////////////////////////////////////////
  // v1.6 - 20060624 - Newfie Pendragon
// Runtime Variables (Dont need to change below here unless making a derivative)
  //     - Added active repositioning (pieces move as the base piece moves)
vector vOffset;
  //     - Pieces use WarpPos technique to instantanetly move large distances
rotation rRotation;
  //       - Pieces no longer move until the the "Record" option has been used
integer bNeedMove;
  //       at least once
vector vDestPos;
  //     - Pieces will not move if base is not same owner as the pieces
rotation rDestRot;
  //     - Pieces no longer 'bounce' when hitting the ground
integer bMovingSingle = FALSE;
  // v1.7 - 20060821 - Correction for non-zero rotation (thanks Ed44 Gupta!)
integer bAbsolute = FALSE;
  // v1.9 - 20070630 - Added check to keep within sim edges
integer bRecorded = FALSE;
  ///////////////////////////////////////////////////////////////////////////////
 
 
 
  //////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
  // Configurable Settings
string first_word(string In_String, string Token)
  float fTimerInterval = 0.25;    // Time in seconds between movement 'ticks'
{
  integer PRIMCHAN = -19730611;    // Channel used by Base Prim to talk to Component Prims;
    //This routine searches for the first word in a string,
                                  // ***THIS MUST MATCH IN BOTH SCRIPTS!***
    // and returns it.  If no word boundary found, returns
 
    // the whole string.
  //////////////////////////////////////////////////////////////////////////////////////////
    if(Token == "") Token = " ";
  // Runtime Variables (Dont need to change below here unless making a derivative)
    integer pos = llSubStringIndex(In_String, Token);
  vector vOffset;
 
  rotation rRotation;
    //Found it?
  integer bNeedMove;
    if( pos >= 1 )
  vector vDestPos;
        return llGetSubString(In_String, 0, pos - 1);
  rotation rDestRot;
    else
  integer bRecorded = FALSE;
        return In_String;
 
}
 
 
  ////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
  string first_word(string In_String, string Token)
string other_words(string In_String, string Token)
  {
{
      //This routine searches for the first word in a string,
    //This routine searches for the other-than-first words in a string,
      // and returns it. If no word boundary found, returns
    // and returns it. If no word boundary found, returns
      // the whole string.
    // an empty string.
      if(Token == "") Token = " ";
    if( Token == "" ) Token = " ";
      integer pos = llSubStringIndex(In_String, Token);
 
 
    integer pos = llSubStringIndex(In_String, Token);
      //Found it?
 
      if( pos >= 1 )
    //Found it?
          return llGetSubString(In_String, 0, pos - 1);
    if( pos >= 1 )
      else
        return llGetSubString(In_String, pos + 1, llStringLength(In_String));
          return In_String;
    else
  }
        return "";
 
}
  ////////////////////////////////////////////////////////////////////////////////
 
  string other_words(string In_String, string Token)
////////////////////////////////////////////////////////////////////////////////
  {
do_move()
      //This routine searches for the other-than-first words in a string,
{
      // and returns it.  If no word boundary found, returns
    integer i = 0;
      // the an empty string.
    vector vLastPos = ZERO_VECTOR;
      if( Token == "" ) Token = " ";
    while( (i < 5) && (llGetPos() != vDestPos) )
 
    {
      integer pos = llSubStringIndex(In_String, Token);
        list lParams = [];
 
 
      //Found it?
        //If we're not there....
      if( pos >= 1 )
        if( llGetPos() != vDestPos )
          return llGetSubString(In_String, pos + 1, llStringLength(In_String));
        {
      else
            //We may be stuck on the ground...
          return "";
            //Did we move at all compared to last loop?
  }
            if( llGetPos() == vLastPos )
 
            {
  ////////////////////////////////////////////////////////////////////////////////
                //Yep, stuck...move straight up 10m (attempt to dislodge)
  do_move()
                lParams = [ PRIM_POSITION, llGetPos() + <0, 0, 10.0> ];
  {
                //llSetPos(llGetPos() + <0, 0, 10.0>);
      integer i = 0;
            } else {
      vector vLastPos = ZERO_VECTOR;
                //Record our spot for 'stuck' detection
      while( (i < 5) && (llGetPos() != vDestPos) )
                vLastPos = llGetPos();
      {
            }
          list lParams = [];
        }
 
 
          //If we're not there....
        //Try to move to destination
          if( llGetPos() != vDestPos )
        //Upgraded to attempt to use the llSetPrimitiveParams fast-move hack
          {
        //(Newfie, June 2006)
              //We may be stuck on the ground...
        integer iHops = llAbs(llCeil(llVecDist(llGetPos(), vDestPos) / 10.0));
              //Did we move at all compared to last loop?
        integer x;
              if( llGetPos() == vLastPos )
        for( x = 0; x < iHops; x++ ) {
              {
            lParams += [ PRIM_POSITION, vDestPos ];
                  //Yep, stuck...move straight up 10m (attempt to dislodge)
        }
                  lParams = [ PRIM_POSITION, llGetPos() + <0, 0, 10.0> ];
        llSetPrimitiveParams(lParams);
                  //llSetPos(llGetPos() + <0, 0, 10.0>);
        //llSleep(0.1);
              } else {
        i++;
                  //Record our spot for 'stuck' detection
    }
                  vLastPos = llGetPos();
 
              }
    //Set rotation
          }
    llSetRot(rDestRot);
 
}
          //Try to move to destination
 
          //Upgraded to attempt to use the llSetPrimitiveParams fast-move hack
start_move(string sText, key kID)
          //(Newfie, June 2006)
{
          integer iHops = llAbs(llCeil(llVecDist(llGetPos(), vDestPos) / 10.0));
    //Don't move if we've not yet recorded a position
          integer x;
    if( !bRecorded ) return;
          for( x = 0; x < iHops; x++ ) {
 
              lParams += [ PRIM_POSITION, vDestPos ];
    //Also ignore commands from bases with a different owner than us
          }
    //(Anti-hacking measure)
          llSetPrimitiveParams(lParams);
    if( llGetOwner() != llGetOwnerKey(kID) ) return;
          //llSleep(0.1);
 
          i++;
 
      }
    //Calculate our destination position relative to base?
 
    if(!bAbsolute) {
      //Set rotation
        //Relative position
      llSetRot(rDestRot);
        //Calculate our destination position
  }
        sText = other_words(sText, " ");
 
        list lParams = llParseString2List(sText, [ "|" ], []);
 
        vector vBase = (vector)llList2String(lParams, 0);
  //////////////////////////////////////////////////////////////////////////////////////////
        rotation rBase = (rotation)llList2String(lParams, 1);
  //////////////////////////////////////////////////////////////////////////////////////////
 
  //////////////////////////////////////////////////////////////////////////////////////////
        vDestPos = (vOffset * rBase) + vBase;
  default
        rDestRot = rRotation * rBase;
  {
    } else {
      //////////////////////////////////////////////////////////////////////////////////////////
        //Sim position
      state_entry()
        vDestPos = vOffset;
      {
        rDestRot = rRotation;
          //Open up the listener
    }
          llListen(PRIMCHAN, "", NULL_KEY, "");
 
      }
    //Make sure our calculated position is within the sim
 
    if(vDestPos.x < 0.0) vDestPos.x = 0.0;
      //////////////////////////////////////////////////////////////////////////////////////////
    if(vDestPos.x > 255.0) vDestPos.x = 255.0;
      on_rez(integer iStart)
    if(vDestPos.y < 0.0) vDestPos.y = 0.0;
      {
    if(vDestPos.y > 255.0) vDestPos.y = 255.0;
          //Set the channel to what's specified
    if(vDestPos.z > 4096.0) vDestPos.x = 4096.0;
          if( iStart != 0 )
 
          {
    //Turn on our timer to perform the move?
              PRIMCHAN = iStart;
    if( !bNeedMove )
              state reset_listeners;
    {
          }
        llSetTimerEvent(fTimerInterval);
      }
        bNeedMove = TRUE;
 
    }
      //////////////////////////////////////////////////////////////////////////////////////////
    return;
      listen(integer iChan, string sName, key kID, string sText)
}
      {
 
          string sCmd = llToUpper(first_word(sText, " "));
//////////////////////////////////////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////////////////////////////////////
          if( sCmd == "RECORD" )
//////////////////////////////////////////////////////////////////////////////////////////
          {
default
              sText = other_words(sText, " ");
{
              list lParams = llParseString2List(sText, [ "|" ], []);
    //////////////////////////////////////////////////////////////////////////////////////////
              vector vBase = (vector)llList2String(lParams, 0);
    state_entry()
              rotation rBase = (rotation)llList2String(lParams, 1);
    {
 
        //Open up the listener
              vOffset = (llGetPos() - vBase) / rBase;
        llListen(PRIMCHAN, "", NULL_KEY, "");
              rRotation = llGetRot() / rBase;
        llRegionSay(PRIMCHAN, "READYTOPOS");
              bRecorded = TRUE;
    }
              llOwnerSay("Recorded position.");
 
              return;
    //////////////////////////////////////////////////////////////////////////////////////////
          }
    on_rez(integer iStart)
 
    {
          //////////////////////////////////////////////////////////////////////////////////////////
        //Set the channel to what's specified
          if( sCmd == "MOVE" )
        if( iStart != 0 )
          {
        {
              //Don't move if we've not yet recorded a position
            PRIMCHAN = iStart;
              if( !bRecorded ) return;
            state reset_listeners;
 
        }
              //Also ignore commands from bases with a different owner than us
    }
              //(Anti-hacking measure)
 
              if( llGetOwner() != llGetOwnerKey(kID) ) return;
    //////////////////////////////////////////////////////////////////////////////////////////
 
    listen(integer iChan, string sName, key kID, string sText)
 
    {
              //Calculate our destination position
        string sCmd = llToUpper(first_word(sText, " "));
              sText = other_words(sText, " ");
 
              list lParams = llParseString2List(sText, [ "|" ], []);
        if( sCmd == "RECORD" )
              vector vBase = (vector)llList2String(lParams, 0);
        {
              rotation rBase = (rotation)llList2String(lParams, 1);
            //Record position relative to base prim
 
            sText = other_words(sText, " ");
              //Calculate our destination position
            list lParams = llParseString2List(sText, [ "|" ], []);
              vDestPos = (vOffset * rBase) + vBase;
            vector vBase = (vector)llList2String(lParams, 0);
              rDestRot = rRotation * rBase;
            rotation rBase = (rotation)llList2String(lParams, 1);
 
 
              //Make sure our calculated position is within the sim
            vOffset = (llGetPos() - vBase) / rBase;
              if(vDestPos.x < 0.0) vDestPos.x = 0.0;
            rRotation = llGetRot() / rBase;
              if(vDestPos.x > 255.0) vDestPos.x = 255.0;
            bAbsolute = FALSE;
              if(vDestPos.y < 0.0) vDestPos.y = 0.0;
            bRecorded = TRUE;
              if(vDestPos.y > 255.0) vDestPos.y = 255.0;
            llOwnerSay("Recorded position.");
              if(vDestPos.x > 768.0) vDestPos.x = 768.0;
            return;
 
        }
              //Turn on our timer to perform the move?
 
              if( !bNeedMove )
        if( sCmd == "RECORDABS" )
              {
        {
                  llSetTimerEvent(fTimerInterval);
            //Record absolute position
                  bNeedMove = TRUE;
            rRotation = llGetRot();
              }
            vOffset = llGetPos();
              return;
            bAbsolute = TRUE;
          }
            bRecorded = TRUE;
 
            llOwnerSay("Recorded sim position.");
          //////////////////////////////////////////////////////////////////////////////////////////
            return;
          if( sCmd == "DONE" )
        }
          {
 
              //We are done, remove script
        //////////////////////////////////////////////////////////////////////////////////////////
              llRemoveInventory(llGetScriptName());
        if( sCmd == "MOVE" )
              return;
        {
          }
            start_move(sText, kID);
 
            return;
          //////////////////////////////////////////////////////////////////////////////////////////
        }
          if( sCmd == "CLEAN" )
 
          {
        if( sCmd == "MOVESINGLE" )
              //Clean up
        {
              llDie();
            //If we haven't gotten this before, position ourselves
              return;
            if(!bMovingSingle) {
          }
                //Record that we are a single-prim move
 
                bMovingSingle = TRUE;
          //////////////////////////////////////////////////////////////////////////////////////////
 
          if( sCmd == "RESET" )
                //Now move it
          {
                start_move(sText, kID);
              llResetScript();
                return;
          }
            }
      }
        }
 
 
      //////////////////////////////////////////////////////////////////////////////////////////
        //////////////////////////////////////////////////////////////////////////////////////////
      timer()
        if( sCmd == "DONE" )
      {
        {
          //Turn ourselves off
            //We are done, remove script
          llSetTimerEvent(0.0);
            llRemoveInventory(llGetScriptName());
 
            return;
          //Do we need to move?
        }
          if( bNeedMove )
 
          {
        //////////////////////////////////////////////////////////////////////////////////////////
              //Perform the move and clean up
        if( sCmd == "CLEAN" )
              do_move();
        {
              bNeedMove = FALSE;
            //Clean up
          }
            llDie();
          return;
            return;
      }
        }
  }
 
 
        //////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////////////////////////
        if( sCmd == "RESET" )
  //////////////////////////////////////////////////////////////////////////////////////////
        {
  //////////////////////////////////////////////////////////////////////////////////////////
            llResetScript();
  state reset_listeners
        }
  {
    }
      //////////////////////////////////////////////////////////////////////////////////////////
 
      state_entry()
    //////////////////////////////////////////////////////////////////////////////////////////
      {
    timer()
          state default;
    {
      }
        //Turn ourselves off
  }
        llSetTimerEvent(0.0);
 
        //Do we need to move?
        if( bNeedMove )
        {
            //Perform the move and clean up
            do_move();
 
            //If single-prim move, announce to base we're done
            if(bMovingSingle) {
                llRegionSay(PRIMCHAN, "ATDEST");
            }
 
            //Done moving
            bNeedMove = FALSE;
        }
        return;
    }
}
 
 
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
state reset_listeners
{
    //////////////////////////////////////////////////////////////////////////////////////////
    state_entry()
    {
        state default;
    }
}
</syntaxhighlight>
 
== Change Log ==
* v1.0 - March 28, 2006 - Newfie Pendragon
** Original Version
----
* v1.1 - March 31, 2006 - Kalidor Lazarno
** Added a Dialog Engine to the base script
----
* v1.5 - June 12, 2006 - Androclese Antonelli
** Base Script:
*** Added a random number generator to the dialog engine to eliminate problems with multiple BB boxes cross-talking
*** Added a timer to the listen command to put it asleep after 10sec.
*** Added a Menu Description
*** Added a "creator" flag so the owner could use the same object with full menu options and only a single flag change
*** Added an "ingroup" flag to enable/disable the same group use function
*** Non-Admin usage cleans the inventory items as they spawn
----
* v1.6 - 20060624 - Newfie Pendragon
** Base Script:
*** Added active repositioning (building moves as the base piece moves)
*** Added "Reset" Option to unlink parts from base temporarily
*** Modified creator flag to automatically set based if owner is creator
*** Minor changes to improve code readability (for those learning LSL)
** Component Script:
*** Added active repositioning (pieces move as the base piece moves)
*** Pieces use WarpPos technique to instantanetly move large distances
*** Pieces no longer move until the the "Record" option has been used at least once
*** Pieces will not move if base is not same owner as the pieces
*** Pieces no longer 'bounce' when hitting the ground
----
* v1.7  - August 21, 2006
** Component Script:
*** Correction for non-zero rotation (thanks Ed44 Gupta!)
----
* v1.8 - 20070429 - Newfie Pendragon
** Base Script:
*** Added a variable to allow a user to tweak how long a listener is open, and changed the default to 30 seconds.
----
* v1.9 - 20070630 - Newfie Pendragon
** Base Script:
*** Changed to use llRegionSay - no more 96m max distance (same sim)
*** Changed rez sequence to be less affected by lag/gray goo fence
*** Timer always on, less code/more reliable
** Component Script:
***Added check to keep within sim edges
----
* v1.10 - February 9, 2008
** Base Script:
*** New setting '''creatorUUID''' - allows user to explicitly state creator of object, in case it differs from base prim
*** New setting '''deleteOnRez''' - Determines if script is to delete building prims from inventory as they are rezzed (setting to FALSE means building can be rezzed multiple times)
*** New setting '''allowClean''' - Determines if end user can use the 'CLEAN' menu option
*** New setting '''dieOnClean''' - Include base prim when cleaning
*** New setting '''recordSimLocation''' - when TRUE using 'RECORD' menu option, location is in exact sim coordinates instead of relative to base prim.
*** New setting '''bulkBuild''' - when set to FALSE, will rez/position each prim individually, ala Rez-Faux/etc
*** Bumped lag-wait timeout to 10 seconds (original was 3 seconds, way too short)
*** New setting '''SGCompatible''' - set to TRUE to enable ShapeGen support (see contributions by Andromeda Quonset)
*** Contributions by Huney Jewell:
**** Added configurable constant to determine, which Menu Options will be displayed
**** Menu Option 'Clean' now also deletes the prim which contains this script
*** Contributions by Andromeda Quonset:
**** Added Channel command to dialog box
**** Added second dialog box for inputting channel
**** Added changing to default channel when invoking RECORD function
**** Changed channel assignment on-rez, default channel, so ends with 0000
** Component Script:
*** Added logic to recognize sim-exact vs. relative position recording.
*** Added logic to aid in single prim rez/move feature
 
{{LSLC|Library}}
 
{{#vardefine:sort|Builder's Buddy}}

Latest revision as of 22:55, 30 May 2023

This is a repost of the current Builder's Buddy scripts, as originally released on the prior LSL wiki. There are two scripts; One goes in a "base" prim, which is the piece that is moved/rotated/etc. The component script goes into each linked set that makes up the rest of the large build. In short, only one base script, many component scripts.

The most current "official" version of these scripts is 1.10.


Help Documentation

Complete Step-by-Step help is posted here: Builder's Buddy Help Document . The help document also includes a short quick end-user guide for you to distribute to end-users of your products.


Obligatory Copyright Notice

There are only a few points here:

  • Use this script as you wish, to modify, sell, etc.
  • If you use this script in a for-sale product, please give credit to Newfie Pendragon as the creator of the script.
  • If you wish to modify this script and release the changes for public use, please submit changes to Newfie Pendragon. This is to ensure a consistent version numbering on releases, and to ensure changes are not wiped out in future releases.

The Base Script

///////////////////////////////////////////////////////////////////////////////
// Builders' Buddy 1.10 (Base Script)
// by Newfie Pendragon, 2006-2008
///////////////////////////////////////////////////////////////////////////////
//
// Script Purpose & Use
// Functions are dependent on the "component script"
//
// QUICK USE:
// - Drop this script in the Base.
// - Drop the "Component" Script in each building part.
// - Touch your Base, and choose RECORD
// - Take all building parts into inventory
// - Drag building parts from inventory into Base Prim
// - Touch your base and choose BUILD
//
// OTHER COMMANDS from the Touch menu
// - To reposition, move/rotate Base Prim choose POSITION
// - To lock into position (removes scripts) choose DONE
// - To delete building pieces: choose CLEAN
///////////////////////////////////////////////////////////////////////////////
// This script is copyrighted material, and has a few (minor) restrictions.
// For complete details, including a revision history, please see
//  http://wiki.secondlife.com/wiki/Builders_Buddy
///////////////////////////////////////////////////////////////////////////////

// Channel used by Base Prim to talk to Component Prims
// This channel must be the same one in the component script
// A negative channel is used because it elimited accidental activations
// by an Avatar talking on obscure channels
integer DefaultPRIMCHAN = -192567;     // Default channel to use
integer PRIMCHAN = DefaultPRIMCHAN;    // Channel used by Base Prim to talk to Component Prims;
                                       // ***THIS MUST MATCH IN BOTH SCRIPTS!***

//The UUID of the creator of the object
//Leave this as "" unless SL displays wrong name in object properties
key creatorUUID = "";

// Set to TRUE to allow group members to use the dialog menu
// Set to FALSE to disallow group members from using the dialog menu
integer ingroup = TRUE;

// Set to TRUE to delete piece from inventory when rezzed
// (WARNING) If set to FALSE, user will be able to rez multiple copies
integer deleteOnRez = FALSE;

// Allow non-creator to use CLEAN command?
// (WARNING) If set to TRUE, it is recommended to set
// deleteOnRez to FALSE, or user could lose entire building
integer allowClean = TRUE;

//When user selects CLEAN, delete the base prim too?
integer dieOnClean = FALSE;

// Set to TRUE to record piece's location based on sim
// coordinates instead of relationship to base prim
integer recordSimLocation = FALSE;

// Set to TRUE to rez all building pieces before positioning,
// or FALSE to do (slower?) one at a time
integer bulkBuild = TRUE;

//Set to FALSE if you dont want the script to say anything while 'working'
integer chatty = TRUE;

//How long to listen for a menu response before shutting down the listener
float fListenTime = 30.0;

//How often (in seconds) to perform any timed checks
float fTimerRate = 0.25;

//How long to sit still before exiting active mode
float fStoppedTime = 30.0;

//SL sometimes blocks rezzing to prevent "gray goo" attacks
//How long we wait (seconds) before we assume SL blocked our rez attempt
integer iRezWait = 10;

//Specify which Menu Options will be displayed
//FALSE will restrict full options to creator
//TRUE will offer full options to anyone
integer fullOptions = FALSE;

//Set to TRUE if you want ShapeGen channel support
// (Last 4 digits of channel affected)
integer SGCompatible = FALSE;


///////////////////////////////////////////////////////////////////////////////
//Part of KEYPAD CODE BY Andromeda Quonset....More added below in seevral places
list Menu2 = [ "-", "0","enter","7","8","9","4","5","6","1","2","3"];
string Input = "";
string Sign = "+";
string SignInput = " ";
string Caption = "Enter a number, include any leading 0's: ";

///////////////////////////////////////////////////////////////////////////////
// DO NOT EDIT BELOW THIS LINE.... NO.. NOT EVEN THEN
///////////////////////////////////////////////////////////////////////////////

//Name each option-these names will be your button names.
string optRecord = "Record";
string optReset = "Reset";
string optBuild = "Build";
string optPos = "Position";
string optClean = "Clean";
string optDone = "Done";
string optChannel = "Channel";

//Menu option descriptions
string descRecord = ": Record the position of all parts\n";
string descReset = ": Forgets the position of all parts\n";
string descBuild = ": Rez inv. items and position them\n";
string descPos = ": Reposition the parts to a new location\n";
string descClean = ": De-Rez all pieces\n";
string descDone = ": Remove all BB scripts and freeze parts in place.\n";
string descChannel = ": Change Channel used on base and parts.\n";

integer MENU_CHANNEL;
integer MENU2_CHANNEL;
integer MENU_HANDLE;
integer MENU2_HANDLE;
key agent;
key objectowner;
integer group;
string title = "";
list optionlist = [];
integer bMoving;
vector vLastPos;
rotation rLastRot;
integer bRezzing;
integer iListenTimeout = 0;
integer iLastRez = 0;
integer iRezIndex;


InvertSign()
{
    if(Sign == "+")
        Sign = "-";
    else
        Sign = "+";
}

//To avoid flooding the sim with a high rate of movements
//(and the resulting mass updates it will bring), we used
// a short throttle to limit ourselves
announce_moved()
{
    llRegionSay(PRIMCHAN, "MOVE " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
    llResetTime();        //Reset our throttle
    vLastPos = llGetPos();
    rLastRot = llGetRot();
    return;
}


rez_object()
{
    //Rez the object indicated by iRezIndex
    llRezObject(llGetInventoryName(INVENTORY_OBJECT, iRezIndex), llGetPos(), ZERO_VECTOR, llGetRot(), PRIMCHAN);
    iLastRez = llGetUnixTime();

    if(!bRezzing) {
        bRezzing = TRUE;
        //timer_on();
    }
}

post_rez_object()
{
    if ( creatorUUID != llGetOwner() ) {
        if(deleteOnRez) llRemoveInventory(llGetInventoryName(INVENTORY_OBJECT, iRezIndex));
    }
}

heard(integer channel, string name, key id, string message)
{
    if( channel == PRIMCHAN ) {
        if( message == "READYTOPOS" ) {
            //New prim ready to be positioned
            vector vThisPos = llGetPos();
            rotation rThisRot = llGetRot();
            llRegionSay(PRIMCHAN, "MOVESINGLE " + llDumpList2String([ vThisPos, rThisRot ], "|"));

        } else if( message == "ATDEST" ) {
            //Rez the next in the sequence (if any)
            iRezIndex--;
            if(iRezIndex >= 0) {
                //Attempt to rez it
                rez_object();
            } else {
                //We are done building, reset our listeners
                iLastRez = 0;
                bRezzing = FALSE;
                state reset_listeners;
            }
        }
        return;

    } else if( channel == MENU_CHANNEL ) {   //Process input from original menu
        if ( message == optRecord ) {
            PRIMCHAN = DefaultPRIMCHAN;
            llOwnerSay("Recording positions...");
            if(recordSimLocation) {
                //Location in sim
                llRegionSay(PRIMCHAN, "RECORDABS " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
            } else {
                //Location relative to base
                llRegionSay(PRIMCHAN, "RECORD " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
            }
            return;
        }
        if( message == optReset ) {
            llOwnerSay("Forgetting positions...");
            llShout(PRIMCHAN, "RESET");
            return;
        }
        if ( message == optBuild ) {
            if(chatty) llOwnerSay("Rezzing build pieces...");

            //If rezzing/positioning one at a time, we need
            // to listen for when they've reached their dest
            if(!bulkBuild) {
                llListen(PRIMCHAN, "", NULL_KEY, "READYTOPOS");
                llListen(PRIMCHAN, "", NULL_KEY, "ATDEST");
            }

            //Start rezzing, last piece first
            iRezIndex = llGetInventoryNumber(INVENTORY_OBJECT) - 1;
            rez_object();
            return;
        }
        if ( message == optPos ) {
            if(chatty) llOwnerSay("Positioning");
            vector vThisPos = llGetPos();
            rotation rThisRot = llGetRot();
            llRegionSay(PRIMCHAN, "MOVE " + llDumpList2String([ vThisPos, rThisRot ], "|"));
            return;
        }
        if ( message == optClean ) {
            llRegionSay(PRIMCHAN, "CLEAN");
            if(dieOnClean) llDie();
            return;
        }
        if ( message == optDone ) {
            llRegionSay(PRIMCHAN, "DONE");
            if(chatty) llOwnerSay("Removing Builder's Buddy scripts.");
            return;
        }
        if ( message == optChannel ) {
            Sign = "+"; //default is a positive number
            Input = "";
            llDialog( agent, Caption, Menu2, MENU2_CHANNEL );
        }

    } else if ( channel == MENU2_CHANNEL ) {    //process input from MENU2
        // if a valid choice was made, implement that choice if possible.
        // (llListFindList returns -1 if Choice is not in the menu list.)
        if ( llListFindList( Menu2, [ message ]) != -1 ) {
            if( llListFindList(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], [message]) != -1) {
                Input += message;
                SignInput = Sign + Input;
                llDialog( agent, Caption + SignInput, Menu2, MENU2_CHANNEL );

            } else if( message == "-" ) {
                InvertSign();
                SignInput = Sign + Input;
                llDialog( agent, Caption + SignInput, Menu2, MENU2_CHANNEL );

            } else if( message == "enter" ) {     //terminate input from menu2
                string CalcChan = Input;

            	//Apply ShapeGen compatibility?
            	if(SGCompatible) {
                    //new assign channel number, forcing last 4 digits to 0000
                	integer ChanSize = llStringLength(Input); //determine number of digits (chars)
	                if(ChanSize > 5) {
    	                CalcChan = llGetSubString(Input, 0, 4);    //Shorten to 5 digits
        	        }
                	CalcChan += "0000"; //append 0000
                    if(Sign == "-")
	                    CalcChan = Sign + CalcChan;
            	}
            	PRIMCHAN = (integer)CalcChan; //assign channel number
            	llOwnerSay("Channel set to " + (string)PRIMCHAN + ".");
            }

        } else {
            llDialog( agent, Caption, Menu2, MENU2_CHANNEL );
        }
    }
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
default {
    ///////////////////////////////////////////////////////////////////////////////
    changed(integer change) {
        if(change & CHANGED_OWNER)
        llResetScript();
    }

    ///////////////////////////////////////////////////////////////////////////////
    state_entry () {
        //Determine the creator UUID
        if(creatorUUID == "") creatorUUID = llGetCreator();

        //Use which menu?
        if (creatorUUID == llGetOwner() || fullOptions) {
            //Display all options
            optionlist = [optPos, optClean, optDone, optRecord, optReset, optBuild, optChannel];
            title = optRecord + descRecord;
            title += optReset + descReset;
            title += optBuild + descBuild;
            title += optPos + descPos;
            title += optClean + descClean;
            title += optDone + descDone;
            title += optChannel + descChannel;

        } else {
            //Display limited options
            if(allowClean) {
                optionlist = [optBuild, optPos, optClean, optDone];
                title = optBuild + descBuild;
                title += optPos + descPos;
                title += optClean + descClean;
                title += optDone + descDone;
            } else {
                optionlist = [optBuild, optPos, optDone];
                title = optBuild + descBuild;
                title += optPos + descPos;
                title += optDone + descDone;
            }
        }

        //Record our position
        vLastPos = llGetPos();
        rLastRot = llGetRot();

        llSetTimerEvent(fTimerRate);
    }

    ///////////////////////////////////////////////////////////////////////////////
    touch_start (integer total_number) {
        group = llDetectedGroup(0); // Is the Agent in the objowners group?
        agent = llDetectedKey(0); // Agent's key
        objectowner = llGetOwner(); // objowners key
        // is the Agent = the owner OR is the agent in the owners group
        if ( (objectowner == agent) || ( group && ingroup )  )  {
            iListenTimeout = llGetUnixTime() + llFloor(fListenTime);
            MENU_CHANNEL = llFloor(llFrand(-99999.0 - -100));
            MENU2_CHANNEL = MENU_CHANNEL + 1;
            MENU_HANDLE = llListen(MENU_CHANNEL,"","","");
            MENU2_HANDLE = llListen(MENU2_CHANNEL,"","","");
            if ( creatorUUID == llGetOwner() || fullOptions) {
                llDialog(agent,title + "Now on Channel " + (string)PRIMCHAN, optionlist, MENU_CHANNEL); //display channel number if authorized
            } else {
                llDialog(agent, title, optionlist, MENU_CHANNEL);
            }
            //timer_on();
        }
    }

    ///////////////////////////////////////////////////////////////////////////////
    listen(integer channel, string name, key id, string message) {
    	heard(channel, name, id, message);
    	return;
    }

    ///////////////////////////////////////////////////////////////////////////////
    moving_start()
    {
        if( !bMoving )
        {
            bMoving = TRUE;
            //timer_on();
            announce_moved();
        }
    }

    ///////////////////////////////////////////////////////////////////////////////
    object_rez(key id) {
        //The object rezzed, perform any post-rez processing
        post_rez_object();

        //Rezzing it all before moving?
        if(bulkBuild) {
            //Move on to the next object
            //Loop through backwards (safety precaution in case of inventory change)
            iRezIndex--;
            if(iRezIndex >= 0) {
                //Attempt to rez it
                rez_object();

            } else {
                //Rezzing complete, now positioning
                iLastRez = 0;
                bRezzing = FALSE;
                if(chatty) llOwnerSay("Positioning");
                llRegionSay(PRIMCHAN, "MOVE " + llDumpList2String([ llGetPos(), llGetRot() ], "|"));
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////////
    timer() {
        //Did we change position/rotation?
        if( (llGetRot() != rLastRot) || (llGetPos() != vLastPos) )
        {
            if( llGetTime() > fTimerRate ) {
                announce_moved();
            }
        }

        //Are we rezzing?
        if(bRezzing) {
            //Did the last one take too long?
            if((llGetUnixTime() - iLastRez) >= iRezWait) {
                //Yes, retry it
                if(chatty) llOwnerSay("Reattempting rez of most recent piece");
                rez_object();
            }
        }

        //Open listener?
        if( iListenTimeout != 0 )
        {
            //Past our close timeout?
            if( iListenTimeout <= llGetUnixTime() )
            {
                iListenTimeout = 0;
                llListenRemove(MENU_HANDLE);
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////////
    on_rez(integer iStart)
    {
        //Reset ourselves
        llResetScript();
    }
}


//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
state reset_listeners
{
    //////////////////////////////////////////////////////////////////////////////////////////
    state_entry()
    {
        state default;
    }
}

The Component Script

///////////////////////////////////////////////////////////////////////////////
// Builders' Buddy 1.10 (Component Script)
// by Newfie Pendragon, 2006-2008
///////////////////////////////////////////////////////////////////////////////
//
// Script Purpose & Use
// Functions are dependent on the "component script"
//
// QUICK USE:
// - Drop this script in the Base.
// - Drop the "Component" Script in each building part.
// - Touch your Base, and choose RECORD
// - Take all building parts into inventory
// - Drag building parts from inventory into Base Prim
// - Touch your base and choose BUILD
//
// OTHER COMMANDS from the Touch menu
// - To reposition, move/rotate Base Prim choose POSITION
// - To lock into position (removes scripts) choose DONE
// - To delete building pieces: choose CLEAN
///////////////////////////////////////////////////////////////////////////////
// This script is copyrighted material, and has a few (minor) restrictions.
// For complete details, including a revision history, please see
//  http://wiki.secondlife.com/wiki/Builders_Buddy
///////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////////////////
// Configurable Settings
float fTimerInterval = 0.25;        // Time in seconds between movement 'ticks'
integer DefaultChannel = -192567; // Andromeda Quonset's default channel
integer PRIMCHAN = DefaultChannel;  // Channel used by Base Prim to talk to Component Prims;
                                    // ***THIS MUST MATCH IN BOTH SCRIPTS!***

//////////////////////////////////////////////////////////////////////////////////////////
// Runtime Variables (Dont need to change below here unless making a derivative)
vector vOffset;
rotation rRotation;
integer bNeedMove;
vector vDestPos;
rotation rDestRot;
integer bMovingSingle = FALSE;
integer bAbsolute = FALSE;
integer bRecorded = FALSE;


////////////////////////////////////////////////////////////////////////////////
string first_word(string In_String, string Token)
{
    //This routine searches for the first word in a string,
    // and returns it.  If no word boundary found, returns
    // the whole string.
    if(Token == "") Token = " ";
    integer pos = llSubStringIndex(In_String, Token);

    //Found it?
    if( pos >= 1 )
        return llGetSubString(In_String, 0, pos - 1);
    else
        return In_String;
}

////////////////////////////////////////////////////////////////////////////////
string other_words(string In_String, string Token)
{
    //This routine searches for the other-than-first words in a string,
    // and returns it. If no word boundary found, returns
    // an empty string.
    if( Token == "" ) Token = " ";

    integer pos = llSubStringIndex(In_String, Token);

    //Found it?
    if( pos >= 1 )
        return llGetSubString(In_String, pos + 1, llStringLength(In_String));
    else
        return "";
}

////////////////////////////////////////////////////////////////////////////////
do_move()
{
    integer i = 0;
    vector vLastPos = ZERO_VECTOR;
    while( (i < 5) && (llGetPos() != vDestPos) )
    {
        list lParams = [];

        //If we're not there....
        if( llGetPos() != vDestPos )
        {
            //We may be stuck on the ground...
            //Did we move at all compared to last loop?
            if( llGetPos() == vLastPos )
            {
                //Yep, stuck...move straight up 10m (attempt to dislodge)
                lParams = [ PRIM_POSITION, llGetPos() + <0, 0, 10.0> ];
                //llSetPos(llGetPos() + <0, 0, 10.0>);
            } else {
                //Record our spot for 'stuck' detection
                vLastPos = llGetPos();
            }
        }

        //Try to move to destination
        //Upgraded to attempt to use the llSetPrimitiveParams fast-move hack
        //(Newfie, June 2006)
        integer iHops = llAbs(llCeil(llVecDist(llGetPos(), vDestPos) / 10.0));
        integer x;
        for( x = 0; x < iHops; x++ ) {
            lParams += [ PRIM_POSITION, vDestPos ];
        }
        llSetPrimitiveParams(lParams);
        //llSleep(0.1);
        i++;
    }

    //Set rotation
    llSetRot(rDestRot);
}

start_move(string sText, key kID)
{
    //Don't move if we've not yet recorded a position
    if( !bRecorded ) return;

    //Also ignore commands from bases with a different owner than us
    //(Anti-hacking measure)
    if( llGetOwner() != llGetOwnerKey(kID) ) return;


    //Calculate our destination position relative to base?
    if(!bAbsolute) {
        //Relative position
        //Calculate our destination position
        sText = other_words(sText, " ");
        list lParams = llParseString2List(sText, [ "|" ], []);
        vector vBase = (vector)llList2String(lParams, 0);
        rotation rBase = (rotation)llList2String(lParams, 1);

        vDestPos = (vOffset * rBase) + vBase;
        rDestRot = rRotation * rBase;
    } else {
        //Sim position
        vDestPos = vOffset;
        rDestRot = rRotation;
    }

    //Make sure our calculated position is within the sim
    if(vDestPos.x < 0.0) vDestPos.x = 0.0;
    if(vDestPos.x > 255.0) vDestPos.x = 255.0;
    if(vDestPos.y < 0.0) vDestPos.y = 0.0;
    if(vDestPos.y > 255.0) vDestPos.y = 255.0;
    if(vDestPos.z > 4096.0) vDestPos.x = 4096.0;

    //Turn on our timer to perform the move?
    if( !bNeedMove )
    {
        llSetTimerEvent(fTimerInterval);
        bNeedMove = TRUE;
    }
    return;
}

//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
default
{
    //////////////////////////////////////////////////////////////////////////////////////////
    state_entry()
    {
        //Open up the listener
        llListen(PRIMCHAN, "", NULL_KEY, "");
        llRegionSay(PRIMCHAN, "READYTOPOS");
    }

    //////////////////////////////////////////////////////////////////////////////////////////
    on_rez(integer iStart)
    {
        //Set the channel to what's specified
        if( iStart != 0 )
        {
            PRIMCHAN = iStart;
            state reset_listeners;
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////////
    listen(integer iChan, string sName, key kID, string sText)
    {
        string sCmd = llToUpper(first_word(sText, " "));

        if( sCmd == "RECORD" )
        {
            //Record position relative to base prim
            sText = other_words(sText, " ");
            list lParams = llParseString2List(sText, [ "|" ], []);
            vector vBase = (vector)llList2String(lParams, 0);
            rotation rBase = (rotation)llList2String(lParams, 1);

            vOffset = (llGetPos() - vBase) / rBase;
            rRotation = llGetRot() / rBase;
            bAbsolute = FALSE;
            bRecorded = TRUE;
            llOwnerSay("Recorded position.");
            return;
        }

        if( sCmd == "RECORDABS" )
        {
            //Record absolute position
            rRotation = llGetRot();
            vOffset = llGetPos();
            bAbsolute = TRUE;
            bRecorded = TRUE;
            llOwnerSay("Recorded sim position.");
            return;
        }

        //////////////////////////////////////////////////////////////////////////////////////////
        if( sCmd == "MOVE" )
        {
            start_move(sText, kID);
            return;
        }

        if( sCmd == "MOVESINGLE" )
        {
            //If we haven't gotten this before, position ourselves
            if(!bMovingSingle) {
                //Record that we are a single-prim move
                bMovingSingle = TRUE;

                //Now move it
                start_move(sText, kID);
                return;
            }
        }

        //////////////////////////////////////////////////////////////////////////////////////////
        if( sCmd == "DONE" )
        {
            //We are done, remove script
            llRemoveInventory(llGetScriptName());
            return;
        }

        //////////////////////////////////////////////////////////////////////////////////////////
        if( sCmd == "CLEAN" )
        {
            //Clean up
            llDie();
            return;
        }

        //////////////////////////////////////////////////////////////////////////////////////////
        if( sCmd == "RESET" )
        {
            llResetScript();
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////////
    timer()
    {
        //Turn ourselves off
        llSetTimerEvent(0.0);

        //Do we need to move?
        if( bNeedMove )
        {
            //Perform the move and clean up
            do_move();

            //If single-prim move, announce to base we're done
            if(bMovingSingle) {
                llRegionSay(PRIMCHAN, "ATDEST");
            }

            //Done moving
            bNeedMove = FALSE;
        }
        return;
    }
}


//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
state reset_listeners
{
    //////////////////////////////////////////////////////////////////////////////////////////
    state_entry()
    {
        state default;
    }
}

Change Log

  • v1.0 - March 28, 2006 - Newfie Pendragon
    • Original Version

  • v1.1 - March 31, 2006 - Kalidor Lazarno
    • Added a Dialog Engine to the base script

  • v1.5 - June 12, 2006 - Androclese Antonelli
    • Base Script:
      • Added a random number generator to the dialog engine to eliminate problems with multiple BB boxes cross-talking
      • Added a timer to the listen command to put it asleep after 10sec.
      • Added a Menu Description
      • Added a "creator" flag so the owner could use the same object with full menu options and only a single flag change
      • Added an "ingroup" flag to enable/disable the same group use function
      • Non-Admin usage cleans the inventory items as they spawn

  • v1.6 - 20060624 - Newfie Pendragon
    • Base Script:
      • Added active repositioning (building moves as the base piece moves)
      • Added "Reset" Option to unlink parts from base temporarily
      • Modified creator flag to automatically set based if owner is creator
      • Minor changes to improve code readability (for those learning LSL)
    • Component Script:
      • Added active repositioning (pieces move as the base piece moves)
      • Pieces use WarpPos technique to instantanetly move large distances
      • Pieces no longer move until the the "Record" option has been used at least once
      • Pieces will not move if base is not same owner as the pieces
      • Pieces no longer 'bounce' when hitting the ground

  • v1.7 - August 21, 2006
    • Component Script:
      • Correction for non-zero rotation (thanks Ed44 Gupta!)

  • v1.8 - 20070429 - Newfie Pendragon
    • Base Script:
      • Added a variable to allow a user to tweak how long a listener is open, and changed the default to 30 seconds.

  • v1.9 - 20070630 - Newfie Pendragon
    • Base Script:
      • Changed to use llRegionSay - no more 96m max distance (same sim)
      • Changed rez sequence to be less affected by lag/gray goo fence
      • Timer always on, less code/more reliable
    • Component Script:
      • Added check to keep within sim edges

  • v1.10 - February 9, 2008
    • Base Script:
      • New setting creatorUUID - allows user to explicitly state creator of object, in case it differs from base prim
      • New setting deleteOnRez - Determines if script is to delete building prims from inventory as they are rezzed (setting to FALSE means building can be rezzed multiple times)
      • New setting allowClean - Determines if end user can use the 'CLEAN' menu option
      • New setting dieOnClean - Include base prim when cleaning
      • New setting recordSimLocation - when TRUE using 'RECORD' menu option, location is in exact sim coordinates instead of relative to base prim.
      • New setting bulkBuild - when set to FALSE, will rez/position each prim individually, ala Rez-Faux/etc
      • Bumped lag-wait timeout to 10 seconds (original was 3 seconds, way too short)
      • New setting SGCompatible - set to TRUE to enable ShapeGen support (see contributions by Andromeda Quonset)
      • Contributions by Huney Jewell:
        • Added configurable constant to determine, which Menu Options will be displayed
        • Menu Option 'Clean' now also deletes the prim which contains this script
      • Contributions by Andromeda Quonset:
        • Added Channel command to dialog box
        • Added second dialog box for inputting channel
        • Added changing to default channel when invoking RECORD function
        • Changed channel assignment on-rez, default channel, so ends with 0000
    • Component Script:
      • Added logic to recognize sim-exact vs. relative position recording.
      • Added logic to aid in single prim rez/move feature