code:
#include "common.h"
#include "main.h"

#include "Timer.h"
#include "ModelIndices.h"
#include "FileMgr.h"
#include "Streaming.h"
#include "Pad.h"
#include "Camera.h"
#include "Coronas.h"
#include "World.h"
#include "Ped.h"
#include "DMAudio.h"
#include "HandlingMgr.h"
#include "Train.h"
#include "AudioScriptObject.h"

static CTrainNode* pTrackNodes;
static int16 NumTrackNodes;
static float StationDist[3] = { 873.0f, 1522.0f, 2481.0f };
static float TotalLengthOfTrack;
static float TotalDurationOfTrack;
static CTrainInterpolationLine aLineBits[17];
static float EngineTrackPosition[2];
static float EngineTrackSpeed[2];

static CTrainNode* pTrackNodes_S;
static int16 NumTrackNodes_S;
static float StationDist_S[4] = { 55.0f, 1388.0f, 2337.0f, 3989.0f };
static float TotalLengthOfTrack_S;
static float TotalDurationOfTrack_S;
static CTrainInterpolationLine aLineBits_S[18];
static float EngineTrackPosition_S[4];
static float EngineTrackSpeed_S[4];

CVector CTrain::aStationCoors[3];
CVector CTrain::aStationCoors_S[4];

static bool bTrainArrivalAnnounced[3] = {false, false, false};

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

	Doors[0].Init(0.8f, 0.0f, 1, 0);
	Doors[1].Init(-0.8f, 0.0f, 0, 0);

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

	m_bProcessDoor = true;
	m_bTrainStopping = false;
	m_nTrackId = TRACK_ELTRAIN;
	m_nNumMaxPassengers = 5;
	m_nDoorTimer = CTimer::GetTimeInMilliseconds();
	m_nDoorState = TRAIN_DOOR_CLOSED;

	bUsesCollision = true;
	SetStatus(STATUS_TRAIN_MOVING);

#ifdef FIX_BUGS
	m_isFarAway = true;
#endif
}

void
CTrain::SetModelIndex(uint32 id)
{
	int i;

	CVehicle::SetModelIndex(id);
	for(i = 0; i < NUM_TRAIN_NODES; i++)
		m_aTrainNodes[i] = nil;
	CClumpModelInfo::FillFrameArray(GetClump(), m_aTrainNodes);
}

void
CTrain::ProcessControl(void)
{
	if(gbModelViewer || m_isFarAway && (CTimer::GetFrameCounter() + m_nWagonId) & 0xF)
		return;

	CTrainNode *trackNodes;
	int16 numTrackNodes;
	float totalLengthOfTrack;
	float *engineTrackPosition;
	float *engineTrackSpeed;

	if(m_nTrackId == TRACK_SUBWAY){
		trackNodes = pTrackNodes_S;
		numTrackNodes = NumTrackNodes_S;
		totalLengthOfTrack = TotalLengthOfTrack_S;
		engineTrackPosition = EngineTrackPosition_S;
		engineTrackSpeed = EngineTrackSpeed_S;
	}else{
		trackNodes = pTrackNodes;
		numTrackNodes = NumTrackNodes;
		totalLengthOfTrack = TotalLengthOfTrack;
		engineTrackPosition = EngineTrackPosition;
		engineTrackSpeed = EngineTrackSpeed;
	}

	float trackPositionRear = engineTrackPosition[m_nWagonGroup] - m_fWagonPosition;
	if(trackPositionRear < 0.0f)
		trackPositionRear += totalLengthOfTrack;

	// Advance current node to appropriate position
	float pos1, pos2;
	int nextTrackNode = m_nCurTrackNode + 1;
	pos1 = trackNodes[m_nCurTrackNode].t;
	if(nextTrackNode < numTrackNodes)
		pos2 = trackNodes[nextTrackNode].t;
	else{
		nextTrackNode = 0;
		pos2 = totalLengthOfTrack;
	}
	while(trackPositionRear < pos1 || trackPositionRear > pos2){
		m_nCurTrackNode = (m_nCurTrackNode+1) % numTrackNodes;
		nextTrackNode = m_nCurTrackNode + 1;
		pos1 = trackNodes[m_nCurTrackNode].t;
		if(nextTrackNode < numTrackNodes)
			pos2 = trackNodes[nextTrackNode].t;
		else{
			nextTrackNode = 0;
			pos2 = totalLengthOfTrack;
		}
	}
	float dist = trackNodes[nextTrackNode].t - trackNodes[m_nCurTrackNode].t;
	if(dist < 0.0f)
		dist += totalLengthOfTrack;
	float f = (trackPositionRear - trackNodes[m_nCurTrackNode].t)/dist;
	CVector posRear = (1.0f - f)*trackNodes[m_nCurTrackNode].p + f*trackNodes[nextTrackNode].p;

	// Now same again for the front
	float trackPositionFront = trackPositionRear + 20.0f;
	if(trackPositionFront > totalLengthOfTrack)
		trackPositionFront -= totalLengthOfTrack;
	int curTrackNodeFront = m_nCurTrackNode;
	int nextTrackNodeFront = curTrackNodeFront + 1;
	pos1 = trackNodes[curTrackNodeFront].t;
	if(nextTrackNodeFront < numTrackNodes)
		pos2 = trackNodes[nextTrackNodeFront].t;
	else{
		nextTrackNodeFront = 0;
		pos2 = totalLengthOfTrack;
	}
	while(trackPositionFront < pos1 || trackPositionFront > pos2){
		curTrackNodeFront = (curTrackNodeFront+1) % numTrackNodes;
		nextTrackNodeFront = curTrackNodeFront + 1;
		pos1 = trackNodes[curTrackNodeFront].t;
		if(nextTrackNodeFront < numTrackNodes)
			pos2 = trackNodes[nextTrackNodeFront].t;
		else{
			nextTrackNodeFront = 0;
			pos2 = totalLengthOfTrack;
		}
	}
	dist = trackNodes[nextTrackNodeFront].t - trackNodes[curTrackNodeFront].t;
	if(dist < 0.0f)
		dist += totalLengthOfTrack;
	f = (trackPositionFront - trackNodes[curTrackNodeFront].t)/dist;
	CVector posFront = (1.0f - f)*trackNodes[curTrackNodeFront].p + f*trackNodes[nextTrackNodeFront].p;

	// Now set matrix
	SetPosition((posRear + posFront)/2.0f);
	CVector fwd = posFront - posRear;
	fwd.Normalise();
	CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f));
	right.Normalise();
	CVector up = CrossProduct(right, fwd);
	GetRight() = right;
	GetUp() = up;
	GetForward() = fwd;

	// Set speed
	m_vecMoveSpeed = fwd*engineTrackSpeed[m_nWagonGroup]/60.0f;
	m_fSpeed = engineTrackSpeed[m_nWagonGroup]/60.0f;
	m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);

	if(engineTrackSpeed[m_nWagonGroup] > 0.001f){
		SetStatus(STATUS_TRAIN_MOVING);
		m_bTrainStopping = false;
		m_bProcessDoor = true;
	}else{
		SetStatus(STATUS_TRAIN_NOT_MOVING);
		m_bTrainStopping = true;
	}

	m_isFarAway = !((posFront - TheCamera.GetPosition()).Magnitude2D() < sq(250.0f));

	if(m_fWagonPosition == 20.0f && m_fSpeed > 0.0001f)
		if(Abs(TheCamera.GetPosition().z - GetPosition().z) < 15.0f)
			CPad::GetPad(0)->StartShake_Train(GetPosition().x, GetPosition().y);

	if(m_bProcessDoor)
		switch(m_nDoorState){
		case TRAIN_DOOR_CLOSED:
			if(m_bTrainStopping){
				m_nDoorTimer = CTimer::GetTimeInMilliseconds() + 1000;
				m_nDoorState = TRAIN_DOOR_OPENING;
				DMAudio.PlayOneShot(m_audioEntityId, SOUND_TRAIN_DOOR_CLOSE, 0.0f);
			}
			break;

		case TRAIN_DOOR_OPENING:
			if(CTimer::GetTimeInMilliseconds() < m_nDoorTimer){
				OpenTrainDoor(1.0f - (m_nDoorTimer - CTimer::GetTimeInMilliseconds())/1000.0f);
			}else{
				OpenTrainDoor(1.0f);
				m_nDoorState = TRAIN_DOOR_OPEN;
			}
			break;

		case TRAIN_DOOR_OPEN:
			if(!m_bTrainStopping){
				m_nDoorTimer = CTimer::GetTimeInMilliseconds() + 1000;
				m_nDoorState = TRAIN_DOOR_CLOSING;
				DMAudio.PlayOneShot(m_audioEntityId, SOUND_TRAIN_DOOR_OPEN, 0.0f);
			}
			break;

		case TRAIN_DOOR_CLOSING:
			if(CTimer::GetTimeInMilliseconds() < m_nDoorTimer){
				OpenTrainDoor((m_nDoorTimer - CTimer::GetTimeInMilliseconds())/1000.0f);
			}else{
				OpenTrainDoor(0.0f);
				m_nDoorState = TRAIN_DOOR_CLOSED;
				m_bProcessDoor = false;
			}
			break;
		}

	GetMatrix().UpdateRW();
	UpdateRwFrame();
	RemoveAndAdd();

	bIsStuck = false;
	bIsInSafePosition = true;
	bWasPostponed = false;

	// request/remove model
	if(m_isFarAway){
		if(m_rwObject)
			DeleteRwObject();
	}else if(CStreaming::HasModelLoaded(MI_TRAIN)){
		if(m_rwObject == nil){
			m_modelIndex = -1;
			SetModelIndex(MI_TRAIN);
		}
	}else{
		if(FindPlayerCoors().z * GetPosition().z >= 0.0f)
			CStreaming::RequestModel(MI_TRAIN, STREAMFLAGS_DEPENDENCY);
	}

	// Hit stuff
	if(m_bIsFirstWagon && GetStatus()== STATUS_TRAIN_MOVING){
		CVector front = GetPosition() + GetForward()*GetColModel()->boundingBox.max.y + m_vecMoveSpeed*CTimer::GetTimeStep();

		int x, xmin, xmax;
		int y, ymin, ymax;

		xmin = CWorld::GetSectorIndexX(front.x - 3.0f);
		if(xmin < 0) xmin = 0;
		xmax = CWorld::GetSectorIndexX(front.x + 3.0f);
		if(xmax > NUMSECTORS_X-1) xmax = NUMSECTORS_X-1;
		ymin = CWorld::GetSectorIndexY(front.y - 3.0f);
		if(ymin < 0) ymin = 0;
		ymax = CWorld::GetSectorIndexY(front.y + 3.0f);
		if(ymax > NUMSECTORS_Y-1) ymax = NUMSECTORS_X-1;

		CWorld::AdvanceCurrentScanCode();

		for(y = ymin; y <= ymax; y++)
			for(x = xmin; x <= xmax; x++){
				CSector *s = CWorld::GetSector(x, y);
				TrainHitStuff(s->m_lists[ENTITYLIST_VEHICLES]);
				TrainHitStuff(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP]);
				TrainHitStuff(s->m_lists[ENTITYLIST_PEDS]);
				TrainHitStuff(s->m_lists[ENTITYLIST_PEDS_OVERLAP]);
			}
	}
}

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

	if(m_bIsFirstWagon){
		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());

		if(behindness < 0.0f){
			// In front of train
			CVector lightPos = mi->m_positions[TRAIN_POS_LIGHT_FRONT];
			CVector lightR = GetMatrix() * lightPos;
			CVector lightL = lightR;
			lightL -= GetRight()*2.0f*lightPos.x;

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

			if(behindness < -0.9f && camDist < 35.0f){
				// directly in front
				CCoronas::RegisterCorona((uintptr)this + 10, 255*intensity, 255*intensity, 255*intensity, 255,
					lightL, size, 80.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, 80.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, 80.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, 80.0f,
					CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
					CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
			}
		}
	}

	if(m_bIsLastWagon){
		CVector lightPos = mi->m_positions[TRAIN_POS_LIGHT_REAR];
		CVector lightR = GetMatrix() * lightPos;
		CVector lightL = lightR;
		lightL -= GetRight()*2.0f*lightPos.x;

		CCoronas::RegisterCorona((uintptr)this + 12, 255, 0, 0, 255,
			lightL, 1.0f, 80.0f,
			CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
			CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
		CCoronas::RegisterCorona((uintptr)this + 13, 255, 0, 0, 255,
			lightR, 1.0f, 80.0f,
			CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON,
			CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f);
	}
}

void
CTrain::Render(void)
{
	CEntity::Render();
}

void
CTrain::TrainHitStuff(CPtrList &list)
{
	CPtrNode *node;
	CPhysical *phys;

	for(node = list.first; node; node = node->next){
		phys = (CPhysical*)node->item;
		if(phys != this && Abs(this->GetPosition().z - phys->GetPosition().z) < 1.5f)
			phys->bHitByTrain = true;
	}
}

void
CTrain::AddPassenger(CPed *ped)
{
	int i = ped->m_vehDoor;
	if((i == TRAIN_POS_LEFT_ENTRY || i == TRAIN_POS_MID_ENTRY || i == TRAIN_POS_RIGHT_ENTRY) && pPassengers[i] == nil){
		pPassengers[i] = ped;
		m_nNumPassengers++;
	}else{
		for(i = 0; i < 6; i++)
			if(pPassengers[i] == nil){
				pPassengers[i] = ped;
				m_nNumPassengers++;
				return;
			}
	}
}

void
CTrain::OpenTrainDoor(float ratio)
{
	if(m_rwObject == nil)
		return;

	CMatrix doorL(RwFrameGetMatrix(m_aTrainNodes[TRAIN_DOOR_LHS]));
	CMatrix doorR(RwFrameGetMatrix(m_aTrainNodes[TRAIN_DOOR_RHS]));
	CVector posL = doorL.GetPosition();
	CVector posR = doorR.GetPosition();

	bool isClosed = Doors[0].IsClosed();	// useless

	Doors[0].Open(ratio);
	Doors[1].Open(ratio);

	if(isClosed)
		Doors[0].RetTranslationWhenClosed();	// useless

	posL.y = Doors[0].m_fPosn;
	posR.y = Doors[1].m_fPosn;

	doorL.SetTranslate(posL);
	doorR.SetTranslate(posR);

	doorL.UpdateRW();
	doorR.UpdateRW();
}

void
CTrain::InitTrains(void)
{
	int i, j;
	CTrain *train;

	// El train
	if(pTrackNodes == nil)
		ReadAndInterpretTrackFile("data\\paths\\tracks.dat", &pTrackNodes, &NumTrackNodes, 3, StationDist,
			&TotalLengthOfTrack, &TotalDurationOfTrack, aLineBits, false);
	// Subway
	if(pTrackNodes_S == nil)
		ReadAndInterpretTrackFile("data\\paths\\tracks2.dat", &pTrackNodes_S, &NumTrackNodes_S, 4, StationDist_S,
			&TotalLengthOfTrack_S, &TotalDurationOfTrack_S, aLineBits_S, true);

	int trainId;
	CStreaming::LoadAllRequestedModels(false);
	if(CModelInfo::GetModelInfo("train", &trainId))
		CStreaming::RequestModel(trainId, 0);
	CStreaming::LoadAllRequestedModels(false);

	// El-Train wagons
	float wagonPositions[] = { 0.0f, 20.0f, 40.0f,  0.0f, 20.0f };
	int8 firstWagon[]  = { 1, 0, 0,  1, 0 };
	int8 lastWagon[]   = { 0, 0, 1,  0, 1 };
	int16 wagonGroup[] = { 0, 0, 0,  1, 1 };
	for(i = 0; i < 5; i++){
		train = new CTrain(MI_TRAIN, PERMANENT_VEHICLE);
		train->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
		train->SetStatus(STATUS_ABANDONED);
		train->bIsLocked = true;
		train->m_fWagonPosition = wagonPositions[i];
		train->m_bIsFirstWagon = firstWagon[i];
		train->m_bIsLastWagon = lastWagon[i];
		train->m_nWagonGroup = wagonGroup[i];
		train->m_nWagonId = i;
		train->m_nCurTrackNode = 0;
		CWorld::Add(train);
	}

	// Subway wagons
	float wagonPositions_S[] = { 0.0f, 20.0f,  0.0f, 20.0f,  0.0f, 20.0f,  0.0f, 20.0f };
	int8 firstWagon_S[]  = { 1, 0,  1, 0,  1, 0,  1, 0 };
	int8 lastWagon_S[]   = { 0, 1,  0, 1,  0, 1,  0, 1 };
	int16 wagonGroup_S[] = { 0, 0,  1, 1,  2, 2,  3, 3 };
	for(i = 0; i < 8; i++){
		train = new CTrain(MI_TRAIN, PERMANENT_VEHICLE);
		train->GetMatrix().SetTranslate(0.0f, 0.0f, 0.0f);
		train->SetStatus(STATUS_ABANDONED);
		train->bIsLocked = true;
		train->m_fWagonPosition = wagonPositions_S[i];
		train->m_bIsFirstWagon = firstWagon_S[i];
		train->m_bIsLastWagon = lastWagon_S[i];
		train->m_nWagonGroup = wagonGroup_S[i];
		train->m_nWagonId = i;
		train->m_nCurTrackNode = 0;
		train->m_nTrackId = TRACK_SUBWAY;
		CWorld::Add(train);
	}

	// This code is actually useless, it seems it was used for announcements once
	for(i = 0; i < 3; i++){
		for(j = 0; pTrackNodes[j].t < StationDist[i]; j++);
		aStationCoors[i] = pTrackNodes[j].p;
	}
	for(i = 0; i < 4; i++){
		for(j = 0; pTrackNodes_S[j].t < StationDist_S[i]; j++);
		aStationCoors_S[i] = pTrackNodes_S[j].p;
	}
}

void
CTrain::Shutdown(void)
{
	delete[] pTrackNodes;
	delete[] pTrackNodes_S;
	pTrackNodes = nil;
	pTrackNodes_S = nil;
}

void
CTrain::ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int16 *numNodes, int32 numStations, float *stationDists,
		float *totalLength, float *totalDuration, CTrainInterpolationLine *interpLines, bool rightRail)
{
	bool readingFile = false;
	int bp, lp;
	int i, tmp;

	if(*nodes == nil){
		readingFile = true;

		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++;
		// BUG: game doesn't terminate string and uses numNodes in sscanf directly
		gString[lp] = '\0';
		sscanf(gString, "%d", &tmp);
		*numNodes = tmp;
		*nodes = new CTrainNode[*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);
		}

		// Coordinates are of one of the rails, but we want the center
		float toCenter = rightRail ? 0.9f : -0.9f;
		CVector fwd;
		for(i = 0; i < *numNodes; i++){
			if(i == *numNodes-1)
				fwd = (*nodes)[0].p - (*nodes)[i].p;
			else
				fwd = (*nodes)[i+1].p - (*nodes)[i].p;
			CVector right = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f));
			right.Normalise();
			(*nodes)[i].p -= right*toCenter;
		}
	}

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

	// Find correct z values
	if(readingFile){
		CColPoint colpoint;
		CEntity *entity;
		for(i = 0; i < *numNodes; i++){
			CVector p = (*nodes)[i].p;
			p.z += 1.0f;
			if(CWorld::ProcessVerticalLine(p, p.z-0.5f, colpoint, entity, true, false, false, false, true, false, nil))
				(*nodes)[i].p.z = colpoint.point.z;
			(*nodes)[i].p.z += 0.2f;
		}
	}

	// Create animation for stopping at stations
	// TODO: figure out magic numbers?
	float position = 0.0f;
	float time = 0.0f;
	int j = 0;
	for(i = 0; i < numStations; i++){
		// Start at full speed
		interpLines[j].type = 1;
		interpLines[j].time = time;
		interpLines[j].position = position;
		interpLines[j].speed = 15.0f;
		interpLines[j].acceleration = 0.0f;
		j++;
		// distance to next keyframe
		float dist = (stationDists[i]-40.0f) - position;
		time += dist/15.0f;
		position += dist;

		// Now slow down 40 units before stop
		interpLines[j].type = 2;
		interpLines[j].time = time;
		interpLines[j].position = position;
		interpLines[j].speed = 15.0f;
		interpLines[j].acceleration = -45.0f/32.0f;
		j++;
		time += 80.0f/15.0f;
		position += 40.0f;	// at station

		// stopping
		interpLines[j].type = 0;
		interpLines[j].time = time;
		interpLines[j].position = position;
		interpLines[j].speed = 0.0f;
		interpLines[j].acceleration = 0.0f;
		j++;
		time += 25.0f;

		// accelerate again
		interpLines[j].type = 2;
		interpLines[j].time = time;
		interpLines[j].position = position;
		interpLines[j].speed = 0.0f;
		interpLines[j].acceleration = 45.0f/32.0f;
		j++;
		time += 80.0f/15.0f;
		position += 40.0f;	// after station
	}
	// last keyframe
	interpLines[j].type = 1;
	interpLines[j].time = time;
	interpLines[j].position = position;
	interpLines[j].speed = 15.0f;
	interpLines[j].acceleration = 0.0f;
	j++;
	*totalDuration = time + (*totalLength - position)/15.0f;

	// end
	interpLines[j].time = *totalDuration;
}

void
PlayAnnouncement(uint8 sound, uint8 station)
{
	// this was gone in a PC version but inlined on PS2
	cAudioScriptObject *obj = new cAudioScriptObject;
	obj->AudioId = sound;
	obj->Posn = CTrain::aStationCoors[station];
	obj->AudioEntity = AEHANDLE_NONE;
	DMAudio.CreateOneShotScriptObject(obj);
}

void
ProcessTrainAnnouncements(void)
{
	for (int i = 0; i < ARRAY_SIZE(StationDist); i++) {
		for (int j = 0; j < ARRAY_SIZE(EngineTrackPosition); j++) {
			if (!bTrainArrivalAnnounced[i]) {
				float preDist = StationDist[i] - 100.0f;
				if (preDist < 0.0f)
					preDist += TotalLengthOfTrack;
				if (EngineTrackPosition[j] > preDist && EngineTrackPosition[j] < StationDist[i]) {
					bTrainArrivalAnnounced[i] = true;
					PlayAnnouncement(SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_1, i);
					break;
				}
			} else {
				float postDist = StationDist[i] + 10.0f;
#ifdef FIX_BUGS
				if (postDist > TotalLengthOfTrack)
					postDist -= TotalLengthOfTrack;
#else
				if (postDist < 0.0f) // does this even make sense here?
					postDist += TotalLengthOfTrack;
#endif
				if (EngineTrackPosition[j] > StationDist[i] && EngineTrackPosition[j] < postDist) {
					bTrainArrivalAnnounced[i] = false;
					PlayAnnouncement(SCRIPT_SOUND_TRAIN_ANNOUNCEMENT_2, i);
					break;
				}
			}
		}
	}
}

void
CTrain::UpdateTrains(void)
{
	int i, j;
	uint32 time;
	float t, deltaT;

	if(TheCamera.GetPosition().x > 200.0f && TheCamera.GetPosition().x < 1600.0f &&
	   TheCamera.GetPosition().y > -1000.0f && TheCamera.GetPosition().y < 500.0f){
		// Update El-Train

		time = CTimer::GetTimeInMilliseconds();
		for(i = 0; i < 2; i++){
			t = TotalDurationOfTrack * (float)(time & 0x1FFFF)/0x20000;
			// find current frame
			for(j = 0; t > aLineBits[j+1].time; j++);

			deltaT = t - aLineBits[j].time;
			switch(aLineBits[j].type){
			case 0:	// standing still
				EngineTrackPosition[i] = aLineBits[j].position;
				EngineTrackSpeed[i] = 0.0f;
				break;
			case 1:	// moving with constant speed
				EngineTrackPosition[i] = aLineBits[j].position + aLineBits[j].speed*deltaT;
				EngineTrackSpeed[i] = (TotalDurationOfTrack*1000.0f/0x20000) * aLineBits[j].speed;
				break;
			case 2:	// accelerating/braking
				EngineTrackPosition[i] = aLineBits[j].position + (aLineBits[j].speed + aLineBits[j].acceleration*deltaT)*deltaT;
				EngineTrackSpeed[i] = (TotalDurationOfTrack*1000.0f/0x20000)*aLineBits[j].speed + 2.0f*aLineBits[j].acceleration*deltaT;
				break;
			}

			// time offset for each train
			time += 0x20000/2;
		}

		ProcessTrainAnnouncements();
	}

	// Update Subway
	time = CTimer::GetTimeInMilliseconds();
	for(i = 0; i < 4; i++){
		t = TotalDurationOfTrack_S * (float)(time & 0x3FFFF)/0x40000;
		// find current frame
		for(j = 0; t > aLineBits_S[j+1].time; j++);

		deltaT = t - aLineBits_S[j].time;
		switch(aLineBits_S[j].type){
		case 0:	// standing still
			EngineTrackPosition_S[i] = aLineBits_S[j].position;
			EngineTrackSpeed_S[i] = 0.0f;
			break;
		case 1:	// moving with constant speed
			EngineTrackPosition_S[i] = aLineBits_S[j].position + aLineBits_S[j].speed*deltaT;
			EngineTrackSpeed_S[i] = (TotalDurationOfTrack*1000.0f/0x40000) * aLineBits_S[j].speed;
			break;
		case 2:	// accelerating/braking
			EngineTrackPosition_S[i] = aLineBits_S[j].position + (aLineBits_S[j].speed + aLineBits_S[j].acceleration*deltaT)*deltaT;
			EngineTrackSpeed_S[i] = (TotalDurationOfTrack*1000.0f/0x40000)*aLineBits_S[j].speed + 2.0f*aLineBits_S[j].acceleration*deltaT;
			break;
		}

		// time offset for each train
		time += 0x40000/4;
	}
}


code:
#pragma once

#include "Vehicle.h"
#include "Door.h"

enum
{
	TRACK_ELTRAIN,
	TRACK_SUBWAY
};

enum
{
	TRAIN_DOOR_CLOSED,
	TRAIN_DOOR_OPENING,
	TRAIN_DOOR_OPEN,
	TRAIN_DOOR_CLOSING
};

enum eTrainNodes
{
	TRAIN_DOOR_LHS = 1,
	TRAIN_DOOR_RHS,
	NUM_TRAIN_NODES
};

struct CTrainNode
{
	CVector p;	// position
	float t;	// xy-distance from start on track
};

struct CTrainInterpolationLine
{
	uint8 type;
	float time;	// when does this keyframe start
	// initial values at start of frame
	float position;
	float speed;
	float acceleration;
};

class CTrain : public CVehicle
{
public:
	// 0x288
	float m_fWagonPosition;
	int16 m_nWagonId;
	int16 m_isFarAway;	// don't update so often?
	int16 m_nCurTrackNode;
	int16 m_nWagonGroup;
	float m_fSpeed;
	bool m_bProcessDoor;
	bool m_bTrainStopping;
	bool m_bIsFirstWagon;
	bool m_bIsLastWagon;
	uint8 m_nTrackId;	// or m_bUsesSubwayTracks?
	uint32 m_nDoorTimer;
	int16 m_nDoorState;
	CTrainDoor Doors[2];
	RwFrame *m_aTrainNodes[NUM_TRAIN_NODES];

	// unused
	static CVector aStationCoors[3];
	static CVector aStationCoors_S[4];

	CTrain(int32 id, uint8 CreatedBy);

	// from CEntity
	void SetModelIndex(uint32 id);
	void ProcessControl(void);
	void PreRender(void);
	void Render(void);

	void AddPassenger(CPed *ped);
	void OpenTrainDoor(float ratio);
	void TrainHitStuff(CPtrList &list);

	static void InitTrains(void);
	static void Shutdown(void);
	static void ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int16 *numNodes, int32 numStations, float *stationDists,
		float *totalLength, float *totalDuration, CTrainInterpolationLine *interpLines, bool rightRail);
	static void UpdateTrains(void);
};

VALIDATE_SIZE(CTrain, 0x2E4);

license: mit


code:
#include "common.h"

#include "Timer.h"
#include "HandlingMgr.h"
#include "Transmission.h"

void
cTransmission::InitGearRatios(void)
{
	static tGear *pGearRatio0 = nil;
	static tGear *pGearRatio1 = nil;
	int i;
	float velocityDiff;

	memset(Gears, 0, sizeof(Gears));

	for(i = 1; i <= nNumberOfGears; i++){
		pGearRatio0 = &Gears[i-1];
		pGearRatio1 = &Gears[i];

		pGearRatio1->fMaxVelocity = (float)i / nNumberOfGears * fMaxVelocity;

		velocityDiff = pGearRatio1->fMaxVelocity - pGearRatio0->fMaxVelocity;

		if(i >= nNumberOfGears){
			pGearRatio1->fShiftUpVelocity = fMaxVelocity;
		}else{
			Gears[i+1].fShiftDownVelocity = velocityDiff*0.42f + pGearRatio0->fMaxVelocity;
			pGearRatio1->fShiftUpVelocity = velocityDiff*0.6667f + pGearRatio0->fMaxVelocity;
		}
	}

	// Reverse gear
	Gears[0].fMaxVelocity = fMaxReverseVelocity;
	Gears[0].fShiftUpVelocity = -0.01f;
	Gears[0].fShiftDownVelocity = fMaxReverseVelocity;

	Gears[1].fShiftDownVelocity = -0.01f;
}

void
cTransmission::CalculateGearForSimpleCar(float speed, uint8 &gear)
{
	static tGear *pGearRatio;

	pGearRatio = &Gears[gear];
	fCurVelocity = speed;
	if(speed > pGearRatio->fShiftUpVelocity)
		gear++;
	else if(speed < pGearRatio->fShiftDownVelocity){
		if(gear - 1 < 0)
			gear = 0;
		else
			gear--;
	}
}

float
cTransmission::CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, float &time, const float &velocity, bool cheat)
{
	static float fAcceleration = 0.0f;
	static float fVelocity;
	static float fCheat;
	static tGear *pGearRatio;

	fVelocity = velocity;
	if(fVelocity < fMaxReverseVelocity){
		fVelocity = fMaxReverseVelocity;
		return 0.0f;
	}
	if(fVelocity > fMaxVelocity){
		fVelocity = fMaxVelocity;
		return 0.0f;
	}
	fCurVelocity = fVelocity;

	assert(gear <= nNumberOfGears);

	pGearRatio = &Gears[gear];
	if(fVelocity > pGearRatio->fShiftUpVelocity){
		if(gear != 0 || gasPedal > 0.0f){
			gear++;
			time = 0.0f;
			return CalculateDriveAcceleration(gasPedal, gear, time, fVelocity, false);
		}
	}else if(fVelocity < pGearRatio->fShiftDownVelocity && gear != 0){
		if(gear != 1 || gasPedal < 0.0f){
			gear--;
			time = 0.0f;
			return CalculateDriveAcceleration(gasPedal, gear, time, fVelocity, false);
		}
	}

	if(time > 0.0f){
		// changing gears currently, can't accelerate
		fAcceleration = 0.0f;
		time -= CTimer::GetTimeStepInSeconds();
	}else{
		float speedMul, accelMul;

		if(gear < 1){
			// going reverse
			accelMul = (Flags & HANDLING_2G_BOOST) ? 2.0f : 1.0f;
			speedMul = -1.0f;
		}else if(nNumberOfGears == 1){
			accelMul = 1.0f;
			speedMul = 1.0f;
		}else{
			// BUG or not? this is 1.0 normally but 0.0 in the highest gear
			float f = 1.0f - (gear-1)/(nNumberOfGears-1);
			speedMul = 3.0f*sq(f) + 1.0f;
			// This is pretty ugly, could be written more clearly
			if(Flags & HANDLING_2G_BOOST){
				if(gear == 1)
					accelMul = (Flags & HANDLING_1G_BOOST) ? 3.0f : 2.0f;
				else if(gear == 2)
					accelMul = 1.3f;
				else
					accelMul = 1.0f;
			}else if(Flags & HANDLING_1G_BOOST && gear == 1){
				accelMul = 3.0f;
			}else
				accelMul = 1.0f;
		}

		if(cheat)
			fCheat = 1.2f;
		else
			fCheat = 1.0f;
		float targetVelocity = Gears[gear].fMaxVelocity*speedMul*fCheat;
		float accel = (targetVelocity - fVelocity) * (fEngineAcceleration*accelMul) / Abs(targetVelocity);
		if(Abs(fVelocity) < Abs(Gears[gear].fMaxVelocity*fCheat))
			fAcceleration = gasPedal * accel * CTimer::GetTimeStep();
		else
			fAcceleration = 0.0f;
	}
	return fAcceleration;
}


code:
#pragma once

struct tGear
{
	float fMaxVelocity;
	float fShiftUpVelocity;
	float fShiftDownVelocity;
};

class cTransmission
{
public:
	// Gear 0 is reverse, 1-5 are forward
	tGear Gears[6];
	char nDriveType;
	char nEngineType;
	int8 nNumberOfGears;
	uint8 Flags;
	float fEngineAcceleration;
	float fMaxVelocity;
	float fMaxCruiseVelocity;
	float fMaxReverseVelocity;
	float fCurVelocity;

	void InitGearRatios(void);
	void CalculateGearForSimpleCar(float speed, uint8 &gear);
	float CalculateDriveAcceleration(const float &gasPedal, uint8 &gear, float &time, const float &velocity, bool cheat);
};


code:
#include "common.h"
#include "main.h"

#include "General.h"
#include "Timer.h"
#include "Pad.h"
#include "Vehicle.h"
#include "Pools.h"
#include "HandlingMgr.h"
#include "CarCtrl.h"
#include "Population.h"
#include "ModelIndices.h"
#include "World.h"
#include "Lights.h"
#include "PointLights.h"
#include "Renderer.h"
#include "DMAudio.h"
#include "Radar.h"
#include "Fire.h"
#include "Darkel.h"
#include "SaveBuf.h"

bool CVehicle::bWheelsOnlyCheat;
bool CVehicle::bAllDodosCheat;
bool CVehicle::bCheat3;
bool CVehicle::bCheat4;
bool CVehicle::bCheat5;
#ifdef ALT_DODO_CHEAT
bool CVehicle::bAltDodoCheat;
#endif
bool CVehicle::m_bDisableMouseSteering = true;

void *CVehicle::operator new(size_t sz) throw() { return CPools::GetVehiclePool()->New();  }
void *CVehicle::operator new(size_t sz, int handle) throw() { return CPools::GetVehiclePool()->New(handle); }
void CVehicle::operator delete(void *p, size_t sz) throw() { CPools::GetVehiclePool()->Delete((CVehicle*)p); }
void CVehicle::operator delete(void *p, int handle) throw() { CPools::GetVehiclePool()->Delete((CVehicle*)p); }

#ifdef FIX_BUGS
// I think they meant that
#define DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE (MYRAND_MAX * 35 / 100)
#define DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE (MYRAND_MAX * 70 / 100)
#else
#define DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE (35000)
#define DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE (70000)
#endif
#define DAMAGE_HEALTH_TO_FLEE_ALWAYS (200.0f)
#define DAMAGE_HEALTH_TO_CATCH_FIRE (250.0f)

CVehicle::CVehicle(uint8 CreatedBy)
{
	int i;

	m_nCurrentGear = 1;
	m_fChangeGearTime = 0.0f;
	m_fSteerInput = 0.0f;
	m_type = ENTITY_TYPE_VEHICLE;
	VehicleCreatedBy = CreatedBy;
	bIsLocked = false;
	bIsLawEnforcer = false;
	bIsAmbulanceOnDuty = false;
	bIsFireTruckOnDuty = false;
#ifdef FIX_BUGS
	bIsHandbrakeOn = false;
#endif
	CCarCtrl::UpdateCarCount(this, false);
	m_fHealth = 1000.0f;
	bEngineOn = true;
	bFreebies = true;
	pDriver = nil;
	m_nNumPassengers = 0;
	m_nNumGettingIn = 0;
	m_nGettingInFlags = 0;
	m_nGettingOutFlags = 0;
	m_nNumMaxPassengers = ARRAY_SIZE(pPassengers);
	for(i = 0; i < m_nNumMaxPassengers; i++)
		pPassengers[i] = nil;
	m_nBombTimer = 0;
	m_pBlowUpEntity = nil;
	m_nPacManPickupsCarried = 0;
	bComedyControls = false;
	bCraneMessageDone = false;
	bExtendedRange = false;
	bTakeLessDamage = false;
	bIsDamaged = false;
	bFadeOut = false;
	bIsBeingCarJacked = false;
	m_nTimeOfDeath = 0;
	m_pCarFire = nil;
	bHasBeenOwnedByPlayer = false;
	bCreateRoadBlockPeds = false;
	bCanBeDamaged = true;
	bUsingSpecialColModel = false;
	bOccupantsHaveBeenGenerated = false;
	bGunSwitchedOff = false;
	m_nGunFiringTime = 0;
	m_nTimeBlocked = 0;
	bLightsOn = false;
	bVehicleColProcessed = false;
	m_numPedsUseItAsCover = 0;
	bIsCarParkVehicle = false;
	bHasAlreadyBeenRecorded = false;
	m_bSirenOrAlarm = false;
	m_nCarHornTimer = 0;
	m_nCarHornPattern = 0;
	m_nAlarmState = 0;
	m_nDoorLock = CARLOCK_UNLOCKED;
	m_nLastWeaponDamage = -1;
	m_fMapObjectHeightAhead = m_fMapObjectHeightBehind = 0.0f;
	m_audioEntityId = DMAudio.CreateEntity(AUDIOTYPE_PHYSICAL, this);
	if(m_audioEntityId >= 0)
		DMAudio.SetEntityStatus(m_audioEntityId, TRUE);
	m_nRadioStation = CGeneral::GetRandomNumber() % USERTRACK;
	m_pCurGroundEntity = nil;
	m_bRainAudioCounter = 0;
	m_bRainSamplesCounter = 0;
	m_comedyControlState = 0;
	m_aCollPolys[0].valid = false;
	m_aCollPolys[1].valid = false;
	AutoPilot.m_nCarMission = MISSION_NONE;
	AutoPilot.m_nTempAction = TEMPACT_NONE;
	AutoPilot.m_nTimeToStartMission = CTimer::GetTimeInMilliseconds();
	AutoPilot.m_bStayInCurrentLevel = false;
	AutoPilot.m_bIgnorePathfinding = false;
}

CVehicle::~CVehicle()
{
	m_nAlarmState = 0;
	if (m_audioEntityId >= 0){
		DMAudio.DestroyEntity(m_audioEntityId);
		m_audioEntityId = -5;
	}
	CRadar::ClearBlipForEntity(BLIP_CAR, CPools::GetVehiclePool()->GetIndex(this));
	if (pDriver)
		pDriver->FlagToDestroyWhenNextProcessed();
	for (int i = 0; i < m_nNumMaxPassengers; i++){
		if (pPassengers[i])
			pPassengers[i]->FlagToDestroyWhenNextProcessed();
	}
	if (m_pCarFire)
		m_pCarFire->Extinguish();
	CCarCtrl::UpdateCarCount(this, true);
	if (bIsAmbulanceOnDuty){
		CCarCtrl::NumAmbulancesOnDuty--;
		bIsAmbulanceOnDuty = false;
	}
	if (bIsFireTruckOnDuty){
		CCarCtrl::NumFiretrucksOnDuty--;
		bIsFireTruckOnDuty = false;
	}
}

void
CVehicle::SetModelIndex(uint32 id)
{
	CEntity::SetModelIndex(id);
	m_aExtras[0] = CVehicleModelInfo::ms_compsUsed[0];
	m_aExtras[1] = CVehicleModelInfo::ms_compsUsed[1];
	m_nNumMaxPassengers = CVehicleModelInfo::GetMaximumNumberOfPassengersFromNumberOfDoors(id);
}

bool
CVehicle::SetupLighting(void)
{
	ActivateDirectional();
	SetAmbientColoursForPedsCarsAndObjects();

	if(bRenderScorched){
		WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f);
	}else{
		CVector coors = GetPosition();
		float lighting = CPointLights::GenerateLightsAffectingObject(&coors);
		if(!bHasBlip && lighting != 1.0f){
			SetAmbientAndDirectionalColours(lighting);
			return true;
		}
	}

	return false;
}

void
CVehicle::RemoveLighting(bool reset)
{
	CRenderer::RemoveVehiclePedLights(this, reset);
}

float
CVehicle::GetHeightAboveRoad(void)
{
	return -1.0f * GetColModel()->boundingBox.min.z;
}

const float fRCPropFallOff = 3.0f;
const float fRCAeroThrust = 0.003f;
const float fRCSideSlipMult = 0.1f;
const float fRCRudderMult = 0.2f;
const float fRCYawMult = -0.01f;
const float fRCRollMult = 0.02f;
const float fRCRollStabilise = -0.08f;
const float fRCPitchMult = 0.005f;
const float fRCTailMult = 0.3f;
const float fRCFormLiftMult = 0.02f;
const float fRCAttackLiftMult = 0.25f;
const CVector vecRCAeroResistance(0.998f, 0.998f, 0.9f);

const float fSeaPropFallOff = 2.3f;
const float fSeaThrust = 0.002f;
const float fSeaSideSlipMult = 0.1f;
const float fSeaRudderMult = 0.01f;
const float fSeaYawMult = -0.0003f;
const float fSeaRollMult = 0.0015f;
const float fSeaRollStabilise = -0.01f;
const float fSeaPitchMult = 0.0002f;
const float fSeaTailMult = 0.01f;
const float fSeaFormLiftMult = 0.012f;
const float fSeaAttackLiftMult = 0.1f;
const CVector vecSeaAeroResistance(0.995f, 0.995f, 0.85f);

const float fSpeedResistanceY = 500.0f;
const float fSpeedResistanceZ = 500.0f;

const CVector vecHeliMoveRes(0.995f, 0.995f, 0.99f);
const CVector vecRCHeliMoveRes(0.99f, 0.99f, 0.99f);
const float fThrustVar = 0.3f;
const float fRotorFallOff = 0.75f;
const float fStabiliseVar = 0.015f;
const float fPitchBrake = 10.0f;
const float fPitchVar = 0.006f;
const float fRollVar = 0.006f;
const float fYawVar = -0.001f;
const CVector vecHeliResistance(0.81f, 0.85f, 0.99f);
const CVector vecRCHeliResistance(0.92f, 0.92f, 0.998f);
const float fSpinSpeedRes = 20.0f;

void
CVehicle::FlyingControl(eFlightModel flightModel)
{
	switch(flightModel){
	case FLIGHT_MODEL_DODO:
	{
		// This seems pretty magic

		// Move Left/Right
		float moveSpeed = m_vecMoveSpeed.Magnitude();
		float sideSpeed = DotProduct(m_vecMoveSpeed, GetRight());
		float sideImpulse = -1.0f * sideSpeed / moveSpeed;
		float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward());
		float magic = m_vecMoveSpeed.MagnitudeSqr() * sq(fwdSpeed);
		float turnImpulse = (sideImpulse*0.003f + m_fSteerAngle*0.001f) *
			magic*m_fTurnMass*CTimer::GetTimeStep();
		ApplyTurnForce(turnImpulse*GetRight(), -4.0f*GetForward());

		float impulse = sideImpulse*0.2f *
			magic*m_fMass*CTimer::GetTimeStep();
		ApplyMoveForce(impulse*GetRight());
		ApplyTurnForce(impulse*GetRight(), 2.0f*GetUp());

		// Move Up/Down
		moveSpeed = m_vecMoveSpeed.Magnitude();
		float upSpeed = DotProduct(m_vecMoveSpeed, GetUp());
		float upImpulse = -1.0f * upSpeed / moveSpeed;
		turnImpulse = (upImpulse*0.002f + -CPad::GetPad(0)->GetSteeringUpDown()/128.0f*0.001f) *
			magic*m_fTurnMass*CTimer::GetTimeStep();
		ApplyTurnForce(turnImpulse*GetUp(), -4.0f*GetForward());

		impulse = (upImpulse*3.5f + 0.5f)*0.05f *
			magic*m_fMass*CTimer::GetTimeStep();
		if(GRAVITY*m_fMass*CTimer::GetTimeStep() < impulse &&
		   GetPosition().z > 100.0f)
			impulse = 0.9f*GRAVITY*m_fMass*CTimer::GetTimeStep();
		CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass);
		ApplyMoveForce(impulse*GetUp());
		ApplyTurnForce(impulse*GetUp(), 2.0f*GetUp() + com);

		m_vecTurnSpeed.y *= Pow(0.9f, CTimer::GetTimeStep());
		moveSpeed = m_vecMoveSpeed.MagnitudeSqr();
		if(moveSpeed > SQR(1.5f))
			m_vecMoveSpeed *= 1.5f/Sqrt(moveSpeed);

		float turnSpeed = m_vecTurnSpeed.MagnitudeSqr();
		if(turnSpeed > SQR(0.2f))
			m_vecTurnSpeed *= 0.2f/Sqrt(turnSpeed);
		break;
	}

	case FLIGHT_MODEL_RCPLANE:
	case FLIGHT_MODEL_SEAPLANE:
	{
		// thrust
		float fThrust = flightModel == FLIGHT_MODEL_RCPLANE ? fRCAeroThrust : fSeaThrust;
		float fThrustFallOff = flightModel == FLIGHT_MODEL_RCPLANE ? fRCPropFallOff : fSeaPropFallOff;

		float fForwSpeed = DotProduct(GetMoveSpeed(), GetForward());
		CVector vecTail = GetColModel()->boundingBox.min.y * GetForward();
		float fPedalState = (CPad::GetPad(0)->GetAccelerate() - CPad::GetPad(0)->GetBrake()) / 255.0f;
		if (fForwSpeed > 0.1f || (flightModel == FLIGHT_MODEL_RCPLANE && fForwSpeed > 0.02f))
			fPedalState += 1.0f;
		else if (fForwSpeed > 0.0f && fPedalState < 0.0f)
			fPedalState = 0.0f;
		float fThrustAccel = (fPedalState - fThrustFallOff * fForwSpeed) * fThrust;

		ApplyMoveForce(fThrustAccel * GetForward() * m_fMass * CTimer::GetTimeStep());

		// left/right
		float fSideSpeed = -DotProduct(GetMoveSpeed(), GetRight());
		float fSteerLR = CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f;
		float fSideSlipAccel;
		if (flightModel == FLIGHT_MODEL_RCPLANE)
			fSideSlipAccel = Abs(fSideSpeed) * fSideSpeed * fRCSideSlipMult;
		else
			fSideSlipAccel = Abs(fSideSpeed) * fSideSpeed * fSeaSideSlipMult;
		ApplyMoveForce(m_fMass * GetRight() * fSideSlipAccel * CTimer::GetTimeStep());

		float fYaw = -DotProduct(GetSpeed(vecTail), GetRight());
		float fYawAccel;
		if (flightModel == FLIGHT_MODEL_RCPLANE)
			fYawAccel = fRCRudderMult * fYaw * Abs(fYaw) + fRCYawMult * fSteerLR * fForwSpeed;
		else
			fYawAccel = fSeaRudderMult * fYaw * Abs(fYaw) + fSeaYawMult * fSteerLR * fForwSpeed;
		ApplyTurnForce(fYawAccel * GetRight() * m_fTurnMass * CTimer::GetTimeStep(), vecTail);

		float fRollAccel;
		if (flightModel == FLIGHT_MODEL_RCPLANE) {
			float fDirectionMultiplier = CPad::GetPad(0)->GetLookRight();
			if (CPad::GetPad(0)->GetLookLeft())
				fDirectionMultiplier = -1;
			fRollAccel = (0.5f * fDirectionMultiplier + fSteerLR) * fRCRollMult;
		}
		else
			fRollAccel = fSteerLR * fSeaRollMult;
		ApplyTurnForce(GetRight() * fRollAccel * fForwSpeed * m_fTurnMass * CTimer::GetTimeStep(), GetUp());

		CVector vecFRight = CrossProduct(GetForward(), CVector(0.0f, 0.0f, 1.0f));
		CVector vecStabilise = (GetUp().z > 0.0f) ? vecFRight : -vecFRight;
		float fStabiliseDirection = (GetRight().z > 0.0f) ? -1.0f : 1.0f;
		float fStabiliseSpeed;
		if (flightModel == FLIGHT_MODEL_RCPLANE)
			fStabiliseSpeed = fRCRollStabilise * fStabiliseDirection * (1.0f - DotProduct(GetRight(), vecStabilise)) * (1.0f - Abs(GetForward().z));
		else
			fStabiliseSpeed = fSeaRollStabilise * fStabiliseDirection * (1.0f - DotProduct(GetRight(), vecStabilise)) * (1.0f - Abs(GetForward().z));
		ApplyTurnForce(fStabiliseSpeed * m_fTurnMass * GetRight(), GetUp()); // no CTimer::GetTimeStep(), is it right? VC doesn't have it too

		// up/down
		float fTail = -DotProduct(GetSpeed(vecTail), GetUp());
		float fSteerUD = -CPad::GetPad(0)->GetSteeringUpDown() / 128.0f;
		float fPitchAccel;
		if (flightModel == FLIGHT_MODEL_RCPLANE)
			fPitchAccel = fRCTailMult * fTail * Abs(fTail) + fRCPitchMult * fSteerUD * fForwSpeed;
		else
			fPitchAccel = fSeaTailMult * fTail * Abs(fTail) + fSeaPitchMult * fSteerUD * fForwSpeed;
		ApplyTurnForce(fPitchAccel * m_fTurnMass * GetUp() * CTimer::GetTimeStep(), vecTail);

		float fLift = DotProduct(GetMoveSpeed(), GetUp()) / Max(0.01f, GetMoveSpeed().Magnitude()); //accel*angle
		float fLiftAccel;
		if (flightModel == FLIGHT_MODEL_RCPLANE)
			fLiftAccel = (fRCFormLiftMult - fRCAttackLiftMult * fLift) * SQR(fForwSpeed);
		else
			fLiftAccel = (fSeaFormLiftMult - fSeaAttackLiftMult * fLift) * SQR(fForwSpeed);
		float fLiftImpulse = fLiftAccel * m_fMass * CTimer::GetTimeStep();
		if (GRAVITY * CTimer::GetTimeStep() * m_fMass < fLiftImpulse) {
			if (flightModel == FLIGHT_MODEL_RCPLANE && GetPosition().z > 50.0f)
				fLiftImpulse = CTimer::GetTimeStep() * 0.9f*GRAVITY * m_fMass;
			else if (flightModel == FLIGHT_MODEL_SEAPLANE && GetPosition().z > 80.0f)
				fLiftImpulse = CTimer::GetTimeStep() * 0.9f*GRAVITY * m_fMass;
		}
		ApplyMoveForce(fLiftImpulse * GetUp());

		CVector vecResistance;
		if (flightModel == FLIGHT_MODEL_RCPLANE)
			vecResistance = vecRCAeroResistance;
		else
			vecResistance = vecSeaAeroResistance;
		float rX = Pow(vecResistance.x, CTimer::GetTimeStep());
		float rY = Pow(vecResistance.y, CTimer::GetTimeStep());
		float rZ = Pow(vecResistance.z, CTimer::GetTimeStep());
		CVector vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix());
		vecTurnSpeed.x *= rX;
		float fResistance = vecTurnSpeed.y * (1.0f / (fSpeedResistanceY * SQR(vecTurnSpeed.y) + 1.0f)) * rY - vecTurnSpeed.y;
		vecTurnSpeed.z *= rZ;
		m_vecTurnSpeed = Multiply3x3(GetMatrix(), vecTurnSpeed);
		ApplyTurnForce(-GetUp() * fResistance * m_fTurnMass, GetRight() + Multiply3x3(GetMatrix(), m_vecCentreOfMass));
		break;
	}
	case FLIGHT_MODEL_HELI:
	{
		CVector vecMoveResistance;
		if (GetModelIndex() == MI_MIAMI_SPARROW)
			vecMoveResistance = vecHeliMoveRes;
		else
			vecMoveResistance = vecRCHeliMoveRes;
		float rmX = Pow(vecMoveResistance.x, CTimer::GetTimeStep());
		float rmY = Pow(vecMoveResistance.y, CTimer::GetTimeStep());
		float rmZ = Pow(vecMoveResistance.z, CTimer::GetTimeStep());
		m_vecMoveSpeed.x *= rmX;
		m_vecMoveSpeed.y *= rmY;
		m_vecMoveSpeed.z *= rmZ;
		if (GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE)
			return;
		float fThrust;
		if (bCheat5)
			fThrust = CPad::GetPad(0)->GetSteeringUpDown() * fThrustVar / 128.0f + 0.95f;
		else
			fThrust = fThrustVar * (CPad::GetPad(0)->GetAccelerate() - 2 * CPad::GetPad(0)->GetBrake()) / 255.0f + 0.95f;
		fThrust -= fRotorFallOff * DotProduct(m_vecMoveSpeed, GetUp());
#if GTA_VERSION >= GTA3_PC_11
		if (fThrust > 0.9f && GetPosition().z > 80.0f)
			fThrust = 0.9f;
#endif
		ApplyMoveForce(GRAVITY * GetUp() * fThrust * m_fMass * CTimer::GetTimeStep());

		if (GetUp().z > 0.0f)
			ApplyTurnForce(-CVector(GetUp().x, GetUp().y, 0.0f) * fStabiliseVar * m_fTurnMass * CTimer::GetTimeStep(), GetUp());

		float fRoll, fPitch, fYaw;
		if (bCheat5) {
			fPitch = CPad::GetPad(0)->GetCarGunUpDown() / 128.0f;
			fRoll = -CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f;
			fYaw = CPad::GetPad(0)->GetCarGunLeftRight() / 128.0f;
		}
		else {
			fPitch = CPad::GetPad(0)->GetSteeringUpDown() / 128.0f;
			fRoll = CPad::GetPad(0)->GetLookLeft();
			if (CPad::GetPad(0)->GetLookRight())
				fRoll = -1.0f;
			fYaw = CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f;
		}
		if (CPad::GetPad(0)->GetHorn()) {
			fYaw = 0.0f;
			fPitch = Clamp(10.0f * DotProduct(m_vecMoveSpeed, GetForward()), -200.0f, 1.3f);
			fRoll = Clamp(10.0f * DotProduct(m_vecMoveSpeed, GetRight()), -200.0f, 1.3f);
		}
		ApplyTurnForce(fPitch * GetUp() * fPitchVar * m_fTurnMass * CTimer::GetTimeStep(), GetForward());
		ApplyTurnForce(fRoll * GetUp() * fRollVar * m_fTurnMass * CTimer::GetTimeStep(), GetRight());
		ApplyTurnForce(fYaw * GetForward() * fYawVar * m_fTurnMass * CTimer::GetTimeStep(), GetRight());

		CVector vecResistance;
		if (GetModelIndex() == MI_MIAMI_SPARROW)
			vecResistance = vecHeliResistance;
		else
			vecResistance = vecRCHeliResistance;
		float rX = Pow(vecResistance.x, CTimer::GetTimeStep());
		float rY = Pow(vecResistance.y, CTimer::GetTimeStep());
		float rZ = Pow(vecResistance.z, CTimer::GetTimeStep());
		CVector vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix());
		float fResistanceMultiplier = Pow(1.0f / (fSpinSpeedRes * SQR(vecTurnSpeed.z) + 1.0f) * rZ, CTimer::GetTimeStep());
		float fResistance = vecTurnSpeed.z * fResistanceMultiplier - vecTurnSpeed.z;
		vecTurnSpeed.x *= rX;
		vecTurnSpeed.y *= rY;
		vecTurnSpeed.z *= fResistanceMultiplier;
		m_vecTurnSpeed = Multiply3x3(GetMatrix(), vecTurnSpeed);
		ApplyTurnForce(-GetRight() * fResistance * m_fTurnMass, GetForward() + Multiply3x3(GetMatrix(), m_vecCentreOfMass));
		break;
	}
	}
}

float fBurstSpeedMax = 0.3f;
float fBurstTyreMod = 0.1f;

void
CVehicle::ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint,
	int32 wheelsOnGround, float thrust, float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, uint16 wheelStatus)
{
	// BUG: using statics here is probably a bad idea
	static bool bAlreadySkidding = false;	// this is never reset
	static bool bBraking;
	static bool bDriving;

#ifdef FIX_SIGNIFICANT_BUGS
	bAlreadySkidding = false;
#endif

	// how much force we want to apply in these axes
	float fwd = 0.0f;
	float right = 0.0f;

	bBraking = brake != 0.0f;
	if(bBraking)
		thrust = 0.0f;
	bDriving = thrust != 0.0f;

	float contactSpeedFwd = DotProduct(wheelContactSpeed, wheelFwd);
	float contactSpeedRight = DotProduct(wheelContactSpeed, wheelRight);

	if(*wheelState != WHEEL_STATE_NORMAL)
		bAlreadySkidding = true;
	*wheelState = WHEEL_STATE_NORMAL;

	adhesion *= CTimer::GetTimeStep();
	if(bAlreadySkidding)
		adhesion *= pHandling->fTractionLoss;

	// moving sideways
	if(contactSpeedRight != 0.0f){
		// exert opposing force
		right = -contactSpeedRight/wheelsOnGround;
		// BUG?
		// contactSpeedRight is independent of framerate but right has timestep as a factor
		// so we probably have to fix this
		// fixing this causes jittery cars at 15fps, and causes the car to move backwards slowly at 18fps
		// at 19fps, the effects are gone ...
		//right *= CTimer::GetTimeStepFix();

		if(wheelStatus == WHEEL_STATUS_BURST){
			float fwdspeed = Min(contactSpeedFwd, fBurstSpeedMax);
			right += fwdspeed * CGeneral::GetRandomNumberInRange(-fBurstTyreMod, fBurstTyreMod);
		}
	}

	if(bDriving){
		fwd = thrust;

		// limit sideways force (why?)
		if(right > 0.0f){
			if(right > adhesion)
				right = adhesion;
		}else{
			if(right < -adhesion)
				right = -adhesion;
		}
	}else if(contactSpeedFwd != 0.0f){
		fwd = -contactSpeedFwd/wheelsOnGround;
#ifdef FIX_BUGS
		// contactSpeedFwd is independent of framerate but fwd has timestep as a factor
		// so we probably have to fix this
		// better get rid of it here too
		//fwd *= CTimer::GetTimeStepFix();
#endif

		if(!bBraking){
			if(m_fGasPedal < 0.01f){
				if(GetModelIndex() == MI_RCBANDIT)
					brake = 0.2f * mod_HandlingManager.fWheelFriction / pHandling->fMass;
				else
					brake = mod_HandlingManager.fWheelFriction / pHandling->fMass;
#ifdef FIX_BUGS
				brake *= CTimer::GetTimeStepFix();
#endif
			}
		}

		if(brake > adhesion){
			if(Abs(contactSpeedFwd) > 0.005f)
				*wheelState = WHEEL_STATE_FIXED;
		}else {
			if(fwd > 0.0f){
				if(fwd > brake)
					fwd = brake;
			}else{
				if(fwd < -brake)
					fwd = -brake;
			}
		}
	}

	float speedSq = sq(right) + sq(fwd);
	if(sq(adhesion) < speedSq){
		if(*wheelState != WHEEL_STATE_FIXED){
			if(bDriving && contactSpeedFwd < 0.2f)
				*wheelState = WHEEL_STATE_SPINNING;
			else
				*wheelState = WHEEL_STATE_SKIDDING;
		}

		float l = Sqrt(speedSq);
		float tractionLoss = bAlreadySkidding ? 1.0f : pHandling->fTractionLoss;
		right *= adhesion * tractionLoss / l;
		fwd *= adhesion * tractionLoss / l;
	}

	if(fwd != 0.0f || right != 0.0f){
		CVector direction = fwd*wheelFwd + right*wheelRight;
		float speed = direction.Magnitude();
		direction.Normalise();

		float impulse = speed*m_fMass;
		float turnImpulse = speed*GetMass(wheelContactPoint, direction);

		ApplyMoveForce(impulse * direction);
		ApplyTurnForce(turnImpulse * direction, wheelContactPoint);
	}
}

void
CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, int32 wheelsOnGround, float thrust,
                           float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, eBikeWheelSpecial special, uint16 wheelStatus)
{
	// TODO: mobile code
}

float
CVehicle::ProcessWheelRotation(tWheelState state, const CVector &fwd, const CVector &speed, float radius)
{
	float angularVelocity;
	switch(state){
	case WHEEL_STATE_SPINNING:
		angularVelocity = -1.1f;	// constant speed forward
		break;
	case WHEEL_STATE_FIXED:
		angularVelocity = 0.0f;		// not moving
		break;
	default:
		angularVelocity = -DotProduct(fwd, speed) / radius;	// forward speed
		break;
	}
	return angularVelocity * CTimer::GetTimeStep();
}

void
CVehicle::InflictDamage(CEntity* damagedBy, eWeaponType weaponType, float damage)
{
	if (!bCanBeDamaged)
		return;
	if (bOnlyDamagedByPlayer && (damagedBy != FindPlayerPed() && damagedBy != FindPlayerVehicle()))
		return;
	bool bFrightensDriver = false;
	switch (weaponType) {
	case WEAPONTYPE_UNARMED:
	case WEAPONTYPE_BASEBALLBAT:
		if (bMeleeProof)
			return;
		break;
	case WEAPONTYPE_COLT45:
	case WEAPONTYPE_UZI:
	case WEAPONTYPE_SHOTGUN:
	case WEAPONTYPE_AK47:
	case WEAPONTYPE_M16:
	case WEAPONTYPE_SNIPERRIFLE:
	case WEAPONTYPE_TOTAL_INVENTORY_WEAPONS:
	case WEAPONTYPE_UZI_DRIVEBY:
		if (bBulletProof)
			return;
		bFrightensDriver = true;
		break;
	case WEAPONTYPE_ROCKETLAUNCHER:
	case WEAPONTYPE_MOLOTOV:
	case WEAPONTYPE_GRENADE:
	case WEAPONTYPE_EXPLOSION:
		if (bExplosionProof)
			return;
		bFrightensDriver = true;
		break;
	case WEAPONTYPE_FLAMETHROWER:
		if (bFireProof)
			return;
		break;
	case WEAPONTYPE_RAMMEDBYCAR:
		if (bCollisionProof)
			return;
		break;
	default:
		break;
	}
	if (m_fHealth > 0.0f) {
		if (VehicleCreatedBy == RANDOM_VEHICLE && pDriver &&
			(GetStatus() == STATUS_SIMPLE || GetStatus() == STATUS_PHYSICS) &&
			AutoPilot.m_nCarMission == MISSION_CRUISE) {
			if (m_randomSeed < DAMAGE_FLEE_IN_CAR_PROBABILITY_VALUE) {
				CCarCtrl::SwitchVehicleToRealPhysics(this);
				AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS;
				AutoPilot.m_nCruiseSpeed = GAME_SPEED_TO_CARAI_SPEED * pHandling->Transmission.fMaxCruiseVelocity;
				SetStatus(STATUS_PHYSICS);
			}
		}
		m_nLastWeaponDamage = weaponType;
		float oldHealth = m_fHealth;
		if (m_fHealth > damage) {
			m_fHealth -= damage;
			if (VehicleCreatedBy == RANDOM_VEHICLE &&
				(m_fHealth < DAMAGE_HEALTH_TO_FLEE_ALWAYS ||
					bFrightensDriver && m_randomSeed > DAMAGE_FLEE_ON_FOOT_PROBABILITY_VALUE)) {
				switch (GetStatus()) {
				case STATUS_SIMPLE:
				case STATUS_PHYSICS:
					if (pDriver) {
						SetStatus(STATUS_ABANDONED);
						pDriver->bFleeAfterExitingCar = true;
						pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, this);
					}
					for (int i = 0; i < m_nNumMaxPassengers; i++) {
						if (pPassengers[i]) {
							pPassengers[i]->bFleeAfterExitingCar = true;
							pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, this);
						}
					}
					break;
				default:
					break;
				}
			}
			if (oldHealth >= DAMAGE_HEALTH_TO_CATCH_FIRE && m_fHealth < DAMAGE_HEALTH_TO_CATCH_FIRE) {
				if (IsCar()) {
					CAutomobile* pThisCar = (CAutomobile*)this;
					pThisCar->Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE);
					pThisCar->m_pSetOnFireEntity = damagedBy;
					if (damagedBy)
						damagedBy->RegisterReference((CEntity**)&pThisCar->m_pSetOnFireEntity);
				}
			}
		}
		else {
			m_fHealth = 0.0f;
			if (weaponType == WEAPONTYPE_EXPLOSION) {
				// between 1000 and 3047. Also not very nice: can't be saved by respray or cheat
				m_nBombTimer = 1000 + CGeneral::GetRandomNumber() & 0x7FF;
				m_pBlowUpEntity = damagedBy;
				if (damagedBy)
					damagedBy->RegisterReference((CEntity**)&m_pBlowUpEntity);
			}
			else
				BlowUpCar(damagedBy);
		}
	}
#ifdef FIX_BUGS // removing dumb case when shooting police car in player's own garage gives wanted level
	if (GetModelIndex() == MI_POLICE && damagedBy == FindPlayerPed() && damagedBy != nil && !bHasBeenOwnedByPlayer)
#else
	if (GetModelIndex() == MI_POLICE && damagedBy == FindPlayerPed())
#endif
		FindPlayerPed()->SetWantedLevelNoDrop(1);
}

void
CVehicle::DoFixedMachineGuns(void)
{
	if(CPad::GetPad(0)->GetCarGunFired() && !bGunSwitchedOff){
		if(CTimer::GetTimeInMilliseconds() > m_nGunFiringTime + 150){
			CVector source, target;
			float dx, dy, len;

			dx = GetForward().x;
			dy = GetForward().y;
			len = Sqrt(SQR(dx) + SQR(dy));
			if(len < 0.1f) len = 0.1f;
			dx /= len;
			dy /= len;

			m_nGunFiringTime = CTimer::GetTimeInMilliseconds();

			source = GetMatrix() * CVector(2.0f, 2.5f, 1.0f);
			target = source + CVector(dx, dy, 0.0f)*60.0f;
			target += CVector(
				((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f,
				((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f,
				((CGeneral::GetRandomNumber()&0xFF)-128) * 0.02f);
			CWeapon::DoTankDoomAiming(this, pDriver, &source, &target);
			FireOneInstantHitRound(&source, &target, 15);

			source = GetMatrix() * CVector(-2.0f, 2.5f, 1.0f);
			target = source + CVector(dx, dy, 0.0f)*60.0f;
			target += CVector(
				((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f,
				((CGeneral::GetRandomNumber()&0xFF)-128) * 0.015f,
				((CGeneral::GetRandomNumber()&0xFF)-128) * 0.02f);
			CWeapon::DoTankDoomAiming(this, pDriver, &source, &target);
			FireOneInstantHitRound(&source, &target, 15);

			DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f);

			m_nAmmoInClip--;
			if(m_nAmmoInClip == 0){
				m_nAmmoInClip = 20;
				m_nGunFiringTime = CTimer::GetTimeInMilliseconds() + 1400;
			}
		}
	}else{
		if(CTimer::GetTimeInMilliseconds() > m_nGunFiringTime + 1400)
			m_nAmmoInClip = 20;
	}
}

void
CVehicle::ExtinguishCarFire(void)
{
	m_fHealth = Max(m_fHealth, 300.0f);
	if(m_pCarFire)
		m_pCarFire->Extinguish();
	if(IsCar()){
		CAutomobile *car = (CAutomobile*)this;
		if(car->Damage.GetEngineStatus() >= ENGINE_STATUS_ON_FIRE)
			car->Damage.SetEngineStatus(ENGINE_STATUS_ON_FIRE-10);
		car->m_fFireBlowUpTimer = 0.0f;
	}
}

bool
CVehicle::ShufflePassengersToMakeSpace(void)
{
	if (m_nNumPassengers >= m_nNumMaxPassengers)
		return false;
	if (pPassengers[1] &&
		!(m_nGettingInFlags & CAR_DOOR_FLAG_LR) &&
		IsRoomForPedToLeaveCar(CAR_DOOR_LR, nil)) {
		if (!pPassengers[2] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RR)) {
			pPassengers[2] = pPassengers[1];
			pPassengers[1] = nil;
			pPassengers[2]->m_vehDoor = CAR_DOOR_RR;
			return true;
		}
		if (!pPassengers[0] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RF)) {
			pPassengers[0] = pPassengers[1];
			pPassengers[1] = nil;
			pPassengers[0]->m_vehDoor = CAR_DOOR_RF;
			return true;
		}
		return false;
	}
	if (pPassengers[2] &&
		!(m_nGettingInFlags & CAR_DOOR_FLAG_RR) &&
		IsRoomForPedToLeaveCar(CAR_DOOR_RR, nil)) {
		if (!pPassengers[1] && !(m_nGettingInFlags & CAR_DOOR_FLAG_LR)) {
			pPassengers[1] = pPassengers[2];
			pPassengers[2] = nil;
			pPassengers[1]->m_vehDoor = CAR_DOOR_LR;
			return true;
		}
		if (!pPassengers[0] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RF)) {
			pPassengers[0] = pPassengers[2];
			pPassengers[2] = nil;
			pPassengers[0]->m_vehDoor = CAR_DOOR_RF;
			return true;
		}
		return false;
	}
	if (pPassengers[0] &&
		!(m_nGettingInFlags & CAR_DOOR_FLAG_RF) &&
		IsRoomForPedToLeaveCar(CAR_DOOR_RF, nil)) {
		if (!pPassengers[1] && !(m_nGettingInFlags & CAR_DOOR_FLAG_LR)) {
			pPassengers[1] = pPassengers[0];
			pPassengers[0] = nil;
			pPassengers[1]->m_vehDoor = CAR_DOOR_LR;
			return true;
		}
		if (!pPassengers[2] && !(m_nGettingInFlags & CAR_DOOR_FLAG_RR)) {
			pPassengers[2] = pPassengers[0];
			pPassengers[0] = nil;
			pPassengers[2]->m_vehDoor = CAR_DOOR_RR;
			return true;
		}
		return false;
	}
	return false;
}

void
CVehicle::ProcessDelayedExplosion(void)
{
	if(m_nBombTimer == 0)
		return;

	int tick = CTimer::GetTimeStep()/60.0f*1000.0f;
	int16 prev = m_nBombTimer;
	if(tick > m_nBombTimer)
		m_nBombTimer = 0;
	else
		m_nBombTimer -= tick;

	if(IsCar() && ((CAutomobile*)this)->m_bombType == CARBOMB_TIMEDACTIVE && (m_nBombTimer & 0xFE00) != (prev & 0xFE00))
		DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_BOMB_TICK, 0.0f);

	if (m_nBombTimer == 0){
		if(FindPlayerVehicle() != this && m_pBlowUpEntity == FindPlayerPed())
			CWorld::Players[CWorld::PlayerInFocus].AwardMoneyForExplosion(this);
		BlowUpCar(m_pBlowUpEntity);
	}
}

bool
CVehicle::IsLawEnforcementVehicle(void)
{
	switch(GetModelIndex()){
	case MI_FBICAR:
	case MI_POLICE:
	case MI_ENFORCER:
	case MI_PREDATOR:
	case MI_RHINO:
	case MI_BARRACKS:
		return true;
	default:
		return false;
	}
}

bool
CVehicle::UsesSiren(uint32 id)
{
	switch(id){
	case MI_FIRETRUCK:
	case MI_AMBULAN:
	case MI_FBICAR:
	case MI_MRWHOOP:
	case MI_POLICE:
	case MI_ENFORCER:
	case MI_PREDATOR:
		return true;
	default:
		return false;
	}
}

bool
CVehicle::IsVehicleNormal(void)
{
	if (!pDriver || m_nNumPassengers != 0 || GetStatus() == STATUS_WRECKED)
		return false;
	switch (GetModelIndex()){
	case MI_FIRETRUCK:
	case MI_AMBULAN:
	case MI_TAXI:
	case MI_POLICE:
	case MI_ENFORCER:
	case MI_BUS:
	case MI_RHINO:
	case MI_BARRACKS:
	case MI_DODO:
	case MI_COACH:
	case MI_CABBIE:
	case MI_RCBANDIT:
	case MI_BORGNINE:
		return false;
	default:
		return true;
	}
}

bool
CVehicle::CarHasRoof(void)
{
	if((pHandling->Flags & HANDLING_HAS_NO_ROOF) == 0)
		return true;
	if(m_aExtras[0] && m_aExtras[1])
		return false;
	return true;
}

bool
CVehicle::IsUpsideDown(void)
{
	if(GetUp().z > -0.9f)
		return false;
	return true;
}

bool
CVehicle::IsOnItsSide(void)
{
	if(GetRight().z < 0.8f && GetRight().z > -0.8f)
		return false;
	return true;
}

bool
CVehicle::CanBeDeleted(void)
{
	int i;

	if(m_nNumGettingIn || m_nGettingOutFlags)
		return false;

	if(pDriver){
		// This looks like it was inlined
		if(pDriver->CharCreatedBy == MISSION_CHAR)
			return false;
		if(pDriver->GetPedState() != PED_DRIVING &&
		   pDriver->GetPedState() != PED_DEAD)
			return false;
	}

	for(i = 0; i < ARRAY_SIZE(pPassengers); i++){
		// Same check as above
		if(pPassengers[i]){
			if(pPassengers[i]->CharCreatedBy == MISSION_CHAR)
				return false;
			if(pPassengers[i]->GetPedState() != PED_DRIVING &&
			   pPassengers[i]->GetPedState() != PED_DEAD)
				return false;
		}
		// and then again... probably because something was inlined
		if(pPassengers[i]){
			if(pPassengers[i]->GetPedState() != PED_DRIVING &&
			   pPassengers[i]->GetPedState() != PED_DEAD)
				return false;
		}
	}

	switch(VehicleCreatedBy){
	case RANDOM_VEHICLE: return true;
	case MISSION_VEHICLE: return false;
	case PARKED_VEHICLE: return true;
	case PERMANENT_VEHICLE: return false;
	}
	return true;
}

bool
CVehicle::CanPedOpenLocks(CPed *ped)
{
	if(m_nDoorLock == CARLOCK_LOCKED ||
	   m_nDoorLock == CARLOCK_LOCKED_INITIALLY ||
	   m_nDoorLock == CARLOCK_LOCKED_PLAYER_INSIDE)
		return false;
	if(ped->IsPlayer() && m_nDoorLock == CARLOCK_LOCKOUT_PLAYER_ONLY)
		return false;
	return true;
}

bool
CVehicle::CanPedEnterCar(void)
{
	// can't enter when car is on side
	if(GetUp().z > 0.1f || GetUp().z < -0.1f){
		// also when car is moving too fast
		if(m_vecMoveSpeed.MagnitudeSqr() > sq(0.2f))
			return false;
		if(m_vecTurnSpeed.MagnitudeSqr() > sq(0.2f))
			return false;
		return true;
	}
	return false;
}

bool
CVehicle::CanPedExitCar(void)
{
	CVector up = GetUp();
	if(up.z > 0.1f || up.z < -0.1f){
#ifdef VC_PED_PORTS
		if (IsBoat())
			return true;
#endif
		// can't exit when car is moving too fast
		if(m_vecMoveSpeed.MagnitudeSqr() > 0.005f)
			return false;
		// if car is slow enough, check turn speed
		if(Abs(m_vecTurnSpeed.x) > 0.01f ||
		   Abs(m_vecTurnSpeed.y) > 0.01f ||
		   Abs(m_vecTurnSpeed.z) > 0.01f)
			return false;
		return true;
	}else{
		// What is this? just > replaced by >= ??

		// can't exit when car is moving too fast
		if(m_vecMoveSpeed.MagnitudeSqr() >= 0.005f)
			return false;
		// if car is slow enough, check turn speed
		if(Abs(m_vecTurnSpeed.x) >= 0.01f ||
		   Abs(m_vecTurnSpeed.y) >= 0.01f ||
		   Abs(m_vecTurnSpeed.z) >= 0.01f)
			return false;
		return true;
	}
}

void
CVehicle::ChangeLawEnforcerState(uint8 enable)
{
	if (enable) {
		if (!bIsLawEnforcer) {
			bIsLawEnforcer = true;
			CCarCtrl::NumLawEnforcerCars++;
		}
	} else {
		if (bIsLawEnforcer) {
			bIsLawEnforcer = false;
			CCarCtrl::NumLawEnforcerCars--;
		}
	}
}

CPed*
CVehicle::SetUpDriver(void)
{
	if(pDriver)
		return pDriver;
	if(VehicleCreatedBy != RANDOM_VEHICLE)
		return nil;

	pDriver = CPopulation::AddPedInCar(this);
	pDriver->m_pMyVehicle = this;
	pDriver->m_pMyVehicle->RegisterReference((CEntity**)&pDriver->m_pMyVehicle);
	pDriver->bInVehicle = true;
	pDriver->SetPedState(PED_DRIVING);
	if(bIsBus)
		pDriver->bRenderPedInCar = false;
	return pDriver;
}

CPed*
CVehicle::SetupPassenger(int n)
{
	if(pPassengers[n])
		return pPassengers[n];

	pPassengers[n] = CPopulation::AddPedInCar(this);
	pPassengers[n]->m_pMyVehicle = this;
	pPassengers[n]->m_pMyVehicle->RegisterReference((CEntity**)&pPassengers[n]->m_pMyVehicle);
	pPassengers[n]->bInVehicle = true;
	pPassengers[n]->SetPedState(PED_DRIVING);
	if(bIsBus)
		pPassengers[n]->bRenderPedInCar = false;
	++m_nNumPassengers;
	return pPassengers[n];
}

void
CVehicle::SetDriver(CPed *driver)
{
	pDriver = driver;
	pDriver->RegisterReference((CEntity**)&pDriver);

	if(bFreebies && driver == FindPlayerPed()){
		if(GetModelIndex() == MI_AMBULAN)
			FindPlayerPed()->m_fHealth = Min(FindPlayerPed()->m_fHealth + 20.0f, 100.0f);
		else if(GetModelIndex() == MI_TAXI)
			CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 25;
		else if(GetModelIndex() == MI_POLICE)
			driver->GiveWeapon(WEAPONTYPE_SHOTGUN, 5);
		else if(GetModelIndex() == MI_ENFORCER)
			driver->m_fArmour = Max(driver->m_fArmour, 100.0f);
		else if(GetModelIndex() == MI_CABBIE || GetModelIndex() == MI_BORGNINE)
			CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 25;
		bFreebies = false;
	}

	ApplyTurnForce(0.0f, 0.0f, -0.2f*driver->m_fMass,
		driver->GetPosition().x - GetPosition().x,
		driver->GetPosition().y - GetPosition().y,
		0.0f);
}

bool
CVehicle::AddPassenger(CPed *passenger)
{
	int i;

	ApplyTurnForce(0.0f, 0.0f, -0.2f*passenger->m_fMass,
		passenger->GetPosition().x - GetPosition().x,
		passenger->GetPosition().y - GetPosition().y,
		0.0f);

	for(i = 0; i < m_nNumMaxPassengers; i++)
		if(pPassengers[i] == nil){
			pPassengers[i] = passenger;
			m_nNumPassengers++;
			return true;
		}
	return false;
}

bool
CVehicle::AddPassenger(CPed *passenger, uint8 n)
{
	if(bIsBus)
		return AddPassenger(passenger);

	ApplyTurnForce(0.0f, 0.0f, -0.2f*passenger->m_fMass,
		passenger->GetPosition().x - GetPosition().x,
		passenger->GetPosition().y - GetPosition().y,
		0.0f);

	if(n < m_nNumMaxPassengers && pPassengers[n] == nil){
		pPassengers[n] = passenger;
		m_nNumPassengers++;
		return true;
	}
	return false;
}

void
CVehicle::RemoveDriver(void)
{
#ifdef FIX_BUGS
	if (GetStatus() != STATUS_WRECKED)
#endif
		SetStatus(STATUS_ABANDONED);
	pDriver = nil;
}

void
CVehicle::RemovePassenger(CPed *p)
{
	if (IsTrain()){
		for (int i = 0; i < ARRAY_SIZE(pPassengers); i++){
			if (pPassengers[i] == p) {
				pPassengers[i] = nil;
				m_nNumPassengers--;
				return;
			}
		}
		return;
	}
	for (int i = 0; i < m_nNumMaxPassengers; i++){
		if (pPassengers[i] == p){
			pPassengers[i] = nil;
			m_nNumPassengers--;
			return;
		}
	}
}

void
CVehicle::ProcessCarAlarm(void)
{
	uint32 step;

	if(m_nAlarmState == 0 || m_nAlarmState == -1)
		return;

	step = CTimer::GetTimeStepInMilliseconds();
	if((uint16)m_nAlarmState < step)
		m_nAlarmState = 0;
	else
		m_nAlarmState -= step;
}

bool
CVehicle::IsSphereTouchingVehicle(float sx, float sy, float sz, float radius)
{
	float x, y, z;
	// sphere relative to vehicle
	CVector sph = CVector(sx, sy, sz) - GetPosition();
	CColModel *colmodel = GetColModel();

	x = DotProduct(sph, GetRight());
	if(colmodel->boundingBox.min.x - radius > x ||
	   colmodel->boundingBox.max.x + radius < x)
		return false;
	y = DotProduct(sph, GetForward());
	if(colmodel->boundingBox.min.y - radius > y ||
	   colmodel->boundingBox.max.y + radius < y)
		return false;
	z = DotProduct(sph, GetUp());
	if(colmodel->boundingBox.min.z - radius > z ||
	   colmodel->boundingBox.max.z + radius < z)
		return false;

	return true;
}

void
DestroyVehicleAndDriverAndPassengers(CVehicle* pVehicle)
{
	if (pVehicle->pDriver) {
		CDarkel::RegisterKillByPlayer(pVehicle->pDriver, WEAPONTYPE_UNIDENTIFIED);
		pVehicle->pDriver->FlagToDestroyWhenNextProcessed();
	}
	for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) {
		if (pVehicle->pPassengers[i]) {
			CDarkel::RegisterKillByPlayer(pVehicle->pPassengers[i], WEAPONTYPE_UNIDENTIFIED);
			pVehicle->pPassengers[i]->FlagToDestroyWhenNextProcessed();
		}
	}
	CWorld::Remove(pVehicle);
	delete pVehicle;
}

#ifdef COMPATIBLE_SAVES
void
CVehicle::Save(uint8*& buf)
{
	ZeroSaveBuf(buf, 4);
	WriteSaveBuf(buf, GetRight().x);
	WriteSaveBuf(buf, GetRight().y);
	WriteSaveBuf(buf, GetRight().z);
	ZeroSaveBuf(buf, 4);
	WriteSaveBuf(buf, GetForward().x);
	WriteSaveBuf(buf, GetForward().y);
	WriteSaveBuf(buf, GetForward().z);
	ZeroSaveBuf(buf, 4);
	WriteSaveBuf(buf, GetUp().x);
	WriteSaveBuf(buf, GetUp().y);
	WriteSaveBuf(buf, GetUp().z);
	ZeroSaveBuf(buf, 4);
	WriteSaveBuf(buf, GetPosition().x);
	WriteSaveBuf(buf, GetPosition().y);
	WriteSaveBuf(buf, GetPosition().z);
	ZeroSaveBuf(buf, 16);
	SaveEntityFlags(buf);
	ZeroSaveBuf(buf, 212);
	AutoPilot.Save(buf);
	WriteSaveBuf(buf, m_currentColour1);
	WriteSaveBuf(buf, m_currentColour2);
	ZeroSaveBuf(buf, 2);
	WriteSaveBuf(buf, m_nAlarmState);
	ZeroSaveBuf(buf, 43);
	WriteSaveBuf(buf, m_nNumMaxPassengers);
	ZeroSaveBuf(buf, 2);
	WriteSaveBuf(buf, field_1D0[0]);
	WriteSaveBuf(buf, field_1D0[1]);
	WriteSaveBuf(buf, field_1D0[2]);
	WriteSaveBuf(buf, field_1D0[3]);
	ZeroSaveBuf(buf, 8);
	WriteSaveBuf(buf, m_fSteerAngle);
	WriteSaveBuf(buf, m_fGasPedal);
	WriteSaveBuf(buf, m_fBrakePedal);
	WriteSaveBuf(buf, VehicleCreatedBy);
	uint8 flags = 0;
	if (bIsLawEnforcer) flags |= BIT(0);
	if (bIsLocked) flags |= BIT(3);
	if (bEngineOn) flags |= BIT(4);
	if (bIsHandbrakeOn) flags |= BIT(5);
	if (bLightsOn) flags |= BIT(6);
	if (bFreebies) flags |= BIT(7);
	WriteSaveBuf(buf, flags);
	ZeroSaveBuf(buf, 10);
	WriteSaveBuf(buf, m_fHealth);
	WriteSaveBuf(buf, m_nCurrentGear);
	ZeroSaveBuf(buf, 3);
	WriteSaveBuf(buf, m_fChangeGearTime);
	ZeroSaveBuf(buf, 4);
	WriteSaveBuf(buf, m_nTimeOfDeath);
	ZeroSaveBuf(buf, 2);
	WriteSaveBuf(buf, m_nBombTimer);
	ZeroSaveBuf(buf, 12);
	WriteSaveBuf(buf, m_nDoorLock);
	ZeroSaveBuf(buf, 96);
}

void
CVehicle::Load(uint8*& buf)
{
	CMatrix tmp;
	SkipSaveBuf(buf, 4);
	ReadSaveBuf(&tmp.GetRight().x, buf);
	ReadSaveBuf(&tmp.GetRight().y, buf);
	ReadSaveBuf(&tmp.GetRight().z, buf);
	SkipSaveBuf(buf, 4);
	ReadSaveBuf(&tmp.GetForward().x, buf);
	ReadSaveBuf(&tmp.GetForward().y, buf);
	ReadSaveBuf(&tmp.GetForward().z, buf);
	SkipSaveBuf(buf, 4);
	ReadSaveBuf(&tmp.GetUp().x, buf);
	ReadSaveBuf(&tmp.GetUp().y, buf);
	ReadSaveBuf(&tmp.GetUp().z, buf);
	SkipSaveBuf(buf, 4);
	ReadSaveBuf(&tmp.GetPosition().x, buf);
	ReadSaveBuf(&tmp.GetPosition().y, buf);
	ReadSaveBuf(&tmp.GetPosition().z, buf);
	m_matrix = tmp;
	SkipSaveBuf(buf, 16);
	LoadEntityFlags(buf);
	SkipSaveBuf(buf, 212);
	AutoPilot.Load(buf);
	ReadSaveBuf(&m_currentColour1, buf);
	ReadSaveBuf(&m_currentColour2, buf);
	SkipSaveBuf(buf, 2);
	ReadSaveBuf(&m_nAlarmState, buf);
	SkipSaveBuf(buf, 43);
	ReadSaveBuf(&m_nNumMaxPassengers, buf);
	SkipSaveBuf(buf, 2);
	ReadSaveBuf(&field_1D0[0], buf);
	ReadSaveBuf(&field_1D0[1], buf);
	ReadSaveBuf(&field_1D0[2], buf);
	ReadSaveBuf(&field_1D0[3], buf);
	SkipSaveBuf(buf, 8);
	ReadSaveBuf(&m_fSteerAngle, buf);
	ReadSaveBuf(&m_fGasPedal, buf);
	ReadSaveBuf(&m_fBrakePedal, buf);
	ReadSaveBuf(&VehicleCreatedBy, buf);
	uint8 flags;
	ReadSaveBuf(&flags, buf);
	bIsLawEnforcer = !!(flags & BIT(0));
	bIsLocked = !!(flags & BIT(3));
	bEngineOn = !!(flags & BIT(4));
	bIsHandbrakeOn = !!(flags & BIT(5));
	bLightsOn = !!(flags & BIT(6));
	bFreebies = !!(flags & BIT(7));
	SkipSaveBuf(buf, 10);
	ReadSaveBuf(&m_fHealth, buf);
	ReadSaveBuf(&m_nCurrentGear, buf);
	SkipSaveBuf(buf, 3);
	ReadSaveBuf(&m_fChangeGearTime, buf);
	SkipSaveBuf(buf, 4);
	ReadSaveBuf(&m_nTimeOfDeath, buf);
	SkipSaveBuf(buf, 2);
	ReadSaveBuf(&m_nBombTimer, buf);
	SkipSaveBuf(buf, 12);
	ReadSaveBuf(&m_nDoorLock, buf);
	SkipSaveBuf(buf, 96);
}
#endif


code:
#pragma once

#include "Physical.h"
#include "AutoPilot.h"
#include "ModelIndices.h"
#include "AnimationId.h"
#include "WeaponType.h"
#include "Collision.h"

class CPed;
class CFire;
struct tHandlingData;

enum {
	RANDOM_VEHICLE = 1,
	MISSION_VEHICLE = 2,
	PARKED_VEHICLE = 3,
	PERMANENT_VEHICLE = 4,
};

enum eCarLock {
	CARLOCK_NOT_USED,
	CARLOCK_UNLOCKED,
	CARLOCK_LOCKED,
	CARLOCK_LOCKOUT_PLAYER_ONLY,
	CARLOCK_LOCKED_PLAYER_INSIDE,
	CARLOCK_LOCKED_INITIALLY,
	CARLOCK_FORCE_SHUT_DOORS
};

enum eDoors
{
	DOOR_BONNET = 0,
	DOOR_BOOT,
	DOOR_FRONT_LEFT,
	DOOR_FRONT_RIGHT,
	DOOR_REAR_LEFT,
	DOOR_REAR_RIGHT
};

enum ePanels
{
	VEHPANEL_FRONT_LEFT,
	VEHPANEL_FRONT_RIGHT,
	VEHPANEL_REAR_LEFT,
	VEHPANEL_REAR_RIGHT,
	VEHPANEL_WINDSCREEN,
	VEHBUMPER_FRONT,
	VEHBUMPER_REAR,
};

enum eLights
{
	VEHLIGHT_FRONT_LEFT,
	VEHLIGHT_FRONT_RIGHT,
	VEHLIGHT_REAR_LEFT,
	VEHLIGHT_REAR_RIGHT,
};

enum
{
	CAR_PIECE_BONNET = 1,
	CAR_PIECE_BOOT,
	CAR_PIECE_BUMP_FRONT,
	CAR_PIECE_BUMP_REAR,
	CAR_PIECE_DOOR_LF,
	CAR_PIECE_DOOR_RF,
	CAR_PIECE_DOOR_LR,
	CAR_PIECE_DOOR_RR,
	CAR_PIECE_WING_LF,
	CAR_PIECE_WING_RF,
	CAR_PIECE_WING_LR,
	CAR_PIECE_WING_RR,
	CAR_PIECE_WHEEL_LF,
	CAR_PIECE_WHEEL_RF,
	CAR_PIECE_WHEEL_LR,
	CAR_PIECE_WHEEL_RR,
	CAR_PIECE_WINDSCREEN,
};

enum tWheelState
{
	WHEEL_STATE_NORMAL,	// standing still or rolling normally
	WHEEL_STATE_SPINNING,	// rotating but not moving
	WHEEL_STATE_SKIDDING,
	WHEEL_STATE_FIXED,	// not rotating
};

enum eFlightModel
{
	FLIGHT_MODEL_DODO,
	// not used in III
	FLIGHT_MODEL_RCPLANE,
	FLIGHT_MODEL_HELI,
	FLIGHT_MODEL_SEAPLANE
};

// TODO: what is this even?
enum eBikeWheelSpecial {
	BIKE_WHEELSPEC_0, // both wheels on ground
	BIKE_WHEELSPEC_1, // rear wheel on ground
	BIKE_WHEELSPEC_2, // only front wheel on ground
	BIKE_WHEELSPEC_3, // can't happen
};

class CVehicle : public CPhysical
{
public:
	// 0x128
	tHandlingData *pHandling;
	CAutoPilot AutoPilot;
	uint8 m_currentColour1;
	uint8 m_currentColour2;
	int8 m_aExtras[2];
	int16 m_nAlarmState;
	int16 m_nMissionValue;
	CPed *pDriver;
	CPed *pPassengers[8];
	uint8 m_nNumPassengers;
	int8 m_nNumGettingIn;
	int8 m_nGettingInFlags;
	int8 m_nGettingOutFlags;
	uint8 m_nNumMaxPassengers;
	float field_1D0[4];
	CEntity *m_pCurGroundEntity;
	CFire *m_pCarFire;
	float m_fSteerAngle;
	float m_fGasPedal;
	float m_fBrakePedal;
	uint8 VehicleCreatedBy;

	// cf. https://github.com/DK22Pac/plugin-sdk/blob/master/plugin_sa/game_sa/CVehicle.h from R*
	uint8 bIsLawEnforcer: 1; // Is this guy chasing the player at the moment
	uint8 bIsAmbulanceOnDuty: 1; // Ambulance trying to get to an accident
	uint8 bIsFireTruckOnDuty: 1; // Firetruck trying to get to a fire
	uint8 bIsLocked: 1; // Is this guy locked by the script (cannot be removed)
	uint8 bEngineOn: 1; // For sound purposes. Parked cars have their engines switched off (so do destroyed cars)
	uint8 bIsHandbrakeOn: 1; // How's the handbrake doing ?
	uint8 bLightsOn: 1; // Are the lights switched on ?
	uint8 bFreebies: 1; // Any freebies left in this vehicle ?

	uint8 bIsVan: 1; // Is this vehicle a van (doors at back of vehicle)
	uint8 bIsBus: 1; // Is this vehicle a bus
	uint8 bIsBig: 1; // Is this vehicle a bus
	uint8 bLowVehicle: 1; // Need this for sporty type cars to use low getting-in/out anims
	uint8 bComedyControls : 1; // Will make the car hard to control (hopefully in a funny way)
	uint8 bWarnedPeds : 1; // Has scan and warn peds of danger been processed?
	uint8 bCraneMessageDone : 1; // A crane message has been printed for this car allready
	uint8 bExtendedRange : 1; // This vehicle needs to be a bit further away to get deleted

	uint8 bTakeLessDamage : 1; // This vehicle is stronger (takes about 1/4 of damage)
	uint8 bIsDamaged : 1; // This vehicle has been damaged and is displaying all its components
	uint8 bHasBeenOwnedByPlayer : 1;// To work out whether stealing it is a crime
	uint8 bFadeOut : 1; // Fade vehicle out
	uint8 bIsBeingCarJacked : 1; // Fade vehicle out
	uint8 bCreateRoadBlockPeds : 1; // If this vehicle gets close enough we will create peds (coppers or gang members) round it
	uint8 bCanBeDamaged : 1; // Set to FALSE during cut scenes to avoid explosions
	uint8 bUsingSpecialColModel : 1;// Is player vehicle using special collision model, stored in player strucure

	uint8 bOccupantsHaveBeenGenerated : 1;  // Is true if the occupants have already been generated. (Shouldn't happen again)
	uint8 bGunSwitchedOff : 1;  // Level designers can use this to switch off guns on boats
	uint8 bVehicleColProcessed : 1;// Has ProcessEntityCollision been processed for this car?
	uint8 bIsCarParkVehicle : 1; // Car has been created using the special CAR_PARK script command
	uint8 bHasAlreadyBeenRecorded : 1; // Used for replays

	int8 m_numPedsUseItAsCover;
	uint8 m_nAmmoInClip;    // Used to make the guns on boat do a reload (20 by default)
	int8 m_nPacManPickupsCarried;
	uint8 m_nRoadblockType;
	int16 m_nRoadblockNode;
	float m_fHealth;           // 1000.0f = full health. 250.0f = fire. 0 -> explode
	uint8 m_nCurrentGear;
	float m_fChangeGearTime;
	uint32 m_nGunFiringTime;    // last time when gun on vehicle was fired (used on boats)
	uint32 m_nTimeOfDeath;
	uint16 m_nTimeBlocked;
	int16 m_nBombTimer;        // goes down with each frame
	CEntity *m_pBlowUpEntity;
	float m_fMapObjectHeightAhead;	// front Z?
	float m_fMapObjectHeightBehind;	// rear Z?
	eCarLock m_nDoorLock;
	int8 m_nLastWeaponDamage; // see eWeaponType, -1 if no damage
	uint8 m_nRadioStation;
	uint8 m_bRainAudioCounter;
	uint8 m_bRainSamplesCounter;
	uint8 m_nCarHornTimer;
	uint8 m_nCarHornPattern; // last horn?
	bool m_bSirenOrAlarm;
	int8 m_comedyControlState;
	CStoredCollPoly m_aCollPolys[2];     // poly which is under front/rear part of car
	float m_fSteerInput;
	eVehicleType m_vehType;

	static void *operator new(size_t) throw();
	static void *operator new(size_t sz, int slot) throw();
	static void operator delete(void*, size_t) throw();
	static void operator delete(void*, int) throw();

	CVehicle(void) {}	// FAKE
	CVehicle(uint8 CreatedBy);
	~CVehicle(void);
	// from CEntity
	void SetModelIndex(uint32 id);
	bool SetupLighting(void);
	void RemoveLighting(bool);
	void FlagToDestroyWhenNextProcessed(void) {}

	virtual void ProcessControlInputs(uint8) {}
	virtual void GetComponentWorldPosition(int32 component, CVector &pos) {}
	virtual bool IsComponentPresent(int32 component) { return false; }
	virtual void SetComponentRotation(int32 component, CVector rotation) {}
	virtual void OpenDoor(int32, eDoors door, float) {}
	virtual void ProcessOpenDoor(uint32, uint32, float) {}
	virtual bool IsDoorReady(eDoors door) { return false; }
	virtual bool IsDoorFullyOpen(eDoors door) { return false; }
	virtual bool IsDoorClosed(eDoors door) { return false; }
	virtual bool IsDoorMissing(eDoors door) { return false; }
	virtual void RemoveRefsToVehicle(CEntity *ent) {}
	virtual void BlowUpCar(CEntity *ent) {}
	virtual bool SetUpWheelColModel(CColModel *colModel) { return false; }
	virtual void BurstTyre(uint8 tyre) {}
	virtual bool IsRoomForPedToLeaveCar(uint32 component, CVector *forcedDoorPos) { return false;}
	virtual float GetHeightAboveRoad(void);
	virtual void PlayCarHorn(void) {}
#ifdef COMPATIBLE_SAVES
	virtual void Save(uint8*& buf);
	virtual void Load(uint8*& buf);
#endif

	bool IsCar(void) { return m_vehType == VEHICLE_TYPE_CAR; }
	bool IsBoat(void) { return m_vehType == VEHICLE_TYPE_BOAT; }
	bool IsTrain(void) { return m_vehType == VEHICLE_TYPE_TRAIN; }
	bool IsHeli(void) { return m_vehType == VEHICLE_TYPE_HELI; }
	bool IsPlane(void) { return m_vehType == VEHICLE_TYPE_PLANE; }
	bool IsBike(void) { return m_vehType == VEHICLE_TYPE_BIKE; }

	void FlyingControl(eFlightModel flightModel);
	void ProcessWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint,
		int32 wheelsOnGround, float thrust, float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, uint16 wheelStatus);
	void ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, int32 wheelsOnGround, float thrust,
	                      float brake, float adhesion, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, eBikeWheelSpecial special, uint16 wheelStatus);
	void ExtinguishCarFire(void);
	void ProcessDelayedExplosion(void);
	float ProcessWheelRotation(tWheelState state, const CVector &fwd, const CVector &speed, float radius);
	bool IsLawEnforcementVehicle(void);
	void ChangeLawEnforcerState(uint8 enable);
	bool UsesSiren(uint32 id);
	bool IsVehicleNormal(void);
	bool CarHasRoof(void);
	bool IsUpsideDown(void);
	bool IsOnItsSide(void);
	bool CanBeDeleted(void);
	bool CanPedOpenLocks(CPed *ped);
	bool CanPedEnterCar(void);
	bool CanPedExitCar(void);
	// do these two actually return something?
	CPed *SetUpDriver(void);
	CPed *SetupPassenger(int n);
	void SetDriver(CPed *driver);
	bool AddPassenger(CPed *passenger);
	bool AddPassenger(CPed *passenger, uint8 n);
	void RemovePassenger(CPed *passenger);
	void RemoveDriver(void);
	void ProcessCarAlarm(void);
	bool IsSphereTouchingVehicle(float sx, float sy, float sz, float radius);
	bool ShufflePassengersToMakeSpace(void);
	void InflictDamage(CEntity *damagedBy, eWeaponType weaponType, float damage);
	void DoFixedMachineGuns(void);

#ifdef FIX_BUGS
	bool IsAlarmOn(void) { return m_nAlarmState != 0 && m_nAlarmState != -1 && GetStatus() != STATUS_WRECKED; }
#else
	bool IsAlarmOn(void) { return m_nAlarmState != 0 && m_nAlarmState != -1; }
#endif
	CVehicleModelInfo* GetModelInfo() { return (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); }
	bool IsTaxi(void) { return GetModelIndex() == MI_TAXI || GetModelIndex() == MI_CABBIE || GetModelIndex() == MI_BORGNINE; }
	AnimationId GetDriverAnim(void) { return IsCar() && bLowVehicle ? ANIM_STD_CAR_SIT_LO : (IsBoat() && GetModelIndex() != MI_SPEEDER ? ANIM_STD_BOAT_DRIVE : ANIM_STD_CAR_SIT); }

	static bool bWheelsOnlyCheat;
	static bool bAllDodosCheat;
	static bool bCheat3;
	static bool bCheat4;
	static bool bCheat5;
#ifdef ALT_DODO_CHEAT
	static bool bAltDodoCheat;
#endif
	static bool m_bDisableMouseSteering;
};

VALIDATE_SIZE(CVehicle, 0x288);

void DestroyVehicleAndDriverAndPassengers(CVehicle* pVehicle);


code:
#include "common.h"

#include "BulletInfo.h"

#include "AnimBlendAssociation.h"
#include "DMAudio.h"
#include "AudioScriptObject.h"
#ifdef FIX_BUGS
#include "Collision.h"
#endif
#include "RpAnimBlend.h"
#include "Entity.h"
#include "EventList.h"
#include "Fire.h"
#include "Glass.h"
#include "Particle.h"
#include "Ped.h"
#include "Object.h"
#include "Stats.h"
#include "Timer.h"
#include "Vehicle.h"
#include "Weapon.h"
#include "WeaponInfo.h"
#include "World.h"
#include "SurfaceTable.h"

#ifdef SQUEEZE_PERFORMANCE
uint32 bulletInfoInUse;
#endif

#define BULLET_LIFETIME (1000)
#define NUM_PED_BLOOD_PARTICLES (8)
#define BLOOD_PARTICLE_OFFSET (CVector(0.0f, 0.0f, 0.0f))
#define NUM_VEHICLE_SPARKS (16)
#define NUM_OTHER_SPARKS (8)
#define BULLET_HIT_FORCE (7.5f)
#define MAP_BORDER (1960.0f)

CBulletInfo gaBulletInfo[CBulletInfo::NUM_BULLETS];
bool bPlayerSniperBullet;
CVector PlayerSniperBulletStart;
CVector PlayerSniperBulletEnd;

void CBulletInfo::Initialise(void)
{
	debug("Initialising CBulletInfo...\n");
	for (int i = 0; i < NUM_BULLETS; i++) {
		gaBulletInfo[i].m_bInUse = false;
		gaBulletInfo[i].m_eWeaponType = WEAPONTYPE_COLT45;
		gaBulletInfo[i].m_fTimer = 0.0f;
		gaBulletInfo[i].m_pSource = nil;
	}
	debug("CBulletInfo ready\n");
#ifdef SQUEEZE_PERFORMANCE
	bulletInfoInUse = 0;
#endif
}

void CBulletInfo::Shutdown(void)
{
	debug("Shutting down CBulletInfo...\n");
	debug("CBulletInfo shut down\n");
}

bool CBulletInfo::AddBullet(CEntity* pSource, eWeaponType type, CVector vecPosition, CVector vecSpeed)
{
	int i;
	for (i = 0; i < NUM_BULLETS; i++) {
		if (!gaBulletInfo[i].m_bInUse)
			break;
	}
	if (i == NUM_BULLETS)
		return false;
	gaBulletInfo[i].m_pSource = pSource;
	gaBulletInfo[i].m_eWeaponType = type;
	gaBulletInfo[i].m_nDamage = CWeaponInfo::GetWeaponInfo(type)->m_nDamage;
	gaBulletInfo[i].m_vecPosition = vecPosition;
	gaBulletInfo[i].m_vecSpeed = vecSpeed;
	gaBulletInfo[i].m_fTimer = CTimer::GetTimeInMilliseconds() + BULLET_LIFETIME;
	gaBulletInfo[i].m_bInUse = true;

#ifdef SQUEEZE_PERFORMANCE
	bulletInfoInUse++;
#endif
	return true;
}

void CBulletInfo::Update(void)
{
#ifdef SQUEEZE_PERFORMANCE
	if (bulletInfoInUse == 0)
		return;
#endif
	bool bAddSound = true;
	bPlayerSniperBullet = false;
	for (int i = 0; i < NUM_BULLETS; i++) {
		CBulletInfo* pBullet = &gaBulletInfo[i];
		if (pBullet->m_pSource && pBullet->m_pSource->IsPed() && !((CPed*)pBullet->m_pSource)->IsPointerValid())
			pBullet->m_pSource = nil;
		if (!pBullet->m_bInUse)
			continue;
		if (CTimer::GetTimeInMilliseconds() > pBullet->m_fTimer) {
			pBullet->m_bInUse = false;
#ifdef SQUEEZE_PERFORMANCE
			bulletInfoInUse--;
#endif
		}
		CVector vecOldPos = pBullet->m_vecPosition;
		CVector vecNewPos = pBullet->m_vecPosition + pBullet->m_vecSpeed * CTimer::GetTimeStep() * 0.5f;
		CWorld::bIncludeCarTyres = true;
		CWorld::bIncludeDeadPeds = true;
		CWorld::pIgnoreEntity = pBullet->m_pSource;
		CColPoint point;
		CEntity* pHitEntity;
		if (CWorld::ProcessLineOfSight(vecOldPos, vecNewPos, point, pHitEntity, true, true, true, true, true, true)) {
			if (pBullet->m_pSource && (pHitEntity->IsPed() || pHitEntity->IsVehicle()))
				CStats::InstantHitsHitByPlayer++;
			if (pHitEntity->IsPed()) {
				CPed* pPed = (CPed*)pHitEntity;
				if (!pPed->DyingOrDead() && pPed != pBullet->m_pSource) {
					if (pPed->DoesLOSBulletHitPed(point)) {
						if (pPed->IsPedInControl() && !pPed->bIsDucking) {
							pPed->ClearAttackByRemovingAnim();
							CAnimBlendAssociation* pAnim = CAnimManager::AddAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HITBYGUN_FRONT);
							pAnim->SetBlend(0.0f, 8.0f);
						}
						pPed->InflictDamage(pBullet->m_pSource, pBullet->m_eWeaponType, pBullet->m_nDamage, (ePedPieceTypes)point.pieceB, pPed->GetLocalDirection(pPed->GetPosition() - point.point));
						CEventList::RegisterEvent(pPed->m_nPedType == PEDTYPE_COP ? EVENT_SHOOT_COP : EVENT_SHOOT_PED, EVENT_ENTITY_PED, pPed, (CPed*)pBullet->m_pSource, 1000);
						pBullet->m_bInUse = false;
#ifdef SQUEEZE_PERFORMANCE
						bulletInfoInUse--;
#endif
						vecNewPos = point.point;
					}
					else {
						bAddSound = false;
					}
				}
				if (CGame::nastyGame) {
					CVector vecParticleDirection = (point.point - pPed->GetPosition()) * 0.01f;
					vecParticleDirection.z = 0.01f;
					if (pPed->GetIsOnScreen()) {
						for (int j = 0; j < NUM_PED_BLOOD_PARTICLES; j++)
							CParticle::AddParticle(PARTICLE_BLOOD_SMALL, point.point + BLOOD_PARTICLE_OFFSET, vecParticleDirection);
					}
					if (pPed->GetPedState() == PED_DEAD) {
						CAnimBlendAssociation* pAnim;
						if (RpAnimBlendClumpGetFirstAssociation(pPed->GetClump(), ASSOC_FRONTAL))
							pAnim = CAnimManager::BlendAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR_FRONT, 8.0f);
						else
							pAnim = CAnimManager::BlendAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR, 8.0f);
						if (pAnim) {
							pAnim->SetCurrentTime(0.0f);
							pAnim->flags |= ASSOC_RUNNING;
							pAnim->flags &= ~ASSOC_FADEOUTWHENDONE;
						}
					}
					pBullet->m_bInUse = false;
#ifdef SQUEEZE_PERFORMANCE
					bulletInfoInUse--;
#endif
					vecNewPos = point.point;
				}
			}
			else if (pHitEntity->IsVehicle()) {
				CVehicle* pVehicle = (CVehicle*)pHitEntity;
				pVehicle->InflictDamage(pBullet->m_pSource, pBullet->m_eWeaponType, pBullet->m_nDamage);
				if (pBullet->m_eWeaponType == WEAPONTYPE_FLAMETHROWER) // huh?
					gFireManager.StartFire(pVehicle, pBullet->m_pSource, 0.8f, true);
				else {
					for (int j = 0; j < NUM_VEHICLE_SPARKS; j++)
						CParticle::AddParticle(PARTICLE_SPARK, point.point, point.normal / 20);
				}
#ifdef FIX_BUGS
				pBullet->m_bInUse = false;
#ifdef SQUEEZE_PERFORMANCE
				bulletInfoInUse--;
#endif
				vecNewPos = point.point;
#endif
			}
			else {
				for (int j = 0; j < NUM_OTHER_SPARKS; j++)
					CParticle::AddParticle(PARTICLE_SPARK, point.point, point.normal / 20);
				if (pHitEntity->IsObject()) {
					CObject* pObject = (CObject*)pHitEntity;
					if (!pObject->bInfiniteMass) {
						if (pObject->GetIsStatic() && pObject->m_fUprootLimit <= 0.0f) {
							pObject->SetIsStatic(false);
							pObject->AddToMovingList();
						}
						if (!pObject->GetIsStatic())
							pObject->ApplyMoveForce(-BULLET_HIT_FORCE * point.normal);
					}
				}
#ifdef FIX_BUGS
				pBullet->m_bInUse = false;
#ifdef SQUEEZE_PERFORMANCE
				bulletInfoInUse--;
#endif
				vecNewPos = point.point;
#endif
			}
			if (pBullet->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE && bAddSound) {
				cAudioScriptObject* pAudio;
				switch (pHitEntity->GetType()) {
				case ENTITY_TYPE_BUILDING:
					pAudio = new cAudioScriptObject();
					pAudio->Posn = pHitEntity->GetPosition();
					pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_1;
					pAudio->AudioEntity = AEHANDLE_NONE;
					DMAudio.CreateOneShotScriptObject(pAudio);
					break;
				case ENTITY_TYPE_OBJECT:
					pAudio = new cAudioScriptObject();
					pAudio->Posn = pHitEntity->GetPosition();
					pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_2;
					pAudio->AudioEntity = AEHANDLE_NONE;
					DMAudio.CreateOneShotScriptObject(pAudio);
					break;
				case ENTITY_TYPE_DUMMY:
					pAudio = new cAudioScriptObject();
					pAudio->Posn = pHitEntity->GetPosition();
					pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_3;
					pAudio->AudioEntity = AEHANDLE_NONE;
					DMAudio.CreateOneShotScriptObject(pAudio);
					break;
				case ENTITY_TYPE_PED:
					DMAudio.PlayOneShot(((CPed*)pHitEntity)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f);
					((CPed*)pHitEntity)->Say(SOUND_PED_BULLET_HIT);
					break;
				case ENTITY_TYPE_VEHICLE:
					DMAudio.PlayOneShot(((CVehicle*)pHitEntity)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f);
					break;
				default: break;
				}
			}
			CGlass::WasGlassHitByBullet(pHitEntity, point.point);
			CWeapon::BlowUpExplosiveThings(pHitEntity);
		}
		CWorld::pIgnoreEntity = nil;
		CWorld::bIncludeDeadPeds = false;
		CWorld::bIncludeCarTyres = false;
		if (pBullet->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE) {
			bPlayerSniperBullet = true;
			PlayerSniperBulletStart = pBullet->m_vecPosition;
			PlayerSniperBulletEnd = vecNewPos;
		}
		pBullet->m_vecPosition = vecNewPos;
		if (pBullet->m_vecPosition.x < -MAP_BORDER || pBullet->m_vecPosition.x > MAP_BORDER ||
			pBullet->m_vecPosition.y < -MAP_BORDER || pBullet->m_vecPosition.y > MAP_BORDER) {
			pBullet->m_bInUse = false;
#ifdef SQUEEZE_PERFORMANCE
			bulletInfoInUse--;
#endif
		}
	}
}

bool CBulletInfo::TestForSniperBullet(float x1, float x2, float y1, float y2, float z1, float z2)
{
	if (!bPlayerSniperBullet)
		return false;
#ifdef FIX_BUGS // original code is not going work anyway...
	CColLine line(PlayerSniperBulletStart, PlayerSniperBulletEnd);
	CColBox box;
	box.Set(CVector(x1, y1, z1), CVector(x2, y2, z2), SURFACE_DEFAULT, 0);
	return CCollision::TestLineBox(line, box);
#else
	float minP = 0.0f;
	float maxP = 1.0f;
	float minX = Min(PlayerSniperBulletStart.x, PlayerSniperBulletEnd.x);
	float maxX = Max(PlayerSniperBulletStart.x, PlayerSniperBulletEnd.x);
	if (minX < x2 || maxX > x1) {
		if (minX < x1)
			minP = Min(minP, (x1 - minX) / (maxX - minX));
		if (maxX > x2)
			maxP = Max(maxP, (maxX - x2) / (maxX - minX));
	}
	else
		return false;
	float minY = Min(PlayerSniperBulletStart.y, PlayerSniperBulletEnd.y);
	float maxY = Max(PlayerSniperBulletStart.y, PlayerSniperBulletEnd.y);
	if (minY < y2 || maxY > y1) {
		if (minY < y1)
			minP = Min(minP, (y1 - minY) / (maxY - minY));
		if (maxY > y2)
			maxP = Max(maxP, (maxY - y2) / (maxY - minY));
	}
#ifdef FIX_BUGS
	else
		return false;
#endif
	float minZ = Min(PlayerSniperBulletStart.z, PlayerSniperBulletEnd.z);
	float maxZ = Max(PlayerSniperBulletStart.z, PlayerSniperBulletEnd.z);
	if (minZ < z2 || maxZ > z1) {
		if (minZ < z1)
			minP = Min(minP, (z1 - minZ) / (maxZ - minZ));
		if (maxZ > z2)
			maxP = Max(maxP, (maxZ - z2) / (maxZ - minZ));
	}
	else
		return false;
	return minP <= maxP;
#endif
}


code:
#pragma once

#include "WeaponType.h"

class CEntity;

class CBulletInfo
{
	eWeaponType m_eWeaponType;
	CEntity* m_pSource;
	float m_fTimer; // big mistake
	bool m_bInUse;
	CVector m_vecPosition;
	CVector m_vecSpeed;
	int16 m_nDamage;
public:
	enum {
		NUM_BULLETS = 100
	};
	static void Initialise(void);
	static void Shutdown(void);
	static bool AddBullet(CEntity* pSource, eWeaponType type, CVector vecPosition, CVector vecSpeed);
	static void Update(void);
	static bool TestForSniperBullet(float x1, float x2, float y1, float y2, float z1, float z2);
};