Table of Contents

Shortnote

The following is a Wandering NPC created in OpenSim. It uses the same technique as in the Second Life wanderer, but applies the calculations, and a little bit extra, to OpenSim NPCs. Please refer to the super section to find out how to configure your OpenSim server in order to activate the necessary options that will allow you to create NPCs.

Video

Script Configuration

The script can be configured in the configuration section. It has to be placed along with two animations, for walking, respectively standing in the same primitive as this script. By default, using the configuration options below, OpenSim rezzes a clone of the owner that starts to wander around in a 5 meter radius circle.

The STANDING_ANIMATION_CYCLE_TIME represents how much time it takes for the standing animation to repeat. Using the default settings, the NPC will travel to a certain point and will trigger the standing animation for a random amount of STANDING_ANIMATION_CYCLES, each of them taking STANDING_ANIMATION_CYCLE_TIME seconds. This gives the NPC a fluid motion as well as takes care of the transitions from walking to standing and vice-versa.

Design Limitations and Considerations

Code

This script was tested and works on OpenSim version 0.7.4!

wandering_npc.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2012 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                  CONFIGURATION                       //
//////////////////////////////////////////////////////////
 
// Specifies the radius of the circle in which the NPC
// will travel in.
float MOVEMENT_RANGE = 5.0;
// How often to check that the NPC has reached a 
// waypoint.
float POLL_TIME = 0.1;
// Set this to the name of the animation for the walk
// equence. This animation has to be placed in the
// same primitive as this script.
string ANIMATION_WALK="Walk";
// Set this to the name of the animation for the stand
// sequence. This animation has to be placed in the 
// same primitive as this script.
string ANIMATION_STAND="Stand";
// How much time, in seconds, does a standing animation 
// cycle take? 
float STANDING_ANIMATION_CYCLE_TIME = 20;
// How many cycles to wait, randomly?
integer STANDING_ANIMATION_CYCLES = 3;
 
///////////////////////////////////////////////////////////////////////////
//                              INTERNALS                                //
///////////////////////////////////////////////////////////////////////////
 
vector wasCirclePoint(float radius) {
    float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
    float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
    if(llPow(x,2) + llPow(y,2) <= llPow(radius,2))
        return <x, y, 0>;
    return wasCirclePoint(radius);
}
 
// Vector that will be filled by the script with
// the initial starting position in region coordinates.
vector iPos = ZERO_VECTOR;
// Storage for destination position.
vector dPos = ZERO_VECTOR;
// Key of the NPC
key npcKey = NULL_KEY; 
 
default {
    state_entry() {
        llSetStatus(STATUS_PHANTOM, TRUE);
        osNpcRemove(llGetObjectDesc());
        iPos = llGetPos();
        osAgentSaveAppearance(llGetOwner(), "appearance");
        npcKey = osNpcCreate("Alter", "Ego", iPos, "appearance");
        llSetObjectDesc(npcKey);
        // It seems that 1 second is a magic wait-time that must 
        // separate the osNpcCreate from other commands or else 
        // the NPC rezzes as a cloud.
        llSetTimerEvent(1);
    }
    timer() {
        llSetTimerEvent(0);
        osNpcLoadAppearance(npcKey, "appearance");
        state wander;
    }
}
 
state wander
{
    state_entry() {
        dPos = iPos + wasCirclePoint(MOVEMENT_RANGE);
        osNpcPlayAnimation(npcKey, ANIMATION_WALK);
        osNpcMoveToTarget(npcKey, dPos, OS_NPC_NO_FLY);
        llSetTimerEvent(POLL_TIME);
    }
 
    timer() {
        // Another magic value (2), it seems that even on flat ground
	// the NPC never really reaches the destination position.
	// Instead, it reaches some point that is always (experimentally)
	// smaller than 2 meters. Hence the comparison.
        if (llVecDist(osNpcGetPos(npcKey), dPos) > 2) return; 
        osNpcStopAnimation(npcKey, ANIMATION_WALK);
        osNpcPlayAnimation(npcKey, ANIMATION_STAND);
        llSetTimerEvent(0);
        state wait;
    }
}
 
state wait {
    state_entry() {
        llSetTimerEvent(STANDING_ANIMATION_CYCLE_TIME * 1+llFrand(STANDING_ANIMATION_CYCLES-1));
    }
    timer() {
        llSetTimerEvent(0);
        osNpcStopAnimation(npcKey, ANIMATION_STAND);
        state wander;
    }
}