#include "common.h"
#include "main.h"

#include "General.h"
#include "CutsceneMgr.h"
#include "ModelIndices.h"
#include "FileMgr.h"
#include "Streaming.h"
#include "Replay.h"
#include "Camera.h"
#include "DMAudio.h"
#include "Wanted.h"
#include "Coronas.h"
#include "Particle.h"
#include "Explosion.h"
#include "Fluff.h"
#include "World.h"
#include "HandlingMgr.h"
#include "Heli.h"
#include "Plane.h"
#include "MemoryHeap.h"

CPlaneNode *pPathNodes;
CPlaneNode *pPath2Nodes;
CPlaneNode *pPath3Nodes;
CPlaneNode *pPath4Nodes;
int32 NumPathNodes;
int32 NumPath2Nodes;
int32 NumPath3Nodes;
int32 NumPath4Nodes;
float TotalLengthOfFlightPath;
float TotalLengthOfFlightPath2;
float TotalLengthOfFlightPath3;
float TotalLengthOfFlightPath4;
float TotalDurationOfFlightPath;
float TotalDurationOfFlightPath2;
float TotalDurationOfFlightPath3;
float TotalDurationOfFlightPath4;
float LandingPoint;
float TakeOffPoint;
CPlaneInterpolationLine aPlaneLineBits[6];

float PlanePathPosition[3];
float OldPlanePathPosition[3];
float PlanePathSpeed[3];
float PlanePath2Position[5];
float PlanePath3Position[4];
float PlanePath2Speed[5];
float PlanePath3Speed[4];


enum
{
	CESNA_STATUS_NONE,	// doesn't even exist
	CESNA_STATUS_FLYING,
	CESNA_STATUS_DESTROYED,
	CESNA_STATUS_LANDED,
};

int32 CesnaMissionStatus;
int32 CesnaMissionStartTime;
CPlane *pDrugRunCesna;
int32 DropOffCesnaMissionStatus;
int32 DropOffCesnaMissionStartTime;
CPlane *pDropOffCesna;

CPlane::CPlane(int32 id, uint8 CreatedBy)
 : CVehicle(CreatedBy)
{
	CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id);
	m_vehType = VEHICLE_TYPE_PLANE;
	pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId);
	SetModelIndex(id);

	m_fMass = 100000000.0f;
	m_fTurnMass = 100000000.0f;
	m_fAirResistance = 0.9994f;
	m_fElasticity = 0.05f;

	bUsesCollision = false;
	m_bHasBeenHit = false;
	m_bIsDrugRunCesna = false;
	m_bIsDropOffCesna = false;
	m_bTempPlane = false;

	SetStatus(STATUS_PLANE);
	bIsBIGBuilding = true;
	m_level = LEVEL_GENERIC;

	m_isFarAway = false;
#ifdef CPLANE_ROTORS
	m_fRotorRotation = 0.0f;
#endif
}

CPlane::~CPlane()
{
	DeleteRwObject();
}

void
CPlane::SetModelIndex(uint32 id)
{
	CVehicle::SetModelIndex(id);
#ifdef CPLANE_ROTORS
	int i;
	for(i = 0; i < NUM_PLANE_NODES; i++)
		m_aPlaneNodes[i] = nil;
	if(GetModelIndex() == MI_CHOPPER){
		// This is surprisingly annoying...
		RwFrame *heliNodes[NUM_HELI_NODES];
		for(i = 0; i < NUM_HELI_NODES; i++)
			heliNodes[i] = nil;
		CClumpModelInfo::FillFrameArray(GetClump(), heliNodes);
		m_aPlaneNodes[PLANE_TOPROTOR] = heliNodes[HELI_TOPROTOR];
		m_aPlaneNodes[PLANE_BACKROTOR] = heliNodes[HELI_BACKROTOR];
	}else
		CClumpModelInfo::FillFrameArray(GetClump(), m_aPlaneNodes);
#endif
}

void
CPlane::DeleteRwObject(void)
{
	if(m_rwObject && RwObjectGetType(m_rwObject) == rpATOMIC){
		GetMatrix().Detach();
		if(RwObjectGetType(m_rwObject) == rpATOMIC){	// useless check
			RwFrame *f = RpAtomicGetFrame((RpAtomic*)m_rwObject);
			RpAtomicDestroy((RpAtomic*)m_rwObject);
			RwFrameDestroy(f);
		}
		m_rwObject = nil;
	}
	CEntity::DeleteRwObject();
}

// There's a LOT of copy and paste in here. Maybe this could be refactored somehow
void
CPlane::ProcessControl(void)
{
	int i;
	CVector pos;

	if(CReplay::IsPlayingBack())
		return;

	if(GetModelIndex() == MI_AIRTRAIN){
		if(GetPosition().z > 100.0f)
			CPlaneTrails::RegisterPoint(GetPosition(), m_nPlaneId);
	}else if(GetModelIndex() == MI_DEADDODO)
		CPlaneBanners::RegisterPoint(GetPosition(), m_nPlaneId);

	// Explosion
	if(m_bHasBeenHit){
		// BUG: since this is all based on frames, you can skip the explosion processing when you go into the menu
		if(GetModelIndex() == MI_AIRTRAIN){
			int frm = CTimer::GetFrameCounter() - m_nFrameWhenHit;
			if(frm == 20){
				static int nFrameGen;
				CRGBA colors[8];

				CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), 0);

				colors[0] = CRGBA(0, 0, 0, 255);
				colors[1] = CRGBA(224, 230, 238, 255);
				colors[2] = CRGBA(224, 230, 238, 255);
				colors[3] = CRGBA(0, 0, 0, 255);
				colors[4] = CRGBA(224, 230, 238, 255);
				colors[5] = CRGBA(0, 0, 0, 255);
				colors[6] = CRGBA(0, 0, 0, 255);
				colors[7] = CRGBA(224, 230, 238, 255);

				CVector dir;
				for(i = 0; i < 40; i++){
					dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f);
					dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f);
					dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f);
					int rotSpeed = CGeneral::GetRandomNumberInRange(10, 30);
					if(CGeneral::GetRandomNumber() & 1)
						rotSpeed = -rotSpeed;
					int f = ++nFrameGen & 3;
					CParticle::AddParticle(PARTICLE_HELI_DEBRIS, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), dir,
						nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f),
						colors[nFrameGen&7], rotSpeed, 0, f, 0);
				}
			}
			if(frm >= 40 && frm <= 80 && frm & 1){
				if(frm & 1){
					pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f;
					pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f;
					pos.y = frm - 40;
					pos = GetMatrix() * pos;
				}else{
					pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f;
					pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.2f;
					pos.y = 40 - frm;
					pos = GetMatrix() * pos;
				}
				CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, pos, 0);
			}
			if(frm == 60)
				bRenderScorched = true;
			if(frm == 82){
				TheCamera.SetFadeColour(255, 255, 255);
				TheCamera.Fade(0.0f, FADE_OUT);
				TheCamera.ProcessFade();
				TheCamera.Fade(1.0f, FADE_IN);
				FlagToDestroyWhenNextProcessed();
			}
		}else{
			int frm = CTimer::GetFrameCounter() - m_nFrameWhenHit;
			if(frm == 20){
				static int nFrameGen;
				CRGBA colors[8];

				CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), 0);

				colors[0] = CRGBA(0, 0, 0, 255);
				colors[1] = CRGBA(224, 230, 238, 255);
				colors[2] = CRGBA(224, 230, 238, 255);
				colors[3] = CRGBA(0, 0, 0, 255);
				colors[4] = CRGBA(252, 66, 66, 255);
				colors[5] = CRGBA(0, 0, 0, 255);
				colors[6] = CRGBA(0, 0, 0, 255);
				colors[7] = CRGBA(252, 66, 66, 255);

				CVector dir;
				for(i = 0; i < 40; i++){
					dir.x = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f);
					dir.y = CGeneral::GetRandomNumberInRange(-2.0f, 2.0f);
					dir.z = CGeneral::GetRandomNumberInRange(0.0f, 2.0f);
					int rotSpeed = CGeneral::GetRandomNumberInRange(30.0f, 20.0f);
					if(CGeneral::GetRandomNumber() & 1)
						rotSpeed = -rotSpeed;
					int f = ++nFrameGen & 3;
					CParticle::AddParticle(PARTICLE_HELI_DEBRIS, GetMatrix() * CVector(0.0f, 0.0f, 0.0f), dir,
						nil, CGeneral::GetRandomNumberInRange(0.1f, 1.0f),
						colors[nFrameGen&7], rotSpeed, 0, f, 0);
				}
			}
			if(frm >= 40 && frm <= 60 && frm & 1){
				if(frm & 1){
					pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f;
					pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f;
					pos.y = (frm - 40)*0.3f;
					pos = GetMatrix() * pos;
				}else{
					pos.x = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f;
					pos.z = ((CGeneral::GetRandomNumber() & 0x3F) - 32) * 0.1f;
					pos.y = (40 - frm)*0.3f;
					pos = GetMatrix() * pos;
				}
				CExplosion::AddExplosion(nil, FindPlayerPed(), EXPLOSION_HELI, pos, 0);
			}
			if(frm == 30)
				bRenderScorched = true;
			if(frm == 62){
				TheCamera.SetFadeColour(200, 200, 200);
				TheCamera.Fade(0.0f, FADE_OUT);
				TheCamera.ProcessFade();
				TheCamera.Fade(1.0f, FADE_IN);
				if(m_bIsDrugRunCesna){
					CesnaMissionStatus = CESNA_STATUS_DESTROYED;
					pDrugRunCesna = nil;
				}
				if(m_bIsDropOffCesna){
					DropOffCesnaMissionStatus = CESNA_STATUS_DESTROYED;
					pDropOffCesna = nil;
				}
				FlagToDestroyWhenNextProcessed();
			}
		}
	}

	// Update plane position and speed
	if(GetModelIndex() == MI_AIRTRAIN || !m_isFarAway || ((CTimer::GetFrameCounter() + m_randomSeed) & 7) == 0){
		if(GetModelIndex() == MI_AIRTRAIN){
			float pathPositionRear = PlanePathPosition[m_nPlaneId] - 30.0f;
			if(pathPositionRear < 0.0f)
				pathPositionRear += TotalLengthOfFlightPath;
			float pathPosition = pathPositionRear + 30.0f;

			float pitch = 0.0f;
			float distSinceTakeOff = pathPosition - TakeOffPoint;
			if(distSinceTakeOff <= 0.0f && distSinceTakeOff > -70.0f){
				// shortly before take off
				pitch = 1.0f - distSinceTakeOff/-70.0f;
			}else if(distSinceTakeOff >= 0.0f && distSinceTakeOff < 100.0f){
				// shortly after take off
				pitch = 1.0f - distSinceTakeOff/100.0f;
			}

			float distSinceLanding = pathPosition - LandingPoint;
			if(distSinceLanding <= 0.0f && distSinceLanding > -200.0f){
				// shortly before landing
				pitch = 1.0f - distSinceLanding/-200.0f;
			}else if(distSinceLanding >= 0.0f && distSinceLanding < 70.0f){
				// shortly after landing
				pitch = 1.0f - distSinceLanding/70.0f;
			}


			// Advance current node to appropriate position
			float pos1, pos2;
			int nextTrackNode = m_nCurPathNode + 1;
			pos1 = pPathNodes[m_nCurPathNode].t;
			if(nextTrackNode < NumPathNodes)
				pos2 = pPathNodes[nextTrackNode].t;
			else{
				nextTrackNode = 0;
				pos2 = TotalLengthOfFlightPath;
			}
			while(pathPositionRear < pos1 || pathPositionRear > pos2){
				m_nCurPathNode = (m_nCurPathNode+1) % NumPathNodes;
				nextTrackNode = m_nCurPathNode + 1;
				pos1 = pPathNodes[m_nCurPathNode].t;
				if(nextTrackNode < NumPathNodes)
					pos2 = pPathNodes[nextTrackNode].t;
				else{
					nextTrackNode = 0;
					pos2 = TotalLengthOfFlightPath;
				}
			}
			bool bothOnGround = pPathNodes[m_nCurPathNode].bOnGround && pPathNodes[nextTrackNode].bOnGround;
			if(PlanePathPosition[m_nPlaneId] >= LandingPoint && OldPlanePathPosition[m_nPlaneId] < LandingPoint)
				DMAudio.PlayOneShot(m_audioEntityId, SOUND_PLANE_ON_GROUND, 0.0f);
			float dist = pPathNodes[nextTrackNode].t - pPathNodes[m_nCurPathNode].t;
			if(dist < 0.0f)
				dist += TotalLengthOfFlightPath;
			float f = (pathPositionRear - pPathNodes[m_nCurPathNode].t)/dist;
			CVector posRear = (1.0f - f)*pPathNodes[m_nCurPathNode].p + f*pPathNodes[nextTrackNode].p;

			// Same for the front
			float pathPositionFront = pathPositionRear + 60.0f;
			if(pathPositionFront > TotalLengthOfFlightPath)
				pathPositionFront -= TotalLengthOfFlightPath;
			int curPathNodeFront = m_nCurPathNode;
			int nextPathNodeFront = curPathNodeFront + 1;
			pos1 = pPathNodes[curPathNodeFront].t;
			if(nextPathNodeFront < NumPathNodes)
				pos2 = pPathNodes[nextPathNodeFront].t;
			else{
				nextPathNodeFront = 0;
				pos2 = TotalLengthOfFlightPath;
			}
			while(pathPositionFront < pos1 || pathPositionFront > pos2){
				curPathNodeFront = (curPathNodeFront+1) % NumPathNodes;
				nextPathNodeFront = curPathNodeFront + 1;
				pos1 = pPathNodes[curPathNodeFront].t;
				if(nextPathNodeFront < NumPathNodes)
					pos2 = pPathNodes[nextPathNodeFront].t;
				else{
					nextPathNodeFront = 0;
					pos2 = TotalLengthOfFlightPath;
				}
			}
			dist = pPathNodes[nextPathNodeFront].t - pPathNodes[curPathNodeFront].t;
			if(dist < 0.0f)
				dist += TotalLengthOfFlightPath;
			f = (pathPositionFront - pPathNodes[curPathNodeFront].t)/dist;
			CVector posFront = (1.0f - f)*pPathNodes[curPathNodeFront].p + f*pPathNodes[nextPathNodeFront].p;

			// And for another point 60 units in front of the plane, used to calculate roll
			float pathPositionFront2 = pathPositionFront + 60.0f;
			if(pathPositionFront2 > TotalLengthOfFlightPath)
				pathPositionFront2 -= TotalLengthOfFlightPath;
			int curPathNodeFront2 = m_nCurPathNode;
			int nextPathNodeFront2 = curPathNodeFront2 + 1;
			pos1 = pPathNodes[curPathNodeFront2].t;
			if(nextPathNodeFront2 < NumPathNodes)
				pos2 = pPathNodes[nextPathNodeFront2].t;
			else{
				nextPathNodeFront2 = 0;
				pos2 = TotalLengthOfFlightPath;
			}
			while(pathPositionFront2 < pos1 || pathPositionFront2 > pos2){
				curPathNodeFront2 = (curPathNodeFront2+1) % NumPathNodes;
				nextPathNodeFront2 = curPathNodeFront2 + 1;
				pos1 = pPathNodes[curPathNodeFront2].t;
				if(nextPathNodeFront2 < NumPathNodes)
					pos2 = pPathNodes[nextPathNodeFront2].t;
				else{
					nextPathNodeFront2 = 0;
					pos2 = TotalLengthOfFlightPath;
				}
			}
			dist = pPathNodes[nextPathNodeFront2].t - pPathNodes[curPathNodeFront2].t;
			if(dist < 0.0f)
				dist += TotalLengthOfFlightPath;
			f = (pathPositionFront2 - pPathNodes[curPathNodeFront2].t)/dist;
			CVector posFront2 = (1.0f - f)*pPathNodes[curPathNodeFront2].p + f*pPathNodes[nextPathNodeFront2].p;

			// Now set matrix
			GetMatrix().SetTranslateOnly((posRear + posFront) / 2.0f);
			GetMatrix().GetPosition().z += 4.3f;
			CVector fwd = posFront - posRear;
			fwd.Normalise();
			if(pitch != 0.0f){
				fwd.z += 0.4f*pitch;
				fwd.Normalise();
			}
			CVector fwd2 = posFront2 - posRear;
			fwd2.Normalise();
			CVector roll = CrossProduct(fwd, fwd2);
			CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f));
			if(!bothOnGround)
				right.z += 3.0f*roll.z;
			right.Normalise();
			CVector up = CrossProduct(right, fwd);
			GetMatrix().GetRight() = right;
			GetMatrix().GetUp() = up;
			GetMatrix().GetForward() = fwd;
			// Set speed
			m_vecMoveSpeed = fwd*PlanePathSpeed[m_nPlaneId]/60.0f;
			m_fSpeed = PlanePathSpeed[m_nPlaneId]/60.0f;
			m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);

			m_isFarAway = !((posFront - TheCamera.GetPosition()).MagnitudeSqr2D() < sq(300.0f));
		}else{
			float planePathPosition;
			float totalLengthOfFlightPath;
			CPlaneNode *pathNodes;
			float planePathSpeed;
			int numPathNodes;

			if(GetModelIndex() == MI_CHOPPER){
				planePathPosition = PlanePath3Position[m_nPlaneId];
				totalLengthOfFlightPath = TotalLengthOfFlightPath3;
				pathNodes = pPath3Nodes;
				planePathSpeed = PlanePath3Speed[m_nPlaneId];
				numPathNodes = NumPath3Nodes;
			}else{
				planePathPosition = PlanePath2Position[m_nPlaneId];
				totalLengthOfFlightPath = TotalLengthOfFlightPath2;
				pathNodes = pPath2Nodes;
				planePathSpeed = PlanePath2Speed[m_nPlaneId];
				numPathNodes = NumPath2Nodes;
			}

			// Advance current node to appropriate position
			float pathPositionRear = planePathPosition - 10.0f;
			if(pathPositionRear < 0.0f)
				pathPositionRear += totalLengthOfFlightPath;
			float pos1, pos2;
			int nextTrackNode = m_nCurPathNode + 1;
			pos1 = pathNodes[m_nCurPathNode].t;
			if(nextTrackNode < numPathNodes)
				pos2 = pathNodes[nextTrackNode].t;
			else{
				nextTrackNode = 0;
				pos2 = totalLengthOfFlightPath;
			}
			while(pathPositionRear < pos1 || pathPositionRear > pos2){
				m_nCurPathNode = (m_nCurPathNode+1) % numPathNodes;
				nextTrackNode = m_nCurPathNode + 1;
				pos1 = pathNodes[m_nCurPathNode].t;
				if(nextTrackNode < numPathNodes)
					pos2 = pathNodes[nextTrackNode].t;
				else{
					nextTrackNode = 0;
					pos2 = totalLengthOfFlightPath;
				}
			}
			float dist = pathNodes[nextTrackNode].t - pathNodes[m_nCurPathNode].t;
			if(dist < 0.0f)
				dist += totalLengthOfFlightPath;
			float f = (pathPositionRear - pathNodes[m_nCurPathNode].t)/dist;
			CVector posRear = (1.0f - f)*pathNodes[m_nCurPathNode].p + f*pathNodes[nextTrackNode].p;

			// Same for the front
			float pathPositionFront = pathPositionRear + 20.0f;
			if(pathPositionFront > totalLengthOfFlightPath)
				pathPositionFront -= totalLengthOfFlightPath;
			int curPathNodeFront = m_nCurPathNode;
			int nextPathNodeFront = curPathNodeFront + 1;
			pos1 = pathNodes[curPathNodeFront].t;
			if(nextPathNodeFront < numPathNodes)
				pos2 = pathNodes[nextPathNodeFront].t;
			else{
				nextPathNodeFront = 0;
				pos2 = totalLengthOfFlightPath;
			}
			while(pathPositionFront < pos1 || pathPositionFront > pos2){
				curPathNodeFront = (curPathNodeFront+1) % numPathNodes;
				nextPathNodeFront = curPathNodeFront + 1;
				pos1 = pathNodes[curPathNodeFront].t;
				if(nextPathNodeFront < numPathNodes)
					pos2 = pathNodes[nextPathNodeFront].t;
				else{
					nextPathNodeFront = 0;
					pos2 = totalLengthOfFlightPath;
				}
			}
			dist = pathNodes[nextPathNodeFront].t - pathNodes[curPathNodeFront].t;
			if(dist < 0.0f)
				dist += totalLengthOfFlightPath;
			f = (pathPositionFront - pathNodes[curPathNodeFront].t)/dist;
			CVector posFront = (1.0f - f)*pathNodes[curPathNodeFront].p + f*pathNodes[nextPathNodeFront].p;

			// And for another point 30 units in front of the plane, used to calculate roll
			float pathPositionFront2 = pathPositionFront + 30.0f;
			if(pathPositionFront2 > totalLengthOfFlightPath)
				pathPositionFront2 -= totalLengthOfFlightPath;
			int curPathNodeFront2 = m_nCurPathNode;
			int nextPathNodeFront2 = curPathNodeFront2 + 1;
			pos1 = pathNodes[curPathNodeFront2].t;
			if(nextPathNodeFront2 < numPathNodes)
				pos2 = pathNodes[nextPathNodeFront2].t;
			else{
				nextPathNodeFront2 = 0;
				pos2 = totalLengthOfFlightPath;
			}
			while(pathPositionFront2 < pos1 || pathPositionFront2 > pos2){
				curPathNodeFront2 = (curPathNodeFront2+1) % numPathNodes;
				nextPathNodeFront2 = curPathNodeFront2 + 1;
				pos1 = pathNodes[curPathNodeFront2].t;
				if(nextPathNodeFront2 < numPathNodes)
					pos2 = pathNodes[nextPathNodeFront2].t;
				else{
					nextPathNodeFront2 = 0;
					pos2 = totalLengthOfFlightPath;
				}
			}
			dist = pathNodes[nextPathNodeFront2].t - pathNodes[curPathNodeFront2].t;
			if(dist < 0.0f)
				dist += totalLengthOfFlightPath;
			f = (pathPositionFront2 - pathNodes[curPathNodeFront2].t)/dist;
			CVector posFront2 = (1.0f - f)*pathNodes[curPathNodeFront2].p + f*pathNodes[nextPathNodeFront2].p;

			// Now set matrix
			GetMatrix().SetTranslateOnly((posRear + posFront) / 2.0f);
			GetMatrix().GetPosition().z += 1.0f;
			CVector fwd = posFront - posRear;
			fwd.Normalise();
			CVector fwd2 = posFront2 - posRear;
			fwd2.Normalise();
			CVector roll = CrossProduct(fwd, fwd2);
			CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f));
			right.z += 3.0f*roll.z;
			right.Normalise();
			CVector up = CrossProduct(right, fwd);
			GetMatrix().GetRight() = right;
			GetMatrix().GetUp() = up;
			GetMatrix().GetForward() = fwd;

			// Set speed
			m_vecMoveSpeed = fwd*planePathSpeed/60.0f;
			m_fSpeed = planePathSpeed/60.0f;
			m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);

			m_isFarAway = !((posFront - TheCamera.GetPosition()).MagnitudeSqr2D() < sq(300.0f));
		}
	}

	bIsInSafePosition = true;
	GetMatrix().UpdateRW();
	UpdateRwFrame();

	// Handle streaming and such
	CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
	if(m_isFarAway){
		// Switch to LOD model
		if(m_rwObject && RwObjectGetType(m_rwObject) == rpCLUMP){
			DeleteRwObject();
			if(mi->m_planeLodId != -1){
				PUSH_MEMID(MEMID_WORLD);
				m_rwObject = CModelInfo::GetModelInfo(mi->m_planeLodId)->CreateInstance();
				POP_MEMID();
				if(m_rwObject)
					GetMatrix().AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic*)m_rwObject)));
			}
		}
	}else if(CStreaming::HasModelLoaded(GetModelIndex())){
		if(m_rwObject && RwObjectGetType(m_rwObject) == rpATOMIC){
			// Get rid of LOD model
			GetMatrix().Detach();
			if(m_rwObject){	// useless check
				if(RwObjectGetType(m_rwObject) == rpATOMIC){	// useless check
					RwFrame *f = RpAtomicGetFrame((RpAtomic*)m_rwObject);
					RpAtomicDestroy((RpAtomic*)m_rwObject);
					RwFrameDestroy(f);
				}
				m_rwObject = nil;
			}
		}
		// Set high detail model
		if(m_rwObject == nil){
			int id = GetModelIndex();
			m_modelIndex = -1;
			SetModelIndex(id);
		}
	}else{
		CStreaming::RequestModel(GetModelIndex(), STREAMFLAGS_DEPENDENCY);
	}
}

void
CPlane::PreRender(void)
{
	CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());

	CVector lookVector = GetPosition() - TheCamera.GetPosition();
	float camDist = lookVector.Magnitude();
	if(camDist != 0.0f)
		lookVector *= 1.0f/camDist;
	else
		lookVector = CVector(1.0f, 0.0f, 0.0f);
	float behindness = DotProduct(lookVector, GetForward());

	// Wing lights
	if(behindness < 0.0f){
		// in front of plane
		CVector lightPos = mi->m_positions[PLANE_POS_LIGHT_RIGHT];
		CVector lightR = GetMatrix() * lightPos;
		CVector lightL = lightR;
		lightL -= GetRight()*2.0f*lightPos.x;

		float intensity = -0.6f*behindness + 0.4f;
		float size = 1.0f - behindness;

		if(behindness < -0.9f && camDist < 50.0f){
			// directly in front
			CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255,
				lightL, size, 240.0f,
				CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON,
				CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
			CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255,
				lightR, size, 240.0f,
				CCoronas::TYPE_NORMAL, CCoronas::FLARE_HEADLIGHTS, CCoronas::REFLECTION_ON,
				CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
		}else{
			CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255,
				lightL, size, 240.0f,
				CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
				CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
			CCoronas::RegisterCorona((uintptr)this + 11, 255*intensity, 255*intensity, 255*intensity, 255,
				lightR, size, 240.0f,
				CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
				CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
		}
	}

	// Tail light
	if(CTimer::GetTimeInMilliseconds() & 0x200){
		CVector pos = GetMatrix() * mi->m_positions[PLANE_POS_LIGHT_TAIL];

		CCoronas::RegisterCorona((uintptr)this + 12, 255, 0, 0, 255,
			pos, 1.0f, 120.0f,
			CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
			CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
	}

#ifdef CPLANE_ROTORS
	CMatrix mat;
	CVector pos;
	m_fRotorRotation += 3.14f/6.5f;
	if(m_fRotorRotation > 6.28f)
		m_fRotorRotation -= 6.28f;

	if(m_aPlaneNodes[PLANE_TOPROTOR]){
		mat.Attach(RwFrameGetMatrix(m_aPlaneNodes[PLANE_TOPROTOR]));
		pos = mat.GetPosition();
		mat.SetRotateZ(m_fRotorRotation);
		mat.Translate(pos);
		mat.UpdateRW();
	}
	if(m_aPlaneNodes[PLANE_BACKROTOR]){
		mat.Attach(RwFrameGetMatrix(m_aPlaneNodes[PLANE_BACKROTOR]));
		pos = mat.GetPosition();
		mat.SetRotateX(m_fRotorRotation);
		mat.Translate(pos);
		mat.UpdateRW();
	}
#endif
}

void
CPlane::Render(void)
{
	if(!CCutsceneMgr::IsRunning())
		CEntity::Render();
}

#define CRUISE_SPEED (50.0f)
#define TAXI_SPEED (5.0f)

void
CPlane::InitPlanes(void)
{
	int i;

	CesnaMissionStatus = CESNA_STATUS_NONE;

	// Jumbo
	if(pPathNodes == nil){
		pPathNodes = LoadPath("data\\paths\\flight.dat", NumPathNodes, TotalLengthOfFlightPath, true);

		// Figure out which nodes are on ground
		for(i = 0; i < NumPathNodes; i++){
			if(pPathNodes[i].p.z < 14.0f){
				pPathNodes[i].p.z = 14.0f;
				pPathNodes[i].bOnGround = true;
			}else
				pPathNodes[i].bOnGround = false;
		}

		// Find lading and takeoff points
		LandingPoint = -1.0f;
		TakeOffPoint = -1.0f;
		bool lastOnGround = pPathNodes[NumPathNodes-1].bOnGround;
		for(i = 0; i < NumPathNodes; i++){
			if(pPathNodes[i].bOnGround && !lastOnGround)
				LandingPoint = pPathNodes[i].t;
			else if(!pPathNodes[i].bOnGround && lastOnGround)
				TakeOffPoint = pPathNodes[i].t;
			lastOnGround = pPathNodes[i].bOnGround;
		}

		// Animation
		float time = 0.0f;
		float position = 0.0f;
		// Start on ground with slow speed
		aPlaneLineBits[0].type = 1;
		aPlaneLineBits[0].time = time;
		aPlaneLineBits[0].position = position;
		aPlaneLineBits[0].speed = TAXI_SPEED;
		aPlaneLineBits[0].acceleration = 0.0f;
		float dist = (TakeOffPoint-500.0f) - position;
		time += dist/TAXI_SPEED;
		position += dist;

		// Accelerate to take off
		aPlaneLineBits[1].type = 2;
		aPlaneLineBits[1].time = time;
		aPlaneLineBits[1].position = position;
		aPlaneLineBits[1].speed = TAXI_SPEED;
		aPlaneLineBits[1].acceleration = 618.75f/500.0f;
		time += 500.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f);
		position += 500.0f;

		// Fly at cruise speed
		aPlaneLineBits[2].type = 1;
		aPlaneLineBits[2].time = time;
		aPlaneLineBits[2].position = position;
		aPlaneLineBits[2].speed = CRUISE_SPEED;
		aPlaneLineBits[2].acceleration = 0.0f;
		dist = LandingPoint - TakeOffPoint;
		time += dist/CRUISE_SPEED;
		position += dist;

		// Brake after landing
		aPlaneLineBits[3].type = 2;
		aPlaneLineBits[3].time = time;
		aPlaneLineBits[3].position = position;
		aPlaneLineBits[3].speed = CRUISE_SPEED;
		aPlaneLineBits[3].acceleration = -618.75f/500.0f;
		time += 500.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f);
		position += 500.0f;

		// Taxi
		aPlaneLineBits[4].type = 1;
		aPlaneLineBits[4].time = time;
		aPlaneLineBits[4].position = position;
		aPlaneLineBits[4].speed = TAXI_SPEED;
		aPlaneLineBits[4].acceleration = 0.0f;
		time += (TotalLengthOfFlightPath - position)/TAXI_SPEED;

		// end
		aPlaneLineBits[5].time = time;
		TotalDurationOfFlightPath = time;
	}

	// Dodo
	if(pPath2Nodes == nil){
		pPath2Nodes = LoadPath("data\\paths\\flight2.dat", NumPath2Nodes, TotalLengthOfFlightPath2, true);
		TotalDurationOfFlightPath2 = TotalLengthOfFlightPath2/CRUISE_SPEED;
	}

	// Heli
	if(pPath3Nodes == nil){
		pPath3Nodes = LoadPath("data\\paths\\flight3.dat", NumPath3Nodes, TotalLengthOfFlightPath3, false);
		TotalDurationOfFlightPath3 = TotalLengthOfFlightPath3/CRUISE_SPEED;
	}

	CStreaming::LoadAllRequestedModels(false);
	CStreaming::RequestModel(MI_AIRTRAIN, 0);
	CStreaming::LoadAllRequestedModels(false);

	// NB: 3 hardcoded also in CPlaneTrails
	for(i = 0; i < 3; i++){
		CPlane *plane = new CPlane(MI_AIRTRAIN, PERMANENT_VEHICLE);
		plane->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
		plane->SetStatus(STATUS_ABANDONED);
		plane->bIsLocked = true;
		plane->m_nPlaneId = i;
		plane->m_nCurPathNode = 0;
		CWorld::Add(plane);
	}
}

void
CPlane::Shutdown(void)
{
	delete[] pPathNodes;
	delete[] pPath2Nodes;
	delete[] pPath3Nodes;
	delete[] pPath4Nodes;
	pPathNodes = nil;
	pPath2Nodes = nil;
	pPath3Nodes = nil;
	pPath4Nodes = nil;
}

CPlaneNode*
CPlane::LoadPath(char const *filename, int32 &numNodes, float &totalLength, bool loop)
{
	int bp, lp;
	int i;

	CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r");
	*gString = '\0';
	for(bp = 0, lp = 0; work_buff[bp] != '\n'; bp++, lp++)
		gString[lp] = work_buff[bp];
	bp++;
	gString[lp] = '\0';
	sscanf(gString, "%d", &numNodes);
	CPlaneNode *nodes = new CPlaneNode[numNodes];

	for(i = 0; i < numNodes; i++){
		for(lp = 0; work_buff[bp] != '\n' && work_buff[bp] != '\0'; bp++, lp++)
			gString[lp] = work_buff[bp];
		bp++;
		// BUG: game doesn't terminate string
		gString[lp] = '\0';
		sscanf(gString, "%f %f %f", &nodes[i].p.x, &nodes[i].p.y, &nodes[i].p.z);
	}

	// Calculate length of segments and path
	totalLength = 0.0f;
	for(i = 0; i < numNodes; i++){
		nodes[i].t = totalLength;
		float l = (nodes[(i+1) % numNodes].p - nodes[i].p).Magnitude2D();
		if(!loop && i == numNodes-1)
			l = 0.0f;
		totalLength += l;
	}

	return nodes;
}

int32 LastTimeInPlane, LastTimeNotInPlane;
bool bCesnasActivated;
bool bHelisActivated;

void
CPlane::UpdatePlanes(void)
{
	int i, j;
	uint32 time;
	float t, deltaT;

	if(CReplay::IsPlayingBack())
		return;

	// Jumbo jets
	time = CTimer::GetTimeInMilliseconds();
	for(i = 0; i < 3; i++){
		t = TotalDurationOfFlightPath * (float)(time & 0x7FFFF)/0x80000;
		// find current frame
		for(j = 0; t > aPlaneLineBits[j+1].time; j++);

		OldPlanePathPosition[i] = PlanePathPosition[i];
		deltaT = t - aPlaneLineBits[j].time;
		switch(aPlaneLineBits[j].type){
		case 0:	// standing still
			PlanePathPosition[i] = aPlaneLineBits[j].position;
			PlanePathSpeed[i] = 0.0f;
			break;
		case 1:	// moving with constant speed
			PlanePathPosition[i] = aPlaneLineBits[j].position + aPlaneLineBits[j].speed*deltaT;
			PlanePathSpeed[i] = (TotalDurationOfFlightPath*1000.0f/0x80000) * aPlaneLineBits[j].speed;
			break;
		case 2:	// accelerating/braking
			PlanePathPosition[i] = aPlaneLineBits[j].position + (aPlaneLineBits[j].speed + aPlaneLineBits[j].acceleration*deltaT)*deltaT;
			PlanePathSpeed[i] = (TotalDurationOfFlightPath*1000.0f/0x80000)*aPlaneLineBits[j].speed + 2.0f*aPlaneLineBits[j].acceleration*deltaT;
			break;
		}

		// time offset for each plane
		time += 0x80000/3;
	}

	time = CTimer::GetTimeInMilliseconds();

	t = TotalDurationOfFlightPath2/0x80000;
	PlanePath2Position[0] = CRUISE_SPEED * (time & 0x7FFFF)*t;
	PlanePath2Position[1] = CRUISE_SPEED * ((time + 0x80000/5) & 0x7FFFF)*t;
	PlanePath2Position[2] = CRUISE_SPEED * ((time + 0x80000/5*2) & 0x7FFFF)*t;
	PlanePath2Position[3] = CRUISE_SPEED * ((time + 0x80000/5*3) & 0x7FFFF)*t;
	PlanePath2Position[4] = CRUISE_SPEED * ((time + 0x80000/5*4) & 0x7FFFF)*t;
	PlanePath2Speed[0] = CRUISE_SPEED*t;
	PlanePath2Speed[1] = CRUISE_SPEED*t;
	PlanePath2Speed[2] = CRUISE_SPEED*t;
	PlanePath2Speed[3] = CRUISE_SPEED*t;
	PlanePath2Speed[4] = CRUISE_SPEED*t;

	t = TotalDurationOfFlightPath3/0x80000;
	PlanePath3Position[0] = CRUISE_SPEED * (time & 0x7FFFF)*t;
	PlanePath3Position[1] = CRUISE_SPEED * ((time + 0x80000/4) & 0x7FFFF)*t;
	PlanePath3Position[2] = CRUISE_SPEED * ((time + 0x80000/4*2) & 0x7FFFF)*t;
	PlanePath3Position[3] = CRUISE_SPEED * ((time + 0x80000/4*3) & 0x7FFFF)*t;
	PlanePath3Speed[0] = CRUISE_SPEED*t;
	PlanePath3Speed[1] = CRUISE_SPEED*t;
	PlanePath3Speed[2] = CRUISE_SPEED*t;
	PlanePath3Speed[3] = CRUISE_SPEED*t;

	if(FindPlayerVehicle() && (FindPlayerVehicle()->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI ||
	                           FindPlayerVehicle()->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE))
		LastTimeInPlane = CTimer::GetTimeInMilliseconds();
	else
		LastTimeNotInPlane = CTimer::GetTimeInMilliseconds();

	if(CTimer::GetTimeInMilliseconds() - LastTimeNotInPlane > 10000){
		if(!bCesnasActivated){
			if(CStreaming::HasModelLoaded(MI_DEADDODO)){
				for(i = 0; i < 5; i++){
					CPlane *plane = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE);
					plane->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
					plane->SetStatus(STATUS_ABANDONED);
					plane->bIsLocked = true;
					plane->m_nPlaneId = i;
					plane->m_nCurPathNode = 0;
					plane->m_bTempPlane = true;
					CWorld::Add(plane);
				}
				bCesnasActivated = true;
			}else
				CStreaming::RequestModel(MI_DEADDODO, 0);
		}

		if(!bHelisActivated){
			if(CStreaming::HasModelLoaded(MI_CHOPPER)){
				for(i = 0; i < 4; i++){
					CPlane *plane = new CPlane(MI_CHOPPER, PERMANENT_VEHICLE);
					plane->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
					plane->SetStatus(STATUS_ABANDONED);
					plane->bIsLocked = true;
					plane->m_nPlaneId = i;
					plane->m_nCurPathNode = 0;
					plane->m_bTempPlane = true;
					CWorld::Add(plane);
				}
				bHelisActivated = true;
			}else
				CStreaming::RequestModel(MI_CHOPPER, 0);
		}
	}else if(CTimer::GetTimeInMilliseconds() - LastTimeInPlane > 10000)
		RemoveTemporaryPlanes();
}

void
CPlane::RemoveTemporaryPlanes(void)
{
	int i;
	if(!bHelisActivated && !bCesnasActivated)
		return;

	i = CPools::GetVehiclePool()->GetSize();
	while(--i >= 0){
		CPlane *plane = (CPlane*)CPools::GetVehiclePool()->GetSlot(i);
		if(plane && plane->IsPlane() && plane->m_bTempPlane){
			CWorld::Remove(plane);
			delete plane;
		}
	}
	bCesnasActivated = false;
	bHelisActivated = false;
}

bool
CPlane::TestRocketCollision(CVector *rocketPos)
{
	int i;

	i = CPools::GetVehiclePool()->GetSize();
	while(--i >= 0){
		CPlane *plane = (CPlane*)CPools::GetVehiclePool()->GetSlot(i);
		if(plane &&
#ifdef EXPLODING_AIRTRAIN
		   (plane->GetModelIndex() == MI_AIRTRAIN || plane->GetModelIndex() == MI_DEADDODO) &&
#else
		   plane->GetModelIndex() != MI_AIRTRAIN && plane->GetModelIndex() == MI_DEADDODO &&	// strange check
#endif
		   !plane->m_bHasBeenHit && (*rocketPos - plane->GetPosition()).Magnitude() < 25.0f){
			plane->m_nFrameWhenHit = CTimer::GetFrameCounter();
			plane->m_bHasBeenHit = true;
			CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->RegisterCrime_Immediately(CRIME_DESTROYED_CESSNA,
				plane->GetPosition(), i+1983, false);
			return true;
		}
	}
	return false;
}

// unused
// BUG: not in CPlane in the game
void
CPlane::CreateIncomingCesna(void)
{
	if(CesnaMissionStatus == CESNA_STATUS_FLYING){
		CWorld::Remove(pDrugRunCesna);
		delete pDrugRunCesna;
		pDrugRunCesna = nil;
	}
	pDrugRunCesna = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE);
	pDrugRunCesna->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
	pDrugRunCesna->SetStatus(STATUS_ABANDONED);
	pDrugRunCesna->bIsLocked = true;
	pDrugRunCesna->m_nPlaneId = 0;
	pDrugRunCesna->m_nCurPathNode = 0;
	pDrugRunCesna->m_bIsDrugRunCesna = true;
	CWorld::Add(pDrugRunCesna);

        CesnaMissionStatus = CESNA_STATUS_FLYING;
        CesnaMissionStartTime = CTimer::GetTimeInMilliseconds();
        printf("CPlane::CreateIncomingCesna(void)\n");
}

// unused
void
CPlane::CreateDropOffCesna(void)
{
	if(DropOffCesnaMissionStatus == CESNA_STATUS_FLYING){
		CWorld::Remove(pDropOffCesna);
		delete pDropOffCesna;
		pDropOffCesna = nil;
	}
	pDropOffCesna = new CPlane(MI_DEADDODO, PERMANENT_VEHICLE);
	pDropOffCesna->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
	pDropOffCesna->SetStatus(STATUS_ABANDONED);
	pDropOffCesna->bIsLocked = true;
	pDropOffCesna->m_nPlaneId = 0;
	pDropOffCesna->m_nCurPathNode = 0;
	pDropOffCesna->m_bIsDropOffCesna = true;
	CWorld::Add(pDropOffCesna);

        DropOffCesnaMissionStatus = CESNA_STATUS_FLYING;
        DropOffCesnaMissionStartTime = CTimer::GetTimeInMilliseconds();
        printf("CPlane::CreateDropOffCesna(void)\n");
}

// all unused
const CVector CPlane::FindDrugPlaneCoordinates(void) { return pDrugRunCesna->GetPosition(); }
const CVector CPlane::FindDropOffCesnaCoordinates(void) { return pDropOffCesna->GetPosition(); }
bool CPlane::HasCesnaLanded(void) { return CesnaMissionStatus == CESNA_STATUS_LANDED; }
bool CPlane::HasCesnaBeenDestroyed(void) { return CesnaMissionStatus == CESNA_STATUS_DESTROYED; }
bool CPlane::HasDropOffCesnaBeenShotDown(void) { return DropOffCesnaMissionStatus == CESNA_STATUS_DESTROYED; }

void
CPlane::Load(void)
{
	RemoveTemporaryPlanes();
}

void
CPlane::Save(void)
{
	RemoveTemporaryPlanes();
}
