/**
 * @author(s): Reconx86, Cubey Terra
 * @version: N/A
 * @created: 2009.12.01
 * @date: 2016.03.22
 *
 * RCX Engine Script (based on Cubey Terra's Engine for biplanes)
 *
 * @license:
 * You are allowed to share this script full-perm with others and even use it in your own projects.
 * But do not sell the script seperately nor as part of a script package
 */

/*========================================
GLOBAL VARIABLES
========================================*/
integer HUD_Channel = 556874;
integer HUD_UpdateInt = 1;
float engInterval = 0.5;//This also affects data-feed and HUD update

// Switches
integer engineOn;
integer swNarrate;
integer swFuel;
integer swFeed;
integer swFlaps;
integer swGear;
integer swParked;

list feedPoints;
integer feedTotal;

//Toggle lists
list togVtype = ["Taxi", "Airplane", "Boat"];
list CardinalDirections = ["N", "NE", "E", "SE", "S", "SW", "W", "NW", "N"];

// Vtype constants
integer TAXI_MODE = 0;
integer PLANE_MODE = 1;
integer BOAT_MODE = 2;

integer key_count;
integer key_delay = 5;
float regionChange;
float collisionTime;
float controlTime;
float lastControlTime;
integer controlsTaken;
float lastRequest;

// Crashed Vehicle - Beacon Settings
integer beaconInterval = 300;
integer beaconReminders = 1;
float lastBeaconTime = 0x80000000;
integer cachedBeacon;

// Default Thrust Settings
float fwdTime;
integer fwd;
integer fwdLast;
integer fwdMin = -20;
integer fwdMax = 100;
integer fwdInc = 100;

// Default Speed Settings
float spdFull = 20.0;
float spdLanding = 8.0;
float spdCruise = 10.0;// Speed at which you achieve full lift (VS1G)

float buoyancy = 1.0;
float hoverHeight = 0.25;// Hoverheight changed from 0.45 to 0.25 (0.30 keeps interior water-free)
vector taxiAngle;

// Angular motor settings
float angXincr = 0.15;
float angYincr = 0.05;
float angXmax = 4.0;
float angYmax = 4.0;
float angZmax = 0.33;

// Vehicle API variables
float vertAtt = 1.9;
float linDef = 1.0;//0.8

string objOwner;
string objName;
string objDesc;
integer debugVtype;
float updateHUDCurr;
integer seated = TRUE;//temporary engine fix / rcx mod
integer elevPos;// 1 = up, 0 = fwd, -1 = down
integer rudPos;// -1 = left, 0 = fwd, 1 = right
integer vType;
integer lastVtype;
key owner = NULL_KEY;
key pilot = NULL_KEY;
key copilot = NULL_KEY;
integer copilotEnabled;

// Motor variables
vector linMot;
vector angMot;
vector lastLinMot;
vector lastAngMot;
float angX;
float angY;
integer lastDir;

//Prims for hovertext
string primText = "data feed";

// Fuel Settings
integer refueled;
integer fuelPerc = 100;
integer fuelPercLast = 100;
float fuelSecCurr;
integer fuelSecMax = 480;// Total fuel
integer fuelTotal;
string fuelUnit = "%";

// In Flight Data
vector pos;// Current object position
float aoa;// Angle of attack
float speed;
float waterZ;// Current altitude of water
float groundZ;// Current altitude of ground under plane
float floatHeight;// How high the plane *should* float in this region
float aow;// Current altitude over water: pos.z - waterZ - FLOAT_OFFSET)

// Interface Settings
integer set_private_channel_number;
integer set_private_channel_handle;
integer privatelisten;
integer public_channel_handle;

/*========================================
USER DEFINED FUNCTIONS
========================================*/

string rcxGetSlurl()
{
    string prefix = "http://maps.secondlife.com/secondlife";
    string region = llGetRegionName();
    vector pos = llGetPos();
    string posx = (string)llRound(pos.x);
    string posy = (string)llRound(pos.y);
    string posz = (string)llRound(pos.z);
    return prefix + "/" + llEscapeURL(region) + "/" + posx + "/" + posy + "/" + posz;
}

/**
 * @author: Apotheus Silverman
 * 
 * Returns the angle of the given XY-component.
 * The hypotenuse of the right triangle formed with sides X and Y must be 1.
 */

float getAoA(float x, float y)
{
    // Calculate the angle
    float targetAngle = llAsin(y) * RAD_TO_DEG;
    // Adjust values for quadrants 2 and 3
    if (x < 0.0) {
        if (y < 0.0) {
            targetAngle = -180.0 - targetAngle;
        } else {
            targetAngle = 180 - targetAngle;
        }
    }
    return targetAngle;
}

taxiMode()
{
    vType = TAXI_MODE;
    
    llSetVehicleType(debugVtype = VEHICLE_TYPE_CAR);
    llSetVehicleRotationParam( VEHICLE_REFERENCE_FRAME, llEuler2Rot(taxiAngle * DEG_TO_RAD));
    
    llRemoveVehicleFlags(VEHICLE_FLAG_LIMIT_ROLL_ONLY);
    
    llSetVehicleFloatParam( VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, linDef );
    llSetVehicleFloatParam( VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 0.6 );
    llSetVehicleVectorParam( VEHICLE_LINEAR_FRICTION_TIMESCALE, <10, 2, 1000> );
    llSetVehicleFloatParam( VEHICLE_LINEAR_MOTOR_TIMESCALE, 2 );
    
    llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 0.1);
    llSetVehicleFloatParam( VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.8);
    llSetVehicleFloatParam( VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 0.5);
    llSetVehicleFloatParam( VEHICLE_ANGULAR_FRICTION_TIMESCALE, 10.0 );
    
    //llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 0.0);
    //llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 1.0);
    
    llMessageLinked(LINK_SET, vType, "#vType", "");
    setMotor();
}

boatMode()
{
    vType = BOAT_MODE;
    
    llRemoveVehicleFlags(-1);// Start with a clean slate
    llSetVehicleType(debugVtype = VEHICLE_TYPE_SLED);// Sled to glide over water?
    llSetVehicleFlags(VEHICLE_FLAG_HOVER_WATER_ONLY);
    
    llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0);
    llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0);
    llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 500);
    llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 500);
    
    llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE, 1.5);
    llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 200);
    llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_TIMESCALE, 0.1);
    llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 1000.0);
    
    llSetVehicleVectorParam(VEHICLE_LINEAR_FRICTION_TIMESCALE, <100, 1000, 1000>);
    
    llSetVehicleVectorParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE, <1000, 1000, 1000>);
    
    llSetVehicleFloatParam(VEHICLE_HOVER_HEIGHT, hoverHeight);
    llSetVehicleFloatParam(VEHICLE_HOVER_EFFICIENCY, 1.0);
    llSetVehicleFloatParam(VEHICLE_HOVER_TIMESCALE, 0.01);
    
    buoyancy = 1.0;
    llSetVehicleFloatParam(VEHICLE_BUOYANCY, buoyancy);
    llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 0.1);
    llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 1.0);
    
    llSetVehicleFloatParam( VEHICLE_BANKING_EFFICIENCY, 1);
    llSetVehicleFloatParam( VEHICLE_BANKING_MIX, 0.75);
    llSetVehicleFloatParam( VEHICLE_BANKING_TIMESCALE, 0.5);
    
    llRemoveVehicleFlags( 0
        | VEHICLE_FLAG_NO_DEFLECTION_UP
        | VEHICLE_FLAG_LIMIT_ROLL_ONLY
        | VEHICLE_FLAG_HOVER_TERRAIN_ONLY
        | VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT
        | VEHICLE_FLAG_HOVER_UP_ONLY
        | VEHICLE_FLAG_LIMIT_MOTOR_UP
    );
    llMessageLinked(LINK_SET, vType, "#vType", "");
    setMotor();
}

planeMode()
{
    vType = PLANE_MODE;
    llRemoveVehicleFlags(-1);
    llSetVehicleType(debugVtype = VEHICLE_TYPE_AIRPLANE);
    
    // Friction
    llSetVehicleVectorParam( VEHICLE_LINEAR_FRICTION_TIMESCALE, <75, 1, 1> );
    llSetVehicleFloatParam( VEHICLE_ANGULAR_FRICTION_TIMESCALE, 2 );
    
    // Linear Motor
    llSetVehicleFloatParam( VEHICLE_LINEAR_MOTOR_TIMESCALE, 2 );
    llSetVehicleFloatParam( VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 120 );
    
    // Angular Motor
    llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_TIMESCALE, 0 );
    llSetVehicleFloatParam( VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 0.4);
    
    // Linear Deflection
    llSetVehicleFloatParam( VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, linDef );
    llSetVehicleFloatParam( VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 5);
    
    // Angular Deflection
    llSetVehicleFloatParam( VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0);
    llSetVehicleFloatParam( VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 5);
    
    // Reference Frame
    llSetVehicleRotationParam( VEHICLE_REFERENCE_FRAME, ZERO_ROTATION);
    
    // Hover
    llSetVehicleFloatParam( VEHICLE_HOVER_TIMESCALE, 1000 );
    llSetVehicleFloatParam(VEHICLE_HOVER_HEIGHT, 0);
    llSetVehicleFloatParam( VEHICLE_HOVER_EFFICIENCY, 0 );
    
    // Banking
    llSetVehicleFloatParam( VEHICLE_BANKING_EFFICIENCY, 0.7);
    llSetVehicleFloatParam( VEHICLE_BANKING_MIX, 0.5);
    llSetVehicleFloatParam( VEHICLE_BANKING_TIMESCALE, 0.5);
    
    // Vertical attractor 
    llSetVehicleFloatParam( VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 0.6);
    llSetVehicleFloatParam( VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, vertAtt );
    
    llRemoveVehicleFlags( 0
        | VEHICLE_FLAG_NO_DEFLECTION_UP
        | VEHICLE_FLAG_HOVER_WATER_ONLY
        | VEHICLE_FLAG_HOVER_TERRAIN_ONLY
        | VEHICLE_FLAG_HOVER_GLOBAL_HEIGHT
        | VEHICLE_FLAG_HOVER_UP_ONLY
        | VEHICLE_FLAG_LIMIT_MOTOR_UP
    );
    
    llSetVehicleFlags( VEHICLE_FLAG_LIMIT_ROLL_ONLY );
    
    llMessageLinked(LINK_SET, vType, "#vType", "");
    setMotor();
}

// Flight Algorithm Loop
setMotor()
{
    float glideFactor = 0.05;// How effective gravity is in gliding. 0.1 seems a little too much.
    if (engineOn && fuelPerc > 0) {// Engine is on
    
        if (swNarrate && fwdLast != fwd) {
            rcxSay((string)fwd + "% Thrust");
            fwdLast = fwd;
        }
        
        // llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.8);// RCX - Default values
        linMot = <fwd * spdFull * 0.01, 0, 0>;// Simple thrust formula...
        
        if (swFlaps) {
            // Flap-algorithm here (Experimental)
            linMot.x = linMot.x * 0.75;
        }
        
        // Modify linMot and/or angMot as appropriate to vehicle type)
        if (vType == PLANE_MODE) {
            linMot.x = linMot.x - (aoa * glideFactor);// Add appearance of decel/accel in climb/dive
            
        } else if (vType == TAXI_MODE) {
            angMot = angMot * 4;//(reduced from 10 as VEHIHILE_TYPE_CAR is now used for taxi-mode)
            
        } else if (vType == BOAT_MODE) { // Adjust motor properties
            linMot = linMot * 0.6;
            
            // Only limit angMot below takeoff speed. Low speed turns need to be quick
            if (fwd > 50) {
                angMot = angMot * 0.6;
                
            } else if (fwd == 40) {
                angMot = angMot * 0.7;
                
            } else if (fwd == 30) {
                angMot = angMot * 0.8;
                
            } else if (fwd == 20) {
                angMot = angMot * 0.9;
            }
        }
        
        //Prevents wheel from bouncing on tarmac while idling (in taxi-mode)
        if (!(linMot == ZERO_VECTOR && speed == 0.0)) {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, linMot);
            lastLinMot = linMot;
        }
        //Prevents wheel from bouncing on tarmac while idling (in taxi-mode)
        if (!(angMot == ZERO_VECTOR && llGetOmega() == ZERO_VECTOR)) {
            llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, angMot);
            lastAngMot = angMot;
        }
        
    } else { // Engine is off
        if (vType == PLANE_MODE) {
            linMot = <aoa * glideFactor, 0, 0>;// Plane should glide depending on aoa
        } else {
            linMot = ZERO_VECTOR;// No reverse motor while in taxi-mode
        }
        llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, linMot);
        llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, angMot);
    }
}

startEng()
{
    if (!engineOn) {
        if (swFuel || fuelPerc > 0) {
            llSetStatus(STATUS_PHYSICS, TRUE);
            llMessageLinked(LINK_SET, engineOn = TRUE, "#engine", "");
            llMessageLinked(LINK_SET, fwd = 0, "#throttle", "");
            string jMsg = llList2Json(JSON_ARRAY, ["#engine", (string)engineOn]);
            llSay(HUD_Channel, jMsg);
        } else {
            rcxSay("Out of fuel!");
        }
    }
}

stopEng()
{
    if (engineOn) {
        fwd = 0;
        llMessageLinked(LINK_SET, engineOn = FALSE, "#engine", "");
        llSay(HUD_Channel, llList2Json(JSON_ARRAY, ["#engine", (string)engineOn]));
    }
}

rcxSay(string msg)
{
    if ((pilot != owner && pilot != NULL_KEY) || (copilot != owner && copilot != NULL_KEY && copilotEnabled)) {
        llSay(0, msg);
    } else {
        llOwnerSay(msg);
    }
}

string rcxRound(float fNumber)
{
    integer precision = 1;
    string sNumber = (string)fNumber;
    integer sLength = llSubStringIndex(sNumber, ".") + precision;
    //fNumber = fNumber + 5 * llPow(10.0, -(precision + 1));
    sNumber = (string)fNumber;
    sNumber = llGetSubString(sNumber, 0, sLength);
    return sNumber;
}

float getBearing(rotation rot)
{
    vector vOffset = <1, 0, 0> * rot;
    float bearing = llAtan2(vOffset.x, vOffset.y) * RAD_TO_DEG;
    if (bearing < 0.0) {
        bearing += 360.0;
    }
    return bearing;
}

refreshHUD(integer push)
{
    integer i;
    string text;
    list params;
    updateHUDCurr += engInterval;// This would add 0.1 until the value reaches 1.0
    if (engineOn && (updateHUDCurr >= HUD_UpdateInt || push)) { // "push" means update immediately
        
        float bearing = getBearing(llGetRootRotation());
        
        //Assemble text
        string cardinalDirection = llList2String(CardinalDirections, (integer)((bearing + 22.5) / 45));
        text += "HDG: " + rcxRound(bearing) + "° (" + cardinalDirection + ")\n";
        text += "AoA: " + rcxRound(aoa) + "°\n";
        text += "Gears: " + (string)swGear + "\n";
        text += "Mode: " + llList2String(togVtype, vType) + "-mode\n";
        text += "Thrust: " + (string)fwd + "%" + "\n";
        text += "Altitude: " + rcxRound(pos.z) + "m (MSL: " + rcxRound(pos.z - waterZ) + "m)\n";
        text += "Speed: " + rcxRound(speed) + "m/s" + "\n";

        if (swFuel) {
            text += "Fuel: " + (string)fuelPerc + fuelUnit + "\n";
        }
        updateHUDCurr = 0;
        
        //Refresh HUD
        llSay(HUD_Channel, llList2Json(JSON_ARRAY, [
            "#stats " +
            (string)fuelPerc + ", " + 
            (string)fwd + ", " + 
            (string)swGear + ", " + 
            (string)vType + ", " + 
            (string)speed + ", " + 
            (string)aoa + ", "
            , 0
        ]));
        
        //Squawk
        string squawk = llList2Json(JSON_OBJECT, [
            "msg", "rcx.squawk",
            "object.name", objName,
            "object.desc", objDesc,
            "object.owner", objOwner,
            "flight.info", text
        ]);
        llRegionSay(1, squawk);
    }
    if ((engineOn && text != "") || (!engineOn && text == "")) {
        
        if (!swFeed) {
            text = "";
        }
        
        while(i < feedTotal) {
            params += [
                PRIM_LINK_TARGET, llList2Integer(feedPoints, i),
                PRIM_TEXT, text, <0.0, 1.0, 0.0>, engineOn
            ];
            i++;
        }
    }
    llSetLinkPrimitiveParamsFast(0, params);
}

/*========================================
DEFAULT STATE
========================================*/ 
default 
{
    /*========================================
    STATE ENTRY
    ========================================*/
    state_entry() 
    {
        // Init procedure
        llSetObjectDesc("Powered by RCX");//Do Not alter this
        llSetText("", ZERO_VECTOR, FALSE);
        llSetSitText("Fly Me");
        llCollisionSound("", 0.0);
        llSetStatus(STATUS_PHYSICS, FALSE);
        llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, ZERO_VECTOR);
        owner = llGetOwner();
        objName = llGetObjectName();
        objDesc = llGetObjectDesc();
        objOwner = llKey2Name(owner);
        
        // Collect position data
        waterZ = llWater(ZERO_VECTOR);
        pos = llGetPos();
        aow = pos.z - waterZ - hoverHeight;
        
        if ((aow < 0.75 && aow > -1.0) && llGround(ZERO_VECTOR) < waterZ) {
            boatMode();
            
        } else {
            taxiMode();
        }
        
        list lstPrimNames = [];
        integer link_nr;
        key objKey = llGetKey();
        integer links = llGetObjectPrimCount(objKey);
        for (link_nr = 0; link_nr <= links; link_nr++) {
            string link_name = llToLower(llGetLinkName(link_nr));
            if (~llSubStringIndex(link_name, primText)) {
                feedPoints += [link_nr];
            }
        }
        feedTotal = llGetListLength(feedPoints);
        
        swGear = (vType == TAXI_MODE);
        llMessageLinked(LINK_SET, fuelTotal, "#fuel", "");
        llMessageLinked(LINK_SET, vType, "#vType", "");
        llMessageLinked(LINK_SET, swGear, "#gear", "");
        llSay(HUD_Channel, llList2Json(JSON_ARRAY, ["#init", "1"]));
        
        // Set plane to defaults (are these still required?)
        llMessageLinked(LINK_SET, FALSE, "#cmdhook" , "");
        llMessageLinked(LINK_SET, FALSE, "#nav"     , "");
        llMessageLinked(LINK_SET, FALSE, "#strobe"  , "");
        llMessageLinked(LINK_SET, FALSE, "#beacon"  , "");
        llMessageLinked(LINK_SET, FALSE, "#throttle", "");
        llMessageLinked(LINK_ROOT, 0, "#engine.state_entry", "");
        
        llOwnerSay("Engine Script Reset");
    }
    
    on_rez(integer num)
    {
        llResetScript();
    }

    /*========================================
    LINK MESSAGES
    ========================================*/
    link_message(integer sender_num, integer num, string msg, key id)
    {
        if (msg == "#control") {
            key_count++;
            
            controlTime = fwdTime = llGetTime();

            if (engineOn && num & CONTROL_UP) {    
                if (fwd < fwdMax && key_count >= 5) {
                    fwd += fwdInc;
                    fwdTime = llGetTime();
                    key_count = 0;
                    
                } else if (fwd >= fwdMax && fwd < fwdMax && key_count >= 1) {
                    fwd += fwdInc;
                    fwdTime = llGetTime();
                    key_count = 0;
                }
                
            } else if (engineOn && num & CONTROL_DOWN) {
                if (fwd < fwdMax && fwd > fwdMin && key_count >= 5) {
                    fwd -= fwdInc;
                    fwdTime = llGetTime();
                    key_count = 0;
                    
                } else if (fwd >= fwdMax && key_count >= 1) {
                    fwd -= fwdInc;
                    fwdTime = llGetTime();
                    key_count = 0;
                }
                
            } else if (vType == PLANE_MODE) {
                
                if (speed > 0.5 || engineOn) {// Only works if vehicle is moving
                    // ELEVATOR CONTROL
                    if (num & CONTROL_FWD) {
                        elevPos = 1;
                        if (angY < angYmax) {
                            angY += angYincr;
                        }
                        angMot.y = angY;
                        
                    } else if (num & CONTROL_BACK) {
                        elevPos = -1;
                        if (angY < angYmax) {
                            angY += angYincr;
                        }
                        angMot.y = -angY;
                        
                    } else {
                        elevPos = 0;
                        angY = 0;
                        angMot.y = 0;
                    }
                    
                    // RUDDER CONTROL -----------
                    if (num & CONTROL_RIGHT || num & CONTROL_ROT_RIGHT) {
                        rudPos = 1;
                        if (angX < angXmax) {
                            angX += angXincr;
                        }
                        angMot.x = angX * (speed / spdCruise);
                        angMot.z = -angZmax;
                        // Very subtle adjustment for pitching while banking
                        angMot.y = angMot.y / 2;
                        
                    } else if (num & CONTROL_LEFT || num & CONTROL_ROT_LEFT) {
                        rudPos = -1;
                        if (angX < angXmax) {
                            angX += angXincr;
                        }
                        angMot.x = -angX * (speed / spdCruise);
                        angMot.z = angZmax;
                        // Very subtle adjustment for pitching while banking
                        angMot.y = angMot.y / 2;
                        
                    } else {
                        rudPos = 0;
                        angMot.x = 0;
                        angMot.z = 0;
                        angX = 0;
                    }
                }
            } else {// Taxi or boat mode
            
                // Controls only work if vehicle is moving or engine is on
                if (speed > 1.0 || engineOn) {
                
                    // ELEVATOR CONTROL
                    if (num & CONTROL_FWD) {
                        elevPos = 1;
                        if (angY < angYmax) {
                            angY += angYincr;
                        }
                        angMot.y = angY * (speed / spdCruise);
                        
                    } else if (num & CONTROL_BACK) {
                        elevPos = -1;
                        if (angY < angYmax) {
                            angY += angYincr;
                        }
                        angMot.y = -angY * (speed / spdCruise);
                        
                        // If in boat_mode, take this as a cue to go to plane_mode
                        if (vType == BOAT_MODE) {
                            planeMode();
                        }
                        
                    } else {
                        elevPos = 0;
                        angY = 0;
                        angMot.y = 0;
                    }
                    
                    // RUDDER CONTROL
                    if ((num & CONTROL_RIGHT || num & CONTROL_ROT_RIGHT) && !swParked) {
                        rudPos = 1;
                        angMot.x = 0;
                        angMot.z = -angZmax;
                        
                    } else if ((num & CONTROL_LEFT || num & CONTROL_ROT_LEFT) && !swParked) {
                        rudPos = -1;
                        angMot.x = 0;
                        angMot.z = angZmax;
                        
                    } else {
                        rudPos = 0;
                        angMot = ZERO_VECTOR;
                    }
                }
            }// END TAXI ---
            lastDir = num;
            
            if (fwd != fwdLast) {
                llMessageLinked(LINK_SET, fwd, "#throttle", "");
                refreshHUD(TRUE);
                //fwdLast = fwd;// Caution: This broke thrust-narration (in setMotor)
            }
            setMotor();

        } else if (msg == "#engine.status_request") {
            float request = llGetTime();
            if (lastRequest + 0.5 < request) {
                llMessageLinked(LINK_SET, engineOn, "#engine", "");
                llMessageLinked(LINK_SET, fwd, "#throttle", "");
                lastRequest = request;
            }
        
        } else if (msg == "#pilot") {
            pilot = id;
            
        } else if (msg == "#copilot") {
            copilot = id;
        
        } else if (msg == "#copilot.control") {
            copilotEnabled = num;
        
        } else if (msg == "#engine") {
            llSetStatus(STATUS_PHYSICS, engineOn = num);
            string jMsg = llList2Json(JSON_ARRAY, ["#engine", (string)engineOn]);
            llSay(HUD_Channel, jMsg);
            llSetTimerEvent(engInterval);//temporary egine fix / rcx mod
            refreshHUD(TRUE);
            
            if (engineOn) {
                llMessageLinked(LINK_SET, vType, "#vType", "");
                
            } else {
                llMessageLinked(LINK_SET, fwd = 0, "#throttle", "");
            }
        
        } else if (msg == "#gear.set") {
            swGear = num;
            llMessageLinked(LINK_SET, num, "#gear", "");
            rcxSay("Gears " + llList2String(["Retracted", "Extended"], num));
        
        } else if (msg == "#flaps") {
            swFlaps = num;
            
        } else if (msg == "#park") {
            //if (engineOn && vType == TAXI_MODE) {
            if (vType == TAXI_MODE) {
                
                rcxSay("Wheelbrakes " + llList2String(["Off", "On"], swParked = num));
                if (swParked) {
                    llSetVehicleVectorParam(
                        VEHICLE_LINEAR_FRICTION_TIMESCALE, <0, 2, 1000>
                    );
                    llSetVehicleFloatParam(
                        VEHICLE_LINEAR_MOTOR_TIMESCALE, 120
                    );
                    
                } else {
                    //llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 1);
                    llSetVehicleVectorParam(
                        VEHICLE_LINEAR_FRICTION_TIMESCALE, <10, 2, 1000>
                    );
                    llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE, 2 );
                    //llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 120);
                    //line 957 if (vType != PLANE_MODE || (!parked && (vType == TAXI_MODE || vType == PLANE_MODE))
                }
            } else {
                rcxSay("Unable to engage wheelbrakes, you are not in taxi-mode");
            }
        
        } else if (msg == "#taxi_angle") {
            taxiAngle = <0, num, 0>;
            llSetVehicleRotationParam(
                VEHICLE_REFERENCE_FRAME, llEuler2Rot(taxiAngle * DEG_TO_RAD)
             );
            
        } else if (msg == "#throttle.set") {
            fwdTime = controlTime = llGetTime();
            fwd = fwdLast = num;
            llMessageLinked(LINK_SET, fwd, "#throttle", "");
            key_count = 0;
            
        } else if (msg == "#refuel") {
            refueled = TRUE;
            fuelSecCurr = 0;
            fuelPerc = fuelTotal;
            llMessageLinked(LINK_SET, fuelPerc, "#fuel", "");
            rcxSay("Refueled!");
            
        } else if (msg == "#narrate") {
            swNarrate = num;
            
        } else if (msg == "#fuel.enabled") {
            swFuel = num;
        
        } else if (msg == "#feed") {
            swFeed = num;
            refreshHUD(TRUE);
        
        } else {
            if (llJsonGetValue(msg, ["msg"]) != JSON_INVALID) {
                string cmd = llJsonGetValue(msg, ["msg"]);
                if (cmd == "conf.engine") {
                    // Load JSON Engine Settings (CONF)
                    fwdInc = (integer)llJsonGetValue(msg, ["thrust.increments"]);
                    fwdMin = (integer)llJsonGetValue(msg, ["thrust.min"]);
                    fwdMax = (integer)llJsonGetValue(msg, ["thrust.max"]);
                    spdLanding = (float)llJsonGetValue(msg, ["speed.landing"]);
                    spdFull = (float)llJsonGetValue(msg, ["speed.full"]);
                    spdCruise = (float)llJsonGetValue(msg, ["speed.cruise"]);
                    hoverHeight = (float)llJsonGetValue(msg, ["float.height"]);
                    fuelTotal = (integer)llJsonGetValue(msg, ["fuel.total"]);
                    fuelSecMax = (integer)llJsonGetValue(msg, ["fuel.time"]);
                    fuelUnit = llJsonGetValue(msg, ["fuel.unit"]);
                    
                    // Apply Settings
                    if (vType == BOAT_MODE) {
                        llSetVehicleFloatParam(VEHICLE_HOVER_HEIGHT, hoverHeight);
                    }
                    
                    rcxSay("Engine Configuration Loaded");
                }
            }
        }
    }
    
    /*========================================
    RUN TIME PERMS: CHECK PERMISSIONS AND TAKE CONTROLS
    ========================================*/
    run_time_permissions(integer perm) 
    {
        if (controlsTaken = perm & PERMISSION_TAKE_CONTROLS) {
            
            llMessageLinked(LINK_SET, fwdMax, "#fwdMax", "");
            
        } else {
            llSetTimerEvent(1);
        }
    } 
    
    /*========================================
    TIMER
    ========================================*/
    timer()
    {

        /**
         * @author: reconx86
         * @since: 2016.17.04
         *
         * RCX Crashed Vehicle Detection System and Beacon
         */
        float beaconTime = llGetTime();
        integer physical = llGetStatus(STATUS_PHYSICS);
        integer crashed = !physical && seated && engineOn;
        integer abandoned = !physical && !seated && vType == PLANE_MODE;
        //integer guestpark = lastPilot != owner && !seated // Auto-delete guest-craft
        integer autoDelete = crashed || abandoned;
        if (autoDelete) {
            if (beaconTime >= lastBeaconTime + beaconInterval) {
                if (!beaconReminders) {
                    llInstantMessage(llGetOwner(), "Aircraft has been auto-deleted");
                    llDie();
                } else {
                    string msg;
                    msg += "\n" + "Abandoned Aircraft Beacon: " + rcxGetSlurl();
                    msg += "\n" + "Time to deletion: " + (string)beaconInterval + " seconds";
                    if (crashed) {
                        msg += "\n";
                        msg += "\n" + "Tip #1 DO NOT RELOG If you can't move your avatar:";
                        msg += "\n" + "Avatar > Avatar Health > Recreate LSL Bridge";
                        msg += "\n";
                        msg += "\n" + "Tip #2 If you are stuck in the seated position:";
                        msg += "\n" + "Avatar > Avatar Health > Stop Avatar Animations";
                    }
                    llInstantMessage(llGetOwner(), msg);
                }
                lastBeaconTime = beaconTime;
                beaconReminders--;
            }
            
        } else if (!autoDelete && lastBeaconTime != 0x80000000) {
            beaconReminders = 1;
            lastBeaconTime = 0x80000000;// Makes beacon TRUE
            llOwnerSay("Time to deletion reset");
        }
        /**
         * End of RCX Crashed Vehicle Detection System and Beacon
         */
        
        //temporary engine fix / rcx mod
        //if (!controlsTaken) {
            //controlsTaken = llGetPermissions() & PERMISSION_TAKE_CONTROLS;
        //}

        if (seated) {
            pos = llGetPos();
            speed = llVecMag(llGetVel());// Speed in knots
            vector xAxis = llRot2Fwd(llGetRootRotation());
            aoa = (getAoA(0.9, -xAxis.z) + 0.5) * -1;// Angle of attack 
            
            waterZ = llWater(ZERO_VECTOR);// Water height in this region
            groundZ = llGround(ZERO_VECTOR);// Ground height under vehicle
            aow = pos.z - waterZ - hoverHeight;// Altitude over water
            
            // ================== PLANE MODE ================================
            if (vType == PLANE_MODE) {
                
                buoyancy = speed / spdCruise;
                
                // Approximate lift based on speed
                if (swFlaps) {
                    // Flap-algorithm here (Experimental)
                    buoyancy = buoyancy * 1.7;
                } 
                if (buoyancy > 0.977) {
                    buoyancy = 0.977;
                }
                llSetVehicleFloatParam(VEHICLE_BUOYANCY, buoyancy);
                
                if (aow < 0.08) { // If less than 8cm above water to turn into watercraft
                
                    /**
                     * === FIGURE OUT HOW TO LAND ON WATER ===
                     * If plane touches the water with aoa less than this, it becomes a sub,
                     * otherwise, it becomes a floatplane
                     */
                    if (elevPos >= 0) {
                        boatMode();// Stick is centre or forward (otherwise remain in airplane mode)
                    }
                    //===
                    
                } else if (speed < spdLanding) {
                    /**
                     * Taxi Mode Problem:
                     * Just before the airplane crosses a sim-border it collides with the simborder,
                     * This can cause the airplane to inadvertently go into taxi-mode, to counter-act
                     * this problem we check if the region change-time is greater than the
                     * collision-time.
                     */
                    if (swGear && llGetTime() <= collisionTime + 1.0 && collisionTime > regionChange) {
                        taxiMode();
                    }
                }
                
             // ================== BOAT MODE ================================
            } else if (vType == BOAT_MODE) {
            
                // TRANSITION TO FLIGHT FROM FLOATING
                if (speed >= spdLanding && elevPos <= 0) {
                    llApplyImpulse(<0, 0, 125>, 0);// RCX mod (temp disabled)
                    planeMode();
                    
                } else if (aow > 0.5) {
                    planeMode();
                    
                } else if (aow > 0.05 && aoa > 3 && swGear) {
                    taxiMode();
                }
                
            // ================== TAXI MODE ================================
            } else if (vType == TAXI_MODE) {
            // If in taxi-mode (taxiing or parked on ground)
                if (debugVtype == VEHICLE_TYPE_NONE && (fwd != 0 || controlTime == fwdTime)) {
                    taxiMode();
                }
                if (aow < 0.5 && groundZ < waterZ) {
                    boatMode();
                    
                } else if (speed >= spdLanding) {
                    planeMode();
                }
            }
            setMotor();

            if (engineOn) {
                if (swFuel == TRUE) {
                    // FUEL CALCULATIONS =====================================
                    if (!refueled) {
                        fuelSecCurr += engInterval;// Normal fuel consumption
                    } else  {
                        fuelSecCurr = 0;
                        refueled = FALSE;
                    }
                    if (fuelSecCurr < fuelSecMax) { // Still fuel left
                        fuelPerc = llRound(((fuelSecMax - fuelSecCurr) / fuelSecMax) * fuelTotal);
                        if (fuelPerc != fuelPercLast) {
                            fuelPercLast = fuelPerc;
                        }
                    } else {
                        rcxSay("Out of fuel!");
                        llMessageLinked(LINK_SET, 0, "#fuel", "");
                        // turn of lights / engine sounds / but keep physical
                        stopEng();
                    }
                    // =====================================
                }
                refreshHUD(FALSE);
            }
        }
    }
    
    land_collision(vector pos )
    {
        collisionTime = llGetTime();
    }
    
    collision(integer num_detected)
    {
        collisionTime = llGetTime();
    }

    /*========================================
    CHANGED: DETECT AV SITTING/UNSITTING AND GIVE PERMISSIONS
    ========================================*/
    changed(integer change)
    {
        if (change & CHANGED_OWNER) {
            llResetScript();
        }
        /*
        if (change & CHANGED_INVENTORY) {
            llResetScript();//instead of link-message trigger?
        }
        */
        if (change & CHANGED_REGION) {
            //Temporary bug-fix for "Erratic vehicle sim-crossings"
            llSetVehicleType(VEHICLE_TYPE_NONE);
            if (vType == TAXI_MODE) {
                taxiMode();
            } else if (vType == BOAT_MODE) {
                boatMode();
            } else if (vType == PLANE_MODE) {
                planeMode();
            }

            regionChange = llGetTime();
        }
    }
}