Pathfinding Simple Roaming Example With Evade Pursue
A simple example is provided to use Pathfinding to have an object randomly roam around a small area is given below and randomly evade or pursue a person who touches it. 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 five 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.
If someone touches this object while it is roaming who is not in the same group there is a small chance that it will either evade or pursue the person. If the decision is to evade then the script transitions to the Evade_Agent state. If the decision is to chase the agent then the script transitions to the Chase_Agent state.
This code is licensed under the General Public License.
/* *****************************************************************
%%% Desc: Simple example script to demonstrate pathfinding
%%% Author: Dark Mole (contact via IM within Second Life)
%%% File: $HeadURL: simpleMoverWithChaseAndEvade.sl $
%%% Date: $Date: Thu Oct 11 04:44:14 2012 $
%%% Revision: $Rev: .04 $
Copyright 2012 Dark Mole (contact via IM in Second Life
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_With_Evade_Pursue
***************************************************************** */
// 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];
list chasing_parameters =
[CHARACTER_MAX_SPEED, 15.0, // parameters describing
CHARACTER_DESIRED_SPEED, 10.0, // how I can chase someone.
CHARACTER_DESIRED_TURN_SPEED,8.0,
CHARACTER_MAX_ACCEL, 7.5,
CHARACTER_MAX_DECEL, 7.5,
CHARACTER_ORIENTATION, VERTICAL,
TRAVERSAL_TYPE, TRAVERSAL_TYPE_FAST,
CHARACTER_TYPE, CHARACTER_TYPE_A,
CHARACTER_MAX_TURN_RADIUS, 0.8,
CHARACTER_AVOIDANCE_MODE, AVOID_DYNAMIC_OBSTACLES];
// Variables associated with the state of the script
integer active_character = FALSE;
key agentToChase = NULL_KEY;
// 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;
}
else if (llFrand(1.0) < 0.10)
{
// What the heck. Let's chase this person!
agentToChase = llDetectedKey(0);
state Chase_Agent;
}
else if (llFrand(1.0) < 0.10)
{
// What the heck. Run away! Run Away!
agentToChase = llDetectedKey(0);
state Evade_Agent;
}
} // 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
// Chase_Agent state.
//
// This is the state when the program decides to chase the person who
// touched the object. Touch it again to go back to wandering.
//
state Chase_Agent
{
state_entry()
{
// Set the character to the traveling parameters and set a
// timer to make sure something eventually happens here.
// Get information about the person
list agentInfo = llGetObjectDetails(agentToChase,[OBJECT_ROT]);
if(llGetListLength(agentInfo)==0)
{
// well that was a bust. Cannot find this person. Just go
// back to roaming.
state Roaming;
}
// I can locate the agent. Let's chase it down.
// Need to get the offset separately, because the z-component
// will not be zero due to round off errors. The purse offset
// has to have a zero z component.
vector offset = <-1.0,0.0,0.0>*llList2Rot(agentInfo,0);
llUpdateCharacter(chasing_parameters);
llPursue(agentToChase,
[PURSUIT_OFFSET, <offset.x,offset.y,0.0>,
REQUIRE_LINE_OF_SIGHT, FALSE,
PURSUIT_FUZZ_FACTOR, 0.8,
PURSUIT_INTERCEPT, TRUE,
PURSUIT_GOAL_TOLERANCE,1.0]);
llSetTimerEvent(120.0);
llOwnerSay("Chasing this person\nTouch me to stop chasing.");
} // 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)
{
// Stop chasing and go back to wandering mode
state Roaming;
} // touch_start
timer()
{
// This is enough of this nonsense. I am tired. Start
// wandering again.
state Roaming;
} // 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) || (type == PU_FAILURE_TARGET_GONE))
{
// I have found the agent or they are gone. Let's just put
// an end to this.
state Roaming;
}
else
{
// Something went wrong. Just end this now.
state Roaming;
}
} // end path_update event
} // end state Chase_Agent
// Evade_Agent state.
//
// This is the state when the program decides to run away from the
// person who touched the object. Touch it again to go back to
// wandering.
//
state Evade_Agent
{
state_entry()
{
// Set the character to the traveling parameters and set a
// timer to make sure something eventually happens here. Here
// I just use the same settings as used to chase since they
// seem okay. /me shrugs.
llUpdateCharacter(chasing_parameters);
llEvade(agentToChase,[]);
llSetTimerEvent(120.0);
llOwnerSay("Evading this person\nTouch me to stop chasing.");
} // 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)
{
// Stop evading and go back to wandering mode
state Roaming;
} // touch_start
timer()
{
// This is enough of this nonsense. I am tired. Start
// wandering again.
state Roaming;
} // 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) || (type == PU_FAILURE_TARGET_GONE))
{
// I have found the agent or they are gone. Let's just put
// an end to this.
state Roaming;
}
else if ((type == PU_EVADE_HIDDEN) || (type == PU_EVADE_SPOTTED))
{
// I have been spotted. Oh noze.... panic seems like an
// appropriate plan at this point.
vector mySize = llGetScale();
if(mySize.z*0.25 > 2.0)
{
// Cannot jump this high. Tone it down a bit
mySize.z = 8.0;
}
llExecCharacterCmd(CHARACTER_CMD_JUMP,[mySize.z*0.25]);
}
else
{
// Something went wrong. Just end this now.
state Roaming;
}
} // end path_update event
} // end state Evade_Agent