Builders Buddy

From libopenmetaverse - libomv - Developer Wiki

Jump to: navigation, search

The Base script:

 
///////////////////////////////////////////////////////////////////////////////
// Builders' Buddy 1.9 (Base Script)
// by Newfie Pendragon, March 2006
//
// 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,
// it is required that appropriate credit be given to Newfie for
// the script (or portions used in derivatives).  That's a fair price
// in exchange for unlimited use of this script, dontcha think?
//
//  SL Forum thread and new versions found here:
//  http://forums.secondlife.com/showthread.php?t=96792
///////////////////////////////////////////////////////////////////////////////
//
//  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
//
///////////////////////////////////////////////////////////////////////////////
//
//  History
//
// v1.0 - 20060328 - Newfie Pendragon
//      - Original Version
// v1.1 - 20060331 - Kalidor Lazarno
//      - Added a Dialog Engine to the base script
// v1.5 - 20060612 - Androclese Antonelli
//      - Added a random number generator to the dialog engine to elimintate
//        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 n "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
//      - 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)
// v1.8 - 20070429 - Newfie Pendragon
//      - 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
//      - 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
///////////////////////////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////////////////////////////////////
// Configurable Settings
 
 
// 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 PRIMCHAN = -19730611;    // Channel used by Base Prim to talk to Component Prims;
                                 // ***THIS MUST MATCH IN BOTH SCRIPTS!***
 
// 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 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 = 3;
 
 
///////////////////////////////////////////////////////////////////////////////
// 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";
 
//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";
 
integer MENU_CHANNEL;
integer MENU_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;
//integer bTimerOn = FALSE;
 
//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 ( llGetCreator() != llGetOwner() )
        llRemoveInventory(llGetInventoryName(INVENTORY_OBJECT, iRezIndex));
}
 
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
default {
    ///////////////////////////////////////////////////////////////////////////////
    changed(integer change) {
        if(change & CHANGED_OWNER)
        llResetScript();
    }
 
    ///////////////////////////////////////////////////////////////////////////////
    state_entry () {
        //Use which menu?
        if ( llGetCreator() == llGetOwner() ) {
            //Display all options
            optionlist = [optPos, optClean, optDone, optRecord, optReset, optBuild];
            title = optRecord + descRecord;
            title += optReset + descReset;
            title += optBuild + descBuild;
            title += optPos + descPos;
            title += optClean + descClean;
            title += optDone + descDone;
 
        } else {
            //Display limited options
            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));
            MENU_HANDLE = llListen(MENU_CHANNEL,"","","");
            llDialog(agent, title, optionlist, MENU_CHANNEL);
            //timer_on();
        }
    }
 
    ///////////////////////////////////////////////////////////////////////////////
    listen(integer channel, string name, key id, string message) {
        if ( message == optRecord ) {
            llOwnerSay("Recording positions...");
            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...");
            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");
            return;
        }
        if ( message == optDone ) {
            llRegionSay(PRIMCHAN, "DONE");
            if(chatty) llOwnerSay("Removing Builder's Buddy scripts.");
            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();
 
        //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();
    }
}
 


The Component script:

 
///////////////////////////////////////////////////////////////////////////////
// Builders' Buddy 1.9 (Component Pieces)
// by Newfie Pendragon, March 2006
//
// 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,
// it is required that appropriate credit be given to Newfie for
// the script (or portions used in derivatives).  That's a fair price
// in exchange for unlimited use of this script, dontcha think?
//
//  SL Forum thread and new versions found here:
//  http://forums.secondlife.com/showthread.php?t=96792
///////////////////////////////////////////////////////////////////////////////
// INSTRUCTIONS
//  This is the *Component Piece* half of the Builders' Buddy system.
//  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
//  store the building once completed.  It can be in each individual
//  prim, but if you link as much as possible (and put the script in the link
//  set), it'll be much more neighbourly and less strain on the sim.
//
// 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
//
///////////////////////////////////////////////////////////////////////////////
//  History
//
// v1.0 - 20060328 - Newfie Pendragon - Original Version
// v1.5 - 20060612 - Androclese Antonelli
//        - (See base script for details)
// v1.6 - 20060624 - Newfie Pendragon
//      - 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 - 20060821 - Correction for non-zero rotation (thanks Ed44 Gupta!)
// v1.9 - 20070630 - Added check to keep within sim edges
///////////////////////////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////////////////////////////////////
// Configurable Settings
float fTimerInterval = 0.25;     // Time in seconds between movement 'ticks'
integer PRIMCHAN = -19730611;    // 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 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
    // the 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);
}
 
 
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
default
{
    //////////////////////////////////////////////////////////////////////////////////////////
    state_entry()
    {
        //Open up the listener
        llListen(PRIMCHAN, "", NULL_KEY, "");
    }
 
    //////////////////////////////////////////////////////////////////////////////////////////
    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" )
        {
            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;
            bRecorded = TRUE;
            llOwnerSay("Recorded position.");
            return;
        }
 
        //////////////////////////////////////////////////////////////////////////////////////////
        if( sCmd == "MOVE" )
        {
            //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
            sText = other_words(sText, " ");
            list lParams = llParseString2List(sText, [ "|" ], []);
            vector vBase = (vector)llList2String(lParams, 0);
            rotation rBase = (rotation)llList2String(lParams, 1);
 
            //Calculate our destination position
            vDestPos = (vOffset * rBase) + vBase;
            rDestRot = rRotation * rBase;
 
            //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.x > 768.0) vDestPos.x = 768.0;
 
            //Turn on our timer to perform the move?
            if( !bNeedMove )
            {
                llSetTimerEvent(fTimerInterval);
                bNeedMove = TRUE;
            }
            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();
            bNeedMove = FALSE;
        }
        return;
    }
}
 
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
state reset_listeners
{
    //////////////////////////////////////////////////////////////////////////////////////////
    state_entry()
    {
        state default;
    }
}