Flight Assist

Written by: Headmaster
//
// Flight Assist Example
//
// Highlights:
//    - altitude-based assist (lower altitude == less assist, higher == more)
//    - horizontal movement gets relatively less assist than vertical.
//    - HUD color indication of movement mode
//      - brown: not flying (i.e. earth)
//      - green: flying w/ assist
//      - white: hovering w/ assist
//      - black: assist disabled


// gAssistParams enables altitude-based flight assist.   Lower altitudes
// will get less momentum assist so that the client updating overhead
// is less likely to harm controllability.  Plus it's a pain to collide
// with a building at high speed in a damage-enabled area.
//
// see increaseMomentumAssist() for more info
//
list    gAssistParams = [ <1.1, 100, 200>, <1.25, 1000, 800>, <1.4, 10000, -1> ];
float   gInc = 0.1; // The increment to add to the momentum assist parameter on the next control key event
float   gMass;      // current mass (assuming it's constant; can imagine a variable mass assist tho :-) )
integer gMotor = FALSE; // Indicates whether momentum assistance is on
integer gAgentInfo; // A bitmask of the agent/avi current state
integer gReqKeys;  // A bitmask of the control keys required to operate
integer gReqPerms; // A bitmask of the required permissions to operate
key     gWearer;   // UUID of the avi who is wearing this attachment

float   gMomentumAssist = 0; // The current applied momentum assist
float increaseMomentumAssist(vector pos)
{
    integer iii;
    integer len;

    len = llGetListLength(gAssistParams);
    vector aParams;
    for (iii = 0; iii < len; iii++)
    {
        aParams = llList2Vector(gAssistParams, iii);
        if (pos.z < aParams.z)
        {
            jump exitLoop;
        }
    }
    @exitLoop;
    
    if (gMomentumAssist < aParams.y)
    {
        gInc *= aParams.x; // increase momentum assist exponentially
                           // param values closer to 1 result in slower growth
        gMomentumAssist += gInc;
    }
    else
    {
        gMomentumAssist = aParams.y;
    }

    return gMomentumAssist;
}

vector getForwardDir()
{
    vector ret;
    
    ret = <1,0,0>*llGetCameraRot(); // camera rotation leads forward direction; so use it to direct assist
    ret.z = 0;

    return llVecNorm(ret);
}

getPermissions()
{
    integer hasPerms = llGetPermissions();
    integer askPerms = (~hasPerms) & gReqPerms;
    if (hasPerms & PERMISSION_TAKE_CONTROLS)
    {
        llTakeControls(gReqKeys, TRUE, TRUE);
    }
    if (askPerms)
    {
        llRequestPermissions(gWearer, askPerms);
    }
}

gotControlInput(integer held)
{
    gAgentInfo = llGetAgentInfo(gWearer);
        
    if (!(held & gReqKeys))
    {
        if (gAgentInfo & AGENT_FLYING)
        {
            state hover;
        }
    }

    if (gAgentInfo & AGENT_FLYING)
    {
        if (held & gReqKeys)
        {
            vector p = llGetPos();
            vector dir;
            float assist;
            
            assist = increaseMomentumAssist(p);
            
            if (p.z > llGround(ZERO_VECTOR)+50.0)
            {
                llSetBuoyancy(1.0);
            }
            else
            {
                // For some reason, if you are below
                // llGround()+50.0 meters, you will
                // slowly rise to that height if you
                // llSetBuoyancy(1.0).  An avatar can maintain
                // hover below this height w/o assist; so
                // no buoyancy change.
                llSetBuoyancy(0.0);
            }
            
            if (held & CONTROL_FWD)
            {
                dir = getForwardDir();
                // flying too fast horizontally
                // typically makes the avatar
                // uncontrollable since lag is
                // high due to heavy updates;
                // Do a simple reduction of assist
                assist /= 3.0;
            }
            else if (held & CONTROL_BACK)
            {
                dir = -getForwardDir();
                assist /= 3.0;
            }
            else if (held & CONTROL_UP)
            {
                dir = <0,0,1>;
            }
            else if (held & CONTROL_DOWN)
            {
                dir = <0,0,-1>;
            }
                
            llPushObject(gWearer, assist*gMass*dir, ZERO_VECTOR, FALSE);
                
            gMotor = TRUE;
        }
    }
}

onAttach(key avatar)
{
    gWearer = avatar;
    if (gWearer != NULL_KEY)
    {
        getPermissions();
    }
}

default
{
    state_entry()
    {
        gReqKeys = CONTROL_FWD | CONTROL_UP | CONTROL_DOWN | CONTROL_BACK;
        gReqPerms = PERMISSION_TRACK_CAMERA|PERMISSION_TAKE_CONTROLS;
        gMass = llGetMass();
        llSetColor(<0,1,0>, ALL_SIDES);

        // Check if HUD is already attached
        gWearer = llGetOwner(); // for now
        if (llGetAttached() != 0)
        {
            getPermissions();
        }

        llSetTimerEvent(0.1);
    }

    on_rez(integer param)
    {
        llResetScript();
    }
 
    attach(key agent)
    {
        onAttach(agent);
    }
    
    run_time_permissions(integer perm)
    {
        if (perm & PERMISSION_TAKE_CONTROLS)
        {
            llTakeControls(gReqKeys, TRUE, TRUE);
        }
        else
        {
            llWhisper(0, "The flight assist will not operate properly");
        }

        if (!(perm & PERMISSION_TRACK_CAMERA))
        {
            llWhisper(0, "The flight assist will not operate properly");
        }
    }
    
    touch_start(integer num)
    {
        state disabled;
    }

    control(key owner, integer held, integer change)
    {
        gotControlInput(held);
    }
    
    timer()
    {
        gAgentInfo = llGetAgentInfo(gWearer);
        
        if (!(gAgentInfo & AGENT_FLYING))
        {
            state landed;
        }
    }
}

state hover
{
    state_entry()
    {
        llSetColor(<1,1,1>, ALL_SIDES);

        gMotor = FALSE;
        gMomentumAssist = 0;
        gInc   = 0.1;
        // adjust a little for lag
        llMoveToTarget(llGetPos(), 0.1);
        vector pos = llGetPos();
        // you can hover unassisted at ground level + 50 meters
        // in fact, if you set buoyancy to 1.0 there, you will slowly
        // rise to ground level + 50 meters
        if (llGround(ZERO_VECTOR)+50.0 < pos.z)
        {
            llSetBuoyancy(1.0);
        }
        else
        {
            llSetBuoyancy(0.0);
        }
        
        llTakeControls(gReqKeys, TRUE, TRUE);
    }

    on_rez(integer param)
    {
        llResetScript();
    }
 
    at_target(integer number, vector curPos, vector targPos)
    {
        llStopMoveToTarget();
    }

    control(key owner, integer held, integer change)
    {
        gotControlInput(held);

        state default;
    }

    touch_start(integer num)
    {
        state disabled;
    }

    state_exit()
    {
        llStopMoveToTarget();
    }
}


state landed
{
    state_entry()
    {
        llStopMoveToTarget();    // just in case
        llSetBuoyancy(0.0);
        llSetColor(<0.5,0.5,0.25>, ALL_SIDES);
        llSetTimerEvent(0.3);
    }

    on_rez(integer param)
    {
        llResetScript();
    }
 
    touch_start(integer num)
    {
        state disabled;
    }

    timer()
    {
        gAgentInfo = llGetAgentInfo(gWearer);

        if (gAgentInfo & AGENT_FLYING)
        {
            state default;
        }
    }
}

state disabled
{
    state_entry()
    {
        llSetBuoyancy(0.0);
        llStopMoveToTarget();    // just in case
        llSetColor(<0,0,0>, ALL_SIDES);
    }

    on_rez(integer param)
    {
        llResetScript();
    }
 
    touch_start(integer num)
    {
        state landed;
    }
}