From 74fcbc8c0a6bbac8e8057655c5f1133e15c63656 Mon Sep 17 00:00:00 2001
From: aap <aap@papnet.eu>
Date: Tue, 9 Jul 2019 09:57:44 +0200
Subject: [PATCH] more CAutomobile

---
 src/control/AutoPilot.h     |   6 +-
 src/control/Replay.cpp      |   8 +-
 src/core/Camera.cpp         |  10 +-
 src/core/Collision.cpp      |   2 +-
 src/core/Collision.h        |   2 +-
 src/entities/Entity.h       |   2 +
 src/entities/Physical.cpp   |   4 +-
 src/entities/Physical.h     |   4 +-
 src/objects/Object.cpp      |   1 +
 src/objects/Object.h        |   1 +
 src/peds/Ped.cpp            |  15 +-
 src/peds/Ped.h              |   2 +-
 src/render/Renderer.cpp     |   2 +-
 src/vehicles/Automobile.cpp | 348 +++++++++++++++++++++++++++++++++++-
 src/vehicles/Automobile.h   |  10 +-
 src/vehicles/Vehicle.cpp    |   4 +-
 src/vehicles/Vehicle.h      |  10 +-
 17 files changed, 395 insertions(+), 36 deletions(-)

diff --git a/src/control/AutoPilot.h b/src/control/AutoPilot.h
index 97b02f5c..351fd117 100644
--- a/src/control/AutoPilot.h
+++ b/src/control/AutoPilot.h
@@ -59,9 +59,9 @@ enum eCarDrivingStyle : uint8
 
 class CAutoPilot {
 public:
-	void *m_currentAddress;
-	void *m_startingRouteNode;
-	void *m_PreviousRouteNode;
+	uint32 m_currentAddress;
+	uint32 m_startingRouteNode;
+	uint32 m_PreviousRouteNode;
 	uint32 m_nTotalSpeedScaleFactor;
 	uint32 m_nSpeedScaleFactor;
 	uint32 m_nCurrentPathNodeInfo;
diff --git a/src/control/Replay.cpp b/src/control/Replay.cpp
index 56e37dae..2bdb9dfe 100644
--- a/src/control/Replay.cpp
+++ b/src/control/Replay.cpp
@@ -290,14 +290,14 @@ void CReplay::RecordThisFrame(void)
 		CPed* p = peds->GetSlot(i);
 		if (!p || !p->m_rwObject)
 			continue;
-		if (!p->bRecordedForReplay){
+		if (!p->bHasAlreadyBeenRecorded){
 			tPedHeaderPacket* ph = (tPedHeaderPacket*)&Record.m_pBase[Record.m_nOffset];
 			ph->type = REPLAYPACKET_PED_HEADER;
 			ph->index = i;
 			ph->mi = p->GetModelIndex();
 			ph->pedtype = p->m_nPedType;
 			Record.m_nOffset += sizeof(*ph);
-			p->bRecordedForReplay = true;
+			p->bHasAlreadyBeenRecorded = true;
 		}
 		StorePedUpdate(p, i);
 	}
@@ -1346,14 +1346,14 @@ void CReplay::MarkEverythingAsNew(void)
 		CVehicle* v = CPools::GetVehiclePool()->GetSlot(i);
 		if (!v)
 			continue;
-		v->bRecordedForReplay = false;
+		v->bHasAlreadyBeenRecorded = false;
 	}
 	i = CPools::GetPedPool()->GetSize();
 	while (i--) {
 		CPed* p = CPools::GetPedPool()->GetSlot(i);
 		if (!p)
 			continue;
-		p->bRecordedForReplay = false;
+		p->bHasAlreadyBeenRecorded = false;
 	}
 }
 #endif
diff --git a/src/core/Camera.cpp b/src/core/Camera.cpp
index 58e65d24..832f9455 100644
--- a/src/core/Camera.cpp
+++ b/src/core/Camera.cpp
@@ -678,9 +678,13 @@ CCam::Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, fl
 		else if(TargetZOffSet == m_fUnknownZOffSet && TargetZOffSet > m_fCamBufferedHeight){
 			// TODO: figure this out
 			bool foo = false;
-			switch(((CPhysical*)CamTargetEntity)->m_nLastCollType)
-			case 2: case 3: case 5:
-			case 11: case 23: case 26:
+			switch(((CPhysical*)CamTargetEntity)->m_nSurfaceTouched)
+			case SURFACE_GRASS:
+			case SURFACE_DIRT:
+			case SURFACE_PAVEMENT:
+			case SURFACE_STEEL:
+			case SURFACE_TIRE:
+			case SURFACE_STONE:
 				foo = true;
 			if(foo)
 				WellBufferMe(TargetHeight, &m_fCamBufferedHeight, &m_fCamBufferedHeightSpeed, 0.4f, 0.05f, false);
diff --git a/src/core/Collision.cpp b/src/core/Collision.cpp
index d15ccca5..94da1338 100644
--- a/src/core/Collision.cpp
+++ b/src/core/Collision.cpp
@@ -1173,7 +1173,7 @@ enum {
 // This checks model A's spheres and lines against model B's spheres, boxes and triangles.
 // Returns the number of A's spheres that collide.
 // Returned ColPoints are in world space.
-// NB: lines do not seem to be supported very well, use with caution
+// NB: only vehicles can have col models with lines, exactly 4, one for each wheel
 int32
 CCollision::ProcessColModels(const CMatrix &matrixA, CColModel &modelA,
 	const CMatrix &matrixB, CColModel &modelB,
diff --git a/src/core/Collision.h b/src/core/Collision.h
index 5a9058d3..b2fe6564 100644
--- a/src/core/Collision.h
+++ b/src/core/Collision.h
@@ -147,7 +147,7 @@ public:
 	static bool ProcessSphereTriangle(const CColSphere &sph, const CVector *verts, const CColTriangle &tri, const CColTrianglePlane &plane, CColPoint &point, float &mindistsq);
 	static bool ProcessLineOfSight(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSeeThrough);
 	static bool ProcessVerticalLine(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSeeThrough, CStoredCollPoly *poly);
-	static int32 ProcessColModels(const CMatrix &matrix1, CColModel &model1, const CMatrix &matrix2, CColModel &model2, CColPoint *point1, CColPoint *point2, float *linedists);
+	static int32 ProcessColModels(const CMatrix &matrixA, CColModel &modelA, const CMatrix &matrixB, CColModel &modelB, CColPoint *spherepoints, CColPoint *linepoints, float *linedists);
 
 	// TODO:
 	// CCollision::IsStoredPolyStillValidVerticalLine
diff --git a/src/entities/Entity.h b/src/entities/Entity.h
index d055d25f..12a631d2 100644
--- a/src/entities/Entity.h
+++ b/src/entities/Entity.h
@@ -94,6 +94,8 @@ public:
 	uint16 m_level;	// int16
 	CReference *m_pFirstReference;
 
+	CColModel *GetColModel(void) { return CModelInfo::GetModelInfo(m_modelIndex)->GetColModel(); }
+
 	CEntity(void);
 	~CEntity(void);
 
diff --git a/src/entities/Physical.cpp b/src/entities/Physical.cpp
index c67dad84..88287e26 100644
--- a/src/entities/Physical.cpp
+++ b/src/entities/Physical.cpp
@@ -62,7 +62,7 @@ CPhysical::CPhysical(void)
 	m_phy_flagA10 = false;
 	m_phy_flagA20 = false;
 
-	m_nLastCollType = 0;
+	m_nSurfaceTouched = SURFACE_DEFAULT;
 }
 
 CPhysical::~CPhysical(void)
@@ -1918,7 +1918,7 @@ CPhysical::ProcessCollision(void)
 	   bHitByTrain ||
 	   m_status == STATUS_PLAYER || IsPed() && ped->IsPlayer()){
 		if(IsVehicle())
-			((CVehicle*)this)->m_veh_flagD4 = true;
+			((CVehicle*)this)->bVehicleColProcessed = true;
 		if(CheckCollision()){
 			GetMatrix() = savedMatrix;
 			return;
diff --git a/src/entities/Physical.h b/src/entities/Physical.h
index c2fce334..5bd98815 100644
--- a/src/entities/Physical.h
+++ b/src/entities/Physical.h
@@ -61,7 +61,7 @@ public:
 	uint8 bHitByTrain : 1;	// from nick
 	uint8 m_phy_flagA80 : 1;
 
-	uint8 m_nLastCollType;
+	uint8 m_nSurfaceTouched;
 	uint8 m_nZoneLevel;
 
 	CPhysical(void);
@@ -75,7 +75,7 @@ public:
 	void ProcessShift(void);
 	void ProcessCollision(void);
 
-	virtual int32 ProcessEntityCollision(CEntity *ent, CColPoint *point);
+	virtual int32 ProcessEntityCollision(CEntity *ent, CColPoint *colpoints);
 
 	void RemoveAndAdd(void);
 	void AddToMovingList(void);
diff --git a/src/objects/Object.cpp b/src/objects/Object.cpp
index 9bfaf681..f3ba8087 100644
--- a/src/objects/Object.cpp
+++ b/src/objects/Object.cpp
@@ -10,6 +10,7 @@ WRAPPER void CObject::ObjectDamage(float amount) { EAXJMP(0x4BB240); }
 WRAPPER void CObject::DeleteAllTempObjectInArea(CVector, float) { EAXJMP(0x4BBED0); }
 
 int16 &CObject::nNoTempObjects = *(int16*)0x95CCA2;
+int16 &CObject::nBodyCastHealth = *(int16*)0x5F7D4C;	// 1000
 
 void *CObject::operator new(size_t sz) { return CPools::GetObjectPool()->New();  }
 void CObject::operator delete(void *p, size_t sz) { CPools::GetObjectPool()->Delete((CObject*)p); }
diff --git a/src/objects/Object.h b/src/objects/Object.h
index d31a998a..0ce1a3aa 100644
--- a/src/objects/Object.h
+++ b/src/objects/Object.h
@@ -60,6 +60,7 @@ public:
 	int8 m_colour1, m_colour2;
 
 	static int16 &nNoTempObjects;
+	static int16 &nBodyCastHealth;
 
 	static void *operator new(size_t);
 	static void operator delete(void*, size_t);
diff --git a/src/peds/Ped.cpp b/src/peds/Ped.cpp
index bf36d403..e0e0b913 100644
--- a/src/peds/Ped.cpp
+++ b/src/peds/Ped.cpp
@@ -10,6 +10,7 @@
 #include "Ped.h"
 #include "PlayerPed.h"
 #include "General.h"
+#include "SurfaceTable.h"
 #include "VisibilityPlugins.h"
 #include "AudioManager.h"
 #include "HandlingMgr.h"
@@ -430,7 +431,7 @@ CPed::CPed(uint32 pedType) : m_pedIK(this)
 	m_ped_flagI1 = false;
 	m_ped_flagI2 = false;
 	m_ped_flagI4 = false;
-	bRecordedForReplay = false;
+	bHasAlreadyBeenRecorded = false;
 	m_ped_flagI10 = false;
 #ifdef KANGAROO_CHEAT
 	m_ped_flagI80 = false;
@@ -1772,12 +1773,12 @@ CPed::LineUpPedWithCar(PedLineUpPhase phase)
 static void
 particleProduceFootDust(CPed *ped, CVector *pos, float size, int times)
 {
-	switch (ped->m_nLastCollType)
+	switch (ped->m_nSurfaceTouched)
 	{
-		case 1:	// somewhere hard
-		case 3:	// soft dirt
-		case 5:	// pavement
-		case 18:// sand
+		case SURFACE_TARMAC:
+		case SURFACE_DIRT:
+		case SURFACE_PAVEMENT:
+		case SURFACE_SAND:
 			for (int i = 0; i < times; ++i) {
 				CVector adjustedPos = *pos;
 				adjustedPos.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f);
@@ -1879,7 +1880,7 @@ CPed::PlayFootSteps(void)
 		}
 	}
 
-	if (m_nLastCollType == 19) { // Water
+	if (m_nSurfaceTouched == SURFACE_PUDDLE) {
 		float pedSpeed = CVector2D(m_vecMoveSpeed).Magnitude();
 		if (pedSpeed > 0.03f && CTimer::GetFrameCounter() % 2 == 0 && pedSpeed > 0.13f) {
 			float particleSize = pedSpeed * 2.0f;
diff --git a/src/peds/Ped.h b/src/peds/Ped.h
index 2390d1d4..6aba79cf 100644
--- a/src/peds/Ped.h
+++ b/src/peds/Ped.h
@@ -251,7 +251,7 @@ public:
 	uint8 m_ped_flagI1 : 1;
 	uint8 m_ped_flagI2 : 1;
 	uint8 m_ped_flagI4 : 1;
-	uint8 bRecordedForReplay : 1;
+	uint8 bHasAlreadyBeenRecorded : 1;
 	uint8 m_ped_flagI10 : 1;
 	uint8 m_ped_flagI20 : 1;
 	uint8 m_ped_flagI40 : 1;
diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp
index a6f28443..69df63ba 100644
--- a/src/render/Renderer.cpp
+++ b/src/render/Renderer.cpp
@@ -1160,7 +1160,7 @@ CRenderer::IsVehicleCullZoneVisible(CEntity *ent)
 	case STATUS_PHYSICS:
 	case STATUS_ABANDONED:
 	case STATUS_WRECKED:
-		return !(v->m_pCurSurface && v->m_pCurSurface->bZoneCulled2);
+		return !(v->m_pCurGroundEntity && v->m_pCurGroundEntity->bZoneCulled2);
 	return true;
 }
 
diff --git a/src/vehicles/Automobile.cpp b/src/vehicles/Automobile.cpp
index 3968991b..267e901a 100644
--- a/src/vehicles/Automobile.cpp
+++ b/src/vehicles/Automobile.cpp
@@ -8,6 +8,7 @@
 #include "SurfaceTable.h"
 #include "HandlingMgr.h"
 #include "CarCtrl.h"
+#include "PathFind.h"
 #include "Ped.h"
 #include "Object.h"
 #include "Automobile.h"
@@ -50,6 +51,90 @@ WRAPPER void CAutomobile::PreRender(void) { EAXJMP(0x535B40); }
 WRAPPER void CAutomobile::Render(void) { EAXJMP(0x539EA0); }
 
 
+int32
+CAutomobile::ProcessEntityCollision(CEntity *ent, CColPoint *colpoints)
+{
+	int i;
+	CColModel *colModel;
+
+	if(m_status != STATUS_SIMPLE)
+		bVehicleColProcessed = true;
+
+	if(m_veh_flagC80)
+		colModel = &CWorld::Players[CWorld::PlayerInFocus].m_ColModel;
+	else
+		colModel = GetColModel();
+
+	int numWheelCollisions = 0;
+	float prevRatios[4] = { 0.0f, 0.0f, 0.0f, 0.0f};
+	for(i = 0; i < 4; i++)
+		prevRatios[i] = m_aSuspensionSpringRatio[i];
+
+	int numCollisions = CCollision::ProcessColModels(GetMatrix(), *colModel,
+		ent->GetMatrix(), *ent->GetColModel(),
+		colpoints,
+		m_aWheelColPoints, m_aSuspensionSpringRatio);
+
+	// m_aSuspensionSpringRatio are now set to the point where the tyre touches ground.
+	// In ProcessControl these will be re-normalized to ignore the tyre radius.
+
+	if(field_EF || m_phy_flagA80 ||
+	   GetModelIndex() == MI_DODO && (ent->m_status == STATUS_PHYSICS || ent->m_status == STATUS_SIMPLE)){
+		// don't do line collision
+		for(i = 0; i < 4; i++)
+			m_aSuspensionSpringRatio[i] = prevRatios[i];
+	}else{
+		for(i = 0; i < 4; i++)
+			if(m_aSuspensionSpringRatio[i] < 1.0f && m_aSuspensionSpringRatio[i] < prevRatios[i]){
+				numWheelCollisions++;
+
+				// wheel is touching a physical
+				if(ent->IsVehicle() || ent->IsObject()){
+					CPhysical *phys = (CPhysical*)ent;
+
+					m_aGroundPhysical[i] = phys;
+					phys->RegisterReference((CEntity**)&m_aGroundPhysical[i]);
+					m_aGroundOffset[i] = m_aWheelColPoints[i].point - phys->GetPosition();
+
+					if(phys->GetModelIndex() == MI_BODYCAST && m_status == STATUS_PLAYER){
+						// damage body cast
+						float speed = m_vecMoveSpeed.MagnitudeSqr();
+						if(speed > 0.1f){
+							CObject::nBodyCastHealth -= 0.1f*m_fMass*speed;
+							DMAudio.PlayOneShot(m_audioEntityId, SOUND_PED_BODYCAST_HIT, 0.0f);
+						}
+
+						// move body cast
+						if(phys->bIsStatic){
+							phys->bIsStatic = false;
+							phys->m_nStaticFrames = 0;
+							phys->ApplyMoveForce(m_vecMoveSpeed / speed);
+							phys->AddToMovingList();
+						}
+					}
+				}
+
+				m_nSurfaceTouched = m_aWheelColPoints[i].surfaceB;
+				if(ent->IsBuilding())
+					m_pCurGroundEntity = ent;
+			}
+	}
+
+	if(numCollisions > 0 || numWheelCollisions > 0){
+		AddCollisionRecord(ent);
+		if(!ent->IsBuilding())
+			((CPhysical*)ent)->AddCollisionRecord(this);
+
+		if(numCollisions > 0)
+			if(ent->IsBuilding() ||
+			   ent->IsObject() && ((CPhysical*)ent)->bInfiniteMass)
+				bHasHitWall = true;
+	}
+
+	return numCollisions;
+}
+
+
 WRAPPER void CAutomobile::ProcessControlInputs(uint8) { EAXJMP(0x53B660); }
 
 void
@@ -120,7 +205,114 @@ CAutomobile::OpenDoor(int32 component, eDoors door, float openRatio)
 	mat.UpdateRW();
 }
 
-WRAPPER void CAutomobile::ProcessOpenDoor(uint32, uint32, float) { EAXJMP(0x52E910); }
+inline void ProcessDoorOpenAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float end)
+{
+	if(time > start && time < end){
+		float ratio = (time - start)/(end - start);
+		if(car->Doors[door].GetAngleOpenRatio() < ratio)
+			car->OpenDoor(component, door, ratio);
+	}else if(time > end){
+		car->OpenDoor(component, door, 1.0f);
+	}
+}
+
+inline void ProcessDoorCloseAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float end)
+{
+	if(time > start && time < end){
+		float ratio = 1.0f - (time - start)/(end - start);
+		if(car->Doors[door].GetAngleOpenRatio() > ratio)
+			car->OpenDoor(component, door, ratio);
+	}else if(time > end){
+		car->OpenDoor(component, door, 0.0f);
+	}
+}
+
+inline void ProcessDoorOpenCloseAnimation(CAutomobile *car, uint32 component, eDoors door, float time, float start, float mid, float end)
+{
+	if(time > start && time < mid){
+		// open
+		float ratio = (time - start)/(mid - start);
+		if(car->Doors[door].GetAngleOpenRatio() < ratio)
+			car->OpenDoor(component, door, ratio);
+	}else if(time > mid && time < end){
+		// close
+		float ratio = 1.0f - (time - mid)/(end - mid);
+		if(car->Doors[door].GetAngleOpenRatio() > ratio)
+			car->OpenDoor(component, door, ratio);
+	}else if(time > end){
+		car->OpenDoor(component, door, 0.0f);
+	}
+}
+void
+CAutomobile::ProcessOpenDoor(uint32 component, uint32 anim, float time)
+{
+	eDoors door;
+
+	switch(component){
+	case CAR_DOOR_RF: door = DOOR_FRONT_RIGHT; break;
+	case CAR_DOOR_RR: door = DOOR_REAR_RIGHT; break;
+	case CAR_DOOR_LF: door = DOOR_FRONT_LEFT; break;
+	case CAR_DOOR_LR: door = DOOR_REAR_LEFT; break;
+	default: assert(0);
+	}
+
+	if(IsDoorMissing(door))
+		return;
+
+	switch(anim){
+	case ANIM_CAR_QJACK:
+	case ANIM_CAR_OPEN_LHS:
+	case ANIM_CAR_OPEN_RHS:
+		ProcessDoorOpenAnimation(this, component, door, time, 0.66f, 0.8f);
+		break;
+	case ANIM_CAR_CLOSEDOOR_LHS:
+	case ANIM_CAR_CLOSEDOOR_LOW_LHS:
+	case ANIM_CAR_CLOSEDOOR_RHS:
+	case ANIM_CAR_CLOSEDOOR_LOW_RHS:
+		ProcessDoorCloseAnimation(this, component, door, time, 0.2f, 0.63f);
+		break;
+	case ANIM_CAR_ROLLDOOR:
+	case ANIM_CAR_ROLLDOOR_LOW:
+		ProcessDoorOpenCloseAnimation(this, component, door, time, 0.1f, 0.6f, 0.95f);
+		break;
+		break;
+	case ANIM_CAR_GETOUT_LHS:
+	case ANIM_CAR_GETOUT_LOW_LHS:
+	case ANIM_CAR_GETOUT_RHS:
+	case ANIM_CAR_GETOUT_LOW_RHS:
+		ProcessDoorOpenAnimation(this, component, door, time, 0.06f, 0.43f);
+		break;
+	case ANIM_CAR_CLOSE_LHS:
+	case ANIM_CAR_CLOSE_RHS:
+		ProcessDoorCloseAnimation(this, component, door, time, 0.1f, 0.23f);
+		break;
+	case ANIM_CAR_PULLOUT_RHS:
+	case ANIM_CAR_PULLOUT_LOW_RHS:
+		OpenDoor(component, door, 1.0f);
+	case ANIM_COACH_OPEN_L:
+	case ANIM_COACH_OPEN_R:
+		ProcessDoorOpenAnimation(this, component, door, time, 0.66f, 0.8f);
+		break;
+	case ANIM_COACH_OUT_L:
+		ProcessDoorOpenAnimation(this, component, door, time, 0.0f, 0.3f);
+		break;
+	case ANIM_VAN_OPEN_L:
+	case ANIM_VAN_OPEN:
+		ProcessDoorOpenAnimation(this, component, door, time, 0.37f, 0.55f);
+		break;
+	case ANIM_VAN_CLOSE_L:
+	case ANIM_VAN_CLOSE:
+		ProcessDoorCloseAnimation(this, component, door, time, 0.5f, 0.8f);
+		break;
+	case ANIM_VAN_GETOUT_L:
+	case ANIM_VAN_GETOUT:
+		ProcessDoorOpenAnimation(this, component, door, time, 0.5f, 0.6f);
+		break;
+	case NUM_ANIMS:
+		OpenDoor(component, door, time);
+		break;
+	}
+}
 
 bool
 CAutomobile::IsDoorReady(eDoors door)
@@ -252,6 +444,16 @@ CAutomobile::PlayCarHorn(void)
 	}
 }
 
+void
+CAutomobile::PlayHornIfNecessary(void)
+{
+	// TODO: flags
+	if(m_autoPilot.m_nCarCtrlFlags & 2 ||
+	   m_autoPilot.m_nCarCtrlFlags & 1)
+		if(!HasCarStoppedBecauseOfLight())
+			PlayCarHorn();
+}
+
 
 void
 CAutomobile::ResetSuspension(void)
@@ -265,6 +467,139 @@ CAutomobile::ResetSuspension(void)
 	}
 }
 
+void
+CAutomobile::SetupSuspensionLines(void)
+{
+	int i;
+	CVector posn;
+	CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex());
+	CColModel *colModel = mi->GetColModel();
+
+	// Each suspension line starts at the uppermost wheel position
+	// and extends down to the lowermost point on the tyre
+	for(i = 0; i < 4; i++){
+		mi->GetWheelPosn(i, posn);
+		m_aWheelPosition[i] = posn.z;
+
+		// uppermost wheel position
+		posn.z += m_handling->fSuspensionUpperLimit;
+		colModel->lines[i].p0 = posn;
+
+		// lowermost wheel position
+		posn.z += m_handling->fSuspensionLowerLimit - m_handling->fSuspensionUpperLimit;
+		// lowest point on tyre
+		posn.z -= mi->m_wheelScale*0.5f;
+		colModel->lines[i].p1 = posn;
+
+		// this is length of the spring at rest
+		m_aSuspensionSpringLength[i] = m_handling->fSuspensionUpperLimit - m_handling->fSuspensionLowerLimit;
+		m_aSuspensionLineLength[i] = colModel->lines[i].p0.z - colModel->lines[i].p1.z;
+	}
+
+	// Compress spring somewhat to get normal height on road
+	m_fHeightAboveRoad = -(colModel->lines[0].p0.z + (colModel->lines[0].p1.z - colModel->lines[0].p0.z)*
+	                                                  (1.0f - 1.0f/(8.0f*m_handling->fSuspensionForceLevel)));
+	for(i = 0; i < 4; i++)
+		m_aWheelPosition[i] = mi->m_wheelScale*0.5f - m_fHeightAboveRoad;
+
+	// adjust col model to include suspension lines
+	if(colModel->boundingBox.min.z > colModel->lines[0].p1.z)
+		colModel->boundingBox.min.z = colModel->lines[0].p1.z;
+	float radius = max(colModel->boundingBox.min.Magnitude(), colModel->boundingBox.max.Magnitude());
+	if(colModel->boundingSphere.radius < radius)
+		colModel->boundingSphere.radius = radius;
+
+	if(GetModelIndex() == MI_RCBANDIT){
+		colModel->boundingSphere.radius = 2.0f;
+		for(i = 0; i < colModel->numSpheres; i++)
+			colModel->spheres[i].radius = 0.3f;
+	}
+}
+
+bool
+CAutomobile::HasCarStoppedBecauseOfLight(void)
+{
+	int i;
+
+	if(m_status != STATUS_SIMPLE && m_status != STATUS_PHYSICS)
+		return false;
+
+	if(m_autoPilot.m_currentAddress && m_autoPilot.m_startingRouteNode){
+		CPathNode *curnode = &ThePaths.m_pathNodes[m_autoPilot.m_currentAddress];
+		for(i = 0; i < curnode->numLinks; i++)
+			if(ThePaths.m_connections[curnode->firstLink + i] == m_autoPilot.m_startingRouteNode)
+				break;
+		if(i < curnode->numLinks &&
+		   ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3)	// TODO
+			return true;
+	}
+
+	if(m_autoPilot.m_currentAddress && m_autoPilot.m_PreviousRouteNode){
+		CPathNode *curnode = &ThePaths.m_pathNodes[m_autoPilot.m_currentAddress];
+		for(i = 0; i < curnode->numLinks; i++)
+			if(ThePaths.m_connections[curnode->firstLink + i] == m_autoPilot.m_PreviousRouteNode)
+				break;
+		if(i < curnode->numLinks &&
+		   ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[curnode->firstLink + i]].trafficLightType & 3)	// TODO
+			return true;
+	}
+
+	return false;
+}
+
+void
+CAutomobile::SetBusDoorTimer(uint32 timer, uint8 type)
+{
+	if(timer < 1000)
+		timer = 1000;
+	if(type == 0)
+		// open and close
+		m_nBusDoorTimerStart = CTimer::GetTimeInMilliseconds();
+	else
+		// only close
+		m_nBusDoorTimerStart = CTimer::GetTimeInMilliseconds() - 500;
+	m_nBusDoorTimerEnd = m_nBusDoorTimerStart + timer;
+}
+
+void
+CAutomobile::ProcessAutoBusDoors(void)
+{
+	if(CTimer::GetTimeInMilliseconds() < m_nBusDoorTimerEnd){
+		if(m_nBusDoorTimerEnd != 0 && CTimer::GetTimeInMilliseconds() > m_nBusDoorTimerEnd-500){
+			// close door
+			if(!IsDoorMissing(DOOR_FRONT_LEFT) && (m_nGettingInFlags & 1) == 0){
+				if(IsDoorClosed(DOOR_FRONT_LEFT)){
+					m_nBusDoorTimerEnd = CTimer::GetTimeInMilliseconds();
+					OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f);
+				}else{
+					OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT,
+						1.0f - (CTimer::GetTimeInMilliseconds() - (m_nBusDoorTimerEnd-500))/500.0f);
+				}
+			}
+
+			if(!IsDoorMissing(DOOR_FRONT_RIGHT) && (m_nGettingInFlags & 4) == 0){
+				if(IsDoorClosed(DOOR_FRONT_RIGHT)){
+					m_nBusDoorTimerEnd = CTimer::GetTimeInMilliseconds();
+					OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, 0.0f);
+				}else{
+					OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT,
+						1.0f - (CTimer::GetTimeInMilliseconds() - (m_nBusDoorTimerEnd-500))/500.0f);
+				}
+			}
+		}
+	}else{
+		// ended
+		if(m_nBusDoorTimerStart){
+			if(!IsDoorMissing(DOOR_FRONT_LEFT) && (m_nGettingInFlags & 1) == 0)
+				OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f);
+			if(!IsDoorMissing(DOOR_FRONT_RIGHT) && (m_nGettingInFlags & 4) == 0)
+				OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, 0.0f);
+			m_nBusDoorTimerStart = 0;
+			m_nBusDoorTimerEnd = 0;
+		}
+	}
+}
+
 void
 CAutomobile::ProcessSwingingDoor(int32 component, eDoors door)
 {
@@ -465,8 +800,8 @@ CAutomobile::SpawnFlyingComponent(int32 component, uint32 type)
 		obj->m_fAirResistance = 0.99f;
 	}
 
-	if(CCollision::ProcessColModels(obj->GetMatrix(), *CModelInfo::GetModelInfo(obj->GetModelIndex())->GetColModel(),
-			this->GetMatrix(), *CModelInfo::GetModelInfo(this->GetModelIndex())->GetColModel(),
+	if(CCollision::ProcessColModels(obj->GetMatrix(), *obj->GetColModel(),
+			this->GetMatrix(), *this->GetColModel(),
 			aTempPedColPts, nil, nil) > 0)
 		obj->m_pCollidingEntity = this;
 
@@ -644,6 +979,8 @@ public:
 	void PreRender_(void) { CAutomobile::PreRender(); }
 	void Render_(void) { CAutomobile::Render(); }
 
+	int32 ProcessEntityCollision_(CEntity *ent, CColPoint *colpoints){ return CAutomobile::ProcessEntityCollision(ent, colpoints); }
+
 	void ProcessControlInputs_(uint8 x) { CAutomobile::ProcessControlInputs(x); }
 	void GetComponentWorldPosition_(int32 component, CVector &pos) { CAutomobile::GetComponentWorldPosition(component, pos); }
 	bool IsComponentPresent_(int32 component) { return CAutomobile::IsComponentPresent(component); }
@@ -667,6 +1004,7 @@ STARTPATCHES
 	InjectHook(0x52D170, &CAutomobile_::dtor, PATCH_JUMP);
 	InjectHook(0x52D190, &CAutomobile_::SetModelIndex_, PATCH_JUMP);
 	InjectHook(0x535180, &CAutomobile_::Teleport_, PATCH_JUMP);
+	InjectHook(0x53B270, &CAutomobile_::ProcessEntityCollision_, PATCH_JUMP);
 	InjectHook(0x52E5F0, &CAutomobile_::GetComponentWorldPosition_, PATCH_JUMP);
 	InjectHook(0x52E660, &CAutomobile_::IsComponentPresent_, PATCH_JUMP);
 	InjectHook(0x52E680, &CAutomobile_::SetComponentRotation_, PATCH_JUMP);
@@ -681,6 +1019,10 @@ STARTPATCHES
 	InjectHook(0x437690, &CAutomobile_::GetHeightAboveRoad_, PATCH_JUMP);
 	InjectHook(0x53C450, &CAutomobile_::PlayCarHorn_, PATCH_JUMP);
 	InjectHook(0x5353A0, &CAutomobile::ResetSuspension, PATCH_JUMP);
+	InjectHook(0x52D210, &CAutomobile::SetupSuspensionLines, PATCH_JUMP);
+	InjectHook(0x42E220, &CAutomobile::HasCarStoppedBecauseOfLight, PATCH_JUMP);
+	InjectHook(0x53D320, &CAutomobile::SetBusDoorTimer, PATCH_JUMP);
+	InjectHook(0x53D370, &CAutomobile::ProcessAutoBusDoors, PATCH_JUMP);
 	InjectHook(0x535250, &CAutomobile::ProcessSwingingDoor, PATCH_JUMP);
 	InjectHook(0x53C240, &CAutomobile::Fix, PATCH_JUMP);
 	InjectHook(0x53C310, &CAutomobile::SetupDamageAfterLoad, PATCH_JUMP);
diff --git a/src/vehicles/Automobile.h b/src/vehicles/Automobile.h
index 33e86b9d..fc859f92 100644
--- a/src/vehicles/Automobile.h
+++ b/src/vehicles/Automobile.h
@@ -66,6 +66,9 @@ public:
 	void PreRender(void);
 	void Render(void);
 
+	// from CPhysical
+	int32 ProcessEntityCollision(CEntity *ent, CColPoint *colpoints);
+
 	// from CVehicle
 	void ProcessControlInputs(uint8);
 	void GetComponentWorldPosition(int32 component, CVector &pos);
@@ -85,8 +88,13 @@ public:
 	float GetHeightAboveRoad(void);
 	void PlayCarHorn(void);
 
-	void ProcessSwingingDoor(int32 component, eDoors door);
+	void PlayHornIfNecessary(void);
 	void ResetSuspension(void);
+	void SetupSuspensionLines(void);
+	bool HasCarStoppedBecauseOfLight(void);
+	void SetBusDoorTimer(uint32 timer, uint8 type);
+	void ProcessAutoBusDoors(void);
+	void ProcessSwingingDoor(int32 component, eDoors door);
 	void SetupDamageAfterLoad(void);
 	CObject *SpawnFlyingComponent(int32 component, uint32 type);
 	CObject *RemoveBonnetInPedCollision(void);
diff --git a/src/vehicles/Vehicle.cpp b/src/vehicles/Vehicle.cpp
index 1a22e98a..6ea0e61e 100644
--- a/src/vehicles/Vehicle.cpp
+++ b/src/vehicles/Vehicle.cpp
@@ -92,7 +92,7 @@ CVehicle::RemoveLighting(bool reset)
 float
 CVehicle::GetHeightAboveRoad(void)
 {
-	return -1.0f * CModelInfo::GetModelInfo(GetModelIndex())->GetColModel()->boundingBox.min.z;
+	return -1.0f * GetColModel()->boundingBox.min.z;
 }
 
 
@@ -442,7 +442,7 @@ 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 = CModelInfo::GetModelInfo(GetModelIndex())->GetColModel();
+	CColModel *colmodel = GetColModel();
 
 	x = DotProduct(sph, GetRight());
 	if(colmodel->boundingBox.min.x - radius > x ||
diff --git a/src/vehicles/Vehicle.h b/src/vehicles/Vehicle.h
index e974eb88..5aa0a770 100644
--- a/src/vehicles/Vehicle.h
+++ b/src/vehicles/Vehicle.h
@@ -123,7 +123,7 @@ public:
 	int8 m_nGettingOutFlags;
 	uint8 m_nNumMaxPassengers;
 	char field_1CD[19];
-	CEntity *m_pCurSurface;
+	CEntity *m_pCurGroundEntity;
 	CFire *m_pCarFire;
 	float m_fSteerAngle;
 	float m_fGasPedal;
@@ -160,9 +160,9 @@ public:
 
 	uint8 m_veh_flagD1 : 1;
 	uint8 m_veh_flagD2 : 1;
-	uint8 m_veh_flagD4 : 1;
-	uint8 m_veh_flagD8 : 1;
-	uint8 bRecordedForReplay : 1;
+	uint8 bVehicleColProcessed : 1;
+	uint8 bIsCarParkVehicle : 1;
+	uint8 bHasAlreadyBeenRecorded : 1;
 	uint8 m_veh_flagD20 : 1;
 	uint8 m_veh_flagD40 : 1;
 	uint8 m_veh_flagD80 : 1;
@@ -263,7 +263,7 @@ public:
 };
 
 static_assert(sizeof(CVehicle) == 0x288, "CVehicle: error");
-static_assert(offsetof(CVehicle, m_pCurSurface) == 0x1E0, "CVehicle: error");
+static_assert(offsetof(CVehicle, m_pCurGroundEntity) == 0x1E0, "CVehicle: error");
 static_assert(offsetof(CVehicle, m_nAlarmState) == 0x1A0, "CVehicle: error");
 static_assert(offsetof(CVehicle, m_nLastWeaponDamage) == 0x228, "CVehicle: error");