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

#include "General.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 "World.h"
#include "HandlingMgr.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[3];
float PlanePath3Position;
float PlanePath4Position;
float PlanePath2Speed[3];
float PlanePath3Speed;
float PlanePath4Speed;


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;

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

#ifdef FIX_BUGS
	m_isFarAway = false;
#endif
}

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

void
CPlane::SetModelIndex(uint32 id)
{
	CVehicle::SetModelIndex(id);
}

void
CPlane::DeleteRwObject(void)
{
	if(m_rwObject && RwObjectGetType(m_rwObject) == rpATOMIC){
		m_matrix.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;

	// 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(m_bIsDrugRunCesna){
				planePathPosition = PlanePath3Position;
				totalLengthOfFlightPath = TotalLengthOfFlightPath3;
				pathNodes = pPath3Nodes;
				planePathSpeed = PlanePath3Speed;
				numPathNodes = NumPath3Nodes;
				if(CesnaMissionStatus == CESNA_STATUS_LANDED){
					pDrugRunCesna = nil;
					FlagToDestroyWhenNextProcessed();
				}
			}else if(m_bIsDropOffCesna){
				planePathPosition = PlanePath4Position;
				totalLengthOfFlightPath = TotalLengthOfFlightPath4;
				pathNodes = pPath4Nodes;
				planePathSpeed = PlanePath4Speed;
				numPathNodes = NumPath4Nodes;
				if(DropOffCesnaMissionStatus == CESNA_STATUS_LANDED){
					pDropOffCesna = nil;
					FlagToDestroyWhenNextProcessed();
				}
			}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)
					m_matrix.AttachRW(RwFrameGetMatrix(RpAtomicGetFrame((RpAtomic*)m_rwObject)));
			}
		}
	}else if(CStreaming::HasModelLoaded(GetModelIndex())){
		if(m_rwObject && RwObjectGetType(m_rwObject) == rpATOMIC){
			// Get rid of LOD model
			m_matrix.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);
	}
}

void
CPlane::Render(void)
{
	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
		CColPoint colpoint;
		CEntity *entity;
		for(i = 0; i < NumPathNodes; i++){
			if(CWorld::ProcessVerticalLine(pPathNodes[i].p, 1000.0f, colpoint, entity, true, false, false, false, true, false, nil)){
				pPathNodes[i].p.z = colpoint.point.z;
				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-600.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/600.0f;
		time += 600.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f);
		position += 600.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/600.0f;
		time += 600.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f);
		position += 600.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;
	}

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

	// Mission Cesna
	if(pPath4Nodes == nil){
		pPath4Nodes = LoadPath("data\\paths\\flight4.dat", NumPath4Nodes, TotalLengthOfFlightPath4, false);
		TotalDurationOfFlightPath4 = TotalLengthOfFlightPath4/CRUISE_SPEED;
	}

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

	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);
	}


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

	for(i = 0; i < 3; 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;
		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++){
		*gString = '\0';
		for(lp = 0; work_buff[bp] != '\n'; 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;
}

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/3) & 0x7FFFF)*t;
	PlanePath2Position[2] = CRUISE_SPEED * ((time + 0x80000/3*2) & 0x7FFFF)*t;
	PlanePath2Speed[0] = CRUISE_SPEED*t;
	PlanePath2Speed[1] = CRUISE_SPEED*t;
	PlanePath2Speed[2] = CRUISE_SPEED*t;

	if(CesnaMissionStatus == CESNA_STATUS_FLYING){
		PlanePath3Speed = CRUISE_SPEED*TotalDurationOfFlightPath3/0x20000;
		PlanePath3Position = PlanePath3Speed * ((time - CesnaMissionStartTime) & 0x1FFFF);
		if(time - CesnaMissionStartTime >= 128072)
			CesnaMissionStatus = CESNA_STATUS_LANDED;
	}

	if(DropOffCesnaMissionStatus == CESNA_STATUS_FLYING){
		PlanePath4Speed = CRUISE_SPEED*TotalDurationOfFlightPath4/0x80000;
		PlanePath4Position = PlanePath4Speed * ((time - DropOffCesnaMissionStartTime) & 0x7FFFF);
		if(time - DropOffCesnaMissionStartTime >= 521288)
			DropOffCesnaMissionStatus = CESNA_STATUS_LANDED;
	}
}

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;
}

// 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");
}

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");
}

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; }

src/vehicles/Plane.cpp