//------------------------------------------------------------------------
void CVehicleMovementVTOL::UpdateEngine(float deltaTime)
{
	// will update the engine power up to the maximum according to the ignition time

	float damageMult = GetDamageMult();

	float enginePowerMax = m_enginePowerMax * damageMult; 

	if (m_isEnginePowered && !m_isEngineGoingOff)
	{
		if (m_enginePower < enginePowerMax)
		{
			m_enginePower += deltaTime * (enginePowerMax / m_engineIgnitionTime);
			m_enginePower = min(m_enginePower, enginePowerMax);
		}
		else
		{
			m_enginePower = max(enginePowerMax, m_enginePower);
		}
	}
	else
	{
		if (m_enginePower >= 0.0f)
		{
			float powerReduction = enginePowerMax / m_engineIgnitionTime;
			if (m_damage)
				powerReduction *= 2.0f;

			m_enginePower -= deltaTime * powerReduction;
			m_enginePower = max(m_enginePower, 0.0f);
		}
	}
}
//------------------------------------------------------------------------
void CVehicleMovementHelicopter::ProcessActions(const float deltaTime)
{
	FUNCTION_PROFILER(GetISystem(), PROFILE_GAME);

	UpdateDamages(deltaTime);
	UpdateEngine(deltaTime);

	m_velDamp = 0.0f;

	m_playerControls.ProcessActions(deltaTime);

	Limit(m_forwardAction, -1.0f, 1.0f);
	Limit(m_strafeAction, -1.0f, 1.0f);

	m_actionYaw = 0.0f;

	Matrix33 tm(m_PhysPos.q);
	Ang3 angles = Ang3::GetAnglesXYZ(tm);
	Vec3 worldPos =  m_PhysPos.pos;

	// +ve pitch means nose up
	const float &currentPitch = angles.x;
	// +ve roll means to the left
	const float &currentRoll = angles.y;
	// +ve direction mean rotation anti-clockwise about the z axis - 0 means along y
	float currentDir = angles.z;

	float pitchDeg = RAD2DEG(currentPitch);

	if(m_maxPitchAngleMov != 0.0f && pitchDeg >= (m_maxPitchAngleMov * 0.5f))
	{
		float mult = pitchDeg / (m_maxPitchAngleMov);

		if(mult > 1.0f && m_desiredPitch < 0.0f)
		{
			m_desiredPitch *= 0.0f;
			m_actionPitch *= 0.0f;
			m_desiredPitch += 0.5f * mult;
		}
		else if(m_desiredPitch < 0.0f)
		{
			m_desiredPitch *= (1.0f - mult);
			m_desiredPitch += 0.05f;
		}
	}
	else if(m_maxPitchAngleMov != 0.0f && pitchDeg <= (-m_maxPitchAngleMov * 0.5f))
	{
		float mult = abs(pitchDeg) / (m_maxPitchAngleMov);

		if(mult > 1.0f && m_desiredPitch > 0.0f)
		{
			m_desiredPitch *= 0.0f;
			m_actionPitch *= 0.0f;
			m_desiredPitch += 0.5f * mult;
		}
		else if(m_desiredPitch > 0.0f)
		{
			m_desiredPitch *= (1.0f - mult);
			m_desiredPitch -= 0.05f;
		}
	}

	if(m_pInvertPitchVar->GetIVal() == 0)
		m_desiredPitch *= -1.0f;

	Vec3 currentVel = m_PhysDyn.v;
	Vec3 currentVel2D = currentVel;
	currentVel2D.z = 0.0f;

	if(currentRoll >= DEG2RAD(m_maxRollAngle * 0.5f) && m_desiredRoll > 0.001f)
	{
		float r = currentRoll / DEG2RAD(m_maxRollAngle);
		r = min(1.0f, r * 1.0f);
		r = 1.0f - r;
		m_desiredRoll *= r;
		m_desiredRoll = min(1.0f, m_desiredRoll);
	}
	else if(currentRoll <= DEG2RAD(-m_maxRollAngle * 0.5f) && m_desiredRoll < 0.001f)
	{
		float r = abs(currentRoll) / DEG2RAD(m_maxRollAngle);
		r = min(1.0f, r * 1.0f);
		r = 1.0f - r;
		m_desiredRoll *= r;
		m_desiredRoll = max(-1.0f, m_desiredRoll);
	}

	Vec3 currentFwdDir2D = m_currentFwdDir;
	currentFwdDir2D.z = 0.0f;
	currentFwdDir2D.NormalizeSafe();

	Vec3 currentLeftDir2D(-currentFwdDir2D.y, currentFwdDir2D.x, 0.0f);

	currentVel2D.z = 0.0f;

	float currentHeight = worldPos.z;
	float currentFwdSpeed = currentVel.Dot(currentFwdDir2D);

	ProcessActions_AdjustActions(deltaTime);

	float inputMult = m_basicSpeedFraction;

	// desired things
	float turnDecreaseScale = m_yawDecreaseWithSpeed / (m_yawDecreaseWithSpeed + fabs(currentFwdSpeed));

	Vec3 desired_vel2D =
		currentFwdDir2D * m_forwardAction * m_maxFwdSpeed * inputMult +
		currentLeftDir2D * m_strafeAction * m_maxLeftSpeed * inputMult;

	// calculate the angle changes

	Vec3 desiredVelChange2D = desired_vel2D - currentVel2D;

	float desiredTiltAngle = m_tiltPerVelDifference * desiredVelChange2D.GetLength();
	Limit(desiredTiltAngle, -m_maxTiltAngle, m_maxTiltAngle);

	float goal = abs(m_desiredPitch) + abs(m_desiredRoll);
	goal *= 1.5f;
	Interpolate(m_playerAcceleration, goal, 0.25f, deltaTime);
	Limit(m_playerAcceleration, 0.0f, 5.0f);

	if(!iszero(m_desiredPitch))
	{
		m_actionPitch -= m_desiredPitch * m_pitchInputConst;
		Limit(m_actionPitch, -m_maxYawRate, m_maxYawRate);
	}

	m_actionRoll += m_pitchActionPerTilt * m_desiredRoll * (m_playerAcceleration + 1.0f);
	Limit(m_actionRoll, -10.0f, 10.0f);
	Limit(m_actionPitch, -10.0f, 10.0f);

	float relaxRollTolerance = 0.0f;

	if(!iszero(m_turnAction) || abs(m_PhysDyn.w.z) > DEG2RAD(10.0f))
	{
		m_actionYaw += -m_turnAction * m_yawInputConst * GetDamageMult();

		float side = 0.0f;

		if(abs(m_turnAction) > 0.01f)
			side = min(1.0f, max(-1.0f, m_turnAction));

		float roll = DEG2RAD(m_extraRollForTurn * side) - (currentRoll);
		m_actionRoll += max(0.0f, abs(roll)) * side * m_rollForTurnForce;

		float pitchComp = abs(currentPitch) / DEG2RAD(2.50f);

		if(pitchComp > 1.0f)
			roll *= pitchComp;

		roll *= max(1.0f, abs(m_PhysDyn.w.z));

		m_actionRoll += roll;

		Limit(m_actionYaw, -m_maxYawRate, m_maxYawRate);
	}

	m_desiredDir = currentDir;
	m_lastDir = currentDir;

	float boost = Boosting() ? m_boostMult : 1.0f;

	if(m_pAltitudeLimitVar)
	{
		float altitudeLimit = m_pAltitudeLimitVar->GetFVal();

		if(!iszero(altitudeLimit))
		{
			float altitudeLowerOffset;

			if(m_pAltitudeLimitLowerOffsetVar)
			{
				float r = 1.0f - min(1.0f, max(0.0f, m_pAltitudeLimitLowerOffsetVar->GetFVal()));
				altitudeLowerOffset = r * altitudeLimit;
			}
			else
				altitudeLowerOffset = altitudeLimit;

			float mult = 1.0f;

			if(currentHeight >= altitudeLimit)
			{
				if(m_liftAction > 0.f)
				{
					mult = 0.0f;
				}
			}
			else if(currentHeight >= altitudeLowerOffset)
			{
				float zone = altitudeLimit - altitudeLowerOffset;
				mult = (altitudeLimit - currentHeight) / (zone);
			}

			m_liftAction *= mult;

			if(currentPitch > DEG2RAD(0.0f))
			{
				if(m_forwardAction > 0.0f)
					m_forwardAction *= mult;

				if(m_actionPitch > 0.0f)
				{
					m_actionPitch *= mult;
					m_actionPitch += -currentPitch;
				}
			}

			m_desiredHeight = min(altitudeLowerOffset, currentHeight);
		}
	}
	else
	{
		m_desiredHeight = currentHeight;
	}

	ProcessActionsLift(deltaTime);

	if(m_pStabilizeVTOL)
	{
		float stabilizeTime = m_pStabilizeVTOL->GetFVal();

		if(stabilizeTime > 0.0f)
		{
			if(m_relaxTimer < 6.0f)
				m_relaxTimer += deltaTime;
			else
			{
				float r = currentRoll - relaxRollTolerance;
				r = min(1.0f, max(-1.0f, r));

				m_actionRoll += -r * m_relaxForce * (m_relaxTimer / 6.0f);
			}
		}
	}

	if(m_netActionSync.PublishActions(CNetworkMovementHelicopter(this)))
		CHANGED_NETWORK_STATE(m_pVehicle, eEA_GameClientDynamic);
}
//------------------------------------------------------------------------
void CVehicleMovementVTOL::ProcessActions(const float deltaTime)
{
	FUNCTION_PROFILER( GetISystem(), PROFILE_GAME );

	UpdateDamages(deltaTime);
	UpdateEngine(deltaTime);

	m_velDamp = 0.25f;

	m_playerControls.ProcessActions(deltaTime);

	Limit(m_forwardAction, -1.0f, 1.0f);
	Limit(m_strafeAction, -1.0f, 1.0f);

	m_actionYaw = 0.0f;

	Vec3 worldPos = m_pEntity->GetWorldPos();

	IPhysicalEntity* pPhysics = GetPhysics();

	// get the current state

	// roll pitch + yaw

	Matrix34 worldTM = m_pRotorPart ? m_pRotorPart->GetWorldTM() : m_pEntity->GetWorldTM();
//	if (m_pRotorPart)
//		worldTM = m_pRotorPart->GetWorldTM();
//	else
//		worldTM = m_pEntity->GetWorldTM();

	Vec3 specialPos = worldTM.GetTranslation();
	Ang3 angles = Ang3::GetAnglesXYZ(Matrix33(worldTM));

	Matrix33 tm;
	tm.SetRotationXYZ((angles));

	// +ve pitch means nose up
	const float& currentPitch = angles.x;
	// +ve roll means to the left
	const float& currentRoll = angles.y;
	// +ve direction mean rotation anti-clockwise about the z axis - 0 means along y
	float currentDir = angles.z;

	const float maxPitchAngle = 60.0f;
	
	float pitchDeg = RAD2DEG(currentPitch);
	if (pitchDeg >= (maxPitchAngle * 0.75f))
	{
		float mult = pitchDeg / (maxPitchAngle);
		
		if (mult > 1.0f && m_desiredPitch < 0.0f)
		{
			m_desiredPitch *= 0.0f;
			m_actionPitch *= 0.0f;
			m_desiredPitch += 0.2f * mult;
		}
		else if (m_desiredPitch < 0.0f)
		{
			m_desiredPitch *= (1.0f - mult);
			m_desiredPitch += 0.05f;
		}
	}
	else if (pitchDeg <= (-maxPitchAngle * 0.75f))
	{
		float mult = abs(pitchDeg) / (maxPitchAngle);

		if (mult > 1.0f && m_desiredPitch > 0.0f)
		{
			m_desiredPitch *= 0.0f;
			m_actionPitch *= 0.0f;
			m_desiredPitch += 0.2f * mult;
		}
		else if (m_desiredPitch > 0.0f)
		{
			m_desiredPitch *= (1.0f - mult);
			m_desiredPitch -= 0.05f;
		}
	}

	if (currentRoll >= DEG2RAD(m_maxRollAngle * 0.7f) && m_desiredRoll > 0.001f)
	{
		float r = currentRoll / DEG2RAD(m_maxRollAngle);
		r = min(1.0f, r * 1.0f);
		r = 1.0f - r;
		m_desiredRoll *= r;
		m_desiredRoll = min(1.0f, m_desiredRoll);
	}
	else if (currentRoll <= DEG2RAD(-m_maxRollAngle * 0.7f) && m_desiredRoll < 0.001f)
	{
		float r = abs(currentRoll) / DEG2RAD(m_maxRollAngle);
		r = min(1.0f, r * 1.0f);
		r = 1.0f - r;
		m_desiredRoll *= r;
		m_desiredRoll = max(-1.0f, m_desiredRoll);
	}

	Vec3 currentFwdDir2D = m_currentFwdDir;
	currentFwdDir2D.z = 0.0f;
	currentFwdDir2D.NormalizeSafe();

	Vec3 currentLeftDir2D(-currentFwdDir2D.y, currentFwdDir2D.x, 0.0f);

	Vec3 currentVel = m_PhysDyn.v;
	Vec3 currentVel2D = currentVel;
	currentVel2D.z = 0.0f;

	float currentHeight = worldPos.z;
	float currentFwdSpeed = currentVel.Dot(currentFwdDir2D);

	ProcessActions_AdjustActions(deltaTime);

	float inputMult = m_basicSpeedFraction;

	// desired things
	float turnDecreaseScale = m_yawDecreaseWithSpeed / (m_yawDecreaseWithSpeed + fabs(currentFwdSpeed));

	Vec3 desired_vel2D = 
		currentFwdDir2D * m_forwardAction * m_maxFwdSpeed * inputMult + 
		currentLeftDir2D * m_strafeAction * m_maxLeftSpeed * inputMult;

	// calculate the angle changes

	Vec3 desiredVelChange2D = desired_vel2D - currentVel2D;

	float desiredTiltAngle = m_tiltPerVelDifference * desiredVelChange2D.GetLength();
	Limit(desiredTiltAngle, -m_maxTiltAngle, m_maxTiltAngle);

	float goal = abs(m_desiredPitch) + abs(m_desiredRoll);
	goal *= 1.5f;
	Interpolate(m_playerAcceleration, goal, 0.25f, deltaTime);
	Limit(m_playerAcceleration, 0.0f, 5.0f);

	//static float g_angleLift = 4.0f;

	if (abs(m_liftAction) > 0.001f && abs(m_forwardAction) < 0.001)
	{
//		float pitch = RAD2DEG(currentPitch);

		if (m_liftPitchAngle < 0.0f && m_liftAction > 0.0f)
			m_liftPitchAngle = 0.0f;
		else if (m_liftPitchAngle > 0.0f && m_liftAction < 0.0f)
			m_liftPitchAngle = 0.0f;

		Interpolate(m_liftPitchAngle, 1.25f * m_liftAction, 0.75f, deltaTime);

		if (m_liftPitchAngle < 1.0f && m_liftPitchAngle > -1.0f)
			m_desiredPitch += 0.05f * m_liftAction;
	}
	else if (m_liftAction < 0.001f && abs(m_liftPitchAngle) > 0.001)
	{
		Interpolate(m_liftPitchAngle, 0.0f, 1.0f, deltaTime);
		m_desiredPitch += 0.05f * -m_liftPitchAngle;
	}

	/* todo
	else if (m_liftAction < -0.001f)
	{
		m_desiredPitch += min(0.0f, (DEG2RAD(-5.0f) - currentPitch)) * 0.5f * m_liftAction;
	}*/

	if (!iszero(m_desiredPitch))
	{
		m_actionPitch -= m_desiredPitch * m_pitchInputConst;
		Limit(m_actionPitch, -m_maxYawRate, m_maxYawRate);
	}

	float rollAccel = 1.0f;
	if (abs(currentRoll + m_desiredRoll) < abs(currentRoll))
		rollAccel *= 1.25f;

	m_actionRoll += m_pitchActionPerTilt * m_desiredRoll * rollAccel * (m_playerAcceleration + 1.0f);
	Limit(m_actionRoll, -10.0f, 10.0f);
	Limit(m_actionPitch, -10.0f, 10.0f);

	// roll as we turn
	if (!m_strafeAction)
	{
		m_actionYaw += m_yawPerRoll * currentRoll;
	}

	if (abs(m_strafeAction) > 0.001f)
	{
		float side = 0.0f;
		side = min(1.0f, max(-1.0f, m_strafeAction));

		float roll = DEG2RAD(m_extraRollForTurn * 0.25f * side) - (currentRoll);
		m_actionRoll += max(0.0f, abs(roll)) * side * 1.0f;
	}

	float relaxRollTolerance = 0.0f;

	if (abs(m_turnAction) > 0.01f || abs(m_PhysDyn.w.z) > DEG2RAD(3.0f))
	{
		m_actionYaw += -m_turnAction * m_yawInputConst * GetDamageMult();

		float side = 0.0f;
		if (abs(m_turnAction) > 0.01f)
			side = min(1.0f, max(-1.0f, m_turnAction));

		float roll = DEG2RAD(m_extraRollForTurn * side) - (currentRoll);
		m_actionRoll += max(0.0f, abs(roll)) * side * m_rollForTurnForce;

		roll *= max(1.0f, abs(m_PhysDyn.w.z));

		m_actionRoll += roll;

		Limit(m_actionYaw, -m_maxYawRate, m_maxYawRate);
	}

	m_desiredDir = currentDir;
	m_lastDir = currentDir;

	float boost = Boosting() ? m_boostMult : 1.0f;
	float liftActionMax = 1.0f;

	if (m_pAltitudeLimitVar)
	{
		float altitudeLimit = m_pAltitudeLimitVar->GetFVal();

		if (!iszero(altitudeLimit))
		{
			float altitudeLowerOffset;

			if (m_pAltitudeLimitLowerOffsetVar)
			{
				float r = 1.0f - min(1.0f, max(0.0f, m_pAltitudeLimitLowerOffsetVar->GetFVal()));
				altitudeLowerOffset = r * altitudeLimit;
			}
			else
				altitudeLowerOffset = altitudeLimit;

			float mult = 1.0f;

			if (currentHeight >= altitudeLimit)
			{
				if (m_liftAction > 0.f)
				{
					mult = 0.0f;
				}
			}
			else if (currentHeight >= altitudeLowerOffset)
			{
				float zone = altitudeLimit - altitudeLowerOffset;
				mult = (altitudeLimit - currentHeight) / (zone);
			}

			m_liftAction *= mult;

			if (currentPitch > DEG2RAD(0.0f))
			{
				if (m_forwardAction > 0.0f)
					m_forwardAction *= mult;

				if (m_actionPitch > 0.0f)
				{
					m_actionPitch *= mult;
					m_actionPitch += -currentPitch;
				}
			}

			m_desiredHeight = min(altitudeLowerOffset, currentHeight);
		}
	}
	else
	{
		m_desiredHeight = currentHeight;
	}

	if (abs(m_liftAction) > 0.001f)
	{
		m_liftAction = min(liftActionMax, max(-0.2f, m_liftAction));

		m_hoveringPower = (m_powerInputConst * m_liftAction) * boost;
		m_noHoveringTimer = 0.0f;
	}
	else if (!m_isTouchingGround)
	{
		if (m_noHoveringTimer <= 0.0f)
		{
			float gravity;

			pe_simulation_params paramsSim;
			if (pPhysics->GetParams(&paramsSim))
				gravity = abs(paramsSim.gravity.z);
			else
				gravity = 9.2f;

			float upDirZ = m_workingUpDir.z;

			if (abs(m_forwardAction) > 0.01 && upDirZ > 0.0f)
				upDirZ = 1.0f;
			else if (upDirZ > 0.8f)
				upDirZ = 1.0f;

			float upPower = upDirZ;
			upPower -= min(1.0f, abs(m_forwardAction) * abs(angles.x));

			float turbulenceMult = 1.0f - min(m_turbulenceMultMax, m_turbulence);
			Vec3& impulse = m_control.impulse;
			impulse += Vec3(0.0f, 0.0f, upPower) * gravity * turbulenceMult * GetDamageMult();
			impulse.z -= m_PhysDyn.v.z * turbulenceMult;
		}
		else
		{
			m_noHoveringTimer -= deltaTime;
		}
	}

	if (m_pStabilizeVTOL)
	{
		float stabilizeTime = m_pStabilizeVTOL->GetFVal();

		if (stabilizeTime > 0.0f)
		{
			if (m_relaxTimer < 6.0f)
				m_relaxTimer += deltaTime;
			else
			{
				float r = currentRoll - relaxRollTolerance;
				r = min(1.0f, max(-1.0f, r));

				m_actionRoll += -r * m_relaxForce * (m_relaxTimer / 6.0f);
			}

		}
	}

	if (m_netActionSync.PublishActions( CNetworkMovementHelicopter(this) ))
		m_pVehicle->GetGameObject()->ChangedNetworkState(eEA_GameClientDynamic);
}
//------------------------------------------------------------------------
void CVehicleMovementVTOL::PreProcessMovement(const float deltaTime)
{
	CVehicleMovementHelicopter::PreProcessMovement(deltaTime);

	if (abs(m_forwardAction) > 0.0f && m_timeOnTheGround <= 0.01f)
		SetHorizontalMode(1.0f);
	else
		SetHorizontalMode(0.0f);

	if (m_forwardAction > 0.0f && m_timeOnTheGround <= 0.01f)
	{
		m_wingsTimer += deltaTime;
		m_wingsTimer = min(m_wingsTimer, m_timeUntilWingsRotate);
	}
	else
	{
		m_wingsTimer -= deltaTime * 0.65f;
		m_wingsTimer = max(m_wingsTimer, 0.0f);
	}

	Interpolate(m_wingsAnimTime, 1.0f - (m_wingsTimer / m_timeUntilWingsRotate), m_wingsSpeed, deltaTime);

	if (!m_isVTOLMovement)
		return;
	
	IPhysicalEntity* pPhysics = GetPhysics();
	assert(pPhysics);

	float gravity;
	pe_simulation_params paramsSim;
	if (pPhysics->GetParams(&paramsSim))
		gravity = abs(paramsSim.gravity.z);
	else
		gravity = 9.2f;

	float vertical = 1.0f - m_horizontal;

	//m_engineForce = max(1.0f, gravity * vertical) * m_enginePower * max(0.25f, vertical);
	m_engineForce = 0.0f;
	m_engineForce += gravity * vertical * m_enginePower;
	m_engineForce += m_horizontal * m_enginePower;

	Matrix33 tm( m_PhysPos.q);
	Ang3 angles = Ang3::GetAnglesXYZ(tm);

 	m_workingUpDir = m_engineUpDir; //Vec3(0.0f, 0.0f, 1.0f);
	
	m_workingUpDir += (vertical * m_rotorDiskTiltScale * Vec3(angles.y, -angles.x, 0.0f));
	m_workingUpDir += (m_horizontal * m_rotorDiskTiltScale * Vec3(0.0f, 0.0f, angles.z));

	m_workingUpDir = tm * m_workingUpDir;
	m_workingUpDir.z += 0.25f;
	m_workingUpDir.NormalizeSafe();

	float strafe = m_strafeAction * m_strafeForce;

	if (m_noHoveringTimer <= 0.0f)
	{
		Vec3 forwardImpulse;

		float turbulenceMult = 1.0f - min(m_turbulenceMultMax, m_turbulence);

		forwardImpulse = m_currentFwdDir * m_enginePower * m_horizFwdForce * m_horizontal
			* (m_forwardAction + (Boosting() * m_boostForce)) * GetDamageMult() * turbulenceMult;

		if (m_forwardAction < 0.0f)
			forwardImpulse *= m_forwardInverseMult;

		forwardImpulse += m_currentUpDir * m_liftAction * m_enginePower * gravity;
		Vec3 fakeLeftDir = tm * Vec3(-1.0f, 0.0f, 0.0f);
		fakeLeftDir.z = 0.0f;
		forwardImpulse += fakeLeftDir * -strafe * m_enginePower * m_horizLeftForce * turbulenceMult;

 		float horizDamp = 0.25f;
		static float vertDamp = 0.0f;

		if ( m_movementAction.isAI )
			horizDamp *= abs(m_turnAction * 4.0f) + 1.0f;
		else
			horizDamp = m_velDamp;

 		m_control.impulse += forwardImpulse;
		m_control.impulse.x -= m_PhysDyn.v.x * horizDamp * turbulenceMult;
		m_control.impulse.y -= m_PhysDyn.v.y * horizDamp * turbulenceMult;
		m_control.impulse.z -= m_PhysDyn.v.z * vertDamp * turbulenceMult;
	}

	m_workingUpDir.z += 0.45f * m_liftAction;
	m_workingUpDir.NormalizeSafe();


	return;
}