diff --git a/src/audio/DMAudio.cpp b/src/audio/DMAudio.cpp
index 60454bdd..6a53623c 100644
--- a/src/audio/DMAudio.cpp
+++ b/src/audio/DMAudio.cpp
@@ -19,7 +19,7 @@ WRAPPER void cDMAudio::ChangeMusicMode(uint8 mode) { EAXJMP(0x57CCF0); }
 WRAPPER void cDMAudio::PlayFrontEndSound(uint32, uint32) { EAXJMP(0x57CC20); }
 WRAPPER void cDMAudio::PlayFrontEndTrack(uint32, uint32) { EAXJMP(0x57CC80); }
 WRAPPER void cDMAudio::StopFrontEndTrack() { EAXJMP(0x57CCB0); }
-WRAPPER void cDMAudio::PlayOneShot(int32, uint16, float) { EAXJMP(0x57C840); }
+WRAPPER void cDMAudio::PlayOneShot(int32, uint16 /*eSound*/, float) { EAXJMP(0x57C840); }
 WRAPPER void cDMAudio::SetMusicMasterVolume(int8) { EAXJMP(0x57C8C0); }
 WRAPPER void cDMAudio::SetEffectsMasterVolume(int8) { EAXJMP(0x57C890); }
 WRAPPER int8 cDMAudio::SetCurrent3DProvider(int8) { EAXJMP(0x57C9B0); }
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/Darkel.cpp b/src/control/Darkel.cpp
index f050a540..ab28f96e 100644
--- a/src/control/Darkel.cpp
+++ b/src/control/Darkel.cpp
@@ -123,18 +123,18 @@ eKillFrenzyStatus CDarkel::ReadStatus()
 }
 
 #if 1
-WRAPPER int32 CDarkel::RegisterCarBlownUpByPlayer(eKillFrenzyStatus status) { EAXJMP(0x421070); }
+WRAPPER void CDarkel::RegisterCarBlownUpByPlayer(CVehicle *vehicle) { EAXJMP(0x421070); }
 #else
-int32 CDarkel::RegisterCarBlownUpByPlayer(eKillFrenzyStatus status)
+int32 CDarkel::RegisterCarBlownUpByPlayer(CVehicle *vehicle)
 {
 	return 0;
 }
 #endif
 
 #if 1
-WRAPPER void CDarkel::RegisterKillByPlayer(int32 modelid, eWeaponType weapontype, bool flag) { EAXJMP(0x420F60); }
+WRAPPER void CDarkel::RegisterKillByPlayer(CPed *victim, eWeaponType weapontype, bool headshot) { EAXJMP(0x420F60); }
 #else
-void CDarkel::RegisterKillByPlayer(int32 modelid, eWeaponType weapontype, bool flag)
+void CDarkel::RegisterKillByPlayer(CPed *victim, eWeaponType weapontype, bool headshot)
 {
 
 	
diff --git a/src/control/Darkel.h b/src/control/Darkel.h
index da1df24a..35d849d2 100644
--- a/src/control/Darkel.h
+++ b/src/control/Darkel.h
@@ -1,6 +1,9 @@
 #pragma once
 #include "Weapon.h"
 
+class CVehicle;
+class CPed;
+
 enum eKillFrenzyStatus
 {
 	KILLFRENZY_NONE,
@@ -37,8 +40,8 @@ public:
 	static void Init();
 	static int16 QueryModelsKilledByPlayer(int32 modelId);
 	static eKillFrenzyStatus ReadStatus();
-	static int32 RegisterCarBlownUpByPlayer(eKillFrenzyStatus status);
-	static void RegisterKillByPlayer(int32 modelid, eWeaponType weapontype, bool flag);
+	static void RegisterCarBlownUpByPlayer(CVehicle *vehicle);
+	static void RegisterKillByPlayer(CPed *victim, eWeaponType weapontype, bool headshot = false);
 	static void RegisterKillNotByPlayer();
 	static void ResetModelsKilledByPlayer();
 	static void ResetOnPlayerDeath();
diff --git a/src/control/Replay.cpp b/src/control/Replay.cpp
index 9041a003..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);
 	}
@@ -1012,7 +1012,7 @@ void CReplay::ProcessReplayCamera(void)
 	default:
 		break;
 	}
-	TheCamera.m_vecGameCamPos = *TheCamera.GetMatrix().GetPosition();
+	TheCamera.m_vecGameCamPos = TheCamera.GetMatrix().GetPosition();
 	TheCamera.CalculateDerivedValues();
 	RwMatrixUpdate(RwFrameGetMatrix(RwCameraGetFrame(TheCamera.m_pRwCamera)));
 	RwFrameUpdateObjects(RwCameraGetFrame(TheCamera.m_pRwCamera));
@@ -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 6cafaade..4f565897 100644
--- a/src/core/Camera.cpp
+++ b/src/core/Camera.cpp
@@ -18,6 +18,7 @@ const float DefaultFOV = 70.0f;	// beta: 80.0f
 CCamera &TheCamera = *(CCamera*)0x6FACF8;
 bool &CCamera::m_bUseMouse3rdPerson = *(bool *)0x5F03D8;
 
+WRAPPER void CCamera::CamShake(float strength, float x, float y, float z) { EAXJMP(0x46B200); }
 WRAPPER void CCamera::DrawBordersForWideScreen(void) { EAXJMP(0x46B430); }
 WRAPPER void CCamera::CalculateDerivedValues(void) { EAXJMP(0x46EEA0); }
 WRAPPER void CCamera::Restore(void) { EAXJMP(0x46F990); }
@@ -679,9 +680,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/Camera.h b/src/core/Camera.h
index da0e55eb..c0309b5f 100644
--- a/src/core/Camera.h
+++ b/src/core/Camera.h
@@ -454,6 +454,8 @@ int     m_iModeObbeCamIsInForCar;
 	void ProcessMusicFade(void);
 	void SetFadeColour(uint8 r, uint8 g, uint8 b);
 
+	void CamShake(float strength, float x, float y, float z);
+
 	void SetMotionBlur(int r, int g, int b, int a, int type);
 	void SetMotionBlurAlpha(int a);
 	void RenderMotionBlur(void);
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/core/Explosion.cpp b/src/core/Explosion.cpp
new file mode 100644
index 00000000..f55cbcd6
--- /dev/null
+++ b/src/core/Explosion.cpp
@@ -0,0 +1,5 @@
+#include "common.h"
+#include "patcher.h"
+#include "Explosion.h"
+
+WRAPPER void CExplosion::AddExplosion(CEntity *explodingEntity, CEntity *culprit, eExplosionType type, const CVector &pos, uint32) { EAXJMP(0x5591C0); }
diff --git a/src/core/Explosion.h b/src/core/Explosion.h
new file mode 100644
index 00000000..69508490
--- /dev/null
+++ b/src/core/Explosion.h
@@ -0,0 +1,15 @@
+#pragma once
+
+class CEntity;
+
+enum eExplosionType
+{
+	EXPLOSION_3 = 3,
+	EXPLOSION_4
+};
+
+class CExplosion
+{
+public:
+	static void AddExplosion(CEntity *explodingEntity, CEntity *culprit, eExplosionType type, const CVector &pos, uint32);
+};
diff --git a/src/core/FileLoader.cpp b/src/core/FileLoader.cpp
index fdc3b9d7..b949eb98 100644
--- a/src/core/FileLoader.cpp
+++ b/src/core/FileLoader.cpp
@@ -813,6 +813,7 @@ CFileLoader::LoadPedObject(const char *line)
 		if(strcmp(animGroup, CAnimManager::GetAnimGroupName((AssocGroupId)animGroupId)) == 0)
 			break;
 	mi->m_animGroup = animGroupId;
+	mi->m_carsCanDrive = carsCanDrive;
 
 	// ???
 	CModelInfo::GetModelInfo(MI_LOPOLYGUY)->SetColModel(&CTempColModels::ms_colModelPed1);
diff --git a/src/core/Fire.cpp b/src/core/Fire.cpp
index 05d72199..bc59de2f 100644
--- a/src/core/Fire.cpp
+++ b/src/core/Fire.cpp
@@ -2,4 +2,8 @@
 #include "patcher.h"
 #include "Fire.h"
 
-WRAPPER void CFire::Extinguish(void) { EAXJMP(0x479D40); }
\ No newline at end of file
+CFireManager &gFireManager = *(CFireManager*)0x8F31D0;
+
+WRAPPER void CFire::Extinguish(void) { EAXJMP(0x479D40); }
+
+WRAPPER void CFireManager::StartFire(CEntity *entityOnFire, CEntity *culprit, float, uint32) { EAXJMP(0x479590); }
diff --git a/src/core/Fire.h b/src/core/Fire.h
index c7f83fd8..9c9e1dec 100644
--- a/src/core/Fire.h
+++ b/src/core/Fire.h
@@ -1,13 +1,13 @@
 #pragma once
-#include "common.h"
-#include "Entity.h"
+
+class CEntity;
 
 class CFire
 {
-	char m_bIsOngoing;
-	char m_bExists;
-	char m_bPropogationFlag;
-	char m_bAudioSet;
+	bool m_bIsOngoing;
+	bool m_bExists;
+	bool m_bPropogationFlag;
+	bool m_bAudioSet;
 	CVector m_vecPos;
 	CEntity *m_pEntity;
 	CEntity *m_pSource;
@@ -20,4 +20,11 @@ class CFire
 
 public:
 	void Extinguish(void);
-};
\ No newline at end of file
+};
+
+class CFireManager
+{
+public:
+	void StartFire(CEntity *entityOnFire, CEntity *culprit, float, uint32);
+};
+extern CFireManager &gFireManager;
diff --git a/src/core/Frontend.cpp b/src/core/Frontend.cpp
index fdb2420b..2fa9d8ea 100644
--- a/src/core/Frontend.cpp
+++ b/src/core/Frontend.cpp
@@ -2261,7 +2261,7 @@ bool GetPadMoveUp()
 	return
 		(CPad::GetPad(0)->NewState.DPadUp && !CPad::GetPad(0)->OldState.DPadUp) ||
 		(CPad::GetPad(0)->NewKeyState.UP && !CPad::GetPad(0)->OldKeyState.UP) ||
-		(CPad::GetPad(0)->NewState.LeftStickY < 0 && !CPad::GetPad(0)->OldState.LeftStickY < 0);
+		(CPad::GetPad(0)->NewState.LeftStickY < 0 && !(CPad::GetPad(0)->OldState.LeftStickY < 0));
 }
 
 bool GetPadMoveDown()
@@ -2269,7 +2269,7 @@ bool GetPadMoveDown()
 	return 
 		(CPad::GetPad(0)->NewState.DPadDown && !CPad::GetPad(0)->OldState.DPadDown) ||
 		(CPad::GetPad(0)->NewKeyState.DOWN && !CPad::GetPad(0)->OldKeyState.DOWN) ||
-		(CPad::GetPad(0)->NewState.LeftStickY > 0 && !CPad::GetPad(0)->OldState.LeftStickY > 0);
+		(CPad::GetPad(0)->NewState.LeftStickY > 0 && !(CPad::GetPad(0)->OldState.LeftStickY > 0));
 }
 
 bool GetPadMoveLeft()
@@ -2277,7 +2277,7 @@ bool GetPadMoveLeft()
 	return
 		(CPad::GetPad(0)->NewState.DPadLeft && !CPad::GetPad(0)->OldState.DPadLeft) ||
 		(CPad::GetPad(0)->NewKeyState.LEFT && !CPad::GetPad(0)->OldKeyState.LEFT) ||
-		(CPad::GetPad(0)->NewState.LeftStickX < 0 && !CPad::GetPad(0)->OldState.LeftStickX < 0);
+		(CPad::GetPad(0)->NewState.LeftStickX < 0 && !(CPad::GetPad(0)->OldState.LeftStickX < 0));
 }
 
 bool GetPadMoveRight()
@@ -2285,7 +2285,7 @@ bool GetPadMoveRight()
 	return
 		(CPad::GetPad(0)->NewState.DPadRight && !CPad::GetPad(0)->OldState.DPadRight) ||
 		(CPad::GetPad(0)->NewKeyState.RIGHT && !CPad::GetPad(0)->OldKeyState.RIGHT) ||
-		(CPad::GetPad(0)->NewState.LeftStickX > 0 && !CPad::GetPad(0)->OldState.LeftStickX > 0);
+		(CPad::GetPad(0)->NewState.LeftStickX > 0 && !(CPad::GetPad(0)->OldState.LeftStickX > 0));
 }
 
 bool GetMouseForward()
diff --git a/src/core/Placeable.h b/src/core/Placeable.h
index 1dfece69..648b315c 100644
--- a/src/core/Placeable.h
+++ b/src/core/Placeable.h
@@ -17,6 +17,11 @@ public:
 	CMatrix &GetMatrix(void) { return m_matrix; }
 	void SetTransform(RwMatrix *m) { m_matrix = CMatrix(m, false); }
 	void SetHeading(float angle);
+	void SetOrientation(float x, float y, float z){
+		CVector pos = m_matrix.GetPosition();
+		m_matrix.SetRotate(x, y, z);
+		m_matrix.Translate(pos);
+	}
 	bool IsWithinArea(float x1, float y1, float x2, float y2);
 	bool IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2);
 };
diff --git a/src/core/Wanted.cpp b/src/core/Wanted.cpp
index 21853308..4608bfef 100644
--- a/src/core/Wanted.cpp
+++ b/src/core/Wanted.cpp
@@ -34,7 +34,7 @@ int CWanted::NumOfHelisRequired()
 		return 2;
 	default:
 		return 0;
-	};
+	}
 }
 
 void CWanted::SetWantedLevel(int32 level)
@@ -70,11 +70,17 @@ void CWanted::SetWantedLevel(int32 level)
 	UpdateWantedLevel();
 }
 
+void CWanted::SetWantedLevelNoDrop(int32 level)
+{
+	if (level > m_nWantedLevel)
+		SetWantedLevel(level);
+}
+
 void CWanted::ClearQdCrimes()
 {
 	for (int i = 0; i < 16; i++) {
 		m_sCrimes[i].m_eCrimeType = CRIME_NONE;
-	};
+	}
 }
 
 void CWanted::UpdateWantedLevel()
diff --git a/src/core/Wanted.h b/src/core/Wanted.h
index d14bb905..d3f6638b 100644
--- a/src/core/Wanted.h
+++ b/src/core/Wanted.h
@@ -42,6 +42,7 @@ public:
 	bool AreArmyRequired();
 	int NumOfHelisRequired();
 	void SetWantedLevel(int32);
+	void SetWantedLevelNoDrop(int32 level);
 	void ClearQdCrimes();
 	void UpdateWantedLevel();
 };
diff --git a/src/core/config.h b/src/core/config.h
index c3b66ff7..b1efd4b6 100644
--- a/src/core/config.h
+++ b/src/core/config.h
@@ -30,6 +30,8 @@ enum Config {
 	NUMDUMMIES = 2802, // 2368 on PS2
 	NUMAUDIOSCRIPTOBJECTS = 256,
 
+	NUMTEMPOBJECTS = 30,
+
 	// Link list lengths
 	// TODO: alpha list
 	NUMCOLCACHELINKS = 200,
diff --git a/src/core/re3.cpp b/src/core/re3.cpp
index d6e81214..f266ffab 100644
--- a/src/core/re3.cpp
+++ b/src/core/re3.cpp
@@ -154,6 +154,15 @@ spawnCar(int id)
 }
 #endif
 
+void
+FixCar(void)
+{
+	CVehicle *veh = FindPlayerVehicle();
+	if(veh == nil || !veh->IsCar())
+		return;
+	((CAutomobile*)veh)->Fix();
+}
+
 void
 DebugMenuPopulate(void)
 {
@@ -198,6 +207,7 @@ DebugMenuPopulate(void)
 		DebugMenuAddCmd("Cheats", "Strong grip", StrongGripCheat);
 		DebugMenuAddCmd("Cheats", "Nasty limbs", NastyLimbsCheat);
 
+		DebugMenuAddCmd("Debug", "Fix Car", FixCar);
 		DebugMenuAddVarBool8("Debug", "Show Ped Road Groups", (int8*)&gbShowPedRoadGroups, nil);
 		DebugMenuAddVarBool8("Debug", "Show Car Road Groups", (int8*)&gbShowCarRoadGroups, nil);
 		DebugMenuAddVarBool8("Debug", "Show Collision Polys", (int8*)&gbShowCollisionPolys, nil);
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 b2512ec2..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)
@@ -457,7 +457,7 @@ CPhysical::ApplySpringCollision(float springConst, CVector &springDir, CVector &
 	float compression = 1.0f - springRatio;
 	if(compression > 0.0f){
 		float step = min(CTimer::GetTimeStep(), 3.0f);
-		float impulse = -0.008f*m_fMass*step * springConst * compression * bias*2.0f;
+		float impulse = -GRAVITY*m_fMass*step * springConst * compression * bias*2.0f;
 		ApplyMoveForce(springDir*impulse);
 		ApplyTurnForce(springDir*impulse, point);
 	}
@@ -489,7 +489,7 @@ void
 CPhysical::ApplyGravity(void)
 {
 	if(bAffectedByGravity)
-		m_vecMoveSpeed.z -= 0.008f * CTimer::GetTimeStep();
+		m_vecMoveSpeed.z -= GRAVITY * CTimer::GetTimeStep();
 }
 
 void
@@ -1783,7 +1783,7 @@ CPhysical::ProcessShift(void)
 		}
 		bIsStuck = false;
 		bIsInSafePosition = true;
-		m_fDistanceTravelled = (GetPosition() - *matrix.GetPosition()).Magnitude();
+		m_fDistanceTravelled = (GetPosition() - matrix.GetPosition()).Magnitude();
 		RemoveAndAdd();
 	}
 }
@@ -1918,14 +1918,14 @@ 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;
 		}
 	}
 	bHitByTrain = false;
-	m_fDistanceTravelled = (GetPosition() - *savedMatrix.GetPosition()).Magnitude();
+	m_fDistanceTravelled = (GetPosition() - savedMatrix.GetPosition()).Magnitude();
 	m_phy_flagA80 = false;
 
 	bIsStuck = false;
diff --git a/src/entities/Physical.h b/src/entities/Physical.h
index c1f9f1d9..5bd98815 100644
--- a/src/entities/Physical.h
+++ b/src/entities/Physical.h
@@ -8,6 +8,8 @@ enum {
 	PHYSICAL_MAX_COLLISIONRECORDS = 6
 };
 
+#define GRAVITY (0.008f)
+
 class CTreadable;
 
 class CPhysical : public CEntity
@@ -59,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);
@@ -73,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);
@@ -108,7 +110,23 @@ public:
 	}
 
 	const CVector &GetMoveSpeed() { return m_vecMoveSpeed; }
+	void SetMoveSpeed(float x, float y, float z) {
+		m_vecMoveSpeed.x = x;
+		m_vecMoveSpeed.y = y;
+		m_vecMoveSpeed.z = z;
+	}
 	const CVector &GetTurnSpeed() { return m_vecTurnSpeed; }
+	void SetTurnSpeed(float x, float y, float z) {
+		m_vecTurnSpeed.x = x;
+		m_vecTurnSpeed.y = y;
+		m_vecTurnSpeed.z = z;
+	}
+	const CVector &GetCenterOfMass() { return m_vecCentreOfMass; }
+	void SetCenterOfMass(float x, float y, float z) {
+		m_vecCentreOfMass.x = x;
+		m_vecCentreOfMass.y = y;
+		m_vecCentreOfMass.z = z;
+	}
 
 	void ApplyMoveSpeed(void);
 	void ApplyTurnSpeed(void);
diff --git a/src/math/Matrix.h b/src/math/Matrix.h
index eda75e4a..b7d6c207 100644
--- a/src/math/Matrix.h
+++ b/src/math/Matrix.h
@@ -82,6 +82,32 @@ public:
 	CVector &GetRight(void) { return *(CVector*)&m_matrix.right; }
 	CVector &GetForward(void) { return *(CVector*)&m_matrix.up; }
 	CVector &GetUp(void) { return *(CVector*)&m_matrix.at; }
+
+	void SetTranslate(float x, float y, float z){
+		m_matrix.right.x = 1.0f;
+		m_matrix.right.y = 0.0f;
+		m_matrix.right.z = 0.0f;
+
+		m_matrix.up.x = 0.0f;
+		m_matrix.up.y = 1.0f;
+		m_matrix.up.z = 0.0f;
+
+		m_matrix.at.x = 0.0f;
+		m_matrix.at.y = 0.0f;
+		m_matrix.at.z = 1.0f;
+
+		m_matrix.pos.x = x;
+		m_matrix.pos.y = y;
+		m_matrix.pos.z = z;
+	}
+	void SetTranslate(const CVector &trans){ SetTranslate(trans.x, trans.y, trans.z); }
+	void Translate(float x, float y, float z){
+		m_matrix.pos.x += x;
+		m_matrix.pos.y += y;
+		m_matrix.pos.z += z;
+	}
+	void Translate(const CVector &trans){ Translate(trans.x, trans.y, trans.z); }
+
 	void SetScale(float s){
 		m_matrix.right.x = s;
 		m_matrix.right.y = 0.0f;
@@ -99,6 +125,7 @@ public:
 		m_matrix.pos.y = 0.0f;
 		m_matrix.pos.z = 0.0f;
 	}
+
 	void SetRotateXOnly(float angle){
 		float c = cos(angle);
 		float s = sin(angle);
diff --git a/src/modelinfo/VehicleModelInfo.cpp b/src/modelinfo/VehicleModelInfo.cpp
index 810ed042..9b2924e9 100644
--- a/src/modelinfo/VehicleModelInfo.cpp
+++ b/src/modelinfo/VehicleModelInfo.cpp
@@ -414,11 +414,13 @@ CVehicleModelInfo::SetAtomicFlagCB(RwObject *object, void *data)
 	return object;
 }
 
-RpAtomic*
-CVehicleModelInfo::ClearAtomicFlagCB(RpAtomic *atomic, void *data)
+RwObject*
+CVehicleModelInfo::ClearAtomicFlagCB(RwObject *object, void *data)
 {
+	RpAtomic *atomic = (RpAtomic*)object;
+	assert(RwObjectGetType(object) == rpATOMIC);
 	CVisibilityPlugins::ClearAtomicFlag(atomic, (int)data);
-	return atomic;
+	return object;
 }
 
 RwObject*
diff --git a/src/modelinfo/VehicleModelInfo.h b/src/modelinfo/VehicleModelInfo.h
index f66bc21f..37f47489 100644
--- a/src/modelinfo/VehicleModelInfo.h
+++ b/src/modelinfo/VehicleModelInfo.h
@@ -107,7 +107,7 @@ public:
 	void SetAtomicRenderCallbacks(void);
 
 	static RwObject *SetAtomicFlagCB(RwObject *object, void *data);
-	static RpAtomic *ClearAtomicFlagCB(RpAtomic *atomic, void *data);
+	static RwObject *ClearAtomicFlagCB(RwObject *atomic, void *data);
 	void SetVehicleComponentFlags(RwFrame *frame, uint32 flags);
 	void PreprocessHierarchy(void);
 	void GetWheelPosn(int32 n, CVector &pos);
diff --git a/src/objects/Object.cpp b/src/objects/Object.cpp
index e13a2ad0..f3ba8087 100644
--- a/src/objects/Object.cpp
+++ b/src/objects/Object.cpp
@@ -7,8 +7,10 @@
 #include "Object.h"
 
 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); }
@@ -85,7 +87,13 @@ CObject::RemoveLighting(bool reset)
 		WorldReplaceScorchedLightsWithNormal(Scene.world);
 }
 
-WRAPPER void CObject::DeleteAllTempObjectInArea(CVector, float) { EAXJMP(0x4BBED0); }
+
+void
+CObject::RefModelInfo(int32 modelId)
+{
+	m_nRefModelIndex = modelId;
+	CModelInfo::GetModelInfo(modelId)->AddRef();
+}
 
 class CObject_ : public CObject
 {
diff --git a/src/objects/Object.h b/src/objects/Object.h
index 8afc4d0a..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);
@@ -72,6 +73,7 @@ public:
 	void RemoveLighting(bool reset);
 
 	void ObjectDamage(float amount);
+	void RefModelInfo(int32 modelId);
 
 	static void DeleteAllTempObjectInArea(CVector, float);
 };
diff --git a/src/peds/Ped.cpp b/src/peds/Ped.cpp
index 692fc4f2..28cb8823 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"
@@ -27,6 +28,7 @@
 WRAPPER void CPed::KillPedWithCar(CVehicle *veh, float impulse) { EAXJMP(0x4EC430); }
 WRAPPER void CPed::Say(uint16 audio) { EAXJMP(0x4E5A10); }
 WRAPPER void CPed::SetDie(AnimationId anim, float arg1, float arg2) { EAXJMP(0x4D37D0); }
+WRAPPER void CPed::SetDead(void) { EAXJMP(0x4D3970); }
 WRAPPER void CPed::SpawnFlyingComponent(int, int8) { EAXJMP(0x4EB060); }
 WRAPPER void CPed::RestorePreviousState(void) { EAXJMP(0x4C5E30); }
 WRAPPER void CPed::ClearAttack(void) { EAXJMP(0x4E6790); }
@@ -430,7 +432,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;
@@ -669,7 +671,7 @@ CPed::AimGun(void)
 			vector.y = pos.y;
 			vector.z = pos.z;
 		} else {
-			vector = *(m_pSeekTarget->GetPosition());
+			vector = m_pSeekTarget->GetPosition();
 		}
 		Say(SOUND_PED_ATTACK);
 
@@ -1540,7 +1542,7 @@ CPed::GetPositionToOpenCarDoor(CVector *output, CVehicle *veh, uint32 enterType,
 	GetLocalPositionToOpenCarDoor(output, veh, enterType, offset);
 	doorPos = Multiply3x3(vehMat, *output);
 
-	*output = *veh->GetPosition() + doorPos;
+	*output = veh->GetPosition() + doorPos;
 }
 
 void
@@ -1659,7 +1661,7 @@ CPed::LineUpPedWithCar(PedLineUpPhase phase)
 	CVector neededPos;
 
 	if (phase == LINE_UP_TO_CAR_2) {
-		neededPos = *GetPosition();
+		neededPos = GetPosition();
 	} else {
 		GetPositionToOpenCarDoor(&neededPos, veh, m_vehEnterType, seatPosMult);
 	}
@@ -1772,12 +1774,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 +1881,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;
@@ -2087,7 +2089,7 @@ CPed::CalculateNewOrientation(void)
 	if (CReplay::IsPlayingBack() || !IsPedInControl())
 		return;
 
-	CVector pos = *GetPosition();
+	CVector pos = GetPosition();
 
 	GetMatrix().SetRotate(0.0f, 0.0f, m_fRotationCur);
 	
diff --git a/src/peds/Ped.h b/src/peds/Ped.h
index 2390d1d4..f48c30ba 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;
@@ -414,6 +414,7 @@ public:
 	void SetLookFlag(float direction, bool unknown);
 	void SetLookTimer(int time);
 	void SetDie(AnimationId anim, float arg1, float arg2);
+	void SetDead(void);
 	void ApplyHeadShot(eWeaponType weaponType, CVector pos, bool evenOnPlayer);
 	void RemoveBodyPart(PedNode nodeId, int8 unknown);
 	void SpawnFlyingComponent(int, int8 unknown);
diff --git a/src/peds/PlayerPed.cpp b/src/peds/PlayerPed.cpp
index 9005e29f..4b484a7f 100644
--- a/src/peds/PlayerPed.cpp
+++ b/src/peds/PlayerPed.cpp
@@ -24,6 +24,19 @@ void CPlayerPed::ClearWeaponTarget()
 	ClearPointGunAt();
 }
 
+void
+CPlayerPed::SetWantedLevel(int32 level)
+{
+	m_pWanted->SetWantedLevel(level);
+}
+
+void
+CPlayerPed::SetWantedLevelNoDrop(int32 level)
+{
+	m_pWanted->SetWantedLevelNoDrop(level);
+}
+
+
 class CPlayerPed_ : public CPlayerPed
 {
 public:
diff --git a/src/peds/PlayerPed.h b/src/peds/PlayerPed.h
index 25bbdf4a..51a45203 100644
--- a/src/peds/PlayerPed.h
+++ b/src/peds/PlayerPed.h
@@ -43,6 +43,8 @@ public:
 
 	void ReApplyMoveAnims(void);
 	void ClearWeaponTarget();
+	void SetWantedLevel(int32 level);
+	void SetWantedLevelNoDrop(int32 level);
 
 	static void SetupPlayerPed(int32);
 	static void DeactivatePlayerPed(int32);
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 c06c958c..54557dc2 100644
--- a/src/vehicles/Automobile.cpp
+++ b/src/vehicles/Automobile.cpp
@@ -1,10 +1,25 @@
 #include "common.h"
 #include "patcher.h"
+#include "General.h"
+#include "ModelIndices.h"
 #include "VisibilityPlugins.h"
+#include "DMAudio.h"
+#include "Camera.h"
+#include "Darkel.h"
+#include "Fire.h"
+#include "Explosion.h"
+#include "World.h"
 #include "SurfaceTable.h"
 #include "HandlingMgr.h"
+#include "CarCtrl.h"
+#include "PathFind.h"
+#include "Ped.h"
+#include "PlayerPed.h"
+#include "Object.h"
 #include "Automobile.h"
 
+RwObject *GetCurrentAtomicObjectCB(RwObject *object, void *data);
+
 bool &CAutomobile::m_sAllTaxiLights = *(bool*)0x95CD21;
 
 WRAPPER CAutomobile* CAutomobile::ctor(int, uint8) { EAXJMP(0x52C6B0); }
@@ -23,11 +38,110 @@ CAutomobile::SetModelIndex(uint32 id)
 }
 
 WRAPPER void CAutomobile::ProcessControl(void) { EAXJMP(0x531470); }
-WRAPPER void CAutomobile::Teleport(CVector v) { EAXJMP(0x535180); }
+
+void
+CAutomobile::Teleport(CVector pos)
+{
+	CWorld::Remove(this);
+
+	GetPosition() = pos;
+	SetOrientation(0.0f, 0.0f, 0.0f);
+	SetMoveSpeed(0.0f, 0.0f, 0.0f);
+	SetTurnSpeed(0.0f, 0.0f, 0.0f);
+
+	ResetSuspension();
+
+	CWorld::Add(this);
+}
+
 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
@@ -51,17 +165,161 @@ void
 CAutomobile::SetComponentRotation(int32 component, CVector rotation)
 {
 	CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component]));
-	CVector pos = *mat.GetPosition();
+	CVector pos = mat.GetPosition();
 	// BUG: all these set the whole matrix
 	mat.SetRotateX(DEGTORAD(rotation.x));
 	mat.SetRotateY(DEGTORAD(rotation.y));
 	mat.SetRotateZ(DEGTORAD(rotation.z));
-	mat.GetPosition() += pos;
+	mat.Translate(pos);
 	mat.UpdateRW();
 }
 
-WRAPPER void CAutomobile::OpenDoor(int32, eDoors door, float) { EAXJMP(0x52E750); }
-WRAPPER void CAutomobile::ProcessOpenDoor(uint32, uint32, float) { EAXJMP(0x52E910); }
+void
+CAutomobile::OpenDoor(int32 component, eDoors door, float openRatio)
+{
+	CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component]));
+	CVector pos = mat.GetPosition();
+	float axes[3] = { 0.0f, 0.0f, 0.0f };
+	float wasClosed = false;
+
+	if(Doors[door].IsClosed()){
+		// enable angle cull for closed doors
+		RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::ClearAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL);
+		wasClosed = true;
+	}
+
+	Doors[door].Open(openRatio);
+
+	if(wasClosed && Doors[door].RetAngleWhenClosed() != Doors[door].m_fAngle){
+		// door opened
+		HideAllComps();
+		// turn off angle cull for swinging door
+		RwFrameForAllObjects(m_aCarNodes[component], CVehicleModelInfo::SetAtomicFlagCB, (void*)ATOMIC_FLAG_NOCULL);
+		DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_DOOR_OPEN_BONNET + door, 0.0f);
+	}
+
+	if(!wasClosed && openRatio == 0.0f){
+		// door closed
+		if(Damage.GetDoorStatus(door) == DOOR_STATUS_SWINGING)
+			Damage.SetDoorStatus(door, DOOR_STATUS_OK);	// huh?
+		ShowAllComps();
+		DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_DOOR_CLOSE_BONNET + door, 0.0f);
+	}
+
+	axes[Doors[door].m_nAxis] = Doors[door].m_fAngle;
+	mat.SetRotate(axes[0], axes[1], axes[2]);
+	mat.Translate(pos);
+	mat.UpdateRW();
+}
+
+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)
@@ -106,7 +364,88 @@ CAutomobile::RemoveRefsToVehicle(CEntity *ent)
 			m_aGroundPhysical[i] = nil;
 }
 
-WRAPPER void CAutomobile::BlowUpCar(CEntity *ent) { EAXJMP(0x53BC60); }
+void
+CAutomobile::BlowUpCar(CEntity *culprit)
+{
+	int i;
+	RpAtomic *atomic;
+
+	if(!bCanBeDamaged)
+		return;
+
+	// explosion pushes vehicle up
+	m_vecMoveSpeed.z += 0.13f;
+	m_status = STATUS_WRECKED;
+	bRenderScorched = true;
+	m_nTimeOfDeath = CTimer::GetTimeInMilliseconds();
+	Damage.FuckCarCompletely();
+
+	if(GetModelIndex() != MI_RCBANDIT){
+		SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT);
+		SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR);
+		SetDoorDamage(CAR_BONNET, DOOR_BONNET);
+		SetDoorDamage(CAR_BOOT, DOOR_BOOT);
+		SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT);
+		SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT);
+		SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT);
+		SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT);
+		SpawnFlyingComponent(CAR_WHEEL_LF, COMPGROUP_WHEEL);
+		RwFrameForAllObjects(m_aCarNodes[CAR_WHEEL_LF], GetCurrentAtomicObjectCB, &atomic);
+		if(atomic)
+			RpAtomicSetFlags(atomic, 0);
+	}
+
+	m_fHealth = 0.0f;
+	m_nBombTimer = 0;
+	m_auto_flagA1 = false;
+	m_auto_flagA2 = false;
+	m_auto_flagA4 = false;
+
+	TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z);
+
+	// kill driver and passengers
+	if(pDriver){
+		CDarkel::RegisterKillByPlayer(pDriver, WEAPONTYPE_EXPLOSION);
+		if(pDriver->GetPedState() == PED_DRIVING){
+			pDriver->SetDead();
+			if(!pDriver->IsPlayer())
+				pDriver->FlagToDestroyWhenNextProcessed();
+		}else
+			pDriver->SetDie(ANIM_KO_SHOT_FRONT1, 4.0f, 0.0f);
+	}
+	for(i = 0; i < m_nNumMaxPassengers; i++){
+		if(pPassengers[i]){
+			CDarkel::RegisterKillByPlayer(pPassengers[i], WEAPONTYPE_EXPLOSION);
+			if(pPassengers[i]->GetPedState() == PED_DRIVING){
+				pPassengers[i]->SetDead();
+				if(!pPassengers[i]->IsPlayer())
+					pPassengers[i]->FlagToDestroyWhenNextProcessed();
+			}else
+				pPassengers[i]->SetDie(ANIM_KO_SHOT_FRONT1, 4.0f, 0.0f);
+		}
+	}
+
+	bEngineOn = false;
+	bLightsOn = false;
+	m_bSirenOrAlarm = false;
+	bTaxiLight = false;
+	if(bIsAmbulanceOnDuty){
+		bIsAmbulanceOnDuty = false;
+		CCarCtrl::NumAmbulancesOnDuty--;
+	}
+	if(bIsFireTruckOnDuty){
+		bIsFireTruckOnDuty = false;
+		CCarCtrl::NumFiretrucksOnDuty--;
+	}
+	ChangeLawEnforcerState(false);
+
+	gFireManager.StartFire(this, culprit, 0.8f, 1);	// TODO
+	CDarkel::RegisterCarBlownUpByPlayer(this);
+	if(GetModelIndex() == MI_RCBANDIT)
+		CExplosion::AddExplosion(this, culprit, EXPLOSION_4, GetPosition(), 0);	// TODO
+	else
+		CExplosion::AddExplosion(this, culprit, EXPLOSION_3, GetPosition(), 0);	// TODO
+}
 
 bool
 CAutomobile::SetUpWheelColModel(CColModel *colModel)
@@ -139,7 +478,31 @@ CAutomobile::SetUpWheelColModel(CColModel *colModel)
 	return true;
 }
 
-WRAPPER void CAutomobile::BurstTyre(uint8 tyre) { EAXJMP(0x53C0E0); }
+// this probably isn't used in III yet
+void
+CAutomobile::BurstTyre(uint8 wheel)
+{
+	switch(wheel){
+	case CAR_PIECE_WHEEL_LF: wheel = VEHWHEEL_FRONT_LEFT; break;
+	case CAR_PIECE_WHEEL_LR: wheel = VEHWHEEL_REAR_LEFT; break;
+	case CAR_PIECE_WHEEL_RF: wheel = VEHWHEEL_FRONT_RIGHT; break;
+	case CAR_PIECE_WHEEL_RR: wheel = VEHWHEEL_REAR_RIGHT; break;
+	}
+
+	int status = Damage.GetWheelStatus(wheel);
+	if(status == WHEEL_STATUS_OK){
+		Damage.SetWheelStatus(wheel, WHEEL_STATUS_BURST);
+
+		if(m_status == STATUS_SIMPLE){
+			m_status = STATUS_PHYSICS;
+			CCarCtrl::SwitchVehicleToRealPhysics(this);
+		}
+
+		ApplyMoveForce(GetRight() * CGeneral::GetRandomNumberInRange(-0.3f, 0.3f));
+		ApplyTurnForce(GetRight() * CGeneral::GetRandomNumberInRange(-0.3f, 0.3f), GetForward());
+	}
+}
+
 WRAPPER bool CAutomobile::IsRoomForPedToLeaveCar(uint32, CVector *) { EAXJMP(0x53C5B0); }
 
 float
@@ -148,13 +511,437 @@ CAutomobile::GetHeightAboveRoad(void)
 	return m_fHeightAboveRoad;
 }
 
-WRAPPER void CAutomobile::PlayCarHorn(void) { EAXJMP(0x53C450); }
+void
+CAutomobile::PlayCarHorn(void)
+{
+	int r;
+
+	if(m_nCarHornTimer != 0)
+		return;
+
+	r = CGeneral::GetRandomNumber() & 7;
+	if(r < 2){
+		m_nCarHornTimer = 45;
+	}else if(r < 4){
+		if(pDriver)
+			pDriver->Say(SOUND_PED_CAR_COLLISION);
+		m_nCarHornTimer = 45;
+	}else{
+		if(pDriver)
+			pDriver->Say(SOUND_PED_CAR_COLLISION);
+	}
+}
+
+void
+CAutomobile::PlayHornIfNecessary(void)
+{
+	// TODO: flags
+	if(m_autoPilot.m_nCarCtrlFlags & 2 ||
+	   m_autoPilot.m_nCarCtrlFlags & 1)
+		if(!HasCarStoppedBecauseOfLight())
+			PlayCarHorn();
+}
 
 
+void
+CAutomobile::ResetSuspension(void)
+{
+	int i;
+	for(i = 0; i < 4; i++){
+		m_aSuspensionSpringRatio[i] = 1.0f;
+		m_aWheelSkidThing[i] = 0.0f;
+		m_aWheelRotation[i] = 0.0f;
+		m_aWheelState[i] = 0;	// TODO: enum?
+	}
+}
 
+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;
 
-WRAPPER void CAutomobile::SpawnFlyingComponent(int32 component, uint32 type) { EAXJMP(0x530300); }
+		// 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;
+	}
+}
+
+// called on police cars
+void
+CAutomobile::ScanForCrimes(void)
+{
+	if(FindPlayerVehicle() && FindPlayerVehicle()->IsCar())
+		if(FindPlayerVehicle()->m_nAlarmState != -1)
+			// if player's alarm is on, increase wanted level
+			if((FindPlayerVehicle()->GetPosition() - GetPosition()).MagnitudeSqr() < sq(20.0f))
+				CWorld::Players[CWorld::PlayerInFocus].m_pPed->SetWantedLevelNoDrop(1);
+}
+
+void
+CAutomobile::BlowUpCarsInPath(void)
+{
+	int i;
+
+	if(m_vecMoveSpeed.Magnitude() > 0.1f)
+		for(i = 0; i < m_nCollisionRecords; i++)
+			if(m_aCollisionRecords[i] &&
+			   m_aCollisionRecords[i]->IsVehicle() &&
+			   m_aCollisionRecords[i]->GetModelIndex() != MI_RHINO &&
+			   !m_aCollisionRecords[i]->bRenderScorched)
+				((CVehicle*)m_aCollisionRecords[i])->BlowUpCar(this);
+}
+
+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)
+{
+	if(Damage.GetDoorStatus(door) != DOOR_STATUS_SWINGING)
+		return;
+
+	CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component]));
+	CVector pos = mat.GetPosition();
+	float axes[3] = { 0.0f, 0.0f, 0.0f };
+
+	Doors[door].Process(this);
+	axes[Doors[door].m_nAxis] = Doors[door].m_fAngle;
+	mat.SetRotate(axes[0], axes[1], axes[2]);
+	mat.Translate(pos);
+	mat.UpdateRW();
+}
+
+void
+CAutomobile::Fix(void)
+{
+	int component;
+
+	Damage.ResetDamageStatus();
+
+	if(m_handling->Flags & HANDLING_NO_DOORS){
+		Damage.SetDoorStatus(DOOR_FRONT_LEFT, DOOR_STATUS_MISSING);
+		Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_MISSING);
+		Damage.SetDoorStatus(DOOR_REAR_LEFT, DOOR_STATUS_MISSING);
+		Damage.SetDoorStatus(DOOR_REAR_RIGHT, DOOR_STATUS_MISSING);
+	}
+
+	bIsDamaged = false;
+	RpClumpForAllAtomics((RpClump*)m_rwObject, CVehicleModelInfo::HideAllComponentsAtomicCB, (void*)ATOMIC_FLAG_DAM);
+
+	for(component = CAR_BUMP_FRONT; component < NUM_CAR_NODES; component++){
+		if(m_aCarNodes[component]){
+			CMatrix mat(RwFrameGetMatrix(m_aCarNodes[component]));
+			mat.SetTranslate(mat.GetPosition());
+			mat.UpdateRW();
+		}
+	}
+}
+
+void
+CAutomobile::SetupDamageAfterLoad(void)
+{
+	if(m_aCarNodes[CAR_BUMP_FRONT])
+		SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT);
+	if(m_aCarNodes[CAR_BONNET])
+		SetDoorDamage(CAR_BONNET, DOOR_BONNET);
+	if(m_aCarNodes[CAR_BUMP_REAR])
+		SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR);
+	if(m_aCarNodes[CAR_BOOT])
+		SetDoorDamage(CAR_BOOT, DOOR_BOOT);
+	if(m_aCarNodes[CAR_DOOR_LF])
+		SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT);
+	if(m_aCarNodes[CAR_DOOR_RF])
+		SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT);
+	if(m_aCarNodes[CAR_DOOR_LR])
+		SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT);
+	if(m_aCarNodes[CAR_DOOR_RR])
+		SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT);
+	if(m_aCarNodes[CAR_WING_LF])
+		SetPanelDamage(CAR_WING_LF, VEHPANEL_FRONT_LEFT);
+	if(m_aCarNodes[CAR_WING_RF])
+		SetPanelDamage(CAR_WING_RF, VEHPANEL_FRONT_RIGHT);
+	if(m_aCarNodes[CAR_WING_LR])
+		SetPanelDamage(CAR_WING_LR, VEHPANEL_REAR_LEFT);
+	if(m_aCarNodes[CAR_WING_RR])
+		SetPanelDamage(CAR_WING_RR, VEHPANEL_REAR_RIGHT);
+}
+
+RwObject*
+GetCurrentAtomicObjectCB(RwObject *object, void *data)
+{
+	RpAtomic *atomic = (RpAtomic*)object;
+	assert(RwObjectGetType(object) == rpATOMIC);
+	if(RpAtomicGetFlags(atomic) & rpATOMICRENDER)
+		*(RpAtomic**)data = atomic;
+	return object;
+}
+
+CColPoint aTempPedColPts[32];	// this name doesn't make any sense
+
+CObject*
+CAutomobile::SpawnFlyingComponent(int32 component, uint32 type)
+{
+	RpAtomic *atomic;
+	RwFrame *frame;
+	RwMatrix *matrix;
+	CObject *obj;
+
+	if(CObject::nNoTempObjects >= NUMTEMPOBJECTS)
+		return nil;
+
+	atomic = nil;
+	RwFrameForAllObjects(m_aCarNodes[component], GetCurrentAtomicObjectCB, &atomic);
+	if(atomic == nil)
+		return nil;
+
+	obj = new CObject;
+	if(obj == nil)
+		return nil;
+
+	if(component == CAR_WINDSCREEN){
+		obj->SetModelIndexNoCreate(MI_CAR_BONNET);
+	}else switch(type){
+	case COMPGROUP_BUMPER:
+		obj->SetModelIndexNoCreate(MI_CAR_BUMPER);
+		break;
+	case COMPGROUP_WHEEL:
+		obj->SetModelIndexNoCreate(MI_CAR_WHEEL);
+		break;
+	case COMPGROUP_DOOR:
+		obj->SetModelIndexNoCreate(MI_CAR_DOOR);
+		obj->SetCenterOfMass(0.0f, -0.5f, 0.0f);
+		break;
+	case COMPGROUP_BONNET:
+		obj->SetModelIndexNoCreate(MI_CAR_BONNET);
+		obj->SetCenterOfMass(0.0f, 0.4f, 0.0f);
+		break;
+	case COMPGROUP_BOOT:
+		obj->SetModelIndexNoCreate(MI_CAR_BOOT);
+		obj->SetCenterOfMass(0.0f, -0.3f, 0.0f);
+		break;
+	case COMPGROUP_PANEL:
+	default:
+		obj->SetModelIndexNoCreate(MI_CAR_PANEL);
+		break;
+	}
+
+	// object needs base model
+	obj->RefModelInfo(GetModelIndex());
+
+	// create new atomic
+	matrix = RwFrameGetLTM(m_aCarNodes[component]);
+	frame = RwFrameCreate();
+	atomic = RpAtomicClone(atomic);
+	*RwFrameGetMatrix(frame) = *matrix;
+	RpAtomicSetFrame(atomic, frame);
+	CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil);
+	obj->AttachToRwObject((RwObject*)atomic);
+
+	// init object
+	obj->m_fMass = 10.0f;
+	obj->m_fTurnMass = 25.0f;
+	obj->m_fAirResistance = 0.97f;
+	obj->m_fElasticity = 0.1f;
+	obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f;
+	obj->ObjectCreatedBy = TEMP_OBJECT;
+	obj->bIsStatic = true;
+	obj->bIsPickup = false;
+	obj->bUseVehicleColours = true;
+	obj->m_colour1 = m_currentColour1;
+	obj->m_colour2 = m_currentColour2;
+
+	// life time - the more objects the are, the shorter this one will live
+	CObject::nNoTempObjects++;
+	if(CObject::nNoTempObjects > 20)
+		obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000/5.0f;
+	else if(CObject::nNoTempObjects > 10)
+		obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000/2.0f;
+	else
+		obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000;
+
+	obj->m_vecMoveSpeed = m_vecMoveSpeed;
+	if(obj->m_vecMoveSpeed.z > 0.0f){
+		obj->m_vecMoveSpeed.z *= 1.5f;
+	}else if(GetUp().z > 0.0f &&
+	         (component == COMPGROUP_BONNET || component == COMPGROUP_BOOT || component == CAR_WINDSCREEN)){
+		obj->m_vecMoveSpeed.z *= -1.5f;
+		obj->m_vecMoveSpeed.z += 0.04f;
+	}else{
+		obj->m_vecMoveSpeed.z *= 0.25f;
+	}
+	obj->m_vecMoveSpeed.x *= 0.75f;
+	obj->m_vecMoveSpeed.y *= 0.75f;
+
+	obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f;
+
+	// push component away from car
+	CVector dist = obj->GetPosition() - GetPosition();
+	dist.Normalise();
+	if(component == COMPGROUP_BONNET || component == COMPGROUP_BOOT || component == CAR_WINDSCREEN){
+		// push these up some
+		dist += GetUp();
+		if(GetUp().z > 0.0f){
+			// simulate fast upward movement if going fast
+			float speed = CVector2D(m_vecMoveSpeed).MagnitudeSqr();
+			obj->GetPosition() += GetUp()*speed;
+		}
+	}
+	obj->ApplyMoveForce(dist);
+
+	if(type == COMPGROUP_WHEEL){
+		obj->m_fTurnMass = 5.0f;
+		obj->m_vecTurnSpeed.x = 0.5f;
+		obj->m_fAirResistance = 0.99f;
+	}
+
+	if(CCollision::ProcessColModels(obj->GetMatrix(), *obj->GetColModel(),
+			this->GetMatrix(), *this->GetColModel(),
+			aTempPedColPts, nil, nil) > 0)
+		obj->m_pCollidingEntity = this;
+
+	if(bRenderScorched)
+		obj->bRenderScorched = true;
+
+	CWorld::Add(obj);
+
+	return obj;
+}
+
+CObject*
+CAutomobile::RemoveBonnetInPedCollision(void)
+{
+	CObject *obj;
+
+	if(Damage.GetDoorStatus(DOOR_BONNET) != DOOR_STATUS_SWINGING &&
+	   Doors[DOOR_BONNET].RetAngleWhenOpen()*0.4f < Doors[DOOR_BONNET].m_fAngle){
+		// BUG? why not COMPGROUP_BONNET?
+		obj = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_DOOR);
+		// make both doors invisible on car
+		SetComponentVisibility(m_aCarNodes[CAR_BONNET], ATOMIC_FLAG_NONE);
+		Damage.SetDoorStatus(DOOR_BONNET, DOOR_STATUS_MISSING);
+		return obj;
+	}
+	return nil;
+}
 
 void
 CAutomobile::SetPanelDamage(int32 component, ePanels panel, bool noFlyingComponents)
@@ -245,7 +1032,7 @@ void
 CAutomobile::SetComponentVisibility(RwFrame *frame, uint32 flags)
 {
 	HideAllComps();
-	m_veh_flagC2 = true;
+	bIsDamaged = true;
 	RwFrameForAllObjects(frame, SetVehicleAtomicVisibilityCB, (void*)flags);
 }
 
@@ -305,6 +1092,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); }
@@ -327,16 +1116,33 @@ public:
 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);
+	InjectHook(0x52E750, &CAutomobile_::OpenDoor_, PATCH_JUMP);
 	InjectHook(0x52EF10, &CAutomobile_::IsDoorReady_, PATCH_JUMP);
 	InjectHook(0x52EF90, &CAutomobile_::IsDoorFullyOpen_, PATCH_JUMP);
 	InjectHook(0x52EFD0, &CAutomobile_::IsDoorClosed_, PATCH_JUMP);
 	InjectHook(0x52F000, &CAutomobile_::IsDoorMissing_, PATCH_JUMP);
 	InjectHook(0x53BF40, &CAutomobile_::RemoveRefsToVehicle_, PATCH_JUMP);
+	InjectHook(0x53BC60, &CAutomobile_::BlowUpCar_, PATCH_JUMP);
 	InjectHook(0x53BF70, &CAutomobile_::SetUpWheelColModel_, PATCH_JUMP);
+	InjectHook(0x53C0E0, &CAutomobile_::BurstTyre_, PATCH_JUMP);
 	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(0x53E000, &CAutomobile::BlowUpCarsInPath, 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);
+	InjectHook(0x530300, &CAutomobile::SpawnFlyingComponent, PATCH_JUMP);
+	InjectHook(0x535320, &CAutomobile::RemoveBonnetInPedCollision, PATCH_JUMP);
 	InjectHook(0x5301A0, &CAutomobile::SetPanelDamage, PATCH_JUMP);
 	InjectHook(0x530120, &CAutomobile::SetBumperDamage, PATCH_JUMP);
 	InjectHook(0x530200, &CAutomobile::SetDoorDamage, PATCH_JUMP);
diff --git a/src/vehicles/Automobile.h b/src/vehicles/Automobile.h
index a9def14f..0e9bd945 100644
--- a/src/vehicles/Automobile.h
+++ b/src/vehicles/Automobile.h
@@ -4,6 +4,8 @@
 #include "DamageManager.h"
 #include "Door.h"
 
+class CObject;
+
 class CAutomobile : public CVehicle
 {
 public:
@@ -64,12 +66,15 @@ 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);
 	bool IsComponentPresent(int32 component);
 	void SetComponentRotation(int32 component, CVector rotation);
-	void OpenDoor(int32, eDoors door, float);
+	void OpenDoor(int32 component, eDoors door, float openRatio);
 	void ProcessOpenDoor(uint32, uint32, float);
 	bool IsDoorReady(eDoors door);
 	bool IsDoorFullyOpen(eDoors door);
@@ -83,12 +88,23 @@ public:
 	float GetHeightAboveRoad(void);
 	void PlayCarHorn(void);
 
-	void SpawnFlyingComponent(int32 component, uint32 type);
-
-	void SetPanelDamage(int32 component, ePanels panel, bool noFlyingComponents);
-	void SetBumperDamage(int32 component, ePanels panel, bool noFlyingComponents);
-	void SetDoorDamage(int32 component, eDoors door, bool noFlyingComponents);
+	void PlayHornIfNecessary(void);
+	void ResetSuspension(void);
+	void SetupSuspensionLines(void);
+	void ScanForCrimes(void);
+	void BlowUpCarsInPath(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);
+	void SetPanelDamage(int32 component, ePanels panel, bool noFlyingComponents = false);
+	void SetBumperDamage(int32 component, ePanels panel, bool noFlyingComponents = false);
+	void SetDoorDamage(int32 component, eDoors door, bool noFlyingComponents = false);
 
+	void Fix(void);
 	void SetComponentVisibility(RwFrame *frame, uint32 flags);
 	void SetupModelNodes(void);
 	void SetTaxiLight(bool light);
diff --git a/src/vehicles/DamageManager.h b/src/vehicles/DamageManager.h
index 0914ded3..b815f724 100644
--- a/src/vehicles/DamageManager.h
+++ b/src/vehicles/DamageManager.h
@@ -20,6 +20,12 @@ enum ePanelStatus
 	PANEL_STATUS_MISSING,
 };
 
+enum eWheelStatus
+{
+	WHEEL_STATUS_OK,
+	WHEEL_STATUS_BURST
+};
+
 enum tComponent
 {
 	COMPONENT_DEFAULT,
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 1e70d171..cd877da5 100644
--- a/src/vehicles/Vehicle.h
+++ b/src/vehicles/Vehicle.h
@@ -88,6 +88,14 @@ enum eLights
 	VEHLIGHT_REAR_RIGHT,
 };
 
+enum eWheels
+{
+	VEHWHEEL_FRONT_LEFT,
+	VEHWHEEL_FRONT_RIGHT,
+	VEHWHEEL_REAR_LEFT,
+	VEHWHEEL_REAR_RIGHT,
+};
+
 enum
 {
 	CAR_PIECE_WHEEL_LF = 13,
@@ -115,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;
@@ -142,19 +150,19 @@ public:
 	uint8 m_veh_flagB80 : 1;
 
 	uint8 m_veh_flagC1 : 1;
-	uint8 m_veh_flagC2 : 1;		// bIsDamaged
+	uint8 bIsDamaged : 1; // This vehicle has been damaged and is displaying all its components
 	uint8 m_veh_flagC4 : 1;
 	uint8 m_veh_flagC8 : 1;
 	uint8 m_veh_flagC10 : 1;
 	uint8 m_veh_flagC20 : 1;
-	uint8 m_veh_flagC40 : 1;
+	uint8 bCanBeDamaged : 1; // Set to FALSE during cut scenes to avoid explosions
 	uint8 m_veh_flagC80 : 1;
 
 	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;// 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
 	uint8 m_veh_flagD20 : 1;
 	uint8 m_veh_flagD40 : 1;
 	uint8 m_veh_flagD80 : 1;
@@ -181,7 +189,7 @@ public:
 	int8 field_22B;
 	uint8 m_nCarHornTimer;
 	int8 field_22D;
-	uint8 m_nSirenOrAlarm;
+	bool m_bSirenOrAlarm;
 	int8 field_22F;
 	// TODO: this is an array
 	CStoredCollPoly m_frontCollPoly;     // poly which is under front part of car
@@ -255,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");