Pathfinding Simple Roaming Example

From Second Life Wiki
Jump to navigation Jump to search

A simple example is provided to use Pathfinding to have an object randomly roam around a small area is given below. The script is designed to have the object move around in a small area centered at the start location. The object will move back to its start position if anything goes wrong or a group member touches it.

The script has three states. In the default state the object is not moving and can be placed in its start position. From the default state it can transition into the Roaming state by touching it as a member of the same group. In the Roaming state it will simply wander around in a small area centered at the start point. If anything goes wrong with the Pathfinding functions or a group member touches it, the script will transition into the TravelHome state.

In the TravelHome state the script will attempt to move the object back to its start position using the Pathfinding functions. If anything goes wrong with the Pathfinding functions or a group member touches it, the script will delete the Pathfinding character and manually move the object back to its start position using the llSetRegionPos function. It will then transition back into the default state.

This code is licensed under the GNU General Public License.

/* *****************************************************************

   %%% Desc:   Simple example script to demonstrate pathfinding
   %%% Author: Dark Mole (contact via IM within Second Life)
   %%% File:   $HeadURL: simpleMover.sl $
   %%% Date:   $Date: Thu Oct 11 04:44:14 2012 $
   %%% Revision: $Rev: .04 $

   
   Copyright 2012 Linden Lab

   This program is free software: you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation, either version 3 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
   MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see
   <http://www.gnu.org/licenses/>.


   A copy of this script can be found at 
   https://wiki.secondlife.com/wiki/Pathfinding_Simple_Roaming_Example


***************************************************************** */

// Configuration variables.
// Things to change to impact the behavior of the system


float TIME_TO_WAIT_BEFORE_MOVING = 300.0; // number of seconds to wait
                                          // while in the default
                                          // state before it
                                          // automatically starts to
                                          // roam on its own. Note
                                          // that the first time this
                                          // thing rezzes this timer
                                          // is ignored.

// Global Variables. (do not change unless you know what they do!)

// Location related variables

vector   home_position = <-1.0,0.0,0.0>;
rotation home_rotation = ZERO_ROTATION;
string   home_region   = "";

vector wondering_radius = <5.0,5.0,20.0>; // how far I can roam around
list   wondering_parameters = 
  [CHARACTER_MAX_SPEED,         1.0,        // parameters describing
   CHARACTER_DESIRED_SPEED,     0.2,       // how I can roam.
   CHARACTER_DESIRED_TURN_SPEED,0.02,
   CHARACTER_MAX_ACCEL,         0.5,
   CHARACTER_MAX_DECEL,         0.5,
   CHARACTER_ORIENTATION,       VERTICAL,
   TRAVERSAL_TYPE,              TRAVERSAL_TYPE_SLOW,
   CHARACTER_TYPE,              CHARACTER_TYPE_A, 
   CHARACTER_MAX_TURN_RADIUS,   0.5];
   

list traveling_parameters = 
  [CHARACTER_MAX_SPEED,         5.0,        // parameters describing
   CHARACTER_DESIRED_SPEED,     3.0,        // how I can roam when 
   CHARACTER_DESIRED_TURN_SPEED,2.0,        // traveling..
   CHARACTER_MAX_ACCEL,         3.0,
   CHARACTER_MAX_DECEL,         3.0,
   CHARACTER_ORIENTATION,       VERTICAL,
   TRAVERSAL_TYPE,              TRAVERSAL_TYPE_SLOW,
   CHARACTER_TYPE,              CHARACTER_TYPE_A, 
   CHARACTER_MAX_TURN_RADIUS,   1.5];


// Variables associated with the state of the script
integer active_character = FALSE;


// Helper functions 

/* *********************************************************
   forceMoveHome(integer resetScript)

   Routine to force the object to go home. The option "resetScript" is
   to determine if the script should be reset when it is in the same
   region it started from.

********************************************************* */
forceMoveHome(integer resetScript)
{
  // Stop and delete the character
  llExecCharacterCmd(CHARACTER_CMD_SMOOTH_STOP,[]);
  llDeleteCharacter();
  active_character = FALSE;
  if(home_region == llGetRegionName())
    {
      // I am in the same home region. Just move close to the start
      // point.
      llSetRegionPos(home_position);
      if(resetScript)
        {
          llSetPos(home_position);
          llSetRot(home_rotation);
          llResetScript();
        }
    }
  else
    {
      llInstantMessage(llGetOwner(),"There is an error. " +
                       "I wondered away from my start region and cannot go back home.\n" +
                       "I am located in " + llGetRegionName() + " at " + 
                       (string)llGetPos());
    }
}


// Primary (default) state.
// This is the initial state when the program resets.
//
// In this state the object waits for a touch to get started. There is
// a random chance that it might start moving at any time step though.
default
{

    state_entry()
      {
        if(home_position.x < 0.0)
          {
            // This is the first time to enter this state. Do a little
            // house cleaning to start the season.

            // Add any parts to the different parameters that require any
            // calculations.
            wondering_parameters += 
              [CHARACTER_AVOIDANCE_MODE, AVOID_CHARACTERS | AVOID_DYNAMIC_OBSTACLES];
            traveling_parameters += 
              [CHARACTER_AVOIDANCE_MODE, AVOID_CHARACTERS | AVOID_DYNAMIC_OBSTACLES];

            // Set the initial home position and orientation.
            home_position = llGetPos();
            home_rotation = llGetRot();
            home_region   = llGetRegionName();
            llOwnerSay("Touch me to start moving. " +
                       "I will not move until you touch me the first time. " +
                       "After that it will become automatic.");

          }

        else if(home_region == llGetRegionName())
          {
            // I am in the same region I started out in. Make sure I
            // start at the last place and orientation when I left
            // this state.  Make sure that I move close to
            // home. (There is some fudge factor in the llSetRegionPos
            // command according to the wiki).
            llSetPos(home_position);
            llSetRot(home_rotation);
            llSetTimerEvent(TIME_TO_WAIT_BEFORE_MOVING);
          }


        llDeleteCharacter(); // Clear and reset the character
                             // settings. You should always do this so
                             // that when an active object is taken
                             // into inventory it will be properly
                             // reset when rezzed again. (If not here
                             // then in the on_rez event.)
        active_character = FALSE;

        llOwnerSay("Stopped\nTouch me to have me wonder around.");
      } // state_entry

    state_exit()
      {
        llCreateCharacter(wondering_parameters);
        active_character = TRUE;
        llSetTimerEvent(0.0);
      } // state_exit

    on_rez(integer param)
      {
        llResetScript();
      }  // on_rez

    changed( integer change)
      {
        if(change & CHANGED_REGION_START)
          {
            // The simulator this thing is on has reset
            forceMoveHome(TRUE);
          }
      }

    touch_start(integer total_number)
      {
        if(llDetectedGroup(0))
           {
             // This person is in the same group. Assume the idiot can
             // be trusted.
             home_position = llGetPos();        // Define my start position
             home_rotation = llGetRot();        // and start orientation.
             state Roaming;
           }
      } // touch_start

    timer()
      {
        // I have waited to long without any interaction. Time to roam
        // because I am a ramblin' object.
        state Roaming;
      }


} // end default state



// Roaming state.  
//
// This is the state when the program decides to start roaming.  I
// strongly prefer not to use all these states, but it makes for a
// much cleaner code in this case and a better demo.
//
// In this state the object simply roams around for a while. 
state Roaming
{

    state_entry()
      {
        // Set the character to the roaming parameters
        llUpdateCharacter(wondering_parameters);
        llWanderWithin(home_position, wondering_radius, []);
        llOwnerSay("Roaming\nTouch me to have me go back home.");
      } // state_entry

    state_exit()
      {
        llExecCharacterCmd(CHARACTER_CMD_SMOOTH_STOP,[]);
      } // state_exit

    on_rez(integer param)
      {
        llResetScript();
      }  // on_rez

    changed( integer change)
      {
        if(change & CHANGED_REGION_START)
          {
            // The simulator this thing is on has reset
            forceMoveHome(TRUE);
            state default;
          }
      }

    touch_start(integer total_number)
      {
        if(llDetectedGroup(0))
          {
            // Again, if the person is in the same group then let them
            // violate me by touching me.
            state TravelHome;
          }
      } // touch_start


    path_update(integer type, list reserved)
      {
        if ((type == PU_SLOWDOWN_DISTANCE_REACHED) || 
            (type == PU_GOAL_REACHED))
          {
            // While roaming this should not occur. if it does just restart???
            llWanderWithin(home_position, wondering_radius, []);            
          }

        else {
          // Something went wrong with the navmesh. Just go home and wait.
          state TravelHome;
        }


      } // end path_update event


} // end state Roaming


// TravelHome state.  
//
// This is the state when the program decides to go home.  I
// strongly prefer not to use all these states, but it makes for a
// much cleaner code in this case and a better demo.
//
// In this state the object simply tries to go to the home
// position. If it does not make it in the alloted time the character
// is deleted, and it simply sets its position.
state TravelHome
{

    state_entry()
      {
        // Set the character to the traveling parameters and set a
        // timer to make sure something eventually happens here.
        llUpdateCharacter(traveling_parameters);
        llNavigateTo(home_position,[FORCE_DIRECT_PATH,TRUE]);
        llSetTimerEvent(120.0);
        llOwnerSay("Going back home\nTouch me to force the issue.");
      } // state_entry

    state_exit()
      {
        llSetTimerEvent(0.0);
        llExecCharacterCmd(CHARACTER_CMD_SMOOTH_STOP,[]);
      }  // state_exit

    on_rez(integer param)
      {
        llResetScript();
      }  // on_rez

    changed( integer change)
      {
        if(change & CHANGED_REGION_START)
          {
            // The simulator this thing is on has reset
            forceMoveHome(TRUE);
            state default;
          }
      }

    touch_start(integer total_number)
      {
        // force the issue
        if(llDetectedGroup(0))
          {
            // trust in the authority invested in the people in my
            // group because blind trust always works out well.
            forceMoveHome(FALSE);
            state default;
          }
      } // touch_start

    timer()
      {
        // It took too long to get home.
        forceMoveHome(FALSE);
        state default;
      } // timer

    path_update(integer type, list reserved)
      {
        if (type == PU_SLOWDOWN_DISTANCE_REACHED)
          {
            // I am close to home. 
            llSetTimerEvent(10.0);
          }

        else if (type == PU_GOAL_REACHED)
          {
            // I have made it home.
            state default;
          }

        else 
          {
            // Something went wrong. Just force the issue and go home.
            forceMoveHome(FALSE);
            state default;
          }

      } // end path_update event


} // end state TravelHome