// Path Setting Script
//
// In 'edit' mode, user moves the object and 'saves' various positions
// over time, then the positions can be replayed.  This either uses the physical
// move functions to create smooth movement or non-physical movements
// for a slightly more jerky movement!
//
// NOTE: Positions and rotations are relative to the region, so if you
// move the prim, then the positions won't move with it - you'd have to
// reset the script (using the 'reset' button) and store a new path.
//
// Depending on the settings, the system can either loop forever
// or play just once.
//
// It also has the option of resetting if you change owners, which
// might be useful if you want new owners to be able to store their
// own paths.
//
// Kimm Paulino
// Oct 2010
 
integer gDebug = FALSE;
integer gPhysics = FALSE;        // Set to use physical movements
integer gLoop = TRUE;                // Set to continually loop through the movements
integer gResetOnOwnerChange = TRUE;    // Set if want script to auto reset when changing owners
 
list gPositionData;            // Always assume that there are the same numbers
list gRotationData;            // of position and rotation data points
integer gCurrentIdx;
float gTimePeriod = 2.0;
float gTau = 5.0;
key gOwnerId; 
integer gListen; 
integer gTimeHandle; 
integer gTauHandle; 
string gHelpMsg = "Use EDIT mode to move your object, selecting 'Save' to save each position.  Select 'Done' once complete.  Don't forget to save your first position too!"; 
string gErrorMsg = "Something unexpected went wrong, suggest you reset the script!";
string SAVE_BTN = "Save";
string DONE_BTN = "Done";
string TIME_BTN = "Time Adjust";
string TAU_BTN = "Tau Adjust";
string RESET_BTN = "Reset";
string START_BTN = "Start";
string STOP_BTN = "Stop";
string START_MSG = "start";        // What a passer by can type in via chat
integer LISTEN_CH = 600;
integer TIME_CH = 900;
integer TAU_CH = 901;
 
doDebug (string msg)
{
    if (gDebug)
    {
        llOwnerSay (msg);
    }
}
 
doMove()
{ 
    integer num_points = llGetListLength(gPositionData); 
    if (num_points != llGetListLength (gRotationData))
    {
        llOwnerSay (gErrorMsg);
        disableMove();
        return;
    }
 
    if (gCurrentIdx >= num_points)
    {
        if (gLoop)
        {
            // Loop around for another go
            gCurrentIdx = 0;
        }
        else
        {
            // All complete
            disableMove();
            return;
        }
    }
 
    doDebug ("Moving to position " + (string)gCurrentIdx);
 
    vector next_pos = llList2Vector (gPositionData, gCurrentIdx);
    rotation next_rot = llList2Rot (gRotationData, gCurrentIdx);
 
    if (next_pos == ZERO_VECTOR && next_rot == ZERO_ROTATION)
    {
        // ignore
    }
    else
    {
        if (gPhysics)
        {
            llMoveToTarget(next_pos, gTau);
            llLookAt(next_pos,1,1);
            llRotLookAt(next_rot,1,1);
        }
        else
        {
//            doDebug ("moving to: " + (string)next_pos);
            llSetRot (next_rot);
            llSetPos (next_pos);
        }
    }
 
    // Move on to the next point
    gCurrentIdx ++;
} 
 
dialog ()
{
    list buttons;
    if (gPhysics)
    {
        buttons = [SAVE_BTN, DONE_BTN, RESET_BTN, START_BTN, STOP_BTN, TIME_BTN, TAU_BTN];
    }
    else
    {
        buttons = [SAVE_BTN, DONE_BTN, RESET_BTN, START_BTN, STOP_BTN, TIME_BTN];
    }
    llDialog (gOwnerId, gHelpMsg, buttons, LISTEN_CH);
}
 
enableMove ()
{
    if (gPhysics)
    {
        doDebug ("Enabling physical move");
        llSetStatus (PRIM_PHYSICS, TRUE);
    }
    else
    {
        doDebug ("Enabling non-physical move");
        llSetStatus(PRIM_PHYSICS, FALSE); 
    }
    llSetTimerEvent (gTimePeriod);
    gCurrentIdx = 0;
    doMove ();
}
 
disableMove ()
{
    doDebug ("Disabling move");
    llSetStatus (PRIM_PHYSICS, FALSE);
    llSetTimerEvent (0.0);
}
 
default 
{
    on_rez (integer start_param)
    {
        // if we reset on rez, then a user can't take an object into
        // inventory have rerez it with the same path stored.
        //
        // Means that if they do want to clear the path, say because
        // the position in the Sim has changed, then they have to use
        // the 'reset' option.
    }
 
    state_entry() 
    {   
        llOwnerSay ("Ready to start saving positions.  Touch for menu, then go to SL Edit mode to move the object and use 'save' on the menu to save each position.");
        gOwnerId = llGetOwner();
    }
 
    touch_start(integer who)
    {
        gListen = llListen (LISTEN_CH,"",NULL_KEY,"");
        if (llDetectedKey(0) == gOwnerId)
        {
            dialog();
        }
        else
        {
            if (!gLoop)
            {
                // Let nearby users start the moving
                llWhisper  (0, "To start the movement, please type the following into local chat:  /" + (string)LISTEN_CH + " " + START_MSG);
            }
        }
    }
 
    listen (integer channel, string name, key id, string msg)
    {
        vector pos = llGetPos();
        rotation rot = llGetRot();
 
        if (channel == LISTEN_CH)
        {
            if (msg == START_BTN || msg == START_MSG)
            {
                enableMove();
            }
 
            // non-owners can't do anything else
            if (id != gOwnerId)
            {
                return;
            }
 
            if (msg == SAVE_BTN)
            {
                gPositionData += pos;
                gRotationData += rot;
                dialog ();
            } 
            else if (msg == STOP_BTN)
            {
                disableMove();
            }
            else if (msg == RESET_BTN)
            {
                llResetScript();
            }
            else if (msg == TIME_BTN)
            {
                gTimeHandle = llListen (TIME_CH, "", gOwnerId, "");
                llOwnerSay ("Adjust time using: /" + (string)TIME_CH + " ");
            } 
            else if (msg == TAU_BTN)
            {
                gTauHandle = llListen(TAU_CH, "", gOwnerId, "");
                llOwnerSay ("Adjust Tau using: /" + (string)TAU_CH + " ");
            } 
            else if (msg == DONE_BTN)
            {
                llOwnerSay("To reset use: /" + (string)LISTEN_CH + " reset");
                llOwnerSay("To start use: /" + (string)LISTEN_CH + " start");
            }
        }
 
        if (channel == TIME_CH)
        {
            gTimePeriod = (float)msg;
            llListenRemove (gTimeHandle);
            llOwnerSay ("Time period set to " + msg);
        } 
 
        if (channel == TAU_CH)
        {
            gTau = (float)msg;
            llListenRemove (gTauHandle);
            llOwnerSay ("Tau set to " + msg);
        } 
    }
 
    changed(integer ch)
    {
        if(ch & CHANGED_OWNER)
        {
            if (gResetOnOwnerChange)
            {
                // This will clear out all stored positions of course!
                llResetScript();
            }
        }
    }
 
    timer()
    {
         doMove();
    }
}