// RealHeli by Apotheus Silverman
// An advanced, semi-realistic helicopter vehicle script for Second Life.
// This product is released open source. Please give due credit in
// derivative works. :-)
//
// This behavior script is based on my experience with helicopter flight in
// Microsoft Flight Simulator and the included vehicle object is based on
// the Hughes 500 model helicopter.
//
// This vehicle can only be controlled properly in mouselook.
//
// Helicopter controls:
// left/right - tail rotor / rudder
// pgup/pgdn - collective
// mouse - yoke

key currentAgent = NULL_KEY;

float minSoundVolume = 0.4;
float maxSoundVolume = 1.0;

float ROTATION_RATE = 2.0; // Rate of turning
float FWD_THRUST = 20; // Forward thrust motor force
float BACK_THRUST = 7; // Backup thrust
float VERTICAL_THRUST = 7;
// Keep a running linear motor value for better response
vector linear_motor = <0,0,0>;

float collective = 0.0;
float oldCollective = 0.0;
float myMass;
string currentRegion;
vector currentVel;

vector reference_frame = <0,0,0>;

default {
state_entry() {
llPreloadSound("hughes 500");

llSitTarget(<0.88, 0.0, 0.4>, ZERO_ROTATION);
llSetCameraEyeOffset(<-7.0, 0.0, 3.0>);
llSetSitText("Fly");
llSetCameraAtOffset(<0, 0, 1>);

// Reset flags that can get set from other scripts
llSetBuoyancy(0.0);

//hover
llSetVehicleType(VEHICLE_TYPE_AIRPLANE);
llRemoveVehicleFlags(VEHICLE_FLAG_LIMIT_ROLL_ONLY| VEHICLE_FLAG_CAMERA_DECOUPLED);

llSetVehicleRotationParam(VEHICLE_REFERENCE_FRAME, llEuler2Rot(reference_frame * DEG_TO_RAD));
llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.9);
llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0.1);
llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 30);
llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 1000);
llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE, 0.5);
llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 0.5);
llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_TIMESCALE, 0.1);
llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 120);
llSetVehicleVectorParam(VEHICLE_LINEAR_FRICTION_TIMESCALE, <1000,1000,1000>);
llSetVehicleVectorParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE, <2,2,20>);
llSetVehicleFloatParam(VEHICLE_BUOYANCY, 0.0);
llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 0.2);
llSetVehicleFloatParam(VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 2.0);
llSetVehicleFloatParam(VEHICLE_BANKING_EFFICIENCY, 0.0);
llSetVehicleFloatParam(VEHICLE_BANKING_MIX, 0.0);
llSetVehicleFloatParam(VEHICLE_BANKING_TIMESCALE, 0.05);

// Hover mode (mouselook)
llRemoveVehicleFlags(VEHICLE_FLAG_MOUSELOOK_STEER) ;
llSetVehicleFlags(VEHICLE_FLAG_MOUSELOOK_BANK);
llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, <2, 2.5, 0>);
}

touch_start(integer num) {
//llWhisper(0, "Buy me! Right click and choose 'Buy' then take me out of your inventory to fly!");
}

changed(integer change) {
if (change & CHANGED_LINK) {
key agent = llAvatarOnSitTarget();
if (agent) {
if (agent != llGetOwner()) {
// only the owner can use this vehicle
llSay(0, "You aren't the owner of this vehicle.");
llUnSit(agent);
llPushObject(agent, <0,0,10>, ZERO_VECTOR, FALSE);
} else if (currentAgent == NULL_KEY) {
// driver is entering the vehicle
llSetStatus(STATUS_PHYSICS, TRUE);
llRequestPermissions(agent, PERMISSION_TRIGGER_ANIMATION | PERMISSION_TAKE_CONTROLS);
currentRegion = llGetRegionName();
currentVel = llGetVel();
llSetTimerEvent(1.0);
llMessageLinked(LINK_SET, 1, "hud", NULL_KEY);
llLoopSound("hughes 500", minSoundVolume);
}
} else if (currentAgent) {
// driver is getting up
currentAgent = NULL_KEY;
llStopSound();
llSetStatus(STATUS_PHYSICS, FALSE);
llStopAnimation("driving generic");
llReleaseControls();
llSetTimerEvent(0.0);
llMessageLinked(LINK_SET, 0, "hud", NULL_KEY);
}
}
}

run_time_permissions(integer perm) {
if (perm) {
currentAgent = llAvatarOnSitTarget();
llStartAnimation("driving generic");
myMass = llGetMass();
collective = 7.3;
llTakeControls(CONTROL_FWD | CONTROL_BACK | CONTROL_RIGHT | CONTROL_LEFT | CONTROL_ROT_RIGHT | CONTROL_ROT_LEFT | CONTROL_UP | CONTROL_DOWN, TRUE, FALSE);
}
}

timer() {
// Check for and attempt to correct bad sim border crossing
string newRegion = llGetRegionName();
if (newRegion != currentRegion) {
llApplyImpulse((currentVel - llGetVel()) * myMass, FALSE);
currentRegion = newRegion;
llSleep(0.2);
}


// Update VEHICLE_ANGULAR_DEFLECTION_TIMESCALE based on current velocity to simulate drag on the tail
currentVel = llGetVel();
float timescale = llVecMag(currentVel) * -0.05 + 0.7;
if (timescale < 0.01) {
timescale = 0.01;
}
llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, timescale);
// Simulate drag
llApplyImpulse(-currentVel * 0.3, FALSE);
}

control(key id, integer level, integer edge) {
if (level & CONTROL_LEFT) {
llApplyRotationalImpulse(<0,0,2> * myMass, TRUE);
}
if (level & CONTROL_RIGHT) {
llApplyRotationalImpulse(<0,0,-2> * myMass, TRUE);
}

if (level & CONTROL_UP) {
collective += 0.25;
}
if (level & CONTROL_DOWN) {
collective -= 0.25;
}
if (oldCollective != collective) {
//llWhisper(0, "Collective " + (string)collective);
if (llFabs(collective - 9.8) < 0.1) {
// Make sure we can always hover
collective = 9.8;
}
if (collective > 12.8) {
collective = 12.8;
} else if (collective < 7.3) {
collective = 7.3;
}
llSetForce(<0, 0, collective> * llGetMass(), TRUE);
llMessageLinked(LINK_SET, (integer)(((collective - 7.3) / 5.5) * 100.0), "throttle", NULL_KEY);
llAdjustSoundVolume(((collective - 7.3) / 5.5) * (maxSoundVolume - minSoundVolume) + minSoundVolume);
}
oldCollective = collective;
}
}