Pathfinding Simple Roaming Example

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