﻿
// huffware script: simple follower script, by fred huffhines.
//
// this has been condensed from other pet scripts but still retains all the vitamins!
//
// this script is licensed by the GPL v3 which is documented at: http://www.gnu.org/licenses/gpl.html
// do not use it in objects without fully realizing you are implicitly accepting that license.
//

// global constants...

float HEIGHT_ABOVE_AVATAR = 0.3;  // how far above the av do we float.
float ROOM_FOR_ERROR = 0.1;  // how far away from the target locale can we roam.
float MAXIMUM_VELOCITY = 200.0;  // the fastest the object can zoom towards us.
float SENSOR_PERIOD = 1.0;  // how frequently we look for the avatar.
float SENSOR_RANGE =  64.0;  // the farthest away we will try to look for the avatar.

// global variables...

vector last_detected_position;  // where the avatar was last detected.
integer target_identifier;  // the id assigned when we registered our target.
integer last_physics_state;  // this remembers if physics should be enabled currently.
vector perch_position;  // place we inhabit near the owner.    

// returns the location where we should perch, given the target's location.
vector target_to_perch(key av, vector target)
{
    vector av_bounds = llGetAgentSize(av);
    return <target.x, target.y, target.z + av_bounds.z / 2.0 + HEIGHT_ABOVE_AVATAR>;
}

// returns the location of the target given where we're trying to aim at.
vector perch_to_target(key av, vector perch)
{
    vector av_bounds = llGetAgentSize(av);
    return <perch.x, perch.y, perch.z - av_bounds.z / 2 - HEIGHT_ABOVE_AVATAR>;
}
//hmmm: the two target and perch funcs both assume we don't want any lateral offset.
//      and currently they're only taking into account the avatar's size in the z direction.


// makes the pet completely stop travelling around and just sit there.
cease_movement()
{
    llStopMoveToTarget();
    llTargetRemove(target_identifier);
    full_stop();        
    llSetStatus(STATUS_PHYSICS, FALSE);
    last_physics_state = FALSE;
    // if we're not quite there, jump to the perch.
    if (llVecDist(perch_position, llGetPos()) > ROOM_FOR_ERROR) {
        // we're not close enough so make a flying leap.
//llOwnerSay("jumping to perch at " + (string)perch_position + ", was at " + (string)llGetPos());
        llSetPos(perch_position);  // make us very accurate until something changes.
    }
}

// tells the pet to target the avatar "av" at the location "pos".  this will assume
// the avatar is actually present in the same sim when it calculates the avatar's size.
// we use that size in calculating where to perch nearby the avatar.
move_toward_target(key av, vector destination)
{
    // first of all we'll remember where we're supposed to go.
    perch_position = target_to_perch(av, destination);
    // now see how far away we are from that.
    float distance = llVecDist(perch_position, llGetPos());
    if (distance < 1.0) {
        // we're close enough; stop moving for a bit.
//llOwnerSay("dist small enough to go non-phys: " + (string)distance);
        cease_movement();
        return;
    }
    // well, now we know that we need to move somewhere.  let's set up a
    // physics target and head that way.
    llSetStatus(STATUS_PHYSICS, TRUE);
    last_physics_state = TRUE;
    llTargetRemove(target_identifier);
    float time = distance / MAXIMUM_VELOCITY;
    // if we go too low, then SL will ignore the move to target request.
    if (time < 0.14) time = 0.14;
//llOwnerSay("tau=" + (string)time);
    target_identifier = llTarget(perch_position, ROOM_FOR_ERROR);
    llMoveToTarget(perch_position, time);
}

// startup the object.
initialize()
{
    llSetStatus(STATUS_PHYSICS, TRUE);
    last_physics_state = TRUE;
    llSetBuoyancy(1.0);
    llSetStatus(STATUS_PHANTOM, TRUE);
    llSensorRemove();
    // start looking for the owner.
    llSensorRepeat("", llGetOwner(), AGENT, SENSOR_RANGE, PI * 2, SENSOR_PERIOD);
}

// sets the object's speed to "new_velocity".
// if "local_axis" is TRUE, then it will be relative to the object's
// own local coordinates.
set_velocity(vector new_velocity, integer local_axis)
{
    vector current_velocity = llGetVel();
         
    if (local_axis) {
        rotation rot = llGetRot();
        current_velocity /= rot;  // undo the rotation.
    }
    
    new_velocity -= current_velocity;
    new_velocity *= llGetMass();
    
    llApplyImpulse(new_velocity, local_axis);
}

// attempts to bring the object to a complete stop.
full_stop()
{
    llSetForce(<0,0,0>, FALSE);
    set_velocity(<0,0,0>, FALSE);
}

default {
    state_entry() { if (llSubStringIndex(llGetObjectName(), "huffotronic") < 0) state real_default; }
    on_rez(integer parm) { state rerun; }
}
state rerun { state_entry() { state default; } }

state real_default
{
    state_entry() { initialize(); }

    on_rez(integer param) { llResetScript(); }

    sensor(integer num_detected)
    {
        // save where we saw the av just now.
        last_detected_position = llDetectedPos(0);
        // move closer if we're not near enough to our beloved owner.
        move_toward_target(llGetOwner(), last_detected_position);
        // if we find that our rotation is incorrect, we'll fix that here.
        // we only reorient on the sensor period, since that's slower
        // than hitting our target.  plus we try to ensure we never get
        // out of place again.
        vector curr_rot = llRot2Euler(llGetRot());
        if ( (curr_rot.x != 0.0 ) || (curr_rot.y != 0.0) ) {
            // we are out of rotational goodness right now even.  fix that.
            llSetStatus(STATUS_PHYSICS, FALSE);
            llSetStatus(STATUS_ROTATE_X, FALSE);
            llSetStatus(STATUS_ROTATE_Y, FALSE);
            // save the z value before correcting the rotation.
            vector new_rot = ZERO_VECTOR;
            new_rot.z = curr_rot.z;
            llSetRot(llEuler2Rot(new_rot));
            llSetStatus(STATUS_PHYSICS, last_physics_state);
        }
    }

    no_sensor()
    {
        // we lost track of the avatar.  turn off phyics and wait.
        cease_movement();
    }

    at_target(integer number, vector targetpos, vector ourpos)
    {
        // wait until we see the av again before picking a new target.
///??seems bad        cease_movement();
    }
    
    not_at_target()
    {
    }
}
