code:
#pragma once

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

class CObject;

enum eCarNodes
{
	CAR_WHEEL_RF = 1,
	CAR_WHEEL_RM,
	CAR_WHEEL_RB,
	CAR_WHEEL_LF,
	CAR_WHEEL_LM,
	CAR_WHEEL_LB,
	CAR_BUMP_FRONT,
	CAR_BUMP_REAR,
	CAR_WING_RF,
	CAR_WING_RR,
	CAR_DOOR_RF,
	CAR_DOOR_RR,
	CAR_WING_LF,
	CAR_WING_LR,
	CAR_DOOR_LF,
	CAR_DOOR_LR,
	CAR_BONNET,
	CAR_BOOT,
	CAR_WINDSCREEN,
	NUM_CAR_NODES,
};

enum {
	CARWHEEL_FRONT_LEFT,
	CARWHEEL_REAR_LEFT,
	CARWHEEL_FRONT_RIGHT,
	CARWHEEL_REAR_RIGHT
};

enum eBombType
{
	CARBOMB_NONE,
	CARBOMB_TIMED,
	CARBOMB_ONIGNITION,
	CARBOMB_REMOTE,
	CARBOMB_TIMEDACTIVE,
	CARBOMB_ONIGNITIONACTIVE,
};

enum {
	CAR_DOOR_FLAG_UNKNOWN = 0x0,
	CAR_DOOR_FLAG_LF = 0x1,
	CAR_DOOR_FLAG_LR = 0x2,
	CAR_DOOR_FLAG_RF = 0x4,
	CAR_DOOR_FLAG_RR = 0x8
};

class CAutomobile : public CVehicle
{
public:
	// 0x288
	CDamageManager Damage;
	CDoor Doors[6];
	RwFrame *m_aCarNodes[NUM_CAR_NODES];
	CColPoint m_aWheelColPoints[4];
	float m_aSuspensionSpringRatio[4];
	float m_aSuspensionSpringRatioPrev[4];
	float m_aWheelTimer[4];		// set to 4.0 when wheel is touching ground, then decremented
	float m_auto_unused1;
	bool m_aWheelSkidmarkMuddy[4];
	bool m_aWheelSkidmarkBloody[4];
	float m_aWheelRotation[4];
	float m_aWheelPosition[4];
	float m_aWheelSpeed[4];
	uint8 m_auto_unused2;
	uint8 m_bombType : 3;
	uint8 bTaxiLight : 1;
	uint8 bDriverLastFrame  : 1;		// for bombs
	uint8 bFixedColour : 1;
	uint8 bBigWheels : 1;
	uint8 bWaterTight : 1;	// no damage for non-player peds
	uint8 bNotDamagedUpsideDown : 1;
	uint8 bMoreResistantToDamage : 1;
	CEntity *m_pBombRigger;
	int16 m_auto_unk1;
	uint16 m_hydraulicState;
	uint32 m_nBusDoorTimerEnd;
	uint32 m_nBusDoorTimerStart;
	float m_aSuspensionSpringLength[4];
	float m_aSuspensionLineLength[4];
	float m_fHeightAboveRoad;
	float m_fTraction;
	float m_fVelocityChangeForAudio;
	float m_randomValues[6];	// used for what?
	float m_fFireBlowUpTimer;
	CPhysical *m_aGroundPhysical[4];	// physicals touching wheels
	CVector m_aGroundOffset[4];		// from ground object to colpoint
	CEntity *m_pSetOnFireEntity;
	float m_weaponDoorTimerLeft;	// still don't know what exactly this is
	float m_weaponDoorTimerRight;
	float m_fCarGunLR;
	float m_fCarGunUD;
	float m_fPropellerRotation;
	uint8 stuff4[4];
	uint8 m_nWheelsOnGround;
	uint8 m_nDriveWheelsOnGround;
	uint8 m_nDriveWheelsOnGroundPrev;
	float m_fGasPedalAudio;
	tWheelState m_aWheelState[4];

	static bool m_sAllTaxiLights;

	CAutomobile(int32 id, uint8 CreatedBy);

	// from CEntity
	void SetModelIndex(uint32 id);
	void ProcessControl(void);
	void Teleport(CVector v);
	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 component, eDoors door, float openRatio);
	void ProcessOpenDoor(uint32, uint32, float);
	bool IsDoorReady(eDoors door);
	bool IsDoorFullyOpen(eDoors door);
	bool IsDoorClosed(eDoors door);
	bool IsDoorMissing(eDoors door);
	void RemoveRefsToVehicle(CEntity *ent);
	void BlowUpCar(CEntity *ent);
	bool SetUpWheelColModel(CColModel *colModel);
	void BurstTyre(uint8 tyre);
	bool IsRoomForPedToLeaveCar(uint32 component, CVector *doorOffset);
	float GetHeightAboveRoad(void);
	void PlayCarHorn(void);

	void FireTruckControl(void);
	void TankControl(void);
	void HydraulicControl(void);
	void VehicleDamage(float impulse, uint16 damagedPiece);
	void ProcessBuoyancy(void);
	void DoDriveByShootings(void);
	int32 RcbanditCheckHitWheels(void);
	int32 RcbanditCheck1CarWheels(CPtrList &list);
	void PlaceOnRoadProperly(void);
	void dmgDrawCarCollidingParticles(const CVector &pos, float amount);
	void AddDamagedVehicleParticles(void);
	int32 AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed);
	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);
	bool GetAllWheelsOffGround(void);
	void HideAllComps(void);
	void ShowAllComps(void);
	void ReduceHornCounter(void);
#ifdef COMPATIBLE_SAVES
	virtual void Save(uint8*& buf);
	virtual void Load(uint8*& buf);
#endif
	static const uint32 nSaveStructSize;

	static void SetAllTaxiLights(bool set);
};

VALIDATE_SIZE(CAutomobile, 0x5A8);

inline uint8 GetCarDoorFlag(int32 carnode) {
	switch (carnode) {
	case CAR_DOOR_LF:
		return CAR_DOOR_FLAG_LF;
	case CAR_DOOR_LR:
		return CAR_DOOR_FLAG_LR;
	case CAR_DOOR_RF:
		return CAR_DOOR_FLAG_RF;
	case CAR_DOOR_RR:
		return CAR_DOOR_FLAG_RR;
	default:
		return CAR_DOOR_FLAG_UNKNOWN;
	}
}

license: mit


code:
#pragma once

#include "Vehicle.h"

// some miami bike leftovers

enum eBikeNodes {
	BIKE_NODE_NONE,
	BIKE_CHASSIS,
	BIKE_FORKS_FRONT,
	BIKE_FORKS_REAR,
	BIKE_WHEEL_FRONT,
	BIKE_WHEEL_REAR,
	BIKE_MUDGUARD,
	BIKE_HANDLEBARS,
	BIKE_NUM_NODES
};

class CBike : public CVehicle
{
public:
	RwFrame *m_aBikeNodes[BIKE_NUM_NODES]; // assuming
	uint8 unk1[96];
	AnimationId m_bikeSitAnimation;
	uint8 unk2[180];
	float m_aSuspensionSpringRatio[4];

	/* copied from VC, one of the floats here is gone, assuming m_bike_unused1 */
	float m_aSuspensionSpringRatioPrev[4];
	float m_aWheelTimer[4];
	//float m_bike_unused1;
	int m_aWheelSkidmarkType[2];
	bool m_aWheelSkidmarkBloody[2];
	bool m_aWheelSkidmarkUnk[2];
	float m_aWheelRotation[2];
	float m_aWheelSpeed[2];
	float m_aWheelPosition[2];
	float m_aWheelBasePosition[2];
	float m_aSuspensionSpringLength[4];
	float m_aSuspensionLineLength[4];
	float m_fHeightAboveRoad;
	/**/

	float m_fTraction;
};


code:
#include "common.h"

#include "main.h"
#include "General.h"
#include "Timecycle.h"
#include "HandlingMgr.h"
#include "CarCtrl.h"
#include "RwHelper.h"
#include "ModelIndices.h"
#include "VisibilityPlugins.h"
#include "DMAudio.h"
#include "Camera.h"
#include "Darkel.h"
#include "Explosion.h"
#include "Particle.h"
#include "WaterLevel.h"
#include "Floater.h"
#include "World.h"
#include "Pools.h"
#include "Pad.h"
#include "Boat.h"
#include "SaveBuf.h"

#define INVALID_ORIENTATION (-9999.99f)

float MAX_WAKE_LENGTH = 50.0f;
float MIN_WAKE_INTERVAL = 1.0f;
float WAKE_LIFETIME = 400.0f;

float fShapeLength = 0.4f;
float fShapeTime = 0.05f;
float fRangeMult = 0.75f;
float fTimeMult = 1.0f/WAKE_LIFETIME;

CBoat *CBoat::apFrameWakeGeneratingBoats[4];

const uint32 CBoat::nSaveStructSize =
#ifdef COMPATIBLE_SAVES
	1156;
#else
	sizeof(CBoat);
#endif

CBoat::CBoat(int mi, uint8 owner) : CVehicle(owner)
{
	CVehicleModelInfo *minfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(mi);
	m_vehType = VEHICLE_TYPE_BOAT;
	m_fAccelerate = 0.0f;
	m_fBrake = 0.0f;
	m_fSteeringLeftRight = 0.0f;
	m_nPadID = 0;
	m_fMovingRotation = 0.0f;
	SetModelIndex(mi);

	pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)minfo->m_handlingId);
	minfo->ChooseVehicleColour(m_currentColour1, m_currentColour2);

	m_fMass = pHandling->fMass;
	m_fTurnMass = pHandling->fTurnMass / 2.0f;
	m_vecCentreOfMass = pHandling->CentreOfMass;
	m_fAirResistance = pHandling->Dimension.x * pHandling->Dimension.z / m_fMass;
	m_fElasticity = 0.1f;
	m_fBuoyancy = pHandling->fBuoyancy;
	m_fSteerAngle = 0.0f;
	m_fGasPedal = 0.0f;
	m_fBrakePedal = 0.0f;

	m_fThrustZ = 0.25f;
	m_fThrustY = 0.35f;
	m_vecMoveRes = CVector(0.7f, 0.998f, 0.999f);
	m_vecTurnRes = CVector(0.85f, 0.96f, 0.96f);
	m_boat_unused3 = false;

	m_fVolumeUnderWater = 7.0f;
	m_fPrevVolumeUnderWater = 7.0f;
	m_vecBuoyancePoint = CVector(0.0f, 0.0f, 0.0f);

	m_nDeltaVolumeUnderWater = 0;
	bBoatInWater = true;
	bPropellerInWater = true;

	bIsInWater = true;

	m_boat_unused2 = 0;
	m_bIsAnchored = true;
	m_fOrientation = INVALID_ORIENTATION;
	bTouchingWater = true;
	m_fDamage = 0.0f;
	m_pSetOnFireEntity = nil;
	m_nNumWakePoints = 0;

	for (int16 i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++)
		m_afWakePointLifeTime[i] = 0.0f;

	m_nAmmoInClip = 20;
}

void
CBoat::SetModelIndex(uint32 id)
{
	CEntity::SetModelIndex(id);
	SetupModelNodes();
}

void
CBoat::GetComponentWorldPosition(int32 component, CVector &pos)
{
	pos = *RwMatrixGetPos(RwFrameGetLTM(m_aBoatNodes[component]));
}

void
CBoat::ProcessControl(void)
{
	if(m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory)
		return;

	bool onLand = m_fDamageImpulse > 0.0f && m_vecDamageNormal.z > 0.1f;

	PruneWakeTrail();

	int r, g, b;
	RwRGBA splashColor, jetColor;
	r = 114.75f*(CTimeCycle::GetAmbientRed() + 0.5f*CTimeCycle::GetDirectionalRed());
	g = 114.75f*(CTimeCycle::GetAmbientGreen() + 0.5f*CTimeCycle::GetDirectionalGreen());
	b = 114.75f*(CTimeCycle::GetAmbientBlue() + 0.5f*CTimeCycle::GetDirectionalBlue());
	r = Clamp(r, 0, 255);
	g = Clamp(g, 0, 255);
	b = Clamp(b, 0, 255);
	splashColor.red = r;
	splashColor.green = g;
	splashColor.blue = b;
	splashColor.alpha = CGeneral::GetRandomNumberInRange(128, 150);

	r = 242.25f*(CTimeCycle::GetAmbientRed() + 0.5f*CTimeCycle::GetDirectionalRed());
	g = 242.25f*(CTimeCycle::GetAmbientGreen() + 0.5f*CTimeCycle::GetDirectionalGreen());
	b = 242.25f*(CTimeCycle::GetAmbientBlue() + 0.5f*CTimeCycle::GetDirectionalBlue());
	r = Clamp(r, 0, 255);
	g = Clamp(g, 0, 255);
	b = Clamp(b, 0, 255);
	jetColor.red = r;
	jetColor.green = g;
	jetColor.blue = b;
	jetColor.alpha = CGeneral::GetRandomNumberInRange(96, 128);

	CGeneral::GetRandomNumber();	// unused

	ProcessCarAlarm();

	switch(GetStatus()){
	case STATUS_PLAYER:
		m_bIsAnchored = false;
		m_fOrientation = INVALID_ORIENTATION;
		ProcessControlInputs(0);
		if(GetModelIndex() == MI_PREDATOR)
			DoFixedMachineGuns();
		break;
	case STATUS_SIMPLE:
		m_bIsAnchored = false;
		m_fOrientation = INVALID_ORIENTATION;
		CPhysical::ProcessControl();
		bBoatInWater = true;
		bPropellerInWater = true;
		bIsInWater = true;
		return;
	case STATUS_PHYSICS:
		m_bIsAnchored = false;
		m_fOrientation = INVALID_ORIENTATION;
		CCarCtrl::SteerAIBoatWithPhysics(this);
		break;
	case STATUS_ABANDONED:
	case STATUS_WRECKED:
		bBoatInWater = true;
		bPropellerInWater = true;
		bIsInWater = true;
		m_fSteerAngle = 0.0;
		bIsHandbrakeOn = false;
		m_fBrakePedal = 0.5f;
		m_fGasPedal = 0.0f;
		if((GetPosition() - CWorld::Players[CWorld::PlayerInFocus].GetPos()).Magnitude() > 150.0f){
			m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f);
			m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f);
			return;
		}
		break;
	default: break;
	}

	float collisionDamage = pHandling->fCollisionDamageMultiplier * m_fDamageImpulse;
#ifdef FIX_BUGS
	if (collisionDamage > 25.0f && GetStatus() != STATUS_WRECKED && m_fHealth >= 150.0f && !bCollisionProof) {
#else
	if(collisionDamage > 25.0f && GetStatus() != STATUS_WRECKED && m_fHealth >= 150.0f){
#endif
		float prevHealth = m_fHealth;
		if(this == FindPlayerVehicle()){
			if(bTakeLessDamage)
				m_fHealth -= (collisionDamage-25.0f)/6.0f;
			else
				m_fHealth -= (collisionDamage-25.0f)/2.0f;
		}else{
			if(collisionDamage > 60.0f && pDriver)
				pDriver->Say(SOUND_PED_ANNOYED_DRIVER);
			if(bTakeLessDamage)
				m_fHealth -= (collisionDamage-25.0f)/12.0f;
			else
				m_fHealth -= (collisionDamage-25.0f)/4.0f;
		}

		if(m_fHealth <= 0.0f && prevHealth > 0.0f){
			m_fHealth = 1.0f;
			m_pSetOnFireEntity = m_pDamageEntity;
		}
	}

	// Damage particles
	if(m_fHealth <= 600.0f && GetStatus() != STATUS_WRECKED &&
	   Abs(GetPosition().x - TheCamera.GetPosition().x) < 200.0f &&
	   Abs(GetPosition().y - TheCamera.GetPosition().y) < 200.0f){
		float speedSq = m_vecMoveSpeed.MagnitudeSqr();
		CVector smokeDir = 0.8f*m_vecMoveSpeed;
		CVector smokePos;
		switch(GetModelIndex()){
		case MI_SPEEDER:
			smokePos = CVector(0.4f, -2.4f, 0.8f);
			smokeDir += 0.05f*GetRight();
			smokeDir.z += 0.2f*m_vecMoveSpeed.z;
			break;
		case MI_REEFER:
			smokePos = CVector(2.0f, -1.0f, 0.5f);
			smokeDir += 0.07f*GetRight();
			break;
		case MI_PREDATOR:
		default:
			smokePos = CVector(-1.5f, -0.5f, 1.2f);
			smokeDir += -0.08f*GetRight();
			break;
		}

		smokePos = GetMatrix() * smokePos;

		// On fire
		if(m_fHealth < 150.0f){
			CParticle::AddParticle(PARTICLE_CARFLAME, smokePos,
				CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(2.25f/200.0f, 0.09f)),
				nil, 0.9f);
			CVector smokePos2 = smokePos;
			smokePos2.x += CGeneral::GetRandomNumberInRange(-2.25f/4.0f, 2.25f/4.0f);
			smokePos2.y += CGeneral::GetRandomNumberInRange(-2.25f/4.0f, 2.25f/4.0f);
			smokePos2.z += CGeneral::GetRandomNumberInRange(2.25f/4.0f, 2.25f);
			CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, smokePos2, CVector(0.0f, 0.0f, 0.0f));

			m_fDamage += CTimer::GetTimeStepInMilliseconds();
			if(m_fDamage > 5000.0f)
				BlowUpCar(m_pSetOnFireEntity);
		}

		if(speedSq < 0.25f && (CTimer::GetFrameCounter() + m_randomSeed) & 1)
			CParticle::AddParticle(PARTICLE_ENGINE_STEAM, smokePos, smokeDir);
		if(speedSq < 0.25f && m_fHealth <= 350.0f)
			CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, smokePos, 1.25f*smokeDir);
	}

	CPhysical::ProcessControl();

	CVector buoyanceImpulse(0.0f, 0.0f, 0.0f);
	CVector buoyancePoint(0.0f, 0.0f, 0.0f);
	if(mod_Buoyancy.ProcessBuoyancy(this, pHandling->fBuoyancy, &buoyancePoint, &buoyanceImpulse)){
		// Process boat in water
		if(0.1f * m_fMass * GRAVITY*CTimer::GetTimeStep() < buoyanceImpulse.z){
			bBoatInWater = true;
			bIsInWater = true;
		}else{
			bBoatInWater = false;
			bIsInWater = false;
		}

		m_fVolumeUnderWater = mod_Buoyancy.m_volumeUnderWater;
		m_vecBuoyancePoint = buoyancePoint;
		ApplyMoveForce(buoyanceImpulse);
		if(!onLand)
			ApplyTurnForce(buoyanceImpulse, buoyancePoint);

		if(!onLand && bBoatInWater && GetUp().z > 0.0f){
			float impulse;
			if(m_fGasPedal > 0.05f)
				impulse = m_vecMoveSpeed.MagnitudeSqr()*pHandling->fSuspensionForceLevel*buoyanceImpulse.z*CTimer::GetTimeStep()*0.5f*m_fGasPedal;
			else
				impulse = 0.0f;
			impulse = Min(impulse, GRAVITY*pHandling->fSuspensionDampingLevel*m_fMass*CTimer::GetTimeStep());
			ApplyMoveForce(impulse*GetUp());
			ApplyTurnForce(impulse*GetUp(), buoyancePoint - pHandling->fSuspensionBias*GetForward());
		}

		// Handle boat moving forward
		if(Abs(m_fGasPedal) > 0.05f || m_vecMoveSpeed.Magnitude2D() > 0.01f){
			if(bBoatInWater)
				AddWakePoint(GetPosition());

			float steerFactor = 1.0f - DotProduct(m_vecMoveSpeed, GetForward());
			if (GetModelIndex() == MI_GHOST)
				steerFactor = 1.0f - DotProduct(m_vecMoveSpeed, GetForward())*0.3f;
			if(steerFactor < 0.0f) steerFactor = 0.0f;

			CVector propeller(0.0f, -pHandling->Dimension.y*m_fThrustY, -pHandling->Dimension.z*m_fThrustZ);
			propeller = Multiply3x3(GetMatrix(), propeller);
			CVector propellerWorld = GetPosition() + propeller;

			float steerSin = Sin(-m_fSteerAngle * steerFactor);
			float steerCos = Cos(-m_fSteerAngle * steerFactor);
			float waterLevel;
			CWaterLevel::GetWaterLevel(propellerWorld, &waterLevel, true);
			if(propellerWorld.z-0.5f < waterLevel){
				float propellerDepth = waterLevel - (propellerWorld.z - 0.5f);
				if(propellerDepth > 1.0f)
					propellerDepth = 1.0f;
				else
					propellerDepth = SQR(propellerDepth);
				bPropellerInWater = true;

				if(Abs(m_fGasPedal) > 0.05f){
					CVector forceDir = Multiply3x3(GetMatrix(), CVector(-steerSin, steerCos, -Abs(m_fSteerAngle)));
					CVector force = propellerDepth * m_fGasPedal * 40.0f * pHandling->Transmission.fEngineAcceleration * pHandling->fMass * forceDir;
					if(force.z > 0.2f)
						force.z = SQR(1.2f - force.z) + 0.2f;
					if(onLand){
						if(m_fGasPedal < 0.0f){
							force.x *= 5.0f;
							force.y *= 5.0f;
						}
						if(force.z < 0.0f)
							force.z = 0.0f;
						ApplyMoveForce(force * CTimer::GetTimeStep());
					}else{
						ApplyMoveForce(force * CTimer::GetTimeStep());
						ApplyTurnForce(force * CTimer::GetTimeStep(), propeller - pHandling->fTractionBias*GetUp());
						float rightForce = DotProduct(GetRight(), force);
						ApplyTurnForce(-rightForce*GetRight() * CTimer::GetTimeStep(), GetUp());
					}

					// Spray some particles
					CVector jetDir = -0.04f * force;
					if(m_fGasPedal > 0.0f){
						if(GetStatus() == STATUS_PLAYER){
							bool cameraHack = TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN ||
								TheCamera.WhoIsInControlOfTheCamera == CAMCONTROL_OBBE;
							CVector sternPos = GetColModel()->boundingBox.min;
							sternPos.x = 0.0f;
							sternPos.z = 0.0f;
							sternPos = Multiply3x3(GetMatrix(), sternPos);

							CVector jetPos = GetPosition() + sternPos;
							if(cameraHack)
								jetPos.z = 1.0f;
							else
								jetPos.z = 0.0f;

#ifdef PC_PARTICLE
							CVector wakePos = GetPosition() + sternPos;
							wakePos.z -= 0.65f;
#else
							CVector wakePos = GetPosition() + sternPos;
							wakePos.z = -0.3f;
#endif

							CVector wakeDir = 0.75f * jetDir;

							CParticle::AddParticle(PARTICLE_BOAT_THRUSTJET, jetPos, jetDir, nil, 0.0f, jetColor);
#ifdef PC_PARTICLE
							CParticle::AddParticle(PARTICLE_CAR_SPLASH, jetPos, 0.25f * jetDir, nil, 1.0f, splashColor,
								CGeneral::GetRandomNumberInRange(0, 30),
								CGeneral::GetRandomNumberInRange(0, 90), 3);
#endif
							if(!cameraHack)
								CParticle::AddParticle(PARTICLE_BOAT_WAKE, wakePos, wakeDir, nil, 0.0f, jetColor);
						}else if((CTimer::GetFrameCounter() + m_randomSeed) & 1){
#ifdef PC_PARTICLE
							jetDir.z = 0.018f;
							jetDir.x *= 0.01f;
							jetDir.y *= 0.01f;
							propellerWorld.z += 1.5f;

							CParticle::AddParticle(PARTICLE_BOAT_SPLASH, propellerWorld, jetDir, nil, 1.5f, jetColor);
#else
							jetDir.z = 0.018f;
							jetDir.x *= 0.03f;
							jetDir.y *= 0.03f;
							propellerWorld.z += 1.0f;

							CParticle::AddParticle(PARTICLE_BOAT_SPLASH, propellerWorld, jetDir, nil, 0.0f, jetColor);
#endif

#ifdef PC_PARTICLE
							CParticle::AddParticle(PARTICLE_CAR_SPLASH, propellerWorld, 0.1f * jetDir, nil, 0.5f, splashColor,
								CGeneral::GetRandomNumberInRange(0, 30),
								CGeneral::GetRandomNumberInRange(0, 90), 3);
#endif
						}
					}
				}else if(!onLand){
					float force = 50.0f*DotProduct(m_vecMoveSpeed, GetForward());
					force = Min(force, 10.0f);
					CVector propellerForce = propellerDepth * Multiply3x3(GetMatrix(), force*CVector(-steerSin, 0.0f, 0.0f));
					ApplyMoveForce(propellerForce * CTimer::GetTimeStep()*0.5f);
					ApplyTurnForce(propellerForce * CTimer::GetTimeStep()*0.5f, propeller);
				}
			}else
				bPropellerInWater = false;
		}

		// Slow down or push down boat as it approaches the world limits
		m_vecMoveSpeed.x = Min(m_vecMoveSpeed.x, -(GetPosition().x - 1900.0f)*0.01f);	// east
		m_vecMoveSpeed.x = Max(m_vecMoveSpeed.x, -(GetPosition().x - -1515.0f)*0.01f);	// west
		m_vecMoveSpeed.y = Min(m_vecMoveSpeed.y, -(GetPosition().y - 600.0f)*0.01f);	// north
		m_vecMoveSpeed.y = Max(m_vecMoveSpeed.y, -(GetPosition().y - -1900.0f)*0.01f);	// south

		if(!onLand && bBoatInWater)
			ApplyWaterResistance();

		// No idea what exactly is going on here besides drag in YZ
		float fx = Pow(m_vecTurnRes.x, CTimer::GetTimeStep());
		float fy = Pow(m_vecTurnRes.y, CTimer::GetTimeStep());
		float fz = Pow(m_vecTurnRes.z, CTimer::GetTimeStep());
		m_vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix());	// invert - to local space
		// TODO: figure this out
		float magic = 1.0f/(1000.0f * SQR(m_vecTurnSpeed.x) + 1.0f) * fx;
		m_vecTurnSpeed.y *= fy;
		m_vecTurnSpeed.z *= fz;
		float forceUp = (magic - 1.0f) * m_vecTurnSpeed.x * m_fTurnMass;
		m_vecTurnSpeed = Multiply3x3(GetMatrix(), m_vecTurnSpeed);	// back to world
		CVector com = Multiply3x3(GetMatrix(), m_vecCentreOfMass);
		ApplyTurnForce(CVector(0.0f, 0.0f, forceUp), com + GetForward());

		m_nDeltaVolumeUnderWater = (m_fVolumeUnderWater-m_fPrevVolumeUnderWater)*10000;

		// Falling into water
		if(!onLand && bBoatInWater && GetUp().z > 0.0f && m_nDeltaVolumeUnderWater > 200){
			DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, m_nDeltaVolumeUnderWater);

			float speedUp = m_vecMoveSpeed.MagnitudeSqr() * m_nDeltaVolumeUnderWater * 0.0004f;
			if(speedUp + m_vecMoveSpeed.z > pHandling->fBrakeDeceleration)
				speedUp = pHandling->fBrakeDeceleration - m_vecMoveSpeed.z;
			if(speedUp < 0.0f) speedUp = 0.0f;
			float speedFwd = DotProduct(m_vecMoveSpeed, GetForward());
			speedFwd *= -m_nDeltaVolumeUnderWater * 0.01f * pHandling->fTractionLoss;
			CVector speed = speedFwd*GetForward() + CVector(0.0f, 0.0f, speedUp);
			CVector splashImpulse = speed * m_fMass;
			ApplyMoveForce(splashImpulse);
			ApplyTurnForce(splashImpulse, buoyancePoint);
		}

		// Spray particles on sides of boat
#ifdef PC_PARTICLE
		if(m_nDeltaVolumeUnderWater > 75)
#else
		if(m_nDeltaVolumeUnderWater > 120)
#endif
		{
			float speed = m_vecMoveSpeed.Magnitude();
			float splash1Size = speed;
			float splash2Size = float(m_nDeltaVolumeUnderWater) * 0.005f * 0.2f;
			float front = 0.9f * GetColModel()->boundingBox.max.y;
			if(splash1Size > 0.75f) splash1Size = 0.75f;

			CVector dir, pos;

			// right
#ifdef PC_PARTICLE
			dir = -0.5f*m_vecMoveSpeed;
			dir.z += 0.1f*speed;
			dir += 0.5f*GetRight()*speed;
			pos = front*GetForward() + 0.5f*GetRight() + GetPosition() + m_vecBuoyancePoint;
			CWaterLevel::GetWaterLevel(pos, &pos.z, true);
#else
			dir = 0.3f*m_vecMoveSpeed;
			dir.z += 0.05f*speed;
			dir += 0.5f*GetRight()*speed;
			pos = (GetPosition() + m_vecBuoyancePoint) + (1.5f*GetRight());
#endif

#ifdef PC_PARTICLE
			CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, 0.75f * dir, nil, splash1Size, splashColor,
				CGeneral::GetRandomNumberInRange(0, 30),
				CGeneral::GetRandomNumberInRange(0, 90), 1);
			CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size, jetColor);
#else
			CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size);
#endif

			// left
#ifdef PC_PARTICLE
			dir = -0.5f*m_vecMoveSpeed;
			dir.z += 0.1f*speed;
			dir -= 0.5f*GetRight()*speed;
			pos = front*GetForward() - 0.5f*GetRight() + GetPosition() + m_vecBuoyancePoint;
			CWaterLevel::GetWaterLevel(pos, &pos.z, true);
#else
			dir = 0.3f*m_vecMoveSpeed;
			dir.z += 0.05f*speed;
			dir -= 0.5f*GetRight()*speed;
			pos = (GetPosition() + m_vecBuoyancePoint) - (1.5f*GetRight());
#endif

#ifdef PC_PARTICLE
			CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, 0.75f * dir, nil, splash1Size, splashColor,
				CGeneral::GetRandomNumberInRange(0, 30),
				CGeneral::GetRandomNumberInRange(0, 90), 1);
			CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size, jetColor);
#else
			CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, splash2Size);
#endif
		}

		m_fPrevVolumeUnderWater = m_fVolumeUnderWater;
	}else{
		bBoatInWater = false;
		bIsInWater = false;
	}

	if(m_bIsAnchored){
		m_vecMoveSpeed.x = 0.0f;
		m_vecMoveSpeed.y = 0.0f;

		if(m_fOrientation == INVALID_ORIENTATION){
			m_fOrientation = GetForward().Heading();
		}else{
			// is this some inlined CPlaceable method?
			CVector pos = GetPosition();
			GetMatrix().RotateZ(m_fOrientation - GetForward().Heading());
			GetMatrix().SetTranslateOnly(pos);
		}
	}

	ProcessDelayedExplosion();
}

void
CBoat::ProcessControlInputs(uint8 pad)
{
	m_nPadID = pad;
	if(m_nPadID > 3)
		m_nPadID = 3;

	m_fBrake += (CPad::GetPad(pad)->GetBrake()/255.0f - m_fBrake)*0.1f;
	m_fBrake = Clamp(m_fBrake, 0.0f, 1.0f);

	if(m_fBrake < 0.05f){
		m_fBrake = 0.0f;
		m_fAccelerate += (CPad::GetPad(pad)->GetAccelerate()/255.0f - m_fAccelerate)*0.1f;
		m_fAccelerate = Clamp(m_fAccelerate, 0.0f, 1.0f);
	}else
		m_fAccelerate = -m_fBrake*0.2f;

	m_fSteeringLeftRight += (-CPad::GetPad(pad)->GetSteeringLeftRight()/128.0f - m_fSteeringLeftRight)*0.2f;
	m_fSteeringLeftRight = Clamp(m_fSteeringLeftRight, -1.0f, 1.0f);

	float steeringSq = m_fSteeringLeftRight < 0.0f ? -SQR(m_fSteeringLeftRight) : SQR(m_fSteeringLeftRight);
	m_fSteerAngle = pHandling->fSteeringLock * DEGTORAD(steeringSq);
	m_fGasPedal = m_fAccelerate;
}

void
CBoat::ApplyWaterResistance(void)
{
	float fwdSpeed = DotProduct(GetMoveSpeed(), GetForward());
	// TODO: figure out how this works
	float resistance = 0.001f * SQR(m_fVolumeUnderWater) * m_fMass;
	float magic = (SQR(fwdSpeed) + 0.05f) * resistance + 1.0f;
	magic = Abs(magic);
	float fx = Pow(m_vecMoveRes.x/magic, 0.5f*CTimer::GetTimeStep());
	float fy = Pow(m_vecMoveRes.y/magic, 0.5f*CTimer::GetTimeStep());
	float fz = Pow(m_vecMoveRes.z/magic, 0.5f*CTimer::GetTimeStep());

	m_vecMoveSpeed = Multiply3x3(m_vecMoveSpeed, GetMatrix());	// invert - to local space
	m_vecMoveSpeed.x *= fx;
	m_vecMoveSpeed.y *= fy;
	m_vecMoveSpeed.z *= fz;
	float force = (fy - 1.0f) * m_vecMoveSpeed.y * m_fMass;
	m_vecMoveSpeed = Multiply3x3(GetMatrix(), m_vecMoveSpeed);	// back to world

	ApplyTurnForce(force*GetForward(), -GetUp());

	if(m_vecMoveSpeed.z > 0.0f)
		m_vecMoveSpeed.z *= fz;
	else
		m_vecMoveSpeed.z *= (1.0f - fz)*0.5f + fz;
}

RwObject*
GetBoatAtomicObjectCB(RwObject *object, void *data)
{
	RpAtomic *atomic = (RpAtomic*)object;
	assert(RwObjectGetType(object) == rpATOMIC);
	if(RpAtomicGetFlags(atomic) & rpATOMICRENDER)
		*(RpAtomic**)data = atomic;
	return object;

}

void
CBoat::BlowUpCar(CEntity *culprit)
{
	RpAtomic *atomic;
	RwFrame *frame;
	RwMatrix *matrix;
	CObject *obj;

	if(!bCanBeDamaged)
		return;

	// explosion pushes vehicle up
	m_vecMoveSpeed.z += 0.13f;
	SetStatus(STATUS_WRECKED);
	bRenderScorched = true;

	m_fHealth = 0.0;
	m_nBombTimer = 0;
	TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z);

	if(this == FindPlayerVehicle())
		FindPlayerPed()->m_fHealth = 0.0f;	// kill player
	if(pDriver){
		CDarkel::RegisterKillByPlayer(pDriver, WEAPONTYPE_EXPLOSION);
		pDriver->SetDead();
		pDriver->FlagToDestroyWhenNextProcessed();
	}

	bEngineOn = false;
	bLightsOn = false;
	ChangeLawEnforcerState(false);

	CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR, GetPosition(), 0);
	if(m_aBoatNodes[BOAT_MOVING] == nil)
		return;

	// much like CAutomobile::SpawnFlyingComponent from here on

	atomic = nil;
	RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic);
	if(atomic == nil)
		return;

	obj = new CObject();
	if(obj == nil)
		return;

	obj->SetModelIndexNoCreate(MI_CAR_WHEEL);

	// object needs base model
	obj->RefModelInfo(GetModelIndex());

	// create new atomic
	matrix = RwFrameGetLTM(m_aBoatNodes[BOAT_MOVING]);
	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.99f;
	obj->m_fElasticity = 0.1f;
	obj->m_fBuoyancy = obj->m_fMass*GRAVITY/0.75f;
	obj->ObjectCreatedBy = TEMP_OBJECT;
	obj->SetIsStatic(false);
	obj->bIsPickup = false;

	// life time
	CObject::nNoTempObjects++;
	obj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 20000;

	obj->m_vecMoveSpeed = m_vecMoveSpeed;
	if(GetUp().z > 0.0f)
		obj->m_vecMoveSpeed.z = 0.3f;
	else
		obj->m_vecMoveSpeed.z = 0.0f;

	obj->m_vecTurnSpeed = m_vecTurnSpeed*2.0f;
	obj->m_vecTurnSpeed.x = 0.5f;

	// push component away from boat
	CVector dist = obj->GetPosition() - GetPosition();
	dist.Normalise();
	if(GetUp().z > 0.0f)
		dist += GetUp();
	obj->GetMatrix().GetPosition() += dist;

	CWorld::Add(obj);

	atomic = nil;
	RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic);
	if(atomic)
		RpAtomicSetFlags(atomic, 0);
}

RwIm3DVertex KeepWaterOutVertices[4];
RwImVertexIndex KeepWaterOutIndices[6];

void
CBoat::Render()
{
	CMatrix matrix;

	if (m_aBoatNodes[BOAT_MOVING] != nil) {
		matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_MOVING]));

		CVector pos = matrix.GetPosition();
		matrix.SetRotateZ(m_fMovingRotation);
		matrix.Translate(pos);

		matrix.UpdateRW();
		if (CVehicle::bWheelsOnlyCheat) {
			RpAtomicRender((RpAtomic*)GetFirstObject(m_aBoatNodes[BOAT_MOVING]));
		}
	}
	m_fMovingRotation += 0.05f;
	((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->SetVehicleColour(m_currentColour1, m_currentColour2);
	if (!CVehicle::bWheelsOnlyCheat)
		CEntity::Render();
#ifdef NEW_RENDERER
	if(!gbNewRenderer)
#endif
	RenderWaterOutPolys();	// not separate function in III
}

void
CBoat::RenderWaterOutPolys(void)
{
	KeepWaterOutIndices[0] = 0;
	KeepWaterOutIndices[1] = 2;
	KeepWaterOutIndices[2] = 1;
	KeepWaterOutIndices[3] = 1;
	KeepWaterOutIndices[4] = 2;
	KeepWaterOutIndices[5] = 3;
	RwIm3DVertexSetRGBA(&KeepWaterOutVertices[0], 255, 255, 255, 255);
	RwIm3DVertexSetRGBA(&KeepWaterOutVertices[1], 255, 255, 255, 255);
	RwIm3DVertexSetRGBA(&KeepWaterOutVertices[2], 255, 255, 255, 255);
	RwIm3DVertexSetRGBA(&KeepWaterOutVertices[3], 255, 255, 255, 255);
	switch (GetModelIndex()) {
	case MI_SPEEDER:
		RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.15f, 3.61f, 1.03f);
		RwIm3DVertexSetPos(&KeepWaterOutVertices[1],  1.15f, 3.61f, 1.03f);
		RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.15f, 0.06f, 1.03f);
		RwIm3DVertexSetPos(&KeepWaterOutVertices[3],  1.15f, 0.06f, 1.03f);
		break;
	case MI_REEFER:
		RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.9f,   2.83f, 1.0f);
		RwIm3DVertexSetPos(&KeepWaterOutVertices[1],  1.9f,   2.83f, 1.0f);
		RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.66f, -4.48f, 0.83f);
		RwIm3DVertexSetPos(&KeepWaterOutVertices[3],  1.66f, -4.48f, 0.83f);
		break;
	case MI_PREDATOR:
	default:
		RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.45f,   1.9f, 0.96f);
		RwIm3DVertexSetPos(&KeepWaterOutVertices[1],  1.45f,   1.9f, 0.96f);
		RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.45f, -3.75f, 0.96f);
		RwIm3DVertexSetPos(&KeepWaterOutVertices[3],  1.45f, -3.75f, 0.96f);
		break;
	}
	KeepWaterOutVertices[0].u = 0.0f;
	KeepWaterOutVertices[0].v = 0.0f;
	KeepWaterOutVertices[1].u = 1.0f;
	KeepWaterOutVertices[1].v = 0.0f;
	KeepWaterOutVertices[2].u = 0.0f;
	KeepWaterOutVertices[2].v = 1.0f;
	KeepWaterOutVertices[3].u = 1.0f;
	KeepWaterOutVertices[3].v = 1.0f;
#ifdef NEW_RENDERER
	if(!gbNewRenderer)
#endif
{
	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpWaterRaster);
	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDZERO);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
}
	if (!CVehicle::bWheelsOnlyCheat && RwIm3DTransform(KeepWaterOutVertices, 4, GetMatrix().m_attachment, rwIM3D_VERTEXUV)) {
		RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, KeepWaterOutIndices, 6);
		RwIm3DEnd();
	}
#ifdef NEW_RENDERER
	if(!gbNewRenderer)
#endif
{
	RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
}
}

void
CBoat::Teleport(CVector v)
{
	CWorld::Remove(this);
	SetPosition(v);
	SetOrientation(0.0f, 0.0f, 0.0f);
	SetMoveSpeed(0.0f, 0.0f, 0.0f);
	SetTurnSpeed(0.0f, 0.0f, 0.0f);
	CWorld::Add(this);
}

bool
CBoat::IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats)
{
	uint8 numVerts = 0;

	if ( apFrameWakeGeneratingBoats[0] == NULL )
		return false;

	for ( int32 i = 0; i < 4; i++ )
	{
		CBoat *pBoat = apFrameWakeGeneratingBoats[i];
		if ( !pBoat )
			break;

		for ( int j = 0; j < pBoat->m_nNumWakePoints; j++ )
		{
			 float fDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[j]) * fShapeTime + float(j) * fShapeLength + fSize;

			 if ( Abs(pBoat->m_avec2dWakePoints[j].x - sector.x) < fDist
				&& Abs(pBoat->m_avec2dWakePoints[i].y - sector.y) < fDist )
			 {
				 apBoats[numVerts] = pBoat;
				 numVerts = 1; // += ?
				 break;
			 }
		}
	}

	return numVerts != 0;
}

float
CBoat::IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat)
{
	for ( int i = 0; i < pBoat->m_nNumWakePoints; i++ )
	{
		float fMaxDist = (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fShapeTime + float(i) * fShapeLength;

		CVector2D vecDist = pBoat->m_avec2dWakePoints[i] - CVector2D(vecVertex);

		float fDist = vecDist.MagnitudeSqr();

		if ( fDist < SQR(fMaxDist) )
			return 1.0f - Min(fRangeMult * Sqrt(fDist / SQR(fMaxDist)) + (WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[i]) * fTimeMult, 1.0f);
	}

	return 0.0f;
}

void
CBoat::SetupModelNodes()
{
	int i;
	for(i = 0; i < ARRAY_SIZE(m_aBoatNodes); i++)
		m_aBoatNodes[i] = nil;
	CClumpModelInfo::FillFrameArray(GetClump(), m_aBoatNodes);
}

void
CBoat::FillBoatList()
{
	int16 frameId = 0;

	apFrameWakeGeneratingBoats[0] = nil;
	apFrameWakeGeneratingBoats[1] = nil;
	apFrameWakeGeneratingBoats[2] = nil;
	apFrameWakeGeneratingBoats[3] = nil;

	for (int i = CPools::GetVehiclePool()->GetSize() - 1; i >= 0; i--) {
		CBoat *boat = (CBoat *)(CPools::GetVehiclePool()->GetSlot(i));
		if (boat && boat->m_vehType == VEHICLE_TYPE_BOAT) {
			int16 nNumWakePoints = boat->m_nNumWakePoints;
			if (nNumWakePoints != 0) {
				if (frameId >= ARRAY_SIZE(apFrameWakeGeneratingBoats)) {
					int16 frameId2 = -1;
					for (int16 j = 0; j < ARRAY_SIZE(apFrameWakeGeneratingBoats); j++) {
						if (apFrameWakeGeneratingBoats[j]->m_nNumWakePoints < nNumWakePoints) {
							frameId2 = j;
							nNumWakePoints = apFrameWakeGeneratingBoats[j]->m_nNumWakePoints;
						}
					}

					if (frameId2 != -1)
						apFrameWakeGeneratingBoats[frameId2] = boat;
				} else {
					apFrameWakeGeneratingBoats[frameId++] = boat;
				}
			}
		}
	}
}

void
CBoat::PruneWakeTrail(void)
{
	int i;

	for(i = 0; i < ARRAY_SIZE(m_afWakePointLifeTime); i++){
		if(m_afWakePointLifeTime[i] <= 0.0f)
			break;
		if(m_afWakePointLifeTime[i] <= CTimer::GetTimeStep()){
			m_afWakePointLifeTime[i] = 0.0f;
			break;
		}
		m_afWakePointLifeTime[i] -= CTimer::GetTimeStep();
	}
	m_nNumWakePoints = i;
}

void
CBoat::AddWakePoint(CVector point)
{
	int i;
	if(m_afWakePointLifeTime[0] > 0.0f){
		if((CVector2D(GetPosition()) - m_avec2dWakePoints[0]).MagnitudeSqr() < SQR(1.0f)){
			for(i = Min(m_nNumWakePoints, ARRAY_SIZE(m_afWakePointLifeTime)-1); i != 0; i--){
				m_avec2dWakePoints[i] = m_avec2dWakePoints[i-1];
				m_afWakePointLifeTime[i] = m_afWakePointLifeTime[i-1];
			}
			m_avec2dWakePoints[0] = point;
			m_afWakePointLifeTime[0] = 400.0f;
			if(m_nNumWakePoints < ARRAY_SIZE(m_afWakePointLifeTime))
				m_nNumWakePoints++;
		}
	}else{
		m_avec2dWakePoints[0] = point;
		m_afWakePointLifeTime[0] = 400.0f;
		m_nNumWakePoints = 1;
	}
}

#ifdef COMPATIBLE_SAVES
void
CBoat::Save(uint8*& buf)
{
	CVehicle::Save(buf);
	ZeroSaveBuf(buf, 1156 - 648);
}

void
CBoat::Load(uint8*& buf)
{
	CVehicle::Load(buf);
	SkipSaveBuf(buf, 1156 - 648);
}
#endif


code:
#pragma once

#include "Vehicle.h"

enum eBoatNodes
{
	BOAT_MOVING = 1,
	BOAT_RUDDER,
	BOAT_WINDSCREEN,
	NUM_BOAT_NODES
};

class CBoat : public CVehicle
{
public:
	// 0x288
	float m_fThrustZ;
	float m_fThrustY;
	CVector m_vecMoveRes;
	CVector m_vecTurnRes;
	float m_fMovingRotation;
	int32 m_boat_unused1;
	RwFrame *m_aBoatNodes[NUM_BOAT_NODES];
	uint8 bBoatInWater : 1;
	uint8 bPropellerInWater : 1;
	bool m_bIsAnchored;
	float m_fOrientation;
	int32 m_boat_unused2;
	float m_fDamage;
	CEntity *m_pSetOnFireEntity;
	bool m_boat_unused3;
	float m_fAccelerate;
	float m_fBrake;
	float m_fSteeringLeftRight;
	uint8 m_nPadID;
	int32 m_boat_unused4;
	float m_fVolumeUnderWater;
	CVector m_vecBuoyancePoint;
	float m_fPrevVolumeUnderWater;
	int16 m_nDeltaVolumeUnderWater;
	uint16 m_nNumWakePoints;
	CVector2D m_avec2dWakePoints[32];
	float m_afWakePointLifeTime[32];

	CBoat(int, uint8);

	virtual void SetModelIndex(uint32 id);
	virtual void ProcessControl();
	virtual void Teleport(CVector v);
	virtual void PreRender(void) {};
	virtual void Render(void);
	virtual void ProcessControlInputs(uint8);
	virtual void GetComponentWorldPosition(int32 component, CVector &pos);
	virtual bool IsComponentPresent(int32 component) { return true; }
	virtual void BlowUpCar(CEntity *ent);

	void RenderWaterOutPolys(void);
	void ApplyWaterResistance(void);
	void SetupModelNodes();
	void PruneWakeTrail(void);
	void AddWakePoint(CVector point);

	static CBoat *apFrameWakeGeneratingBoats[4];

	static bool IsSectorAffectedByWake(CVector2D sector, float fSize, CBoat **apBoats);
	static float IsVertexAffectedByWake(CVector vecVertex, CBoat *pBoat);
	static void FillBoatList(void);

#ifdef COMPATIBLE_SAVES
	virtual void Save(uint8*& buf);
	virtual void Load(uint8*& buf);
#endif
	static const uint32 nSaveStructSize;

};

VALIDATE_SIZE(CBoat, 0x484);

extern float MAX_WAKE_LENGTH;
extern float MIN_WAKE_INTERVAL;
extern float WAKE_LIFETIME;


code:
#include "common.h"

#include "CarGen.h"

#include "Automobile.h"
#include "Boat.h"
#include "Camera.h"
#include "CarCtrl.h"
#include "CutsceneMgr.h"
#include "General.h"
#include "Pools.h"
#include "Streaming.h"
#include "Timer.h"
#include "Vehicle.h"
#include "World.h"
#include "SaveBuf.h"

uint8 CTheCarGenerators::ProcessCounter;
uint32 CTheCarGenerators::NumOfCarGenerators;
CCarGenerator CTheCarGenerators::CarGeneratorArray[NUM_CARGENS];
uint8 CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter;
uint32 CTheCarGenerators::CurrentActiveCount;

void CCarGenerator::SwitchOff()
{
#ifdef FIX_BUGS
	if (m_nUsesRemaining != 0)
#endif
	{
		m_nUsesRemaining = 0;
		--CTheCarGenerators::CurrentActiveCount;
	}
}

void CCarGenerator::SwitchOn()
{
	m_nUsesRemaining = UINT16_MAX;
	m_nTimer = CalcNextGen();
	++CTheCarGenerators::CurrentActiveCount;
}

uint32 CCarGenerator::CalcNextGen()
{
	return CTimer::GetTimeInMilliseconds() + 4;
}

void CCarGenerator::DoInternalProcessing()
{
	if (CheckForBlockage()) {
		m_nTimer += 4;
		if (m_nUsesRemaining == 0)
			--CTheCarGenerators::CurrentActiveCount;
		return;
	}
	if (CCarCtrl::NumParkedCars >= 10)
		return;
	CStreaming::RequestModel(m_nModelIndex, STREAMFLAGS_DEPENDENCY);
	if (!CStreaming::HasModelLoaded(m_nModelIndex))
		return;
	if (CModelInfo::IsBoatModel(m_nModelIndex)){
		CBoat* pBoat = new CBoat(m_nModelIndex, PARKED_VEHICLE);
		pBoat->SetIsStatic(false);
		pBoat->bEngineOn = false;
		CVector pos = m_vecPos;
		if (pos.z <= -100.0f)
			pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y);
		pos.z += pBoat->GetDistanceFromCentreOfMassToBaseOfModel();
		pBoat->SetPosition(pos);
		pBoat->SetOrientation(0.0f, 0.0f, DEGTORAD(m_fAngle));
		pBoat->SetStatus(STATUS_ABANDONED);
		pBoat->m_nDoorLock = CARLOCK_UNLOCKED;
		CWorld::Add(pBoat);
		if (CGeneral::GetRandomNumberInRange(0, 100) < m_nAlarm)
			pBoat->m_nAlarmState = -1;
		if (CGeneral::GetRandomNumberInRange(0, 100) < m_nDoorlock)
			pBoat->m_nDoorLock = CARLOCK_LOCKED;
		if (m_nColor1 != -1 && m_nColor2){
			pBoat->m_currentColour1 = m_nColor1;
			pBoat->m_currentColour2 = m_nColor2;
		}
		m_nVehicleHandle = CPools::GetVehiclePool()->GetIndex(pBoat);
	}else{
		bool groundFound;
		CVector pos = m_vecPos;
		if (pos.z > -100.0f){
			pos.z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &groundFound);
		}else{
			groundFound = false;
			CColPoint cp;
			CEntity* pEntity;
			groundFound = CWorld::ProcessVerticalLine(CVector(pos.x, pos.y, 1000.0f), -1000.0f,
				cp, pEntity, true, false, false, false, false, false, nil);
			if (groundFound)
				pos.z = cp.point.z;
		}
		if (!groundFound) {
			debug("CCarGenerator::DoInternalProcessing - can't find ground z for new car x = %f y = %f \n", m_vecPos.x, m_vecPos.y);
		}else{
			CAutomobile* pCar;

			// So game crashes if it's bike :D
			if (((CVehicleModelInfo*)CModelInfo::GetModelInfo(m_nModelIndex))->m_vehicleType != VEHICLE_TYPE_BIKE)
				pCar = new CAutomobile(m_nModelIndex, PARKED_VEHICLE);

			pCar->SetIsStatic(false);
			pCar->bEngineOn = false;
			pos.z += pCar->GetDistanceFromCentreOfMassToBaseOfModel();
			pCar->SetPosition(pos);
			pCar->SetOrientation(0.0f, 0.0f, DEGTORAD(m_fAngle));
			pCar->SetStatus(STATUS_ABANDONED);
			pCar->bLightsOn = false;
			pCar->m_nDoorLock = CARLOCK_UNLOCKED;
			CWorld::Add(pCar);
			if (CGeneral::GetRandomNumberInRange(0, 100) < m_nAlarm)
				pCar->m_nAlarmState = -1;
			if (CGeneral::GetRandomNumberInRange(0, 100) < m_nDoorlock)
				pCar->m_nDoorLock = CARLOCK_LOCKED;
			if (m_nColor1 != -1 && m_nColor2) {
				pCar->m_currentColour1 = m_nColor1;
				pCar->m_currentColour2 = m_nColor2;
			}
			m_nVehicleHandle = CPools::GetVehiclePool()->GetIndex(pCar);
		}
	}
#ifdef FIX_BUGS
	if (m_nUsesRemaining < UINT16_MAX)
		--m_nUsesRemaining;
#else
	if (m_nUsesRemaining < ~0)
		--m_nUsesRemaining;
#endif
	m_nTimer = CalcNextGen();
	if (m_nUsesRemaining == 0)
		--CTheCarGenerators::CurrentActiveCount;
}

void CCarGenerator::Process()
{
	if (m_nVehicleHandle == -1 &&
		(CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter || CTimer::GetTimeInMilliseconds() >= m_nTimer) &&
		m_nUsesRemaining != 0 && CheckIfWithinRangeOfAnyPlayer())
		DoInternalProcessing();
	if (m_nVehicleHandle == -1)
		return;
	CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(m_nVehicleHandle);
	if (!pVehicle){
		m_nVehicleHandle = -1;
		return;
	}
	if (pVehicle->GetStatus() != STATUS_PLAYER)
		return;
	m_nTimer += 60000;
	m_nVehicleHandle = -1;
	m_bIsBlocking = true;
	pVehicle->bExtendedRange = false;
}

void CCarGenerator::Setup(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay)
{
	CMatrix m1, m2, m3; /* Unused but present on stack, so I'll leave them. */
	m_vecPos = CVector(x, y, z);
	m_fAngle = angle;
	m_nModelIndex = mi;
	m_nColor1 = color1;
	m_nColor2 = color2;
	m_bForceSpawn = force;
	m_nAlarm = alarm;
	m_nDoorlock = lock;
	m_nMinDelay = min_delay;
	m_nMaxDelay = max_delay;
	m_nVehicleHandle = -1;
	m_nTimer = CTimer::GetTimeInMilliseconds() + 1;
	m_nUsesRemaining = 0;
	m_bIsBlocking = false;
	m_vecInf = CModelInfo::GetColModel(m_nModelIndex)->boundingBox.min;
	m_vecSup = CModelInfo::GetColModel(m_nModelIndex)->boundingBox.max;
	m_fSize = Max(m_vecInf.Magnitude(), m_vecSup.Magnitude());
}

bool CCarGenerator::CheckForBlockage()
{
	int16 entities;
	CWorld::FindObjectsKindaColliding(CVector(m_vecPos), m_fSize, 1, &entities, 2, nil, false, true, true, false, false);
	return entities > 0;
}

bool CCarGenerator::CheckIfWithinRangeOfAnyPlayer()
{
	CVector2D direction = FindPlayerCentreOfWorld(CWorld::PlayerInFocus) - m_vecPos;
	float distance = direction.Magnitude();
	float farclip = 120.0f * TheCamera.GenerationDistMultiplier;
	float nearclip = farclip - 20.0f;
	if (distance >= farclip){
		if (m_bIsBlocking)
			m_bIsBlocking = false;
		return false;
	}
	if (CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter)
		return true;
	if (m_bIsBlocking)
		return false;
	if (distance < nearclip)
		return false;
	return DotProduct2D(direction, FindPlayerSpeed()) <= 0;
}

void CTheCarGenerators::Process()
{
	if (FindPlayerTrain() || CCutsceneMgr::IsCutsceneProcessing())
		return;
	if (++CTheCarGenerators::ProcessCounter == 4)
		CTheCarGenerators::ProcessCounter = 0;
	for (uint32 i = ProcessCounter; i < NumOfCarGenerators; i += 4)
		CTheCarGenerators::CarGeneratorArray[i].Process();
	if (GenerateEvenIfPlayerIsCloseCounter)
		GenerateEvenIfPlayerIsCloseCounter--;
}

int32 CTheCarGenerators::CreateCarGenerator(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay)
{
	CarGeneratorArray[NumOfCarGenerators].Setup(x, y, z, angle, mi, color1, color2, force, alarm, lock, min_delay, max_delay);
	return NumOfCarGenerators++;
}

void CTheCarGenerators::Init()
{
	GenerateEvenIfPlayerIsCloseCounter = 0;
	NumOfCarGenerators = 0;
	ProcessCounter = 0;
	CurrentActiveCount = 0;
}

void CTheCarGenerators::SaveAllCarGenerators(uint8 *buffer, uint32 *size)
{
	const uint32 nGeneralDataSize = sizeof(NumOfCarGenerators) + sizeof(CurrentActiveCount) + sizeof(ProcessCounter) + sizeof(GenerateEvenIfPlayerIsCloseCounter) + sizeof(int16);
	*size = sizeof(int) + nGeneralDataSize + sizeof(uint32) + sizeof(CarGeneratorArray) + SAVE_HEADER_SIZE;
INITSAVEBUF
	WriteSaveHeader(buffer, 'C','G','N','\0', *size - SAVE_HEADER_SIZE);

	WriteSaveBuf(buffer, nGeneralDataSize);
	WriteSaveBuf(buffer, NumOfCarGenerators);
	WriteSaveBuf(buffer, CurrentActiveCount);
	WriteSaveBuf(buffer, ProcessCounter);
	WriteSaveBuf(buffer, GenerateEvenIfPlayerIsCloseCounter);
	WriteSaveBuf(buffer, (int16)0); // alignment
	WriteSaveBuf(buffer, (uint32)sizeof(CarGeneratorArray));
	for (int i = 0; i < NUM_CARGENS; i++)
		WriteSaveBuf(buffer, CarGeneratorArray[i]);
VALIDATESAVEBUF(*size)
}

void CTheCarGenerators::LoadAllCarGenerators(uint8* buffer, uint32 size)
{
	const int32 nGeneralDataSize = sizeof(NumOfCarGenerators) + sizeof(CurrentActiveCount) + sizeof(ProcessCounter) + sizeof(GenerateEvenIfPlayerIsCloseCounter) + sizeof(int16);
	Init();
INITSAVEBUF
	CheckSaveHeader(buffer, 'C','G','N','\0', size - SAVE_HEADER_SIZE);
	uint32 tmp;
	ReadSaveBuf(&tmp, buffer);
	assert(tmp == nGeneralDataSize);
	ReadSaveBuf(&NumOfCarGenerators, buffer);
	ReadSaveBuf(&CurrentActiveCount, buffer);
	ReadSaveBuf(&ProcessCounter, buffer);
	ReadSaveBuf(&GenerateEvenIfPlayerIsCloseCounter, buffer);
	SkipSaveBuf(buffer, 2);
	ReadSaveBuf(&tmp, buffer);
	assert(tmp == sizeof(CarGeneratorArray));
	for (int i = 0; i < NUM_CARGENS; i++)
		ReadSaveBuf(&CarGeneratorArray[i], buffer);
VALIDATESAVEBUF(size)
}


code:
#pragma once
#include "common.h"
#include "config.h"

enum {
	CARGEN_MAXACTUALLIMIT = 100
};

class CCarGenerator
{
	int32 m_nModelIndex;
	CVector m_vecPos;
	float m_fAngle;
	int16 m_nColor1;
	int16 m_nColor2;
	uint8 m_bForceSpawn;
	uint8 m_nAlarm;
	uint8 m_nDoorlock;
	int16 m_nMinDelay;
	int16 m_nMaxDelay;
	uint32 m_nTimer;
	int32 m_nVehicleHandle;
	uint16 m_nUsesRemaining;
	bool m_bIsBlocking;
	CVector m_vecInf;
	CVector m_vecSup;
	float m_fSize;
public:
	void SwitchOff();
	void SwitchOn();
	uint32 CalcNextGen();
	void DoInternalProcessing();
	void Process();
	void Setup(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay);
	bool CheckForBlockage();
	bool CheckIfWithinRangeOfAnyPlayer();
	void SetUsesRemaining(uint16 uses) { m_nUsesRemaining = uses; }
};

class CTheCarGenerators
{
public:
	static uint8 ProcessCounter;
	static uint32 NumOfCarGenerators;
	static CCarGenerator CarGeneratorArray[NUM_CARGENS];
	static uint8 GenerateEvenIfPlayerIsCloseCounter;
	static uint32 CurrentActiveCount;

	static void Process();
	static int32 CreateCarGenerator(float x, float y, float z, float angle, int32 mi, int16 color1, int16 color2, uint8 force, uint8 alarm, uint8 lock, uint16 min_delay, uint16 max_delay);
	static void Init();
	static void SaveAllCarGenerators(uint8 *, uint32 *);
	static void LoadAllCarGenerators(uint8 *, uint32);
};


code:
#include "common.h"

#include "Cranes.h"

#include "Camera.h"
#include "DMAudio.h"
#include "Garages.h"
#include "General.h"
#include "Entity.h"
#include "ModelIndices.h"
#include "Replay.h"
#include "Object.h"
#include "World.h"
#include "SaveBuf.h"

#define MAX_DISTANCE_TO_FIND_CRANE (10.0f)
#define CRANE_UPDATE_RADIUS (300.0f)
#define CRANE_MOVEMENT_PROCESSING_RADIUS (150.0f)
#define CRUSHER_Z (-0.951f)
#define MILITARY_Z (10.7862f)
#define DISTANCE_FROM_PLAYER_TO_REMOVE_VEHICLE (5.0f)
#define DISTANCE_FROM_HOOK_TO_VEHICLE_TO_COLLECT (0.5f)
#define CAR_REWARD_MILITARY_CRANE (1500)
#define CAR_MOVING_SPEED_THRESHOLD (0.01f)
#define CRANE_SLOWDOWN_MULTIPLIER (0.3f)

#define OSCILLATION_SPEED (0.002f)
#define CAR_ROTATION_SPEED (0.0035f)
#define CRANE_MOVEMENT_SPEED (0.001f)
#define HOOK_ANGLE_MOVEMENT_SPEED (0.004f)
#define HOOK_OFFSET_MOVEMENT_SPEED (0.1f)
#define HOOK_HEIGHT_MOVEMENT_SPEED (0.06f)

#define MESSAGE_SHOW_DURATION (4000)

#define MAX_DISTANCE (99999.9f)
#define MIN_VALID_POSITION (-10000.0f)
#define DEFAULT_OFFSET (20.0f)

#ifdef COMPATIBLE_SAVES
#define CRANES_SAVE_SIZE 0x400
#else
#define CRANES_SAVE_SIZE sizeof(aCranes)
#endif

uint32 TimerForCamInterpolation;

uint32 CCranes::CarsCollectedMilitaryCrane;
int32 CCranes::NumCranes;
CCrane CCranes::aCranes[NUM_CRANES];

void CCranes::InitCranes(void)
{
	CarsCollectedMilitaryCrane = 0;
	NumCranes = 0;
	for (int i = 0; i < NUMSECTORS_X; i++) {
		for (int j = 0; j < NUMSECTORS_Y; j++) {
			for (CPtrNode* pNode = CWorld::GetSector(i, j)->m_lists[ENTITYLIST_BUILDINGS].first; pNode; pNode = pNode->next) {
				CEntity* pEntity = (CEntity*)pNode->item;
				if (MODELID_CRANE_1 == pEntity->GetModelIndex() ||
					MODELID_CRANE_2 == pEntity->GetModelIndex() ||
					MODELID_CRANE_3 == pEntity->GetModelIndex())
					AddThisOneCrane(pEntity);
			}
		}
	}
	for (CPtrNode* pNode = CWorld::GetBigBuildingList(LEVEL_INDUSTRIAL).first; pNode; pNode = pNode->next) {
		CEntity* pEntity = (CEntity*)pNode->item;
		if (MODELID_CRANE_1 == pEntity->GetModelIndex() ||
			MODELID_CRANE_2 == pEntity->GetModelIndex() ||
			MODELID_CRANE_3 == pEntity->GetModelIndex())
			AddThisOneCrane(pEntity);
	}
}

void CCranes::AddThisOneCrane(CEntity* pEntity)
{
	pEntity->GetMatrix().ResetOrientation();
	if (NumCranes >= NUM_CRANES)
		return;
	CCrane* pCrane = &aCranes[NumCranes];
	pCrane->Init();
	pCrane->m_pCraneEntity = (CBuilding*)pEntity;
	pCrane->m_nCraneStatus = CCrane::NONE;
	pCrane->m_fHookAngle = NumCranes; // lol wtf
	while (pCrane->m_fHookAngle > TWOPI)
		pCrane->m_fHookAngle -= TWOPI;
	pCrane->m_fHookOffset = DEFAULT_OFFSET;
	pCrane->m_fHookHeight = DEFAULT_OFFSET;
	pCrane->m_nTimeForNextCheck = 0;
	pCrane->m_nCraneState = CCrane::IDLE;
	pCrane->m_bWasMilitaryCrane = false;
	pCrane->m_nAudioEntity = DMAudio.CreateEntity(AUDIOTYPE_CRANE, &aCranes[NumCranes]);
	if (pCrane->m_nAudioEntity >= 0)
		DMAudio.SetEntityStatus(pCrane->m_nAudioEntity, TRUE);
	pCrane->m_bIsTop = (MODELID_CRANE_1 != pEntity->GetModelIndex());
	// Is this used to avoid military crane?
	if (pCrane->m_bIsTop || pEntity->GetPosition().y > 0.0f) {
		CObject* pHook = new CObject(MI_MAGNET, false);
		pHook->ObjectCreatedBy = MISSION_OBJECT;
		pHook->bUsesCollision = false;
		pHook->bExplosionProof = true;
		pHook->bAffectedByGravity = false;
		pCrane->m_pHook = pHook;
		pCrane->CalcHookCoordinates(&pCrane->m_vecHookCurPos.x, &pCrane->m_vecHookCurPos.y, &pCrane->m_vecHookCurPos.z);
		pCrane->SetHookMatrix();
	}
	else
		pCrane->m_pHook = nil;
	NumCranes++;
}

void CCranes::ActivateCrane(float fInfX, float fSupX, float fInfY, float fSupY, float fDropOffX, float fDropOffY, float fDropOffZ, float fHeading, bool bIsCrusher, bool bIsMilitary, float fPosX, float fPosY)
{
	float fMinDistance = MAX_DISTANCE;
	float X = fPosX, Y = fPosY;
	if (X <= MIN_VALID_POSITION || Y <= MIN_VALID_POSITION) {
		X = fDropOffX;
		Y = fDropOffY;
	}
	int index = 0;
	for (int i = 0; i < NumCranes; i++) {
		float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude();
		if (distance < fMinDistance && distance < MAX_DISTANCE_TO_FIND_CRANE) {
			fMinDistance = distance;
			index = i;
		}
	}
#ifdef FIX_BUGS // classic
	if (fMinDistance == MAX_DISTANCE)
		return;
#endif
	CCrane* pCrane = &aCranes[index];
	pCrane->m_fPickupX1 = fInfX;
	pCrane->m_fPickupX2 = fSupX;
	pCrane->m_fPickupY1 = fInfY;
	pCrane->m_fPickupY2 = fSupY;
	pCrane->m_vecDropoffTarget.x = fDropOffX;
	pCrane->m_vecDropoffTarget.y = fDropOffY;
	pCrane->m_vecDropoffTarget.z = fDropOffZ;
	pCrane->m_nCraneStatus = CCrane::ACTIVATED;
	pCrane->m_pVehiclePickedUp = nil;
	pCrane->m_nVehiclesCollected = 0;
	pCrane->m_fDropoffHeading = fHeading;
	pCrane->m_bIsCrusher = bIsCrusher;
	pCrane->m_bIsMilitaryCrane = bIsMilitary;
	bool military = true;
	if (!bIsMilitary && !pCrane->m_bWasMilitaryCrane)
		military = false;
	pCrane->m_bWasMilitaryCrane = military;
	pCrane->m_nTimeForNextCheck = 0;
	pCrane->m_nCraneState = CCrane::IDLE;
	float Z;
	if (bIsCrusher)
		Z = CRUSHER_Z;
	else if (bIsMilitary)
		Z = MILITARY_Z;
	else
		Z = CWorld::FindGroundZForCoord((fInfX + fSupX) / 2, (fInfY + fSupY) / 2);
	pCrane->FindParametersForTarget((fInfX + fSupX) / 2, (fInfY + fSupY) / 2, Z, &pCrane->m_fPickupAngle, &pCrane->m_fPickupDistance, &pCrane->m_fPickupHeight);
	pCrane->FindParametersForTarget(fDropOffX, fDropOffY, fDropOffZ, &pCrane->m_fDropoffAngle, &pCrane->m_fDropoffDistance, &pCrane->m_fDropoffHeight);
}

void CCranes::DeActivateCrane(float X, float Y)
{
	float fMinDistance = MAX_DISTANCE;
	int index = 0;
	for (int i = 0; i < NumCranes; i++) {
		float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude();
		if (distance < fMinDistance && distance < MAX_DISTANCE_TO_FIND_CRANE) {
			fMinDistance = distance;
			index = i;
		}
	}
#ifdef FIX_BUGS // classic
	if (fMinDistance == MAX_DISTANCE)
		return;
#endif
	aCranes[index].m_nCraneStatus = CCrane::DEACTIVATED;
	aCranes[index].m_nCraneState = CCrane::IDLE;
}

bool CCranes::IsThisCarPickedUp(float X, float Y, CVehicle* pVehicle)
{
	int index = 0;
	bool result = false;
	for (int i = 0; i < NumCranes; i++) {
		float distance = (CVector2D(X, Y) - aCranes[i].m_pCraneEntity->GetPosition()).Magnitude();
		if (distance < MAX_DISTANCE_TO_FIND_CRANE && aCranes[i].m_pVehiclePickedUp == pVehicle) {
			if (aCranes[i].m_nCraneState == CCrane::LIFTING_TARGET || aCranes[i].m_nCraneState == CCrane::ROTATING_TARGET)
				result = true;
		}
	}
	return result;
}

void CCranes::UpdateCranes(void)
{
	for (int i = 0; i < NumCranes; i++) {
		if (aCranes[i].m_bIsTop || aCranes[i].m_bIsCrusher ||
			(TheCamera.GetPosition().x + CRANE_UPDATE_RADIUS > aCranes[i].m_pCraneEntity->GetPosition().x &&
				TheCamera.GetPosition().x - CRANE_UPDATE_RADIUS < aCranes[i].m_pCraneEntity->GetPosition().x &&
				TheCamera.GetPosition().y + CRANE_UPDATE_RADIUS > aCranes[i].m_pCraneEntity->GetPosition().y &&
				TheCamera.GetPosition().y + CRANE_UPDATE_RADIUS < aCranes[i].m_pCraneEntity->GetPosition().y))
			aCranes[i].Update();
	}
}

void CCrane::Update(void)
{
	if (CReplay::IsPlayingBack())
		return;
	if (((m_nCraneStatus == ACTIVATED || m_nCraneStatus == DEACTIVATED) &&
		Abs(TheCamera.GetGameCamPosition().x - m_pCraneEntity->GetPosition().x) < CRANE_MOVEMENT_PROCESSING_RADIUS &&
		Abs(TheCamera.GetGameCamPosition().y - m_pCraneEntity->GetPosition().y) < CRANE_MOVEMENT_PROCESSING_RADIUS) ||
		m_nCraneState != IDLE) {
		switch (m_nCraneState) {
		case IDLE:
			if (GoTowardsTarget(m_fPickupAngle, m_fPickupDistance, GetHeightToPickup()) &&
				CTimer::GetTimeInMilliseconds() > m_nTimeForNextCheck) {
				CWorld::AdvanceCurrentScanCode();
#ifdef FIX_BUGS
				int xstart = Max(0, CWorld::GetSectorIndexX(m_fPickupX1));
				int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(m_fPickupX2));
				int ystart = Max(0, CWorld::GetSectorIndexY(m_fPickupY1));
				int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(m_fPickupY2));
#else
				int xstart = CWorld::GetSectorIndexX(m_fPickupX1);
				int xend = CWorld::GetSectorIndexX(m_fPickupX2);
				int ystart = CWorld::GetSectorIndexY(m_fPickupY1);
				int yend = CWorld::GetSectorIndexY(m_fPickupY1);
#endif
				assert(xstart <= xend);
				assert(ystart <= yend);
				for (int i = xstart; i <= xend; i++) {
					for (int j = ystart; j <= yend; j++) {
						FindCarInSectorList(&CWorld::GetSector(i, j)->m_lists[ENTITYLIST_VEHICLES]);
						FindCarInSectorList(&CWorld::GetSector(i, j)->m_lists[ENTITYLIST_VEHICLES_OVERLAP]);
					}
				}
			}
			break;
		case GOING_TOWARDS_TARGET:
			if (m_pVehiclePickedUp){
				if (m_pVehiclePickedUp->GetPosition().x < m_fPickupX1 ||
					m_pVehiclePickedUp->GetPosition().x > m_fPickupX2 ||
					m_pVehiclePickedUp->GetPosition().y < m_fPickupY1 ||
					m_pVehiclePickedUp->GetPosition().y > m_fPickupY2 ||
					m_pVehiclePickedUp->pDriver ||
					Abs(m_pVehiclePickedUp->GetMoveSpeed().x) > CAR_MOVING_SPEED_THRESHOLD ||
					Abs(m_pVehiclePickedUp->GetMoveSpeed().y) > CAR_MOVING_SPEED_THRESHOLD ||
					Abs(m_pVehiclePickedUp->GetMoveSpeed().z) > CAR_MOVING_SPEED_THRESHOLD ||
					(FindPlayerPed()->GetPedState() == PED_ENTER_CAR
#ifdef FIX_BUGS
					|| FindPlayerPed()->GetPedState() == PED_CARJACK
#endif
					) && FindPlayerPed()->m_pSeekTarget == m_pVehiclePickedUp) {
					m_pVehiclePickedUp = nil;
					m_nCraneState = IDLE;
				}
				else {
					float fAngle, fOffset, fHeight;
					FindParametersForTarget(
						m_pVehiclePickedUp->GetPosition().x,
						m_pVehiclePickedUp->GetPosition().y,
						m_pVehiclePickedUp->GetPosition().z + m_pVehiclePickedUp->GetColModel()->boundingBox.max.z,
						&fAngle, &fOffset, &fHeight);
					if (GoTowardsTarget(fAngle, fOffset, fHeight)) {
						CVector distance = m_pVehiclePickedUp->GetPosition() - m_vecHookCurPos;
						distance.z += m_pVehiclePickedUp->GetColModel()->boundingBox.max.z;
						if (distance.MagnitudeSqr() < SQR(DISTANCE_FROM_HOOK_TO_VEHICLE_TO_COLLECT)) {
							m_nCraneState = GOING_TOWARDS_TARGET_ONLY_HEIGHT;
							m_vecHookVelocity *= 0.4f;
							m_pVehiclePickedUp->bLightsOn = false;
							m_pVehiclePickedUp->bUsesCollision = false;
							if (m_bIsCrusher)
								m_pVehiclePickedUp->bCollisionProof = true;
							DMAudio.PlayOneShot(m_nAudioEntity, SOUND_CRANE_PICKUP, 0.0f);
						}
					}
				}
			}
			else
				m_nCraneState = IDLE;
			break;
		case LIFTING_TARGET:
			RotateCarriedCarProperly();
			if (GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, GetHeightToDropoff(), CRANE_SLOWDOWN_MULTIPLIER))
				m_nCraneState = ROTATING_TARGET;
			if (!m_pVehiclePickedUp || m_pVehiclePickedUp->pDriver) {
				m_pVehiclePickedUp = nil;
				m_nCraneState = IDLE;
			}
			break;
		case GOING_TOWARDS_TARGET_ONLY_HEIGHT:
			RotateCarriedCarProperly();
			if (GoTowardsHeightTarget(GetHeightToPickupHeight(), CRANE_SLOWDOWN_MULTIPLIER))
				m_nCraneState = LIFTING_TARGET;
			TimerForCamInterpolation = CTimer::GetTimeInMilliseconds();
			if (!m_pVehiclePickedUp || m_pVehiclePickedUp->pDriver) {
				m_pVehiclePickedUp = nil;
				m_nCraneState = IDLE;
			}
			break;
		case ROTATING_TARGET:
		{
			bool bRotateFinished = RotateCarriedCarProperly();
			bool bMovementFinished = GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, m_fDropoffHeight, 0.3f);
			if (bMovementFinished && bRotateFinished) {
				float fDistanceFromPlayer = m_pVehiclePickedUp ? ((CVector2D)FindPlayerCoors() - (CVector2D)m_pVehiclePickedUp->GetPosition()).Magnitude() : 0.0f;
				if (fDistanceFromPlayer > DISTANCE_FROM_PLAYER_TO_REMOVE_VEHICLE || !m_bWasMilitaryCrane) {
					m_nCraneState = DROPPING_TARGET;
					if (m_pVehiclePickedUp) {
						m_pVehiclePickedUp->bUsesCollision = true;
						m_pVehiclePickedUp->m_nStaticFrames = 0;
						++m_nVehiclesCollected;
						if (m_bIsMilitaryCrane) {
							CCranes::RegisterCarForMilitaryCrane(m_pVehiclePickedUp->GetModelIndex());
							if (!CCranes::HaveAllCarsBeenCollectedByMilitaryCrane()) {
								CWorld::Players[CWorld::PlayerInFocus].m_nMoney += CAR_REWARD_MILITARY_CRANE;
								CGarages::TriggerMessage("GA_10", CAR_REWARD_MILITARY_CRANE, MESSAGE_SHOW_DURATION, -1);
							}
							CWorld::Remove(m_pVehiclePickedUp);
							delete m_pVehiclePickedUp;
						}
					}
					m_pVehiclePickedUp = nil;
				}
			}
			break;
		}
		case DROPPING_TARGET:
			if (GoTowardsTarget(m_fDropoffAngle, m_fDropoffDistance, GetHeightToDropoffHeight(), CRANE_SLOWDOWN_MULTIPLIER)) {
				m_nCraneState = IDLE;
				m_nTimeForNextCheck = CTimer::GetTimeInMilliseconds() + 10000;
			}
			break;
		default:
			break;
		}
		CVector vecHook;
		CalcHookCoordinates(&vecHook.x, &vecHook.y, &vecHook.z);
		m_vecHookVelocity += ((CVector2D)vecHook - (CVector2D)m_vecHookCurPos) * CTimer::GetTimeStep() * CRANE_MOVEMENT_SPEED;
		m_vecHookVelocity *= Pow(0.98f, CTimer::GetTimeStep());
		m_vecHookCurPos.x += m_vecHookVelocity.x * CTimer::GetTimeStep();
		m_vecHookCurPos.y += m_vecHookVelocity.y * CTimer::GetTimeStep();
		m_vecHookCurPos.z = vecHook.z;
		switch (m_nCraneState) {
		case LIFTING_TARGET:
		case GOING_TOWARDS_TARGET_ONLY_HEIGHT:
		case ROTATING_TARGET:
			if (m_pVehiclePickedUp) {
				m_pVehiclePickedUp->SetPosition(m_vecHookCurPos.x, m_vecHookCurPos.y, m_vecHookCurPos.z - m_pVehiclePickedUp->GetColModel()->boundingBox.max.z);
				m_pVehiclePickedUp->SetMoveSpeed(0.0f, 0.0f, 0.0f);
				CVector up(vecHook.x - m_vecHookCurPos.x, vecHook.y - m_vecHookCurPos.y, 20.0f);
				up.Normalise();
				m_pVehiclePickedUp->GetRight() = CrossProduct(m_pVehiclePickedUp->GetForward(), up);
				m_pVehiclePickedUp->GetForward() = CrossProduct(up, m_pVehiclePickedUp->GetRight());
				m_pVehiclePickedUp->GetUp() = up;
			}
			break;
		default:
			break;
		}
	}
	else {
		int16 rnd = (m_pCraneEntity->m_randomSeed + (CTimer::GetTimeInMilliseconds() >> 11)) & 0xF;
		// 16 options, lasting 2048 ms each
		// a bit awkward: why there are 4 periods for -= and 6 for +=? is it a bug?
		if (rnd < 4) {
			m_fHookAngle -= OSCILLATION_SPEED * CTimer::GetTimeStep();
			if (m_fHookAngle < 0.0f)
				m_fHookAngle += TWOPI;
		}
		else if (rnd > 5 && rnd < 12) {
			m_fHookAngle += OSCILLATION_SPEED * CTimer::GetTimeStep();
			if (m_fHookAngle > TWOPI)
				m_fHookAngle -= TWOPI;
		}
		CalcHookCoordinates(&m_vecHookCurPos.x, &m_vecHookCurPos.y, &m_vecHookCurPos.z);
		m_vecHookVelocity.x = m_vecHookVelocity.y = 0.0f;
	}
	float fCos = Cos(m_fHookAngle);
	float fSin = Sin(m_fHookAngle);
	m_pCraneEntity->GetRight().x = fCos;
	m_pCraneEntity->GetForward().y = fCos;
	m_pCraneEntity->GetRight().y = fSin;
	m_pCraneEntity->GetForward().x = -fSin;
	m_pCraneEntity->GetMatrix().UpdateRW();
	m_pCraneEntity->UpdateRwFrame();
	SetHookMatrix();
}

bool CCrane::RotateCarriedCarProperly()
{
	if (m_fDropoffHeading <= 0.0f)
		return true;
	if (!m_pVehiclePickedUp)
		return true;
	float fAngleDelta = m_fDropoffHeading - CGeneral::GetATanOfXY(m_pVehiclePickedUp->GetForward().x, m_pVehiclePickedUp->GetForward().y);
	while (fAngleDelta < -HALFPI)
		fAngleDelta += PI;
	while (fAngleDelta > HALFPI)
		fAngleDelta -= PI;
	float fDeltaThisFrame = CAR_ROTATION_SPEED * CTimer::GetTimeStep();
	if (Abs(fAngleDelta) <= fDeltaThisFrame) // no rotation is actually applied?
		return true;
	m_pVehiclePickedUp->GetMatrix().RotateZ(fAngleDelta < 0 ? -fDeltaThisFrame : fDeltaThisFrame);
	return false;
}

void CCrane::FindCarInSectorList(CPtrList* pList)
{
	CPtrNode* node;
	for (node = pList->first; node; node = node->next) {
		CVehicle* pVehicle = (CVehicle*)node->item;
		if (pVehicle->m_scanCode == CWorld::GetCurrentScanCode())
			continue;
		pVehicle->m_scanCode = CWorld::GetCurrentScanCode();
		if (pVehicle->GetPosition().x < m_fPickupX1 || pVehicle->GetPosition().x > m_fPickupX2 ||
			pVehicle->GetPosition().y < m_fPickupY1 || pVehicle->GetPosition().y > m_fPickupY2)
			continue;
		if (pVehicle->pDriver)
			continue;
		if (Abs(pVehicle->GetMoveSpeed().x) >= CAR_MOVING_SPEED_THRESHOLD ||
			Abs(pVehicle->GetMoveSpeed().y) >= CAR_MOVING_SPEED_THRESHOLD ||
			Abs(pVehicle->GetMoveSpeed().z) >= CAR_MOVING_SPEED_THRESHOLD)
			continue;
		if (!pVehicle->IsCar() || pVehicle->GetStatus() == STATUS_WRECKED || pVehicle->m_fHealth < 250.0f)
			continue;
		if (!DoesCranePickUpThisCarType(pVehicle->GetModelIndex()) ||
			m_bIsMilitaryCrane && CCranes::DoesMilitaryCraneHaveThisOneAlready(pVehicle->GetModelIndex())) {
			if (!pVehicle->bCraneMessageDone) {
				pVehicle->bCraneMessageDone = true;
				if (!m_bIsMilitaryCrane)
					CGarages::TriggerMessage("CR_1", -1, MESSAGE_SHOW_DURATION, -1); // Crane cannot lift this vehicle.
				else if (DoesCranePickUpThisCarType(pVehicle->GetModelIndex()))
					CGarages::TriggerMessage("GA_20", -1, MESSAGE_SHOW_DURATION, -1); // We got more of these than we can shift. Sorry man, no deal.
				else
					CGarages::TriggerMessage("GA_19", -1, MESSAGE_SHOW_DURATION, -1); // We're not interested in that model.
			}
		}
		else {
			m_pVehiclePickedUp = pVehicle;
			pVehicle->RegisterReference((CEntity**)&m_pVehiclePickedUp);
			m_nCraneState = GOING_TOWARDS_TARGET;
		}
	}
}

bool CCrane::DoesCranePickUpThisCarType(uint32 mi)
{
	if (m_bIsCrusher) {
		return mi != MI_FIRETRUCK &&
			mi != MI_TRASH &&
#ifdef FIX_BUGS
			mi != MI_COACH &&
#else
			mi != MI_BLISTA &&
#endif
			mi != MI_SECURICA &&
			mi != MI_BUS &&
			mi != MI_DODO &&
			mi != MI_RHINO;
	}
	if (m_bIsMilitaryCrane) {
		return mi == MI_FIRETRUCK ||
			mi == MI_AMBULAN ||
			mi == MI_ENFORCER ||
			mi == MI_FBICAR ||
			mi == MI_RHINO ||
			mi == MI_BARRACKS ||
			mi == MI_POLICE;
	}
	return true;
}

bool CCranes::DoesMilitaryCraneHaveThisOneAlready(uint32 mi)
{
	switch (mi) {
	case MI_FIRETRUCK: return (CarsCollectedMilitaryCrane & 1);
	case MI_AMBULAN: return (CarsCollectedMilitaryCrane & 2);
	case MI_ENFORCER: return (CarsCollectedMilitaryCrane & 4);
	case MI_FBICAR: return (CarsCollectedMilitaryCrane & 8);
	case MI_RHINO: return (CarsCollectedMilitaryCrane & 0x10);
	case MI_BARRACKS: return (CarsCollectedMilitaryCrane & 0x20);
	case MI_POLICE: return (CarsCollectedMilitaryCrane & 0x40);
	default: break;
	}
	return false;
}

void CCranes::RegisterCarForMilitaryCrane(uint32 mi)
{
	switch (mi) {
	case MI_FIRETRUCK: CarsCollectedMilitaryCrane |= 1; break;
	case MI_AMBULAN: CarsCollectedMilitaryCrane |= 2; break;
	case MI_ENFORCER: CarsCollectedMilitaryCrane |= 4; break;
	case MI_FBICAR: CarsCollectedMilitaryCrane |= 8; break;
	case MI_RHINO: CarsCollectedMilitaryCrane |= 0x10; break;
	case MI_BARRACKS: CarsCollectedMilitaryCrane |= 0x20; break;
	case MI_POLICE: CarsCollectedMilitaryCrane |= 0x40; break;
	default: break;
	}
}

bool CCranes::HaveAllCarsBeenCollectedByMilitaryCrane()
{
	return (CarsCollectedMilitaryCrane & 0x7F) == 0x7F;
}

bool CCrane::GoTowardsTarget(float fAngleToTarget, float fDistanceToTarget, float fTargetHeight, float fSpeedMultiplier)
{
	bool bAngleMovementFinished, bOffsetMovementFinished, bHeightMovementFinished;
	float fHookAngleDelta = fAngleToTarget - m_fHookAngle;
	while (fHookAngleDelta > PI)
		fHookAngleDelta -= TWOPI;
	while (fHookAngleDelta < -PI)
		fHookAngleDelta += TWOPI;
	float fHookAngleChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_ANGLE_MOVEMENT_SPEED;
	if (Abs(fHookAngleDelta) < fHookAngleChangeThisFrame) {
		m_fHookAngle = fAngleToTarget;
		bAngleMovementFinished = true;
	} else {
		if (fHookAngleDelta < 0.0f) {
			m_fHookAngle -= fHookAngleChangeThisFrame;
			if (m_fHookAngle < 0.0f)
				m_fHookAngle += TWOPI;
		}
		else {
			m_fHookAngle += fHookAngleChangeThisFrame;
			if (m_fHookAngle > TWOPI)
				m_fHookAngle -= TWOPI;
		}
		bAngleMovementFinished = false;
	}
	float fHookOffsetDelta = fDistanceToTarget - m_fHookOffset;
	float fHookOffsetChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_OFFSET_MOVEMENT_SPEED;
	if (Abs(fHookOffsetDelta) < fHookOffsetChangeThisFrame) {
		m_fHookOffset = fDistanceToTarget;
		bOffsetMovementFinished = true;
	} else {
		if (fHookOffsetDelta < 0.0f)
			m_fHookOffset -= fHookOffsetChangeThisFrame;
		else
			m_fHookOffset += fHookOffsetChangeThisFrame;
		bOffsetMovementFinished = false;
	}
	float fHookHeightDelta = fTargetHeight - m_fHookHeight;
	float fHookHeightChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_HEIGHT_MOVEMENT_SPEED;
	if (Abs(fHookHeightDelta) < fHookHeightChangeThisFrame) {
		m_fHookHeight = fTargetHeight;
		bHeightMovementFinished = true;
	} else {
		if (fHookHeightDelta < 0.0f)
			m_fHookHeight -= fHookHeightChangeThisFrame;
		else
			m_fHookHeight += fHookHeightChangeThisFrame;
		bHeightMovementFinished = false;
	}
	return bAngleMovementFinished && bOffsetMovementFinished && bHeightMovementFinished;
}

bool CCrane::GoTowardsHeightTarget(float fTargetHeight, float fSpeedMultiplier)
{
	bool bHeightMovementFinished;
	float fHookHeightDelta = fTargetHeight - m_fHookHeight;
	float fHookHeightChangeThisFrame = fSpeedMultiplier * CTimer::GetTimeStep() * HOOK_HEIGHT_MOVEMENT_SPEED;
	if (Abs(fHookHeightDelta) < fHookHeightChangeThisFrame) {
		m_fHookHeight = fTargetHeight;
		bHeightMovementFinished = true;
	} else {
		if (fHookHeightDelta < 0.0f)
			m_fHookHeight -= fHookHeightChangeThisFrame;
		else
			m_fHookHeight += fHookHeightChangeThisFrame;
		bHeightMovementFinished = false;
	}
	return bHeightMovementFinished;
}

void CCrane::FindParametersForTarget(float X, float Y, float Z, float* pAngle, float* pDistance, float* pHeight)
{
	*pAngle = CGeneral::GetATanOfXY(X - m_pCraneEntity->GetPosition().x, Y - m_pCraneEntity->GetPosition().y);
	*pDistance = ((CVector2D(X, Y) - (CVector2D)m_pCraneEntity->GetPosition())).Magnitude();
	*pHeight = Z;
}

void CCrane::CalcHookCoordinates(float* pX, float* pY, float* pZ)
{
	*pX = Cos(m_fHookAngle) * m_fHookOffset + m_pCraneEntity->GetPosition().x;
	*pY = Sin(m_fHookAngle) * m_fHookOffset + m_pCraneEntity->GetPosition().y;
	*pZ = m_fHookHeight;
}

void CCrane::SetHookMatrix()
{
	if (m_pHook == nil)
		return;
	m_pHook->SetPosition(m_vecHookCurPos);
	CVector up(m_vecHookInitPos.x - m_vecHookCurPos.x, m_vecHookInitPos.y - m_vecHookCurPos.y, 20.0f);
	up.Normalise();
	m_pHook->GetRight() = CrossProduct(CVector(0.0f, 1.0f, 0.0f), up);
	m_pHook->GetForward() = CrossProduct(up, m_pHook->GetRight());
	m_pHook->GetUp() = up;
	m_pHook->SetOrientation(0.0f, 0.0f, -HALFPI);
	m_pHook->GetMatrix().UpdateRW();
	m_pHook->UpdateRwFrame();
	CWorld::Remove(m_pHook);
	CWorld::Add(m_pHook);
}

bool CCranes::IsThisCarBeingCarriedByAnyCrane(CVehicle* pVehicle)
{
	for (int i = 0; i < NumCranes; i++) {
		if (pVehicle == aCranes[i].m_pVehiclePickedUp) {
			switch (aCranes[i].m_nCraneState) {
			case CCrane::GOING_TOWARDS_TARGET_ONLY_HEIGHT:
			case CCrane::LIFTING_TARGET:
			case CCrane::ROTATING_TARGET:
				return true;
			default:
				break;
			}
		}
	}
	return false;
}

bool CCranes::IsThisCarBeingTargettedByAnyCrane(CVehicle* pVehicle)
{
	for (int i = 0; i < NumCranes; i++) {
		if (pVehicle == aCranes[i].m_pVehiclePickedUp)
			return true;
	}
	return false;
}

void CCranes::Save(uint8* buf, uint32* size)
{
	INITSAVEBUF

	*size = 2 * sizeof(uint32) + CRANES_SAVE_SIZE;
	WriteSaveBuf(buf, NumCranes);
	WriteSaveBuf(buf, CarsCollectedMilitaryCrane);
	for (int i = 0; i < NUM_CRANES; i++) {
#ifdef COMPATIBLE_SAVES
		int32 tmp = aCranes[i].m_pCraneEntity != nil ? CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert(aCranes[i].m_pCraneEntity) + 1 : 0;
		WriteSaveBuf(buf, tmp);
		tmp = aCranes[i].m_pHook != nil ? CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(aCranes[i].m_pHook) + 1 : 0;
		WriteSaveBuf(buf, tmp);
		WriteSaveBuf(buf, aCranes[i].m_nAudioEntity);
		WriteSaveBuf(buf, aCranes[i].m_fPickupX1);
		WriteSaveBuf(buf, aCranes[i].m_fPickupX2);
		WriteSaveBuf(buf, aCranes[i].m_fPickupY1);
		WriteSaveBuf(buf, aCranes[i].m_fPickupY2);
		WriteSaveBuf(buf, aCranes[i].m_vecDropoffTarget);
		WriteSaveBuf(buf, aCranes[i].m_fDropoffHeading);
		WriteSaveBuf(buf, aCranes[i].m_fPickupAngle);
		WriteSaveBuf(buf, aCranes[i].m_fDropoffAngle);
		WriteSaveBuf(buf, aCranes[i].m_fPickupDistance);
		WriteSaveBuf(buf, aCranes[i].m_fDropoffDistance);
		WriteSaveBuf(buf, aCranes[i].m_fPickupHeight);
		WriteSaveBuf(buf, aCranes[i].m_fDropoffHeight);
		WriteSaveBuf(buf, aCranes[i].m_fHookAngle);
		WriteSaveBuf(buf, aCranes[i].m_fHookOffset);
		WriteSaveBuf(buf, aCranes[i].m_fHookHeight);
		WriteSaveBuf(buf, aCranes[i].m_vecHookInitPos);
		WriteSaveBuf(buf, aCranes[i].m_vecHookCurPos);
		WriteSaveBuf(buf, aCranes[i].m_vecHookVelocity);
		tmp = aCranes[i].m_pVehiclePickedUp != nil ? CPools::GetVehiclePool()->GetJustIndex_NoFreeAssert(aCranes[i].m_pVehiclePickedUp) + 1 : 0;
		WriteSaveBuf(buf, tmp);
		WriteSaveBuf(buf, aCranes[i].m_nTimeForNextCheck);
		WriteSaveBuf(buf, aCranes[i].m_nCraneStatus);
		WriteSaveBuf(buf, aCranes[i].m_nCraneState);
		WriteSaveBuf(buf, aCranes[i].m_nVehiclesCollected);
		WriteSaveBuf(buf, aCranes[i].m_bIsCrusher);
		WriteSaveBuf(buf, aCranes[i].m_bIsMilitaryCrane);
		WriteSaveBuf(buf, aCranes[i].m_bWasMilitaryCrane);
		WriteSaveBuf(buf, aCranes[i].m_bIsTop);
		ZeroSaveBuf(buf, 1);
#else
		CCrane *pCrane = WriteSaveBuf(buf, aCranes[i]);
		if (pCrane->m_pCraneEntity != nil)
			pCrane->m_pCraneEntity = (CBuilding*)(CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert(pCrane->m_pCraneEntity) + 1);
		if (pCrane->m_pHook != nil)
			pCrane->m_pHook = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(pCrane->m_pHook) + 1);
		if (pCrane->m_pVehiclePickedUp != nil)
			pCrane->m_pVehiclePickedUp = (CVehicle*)(CPools::GetVehiclePool()->GetJustIndex_NoFreeAssert(pCrane->m_pVehiclePickedUp) + 1);
#endif
	}

	VALIDATESAVEBUF(*size);
}

void CCranes::Load(uint8* buf, uint32 size)
{
	INITSAVEBUF

	ReadSaveBuf(&NumCranes, buf);
	ReadSaveBuf(&CarsCollectedMilitaryCrane, buf);
	for (int i = 0; i < NUM_CRANES; i++) {
#ifdef COMPATIBLE_SAVES
		int32 tmp;
		ReadSaveBuf(&tmp, buf);
		aCranes[i].m_pCraneEntity = tmp != 0 ? CPools::GetBuildingPool()->GetSlot(tmp - 1) : nil;
		ReadSaveBuf(&tmp, buf);
		aCranes[i].m_pHook = tmp != 0 ? CPools::GetObjectPool()->GetSlot(tmp - 1) : nil;
		ReadSaveBuf(&aCranes[i].m_nAudioEntity, buf);
		ReadSaveBuf(&aCranes[i].m_fPickupX1, buf);
		ReadSaveBuf(&aCranes[i].m_fPickupX2, buf);
		ReadSaveBuf(&aCranes[i].m_fPickupY1, buf);
		ReadSaveBuf(&aCranes[i].m_fPickupY2, buf);
		ReadSaveBuf(&aCranes[i].m_vecDropoffTarget, buf);
		ReadSaveBuf(&aCranes[i].m_fDropoffHeading, buf);
		ReadSaveBuf(&aCranes[i].m_fPickupAngle, buf);
		ReadSaveBuf(&aCranes[i].m_fDropoffAngle, buf);
		ReadSaveBuf(&aCranes[i].m_fPickupDistance, buf);
		ReadSaveBuf(&aCranes[i].m_fDropoffDistance, buf);
		ReadSaveBuf(&aCranes[i].m_fPickupHeight, buf);
		ReadSaveBuf(&aCranes[i].m_fDropoffHeight, buf);
		ReadSaveBuf(&aCranes[i].m_fHookAngle, buf);
		ReadSaveBuf(&aCranes[i].m_fHookOffset, buf);
		ReadSaveBuf(&aCranes[i].m_fHookHeight, buf);
		ReadSaveBuf(&aCranes[i].m_vecHookInitPos, buf);
		ReadSaveBuf(&aCranes[i].m_vecHookCurPos, buf);
		ReadSaveBuf(&aCranes[i].m_vecHookVelocity, buf);
		ReadSaveBuf(&tmp, buf);
		aCranes[i].m_pVehiclePickedUp = tmp != 0 ? CPools::GetVehiclePool()->GetSlot(tmp - 1) : nil;
		ReadSaveBuf(&aCranes[i].m_nTimeForNextCheck, buf);
		ReadSaveBuf(&aCranes[i].m_nCraneStatus, buf);
		ReadSaveBuf(&aCranes[i].m_nCraneState, buf);
		ReadSaveBuf(&aCranes[i].m_nVehiclesCollected, buf);
		ReadSaveBuf(&aCranes[i].m_bIsCrusher, buf);
		ReadSaveBuf(&aCranes[i].m_bIsMilitaryCrane, buf);
		ReadSaveBuf(&aCranes[i].m_bWasMilitaryCrane, buf);
		ReadSaveBuf(&aCranes[i].m_bIsTop, buf);
		SkipSaveBuf(buf, 1);
#else
		ReadSaveBuf(&aCranes[i], buf);
	}
	for (int i = 0; i < NUM_CRANES; i++) {
		CCrane *pCrane = &aCranes[i];
		if (pCrane->m_pCraneEntity != nil)
			pCrane->m_pCraneEntity = CPools::GetBuildingPool()->GetSlot((uintptr)pCrane->m_pCraneEntity - 1);
		if (pCrane->m_pHook != nil)
			pCrane->m_pHook = CPools::GetObjectPool()->GetSlot((uintptr)pCrane->m_pHook - 1);
		if (pCrane->m_pVehiclePickedUp != nil)
			pCrane->m_pVehiclePickedUp = CPools::GetVehiclePool()->GetSlot((uintptr)pCrane->m_pVehiclePickedUp - 1);
#endif
	}
	for (int i = 0; i < NUM_CRANES; i++) {
		aCranes[i].m_nAudioEntity = DMAudio.CreateEntity(AUDIOTYPE_CRANE, &aCranes[i]);
		if (aCranes[i].m_nAudioEntity != 0)
			DMAudio.SetEntityStatus(aCranes[i].m_nAudioEntity, TRUE);
	}

	VALIDATESAVEBUF(size);
}


code:
#pragma once
#include "common.h"

#include "World.h"

class CVehicle;
class CEntity;
class CObject;
class CBuilding;

class CCrane
{
public:
	enum CraneState {
		IDLE = 0,
		GOING_TOWARDS_TARGET = 1,
		LIFTING_TARGET = 2,
		GOING_TOWARDS_TARGET_ONLY_HEIGHT = 3,
		ROTATING_TARGET = 4,
		DROPPING_TARGET = 5
	};
	enum CraneStatus {
		NONE = 0,
		ACTIVATED = 1,
		DEACTIVATED = 2
	};
	CBuilding *m_pCraneEntity;
	CObject *m_pHook;
	int32 m_nAudioEntity;
	float m_fPickupX1;
	float m_fPickupX2;
	float m_fPickupY1;
	float m_fPickupY2;
	CVector m_vecDropoffTarget;
	float m_fDropoffHeading;
	float m_fPickupAngle;
	float m_fDropoffAngle;
	float m_fPickupDistance;
	float m_fDropoffDistance;
	float m_fPickupHeight;
	float m_fDropoffHeight;
	float m_fHookAngle;
	float m_fHookOffset;
	float m_fHookHeight;
	CVector m_vecHookInitPos;
	CVector m_vecHookCurPos;
	CVector2D m_vecHookVelocity;
	CVehicle *m_pVehiclePickedUp;
	uint32 m_nTimeForNextCheck;
	uint8 m_nCraneStatus;
	uint8 m_nCraneState;
	uint8 m_nVehiclesCollected;
	bool m_bIsCrusher;
	bool m_bIsMilitaryCrane;
	bool m_bWasMilitaryCrane;
	bool m_bIsTop;

	void Init(void) { memset(this, 0, sizeof(*this)); }
	void Update(void);
	bool RotateCarriedCarProperly(void);
	void FindCarInSectorList(CPtrList* pList);
	bool DoesCranePickUpThisCarType(uint32 mi);
	bool GoTowardsTarget(float fAngleToTarget, float fDistanceToTarget, float fTargetHeight, float fSpeedMultiplier = 1.0f);
	bool GoTowardsHeightTarget(float fTargetHeight, float fSpeedMultiplier = 1.0f);
	void FindParametersForTarget(float X, float Y, float Z, float* pAngle, float* pDistance, float* pHeight);
	void CalcHookCoordinates(float* pX, float* pY, float* pZ);
	void SetHookMatrix(void);

	float GetHeightToPickup() { return 4.0f + m_fPickupHeight + (m_bIsCrusher ? 4.5f : 0.0f); };
	float GetHeightToDropoff() { return m_bIsCrusher ? (2.0f + m_fDropoffHeight + 3.0f) : (2.0f + m_fDropoffHeight); }
	float GetHeightToPickupHeight() { return m_fPickupHeight + (m_bIsCrusher ? 7.0f : 4.0f); }
	float GetHeightToDropoffHeight() { return m_fDropoffHeight + (m_bIsCrusher ? 7.0f : 2.0f); }
};

VALIDATE_SIZE(CCrane, 128);

class CCranes
{
public:
	static void InitCranes(void);
	static void AddThisOneCrane(CEntity* pCraneEntity);
	static void ActivateCrane(float fInfX, float fSupX, float fInfY, float fSupY, float fDropOffX, float fDropOffY, float fDropOffZ, float fHeading, bool bIsCrusher, bool bIsMilitary, float fPosX, float fPosY);
	static void DeActivateCrane(float fX, float fY);
	static bool IsThisCarPickedUp(float fX, float fY, CVehicle* pVehicle);
	static void UpdateCranes(void);
	static bool DoesMilitaryCraneHaveThisOneAlready(uint32 mi);
	static void RegisterCarForMilitaryCrane(uint32 mi);
	static bool HaveAllCarsBeenCollectedByMilitaryCrane(void);
	static bool IsThisCarBeingCarriedByAnyCrane(CVehicle* pVehicle);
	static bool IsThisCarBeingTargettedByAnyCrane(CVehicle* pVehicle);
	static void Save(uint8* buf, uint32* size);
	static void Load(uint8* buf, uint32 size); // out of class in III PC and later because of SecuROM

	static uint32 CarsCollectedMilitaryCrane;
	static int32 NumCranes;
	static CCrane aCranes[NUM_CRANES];
};


code:
#include "common.h"

#include "General.h"
#include "Vehicle.h"
#include "DamageManager.h"

float G_aComponentDamage[] = { 2.5f, 1.25f, 3.2f, 1.4f, 2.5f, 2.8f, 0.5f };

CDamageManager::CDamageManager(void)
{
	ResetDamageStatus();
	m_fWheelDamageEffect = 0.75f;
	field_18 = 1;
}

void
CDamageManager::ResetDamageStatus(void)
{
	memset(this, 0, sizeof(*this));
}

void
CDamageManager::FuckCarCompletely(void)
{
	int i;

	m_wheelStatus[0] = WHEEL_STATUS_MISSING;
	// wheels 1-3 not reset?

	for(i = 0; i < ARRAY_SIZE(m_doorStatus); i++)
		m_doorStatus[i] = DOOR_STATUS_MISSING;

	for(i = 0; i < 3; i++){
#ifdef FIX_BUGS
		ProgressPanelDamage(VEHBUMPER_FRONT);
		ProgressPanelDamage(VEHBUMPER_REAR);
#else
		// this can't be right
		ProgressPanelDamage(COMPONENT_BUMPER_FRONT);
		ProgressPanelDamage(COMPONENT_BUMPER_REAR);
#endif
	}
	// Why set to no damage?
#ifndef FIX_BUGS
	m_lightStatus = 0;
	m_panelStatus = 0;
#endif
	SetEngineStatus(250);
}

bool
CDamageManager::ApplyDamage(tComponent component, float damage, float unused)
{
	tComponentGroup group;
	uint8 subComp;

	GetComponentGroup(component, &group, &subComp);
	damage *= G_aComponentDamage[group];
	if(damage > 150.0f){
		switch(group){
		case COMPGROUP_WHEEL:
			ProgressWheelDamage(subComp);
			break;
		case COMPGROUP_DOOR:
		case COMPGROUP_BOOT:
			ProgressDoorDamage(subComp);
			break;
		case COMPGROUP_BONNET:
			if(damage > 220.0f)
				ProgressEngineDamage();
			ProgressDoorDamage(subComp);
			break;
		case COMPGROUP_PANEL:
			// so windscreen is a light?
			SetLightStatus((eLights)subComp, 1);
			// fall through
		case COMPGROUP_BUMPER:
			if(damage > 220.0f &&
			   (component == COMPONENT_PANEL_FRONT_LEFT ||
			    component == COMPONENT_PANEL_FRONT_RIGHT ||
			    component == COMPONENT_PANEL_WINDSCREEN))
				ProgressEngineDamage();
			ProgressPanelDamage(subComp);
			break;
		default: break;
		}
		return true;
	}
	return false;
}

bool
CDamageManager::GetComponentGroup(tComponent component, tComponentGroup *componentGroup, uint8 *subComp)
{
	*subComp = -2;	// ??

	// This is done very strangely in the game, maybe an optimized switch?
	if(component >= COMPONENT_PANEL_FRONT_LEFT){
		if(component >= COMPONENT_BUMPER_FRONT)
			*componentGroup = COMPGROUP_BUMPER;
		else
			*componentGroup = COMPGROUP_PANEL;
		*subComp = component - COMPONENT_PANEL_FRONT_LEFT;
		return true;
	}else if(component >= COMPONENT_DOOR_BONNET){
		if(component == COMPONENT_DOOR_BONNET)
			*componentGroup = COMPGROUP_BONNET;
		else if(component == COMPONENT_DOOR_BOOT)
			*componentGroup = COMPGROUP_BOOT;
		else
			*componentGroup = COMPGROUP_DOOR;
		*subComp = component - COMPONENT_DOOR_BONNET;
		return true;
	}else if(component >= COMPONENT_WHEEL_FRONT_LEFT){
		*componentGroup = COMPGROUP_WHEEL;
		*subComp = component - COMPONENT_WHEEL_FRONT_LEFT;
		return true;
	}else if(component >= COMPONENT_DEFAULT){
		*componentGroup = COMPGROUP_DEFAULT;
		*subComp = COMPONENT_DEFAULT;
		return true;
	}else
		return false;
}

void
CDamageManager::SetDoorStatus(int32 door, uint32 status)
{
	m_doorStatus[door] = status;
}

int32
CDamageManager::GetDoorStatus(int32 door)
{
	return m_doorStatus[door];
}

bool
CDamageManager::ProgressDoorDamage(uint8 door)
{
	int status = GetDoorStatus(door);
	if(status == PANEL_STATUS_MISSING)
		return false;
	SetDoorStatus(door, status+1);
	return true;
}

void
CDamageManager::SetPanelStatus(int32 panel, uint32 status)
{
	m_panelStatus = dpb(status, panel*4, 4, m_panelStatus);
}

int32
CDamageManager::GetPanelStatus(int32 panel)
{
	return ldb(panel*4, 4, m_panelStatus);
}

bool
CDamageManager::ProgressPanelDamage(uint8 panel)
{
	int status = GetPanelStatus(panel);
	if(status == DOOR_STATUS_MISSING)
		return false;
	SetPanelStatus(panel, status+1);
	return true;
}

void
CDamageManager::SetLightStatus(eLights light, uint32 status)
{
	m_lightStatus = dpb(status, light*2, 2, m_lightStatus);
}

int32
CDamageManager::GetLightStatus(eLights light)
{
	return ldb(light*2, 2, m_lightStatus);
}

void
CDamageManager::SetWheelStatus(int32 wheel, uint32 status)
{
	m_wheelStatus[wheel] = status;
}

int32
CDamageManager::GetWheelStatus(int32 wheel)
{
	return m_wheelStatus[wheel];
}

bool
CDamageManager::ProgressWheelDamage(uint8 wheel)
{
	int status = GetWheelStatus(wheel);
	if(status == WHEEL_STATUS_MISSING)
		return false;
	SetWheelStatus(wheel, status+1);
	return true;
}

void
CDamageManager::SetEngineStatus(uint32 status)
{
	if(status > 250)
		m_engineStatus = 250;
	else
		m_engineStatus = status;
}

int32
CDamageManager::GetEngineStatus(void)
{
	return m_engineStatus;
}

bool
CDamageManager::ProgressEngineDamage(void)
{
	int status = GetEngineStatus();
	int newstatus = status + 32 + (CGeneral::GetRandomNumber() & 0x1F);
	if(status < ENGINE_STATUS_ON_FIRE && newstatus > ENGINE_STATUS_ON_FIRE-1)
		newstatus = ENGINE_STATUS_ON_FIRE-1;
	SetEngineStatus(newstatus);
	return true;
}


code:
#pragma once

#include "common.h"

// TODO: move some of this into Vehicle.h

enum eEngineStatus
{
	ENGINE_STATUS_STEAM1 = 100,
	ENGINE_STATUS_STEAM2 = 150,
	ENGINE_STATUS_SMOKE = 200,
	ENGINE_STATUS_ON_FIRE = 225
};

enum eDoorStatus
{
	DOOR_STATUS_OK,
	DOOR_STATUS_SMASHED,
	DOOR_STATUS_SWINGING,
	DOOR_STATUS_MISSING
};

enum ePanelStatus
{
	PANEL_STATUS_OK,
	PANEL_STATUS_SMASHED1,
	PANEL_STATUS_SMASHED2,
	PANEL_STATUS_MISSING,
};

enum eWheelStatus
{
	WHEEL_STATUS_OK,
	WHEEL_STATUS_BURST,
	WHEEL_STATUS_MISSING
};

enum eLightStatus
{
	LIGHT_STATUS_OK,
	LIGHT_STATUS_BROKEN
};

enum tComponent
{
	COMPONENT_DEFAULT,
	COMPONENT_WHEEL_FRONT_LEFT,
	COMPONENT_WHEEL_FRONT_RIGHT,
	COMPONENT_WHEEL_REAR_LEFT,
	COMPONENT_WHEEL_REAR_RIGHT,
	COMPONENT_DOOR_BONNET,
	COMPONENT_DOOR_BOOT,
	COMPONENT_DOOR_FRONT_LEFT,
	COMPONENT_DOOR_FRONT_RIGHT,
	COMPONENT_DOOR_REAR_LEFT,
	COMPONENT_DOOR_REAR_RIGHT,
	COMPONENT_PANEL_FRONT_LEFT,
	COMPONENT_PANEL_FRONT_RIGHT,
	COMPONENT_PANEL_REAR_LEFT,
	COMPONENT_PANEL_REAR_RIGHT,
	COMPONENT_PANEL_WINDSCREEN,
	COMPONENT_BUMPER_FRONT,
	COMPONENT_BUMPER_REAR,
};

enum tComponentGroup
{
	COMPGROUP_BUMPER,
	COMPGROUP_WHEEL,
	COMPGROUP_DOOR,
	COMPGROUP_BONNET,
	COMPGROUP_BOOT,
	COMPGROUP_PANEL,
	COMPGROUP_DEFAULT,
};

enum eLights;

class CDamageManager
{
public:

	float m_fWheelDamageEffect;
	uint8 m_engineStatus;
	uint8 m_wheelStatus[4];
	uint8 m_doorStatus[6];
	uint32 m_lightStatus;
	uint32 m_panelStatus;
	uint8 field_18;

	CDamageManager(void);

	void ResetDamageStatus(void);
	void FuckCarCompletely(void);
	bool ApplyDamage(tComponent component, float damage, float unused);
	bool GetComponentGroup(tComponent component, tComponentGroup *componentGroup, uint8 *foo);

	void SetDoorStatus(int32 door, uint32 status);
	int32 GetDoorStatus(int32 door);
	bool ProgressDoorDamage(uint8 door);
	void SetPanelStatus(int32 panel, uint32 status);
	int32 GetPanelStatus(int32 panel);
	bool ProgressPanelDamage(uint8 panel);
	// needed for CReplay
	static int32 GetPanelStatus(uint32 panelstatus, int32 panel) { return ldb(panel*4, 4, panelstatus); }
	void SetLightStatus(eLights light, uint32 status);
	int32 GetLightStatus(eLights light);
	void SetWheelStatus(int32 wheel, uint32 status);
	int32 GetWheelStatus(int32 wheel);
	bool ProgressWheelDamage(uint8 wheel);
	void SetEngineStatus(uint32 status);
	int32 GetEngineStatus(void);
	bool ProgressEngineDamage(void);
};
VALIDATE_SIZE(CDamageManager, 0x1C);


code:
#include "common.h"

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

CDoor::CDoor(void)
{
	memset(this, 0, sizeof(*this));
}

void
CDoor::Open(float ratio)
{
	float open;

	m_fPrevAngle = m_fAngle;
	open = RetAngleWhenOpen();
	if(ratio < 1.0f){
		m_fAngle = open*ratio;
		if(m_fAngle == 0.0f)
			m_fAngVel = 0.0f;
	}else{
		m_nDoorState = DOORST_OPEN;
		m_fAngle = open;
	}
}

void
CDoor::Process(CVehicle *vehicle)
{
	static CVector vecOffset(1.0f, 0.0f, 0.0f);
	CVector speed = vehicle->GetSpeed(vecOffset);
	CVector vecSpeedDiff = speed - m_vecSpeed;
	vecSpeedDiff = Multiply3x3(vecSpeedDiff, vehicle->GetMatrix());

	// air resistance
	float fSpeedDiff = 0.0f;	// uninitialized in game
	switch(m_nAxis){
	case 0:	// x-axis
		if(m_nDirn)
			fSpeedDiff = vecSpeedDiff.y + vecSpeedDiff.z;
		else
			fSpeedDiff = -(vecSpeedDiff.y + vecSpeedDiff.z);
		break;

	// we don't support y axis apparently?

	case 2:	// z-axis
		if(m_nDirn)
			fSpeedDiff = -(vecSpeedDiff.y + vecSpeedDiff.x);
		else
			fSpeedDiff = vecSpeedDiff.y - vecSpeedDiff.x;
		break;
	}
	fSpeedDiff = Clamp(fSpeedDiff, -0.2f, 0.2f);
	if(Abs(fSpeedDiff) > 0.002f)
		m_fAngVel += fSpeedDiff;
	m_fAngVel *= 0.945f;
	m_fAngVel = Clamp(m_fAngVel, -0.3f, 0.3f);

	m_fAngle += m_fAngVel;
	m_nDoorState = DOORST_SWINGING;
	if(m_fAngle > m_fMaxAngle){
		m_fAngle = m_fMaxAngle;
		m_fAngVel *= -0.8f;
		m_nDoorState = DOORST_OPEN;
	}
	if(m_fAngle < m_fMinAngle){
		m_fAngle = m_fMinAngle;
		m_fAngVel *= -0.8f;
		m_nDoorState = DOORST_CLOSED;
	}
	m_vecSpeed = speed;
}

float
CDoor::RetAngleWhenClosed(void)
{
	if(Abs(m_fMaxAngle) < Abs(m_fMinAngle))
		return m_fMaxAngle;
	else
		return m_fMinAngle;
}

float
CDoor::RetAngleWhenOpen(void)
{
	if(Abs(m_fMaxAngle) < Abs(m_fMinAngle))
		return m_fMinAngle;
	else
		return m_fMaxAngle;
}

float
CDoor::GetAngleOpenRatio(void)
{
	float open = RetAngleWhenOpen();
	if(open == 0.0f)
		return 0.0f;
	return m_fAngle/open;
}

bool
CDoor::IsFullyOpen(void)
{
	// why -0.5? that's around 28 deg less than fully open
	if(Abs(m_fAngle) < Abs(RetAngleWhenOpen()) - 0.5f)
		return false;
	return true;
}

bool
CDoor::IsClosed(void)
{
	return m_fAngle == RetAngleWhenClosed();
}

CTrainDoor::CTrainDoor(void)
{
	memset(this, 0, sizeof(*this));
}

void
CTrainDoor::Open(float ratio)
{
	float open;

	m_fPrevPosn = m_fPosn;
	open = RetTranslationWhenOpen();
	if(ratio < 1.0f){
		m_fPosn = open*ratio;
	}else{
		m_nDoorState = DOORST_OPEN;
		m_fPosn = open;
	}
}

float
CTrainDoor::RetTranslationWhenClosed(void)
{
	if(Abs(m_fClosedPosn) < Abs(m_fOpenPosn))
		return m_fClosedPosn;
	else
		return m_fOpenPosn;
}

float
CTrainDoor::RetTranslationWhenOpen(void)
{
	if(Abs(m_fClosedPosn) < Abs(m_fOpenPosn))
		return m_fOpenPosn;
	else
		return m_fClosedPosn;
}

bool
CTrainDoor::IsFullyOpen(void)
{
	// 0.5f again...
	if(Abs(m_fPosn) < Abs(RetTranslationWhenOpen()) - 0.5f)
		return false;
	return true;
}

bool
CTrainDoor::IsClosed(void)
{
	return m_fPosn == RetTranslationWhenClosed();
}


code:
#pragma once

class CVehicle;

enum eDoorState
{
	DOORST_SWINGING,
	// actually wrong though,
	// OPEN is really MAX_ANGLE and CLOSED is MIN_ANGLE
	DOORST_OPEN,
	DOORST_CLOSED
};

class CDoor
{
public:
	float m_fMaxAngle;
	float m_fMinAngle;
	// direction of rotation for air resistance
	int8 m_nDirn;
	// axis in which this door rotates
	int8 m_nAxis;
	int8 m_nDoorState;
	float m_fAngle;
	float m_fPrevAngle;
	float m_fAngVel;
	CVector m_vecSpeed;

	CDoor(void);
	void Init(float minAngle, float maxAngle, int8 dir, int8 axis) {
		m_fMinAngle = minAngle;
		m_fMaxAngle = maxAngle;
		m_nDirn = dir;
		m_nAxis = axis;
	}
	void Open(float ratio);
	void Process(CVehicle *veh);
	float RetAngleWhenClosed(void);	// dead
	float RetAngleWhenOpen(void);
	float GetAngleOpenRatio(void);
	bool IsFullyOpen(void);
	bool IsClosed(void);	// dead
};

class CTrainDoor
{
public:
	float m_fClosedPosn;
	float m_fOpenPosn;
	int8 m_nDirn;
	int8 m_nDoorState;	// same enum as above?
	int8 m_nAxis;
	float m_fPosn;
	float m_fPrevPosn;
	int field_14;	// unused?

	CTrainDoor(void);
	void Init(float open, float closed, int8 dir, int8 axis) {
		m_fOpenPosn = open;
		m_fClosedPosn = closed;
		m_nDirn = dir;
		m_nAxis = axis;
	}
	bool IsClosed(void);
	bool IsFullyOpen(void);
	float RetTranslationWhenClosed(void);
	float RetTranslationWhenOpen(void);
	void Open(float ratio);
};


code:
#include "common.h"

#include "Timer.h"
#include "WaterLevel.h"
#include "ModelIndices.h"
#include "Physical.h"
#include "Vehicle.h"
#include "Floater.h"

cBuoyancy mod_Buoyancy;

float fVolMultiplier = 1.0f;
// amount of boat volume in bounding box
// 1.0-volume is the empty space in the bbox
float fBoatVolumeDistribution[9] = {
	// rear
	0.75f, 0.9f, 0.75f,
	0.95f, 1.0f, 0.95f,
	0.3f, 0.7f, 0.3f
	// bow
};

bool
cBuoyancy::ProcessBuoyancy(CPhysical *phys, float buoyancy, CVector *point, CVector *impulse)
{
	m_numSteps = 2.0f;

	if(!CWaterLevel::GetWaterLevel(phys->GetPosition(), &m_waterlevel, phys->bTouchingWater))
		return false;
	m_matrix = phys->GetMatrix();

	PreCalcSetup(phys, buoyancy);
	SimpleCalcBuoyancy();
	float f = CalcBuoyancyForce(phys, point, impulse);
	if(m_isBoat)
		return true;
	return f != 0.0f;
}

void
cBuoyancy::PreCalcSetup(CPhysical *phys, float buoyancy)
{
	CColModel *colModel;

	m_isBoat = phys->IsVehicle() && ((CVehicle*)phys)->IsBoat();
	colModel = phys->GetColModel();
	m_dimMin = colModel->boundingBox.min;
	m_dimMax = colModel->boundingBox.max;

	if(m_isBoat){
		if(phys->GetModelIndex() == MI_PREDATOR){
			m_dimMax.y *= 0.9f;
			m_dimMin.y *= 0.9f;
		}else if(phys->GetModelIndex() == MI_SPEEDER){
			m_dimMax.y *= 1.1f;
			m_dimMin.y *= 0.9f;
		}else if(phys->GetModelIndex() == MI_REEFER){
			m_dimMin.y *= 0.9f;
		}else{
			m_dimMax.y *= 0.9f;
			m_dimMin.y *= 0.9f;
		}
	}

	m_step = (m_dimMax - m_dimMin)/m_numSteps;

	if(m_step.z > m_step.x && m_step.z > m_step.y){
		m_stepRatio.x = m_step.x/m_step.z;
		m_stepRatio.y = m_step.y/m_step.z;
		m_stepRatio.z = 1.0f;
	}else if(m_step.y > m_step.x && m_step.y > m_step.z){
		m_stepRatio.x = m_step.x/m_step.y;
		m_stepRatio.y = 1.0f;
		m_stepRatio.z = m_step.z/m_step.y;
	}else{
		m_stepRatio.x = 1.0f;
		m_stepRatio.y = m_step.y/m_step.x;
		m_stepRatio.z = m_step.z/m_step.x;
	}

	m_haveVolume = false;
	m_numPartialVolumes = 1.0f;
	m_volumeUnderWater = 0.0f;
	m_impulsePoint = CVector(0.0f, 0.0f, 0.0f);
	m_position = phys->GetPosition();
	m_positionZ = CVector(0.0f, 0.0f, m_position.z);
	m_buoyancy = buoyancy;
	m_waterlevel += m_waterLevelInc;
}

void
cBuoyancy::SimpleCalcBuoyancy(void)
{
	float x, y;
	int ix, i;
	tWaterLevel waterPosition;

	// Floater is divided into 3x3 parts. Process and sum each of them
	ix = 0;
	for(x = m_dimMin.x; x <= m_dimMax.x; x += m_step.x){
		i = ix;
		for(y = m_dimMin.y; y <= m_dimMax.y; y += m_step.y){
			CVector waterLevel(x, y, 0.0f);
			FindWaterLevel(m_positionZ, &waterLevel, &waterPosition);
			fVolMultiplier = m_isBoat ? fBoatVolumeDistribution[i] : 1.0f;
			if(waterPosition != FLOATER_ABOVE_WATER)
				SimpleSumBuoyancyData(waterLevel, waterPosition);
			i += 3;
		}
		ix++;
	}

	m_volumeUnderWater /= (m_dimMax.z - m_dimMin.z)*sq(m_numSteps+1.0f);
}

float
cBuoyancy::SimpleSumBuoyancyData(CVector &waterLevel, tWaterLevel waterPosition)
{
	static float fThisVolume;
	static CVector AverageOfWaterLevel;
	static float fFraction;
	static float fRemainingSlice;

	float submerged = Abs(waterLevel.z - m_dimMin.z);
	// subtract empty space from submerged volume
	fThisVolume = submerged - (1.0f - fVolMultiplier);
	if(fThisVolume < 0.0f)
		return 0.0f;

	if(m_isBoat){
		fThisVolume *= fVolMultiplier;
		if(fThisVolume < 0.5f)
			fThisVolume = 2.0f*sq(fThisVolume);
		if(fThisVolume < 1.0f)
			fThisVolume = sq(fThisVolume);
		fThisVolume = sq(fThisVolume);
	}

	m_volumeUnderWater += fThisVolume;

	AverageOfWaterLevel.x = waterLevel.x * m_stepRatio.x;
	AverageOfWaterLevel.y = waterLevel.y * m_stepRatio.y;
	AverageOfWaterLevel.z = (waterLevel.z+m_dimMin.z)/2.0f * m_stepRatio.z;

	if(m_flipAverage)
		AverageOfWaterLevel = -AverageOfWaterLevel;

	fFraction = 1.0f/m_numPartialVolumes;
	fRemainingSlice = 1.0f - fFraction;
	m_impulsePoint = m_impulsePoint*fRemainingSlice + AverageOfWaterLevel*fThisVolume*fFraction;
	m_numPartialVolumes += 1.0f;
	m_haveVolume = true;
	return fThisVolume;
}

void
cBuoyancy::FindWaterLevel(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition)
{
	*waterPosition = FLOATER_IN_WATER;
	// waterLevel is a local x,y point
	// m_position is the global position of our floater
	// zpos is the global z coordinate of our floater
	CVector xWaterLevel = Multiply3x3(m_matrix, *waterLevel);
	CWaterLevel::GetWaterLevel(xWaterLevel.x + m_position.x, xWaterLevel.y + m_position.y, m_position.z,
		&waterLevel->z, true);
	waterLevel->z -= xWaterLevel.z + zpos.z;	// make local
	if(waterLevel->z > m_dimMax.z){
		waterLevel->z = m_dimMax.z;
		*waterPosition = FLOATER_UNDER_WATER;
	}else if(waterLevel->z < m_dimMin.z){
		waterLevel->z = m_dimMin.z;
		*waterPosition = FLOATER_ABOVE_WATER;
	}
}

bool
cBuoyancy::CalcBuoyancyForce(CPhysical *phys, CVector *point, CVector *impulse)
{
	if(!m_haveVolume)
		return false;

	*point = Multiply3x3(m_matrix, m_impulsePoint);
	*impulse = CVector(0.0f, 0.0f, m_volumeUnderWater*m_buoyancy*CTimer::GetTimeStep());
	return true;
}


code:
#pragma once

class CPhysical;

enum tWaterLevel
{
	FLOATER_ABOVE_WATER,
	FLOATER_IN_WATER,
	FLOATER_UNDER_WATER,
};

class cBuoyancy
{
public:
	CVector m_position;
	CMatrix m_matrix;
	int m_field_54;
	CVector m_positionZ;
	float m_waterlevel;
	float m_waterLevelInc;
	float m_buoyancy;
	CVector m_dimMax;
	CVector m_dimMin;
	float m_numPartialVolumes;
	int m_field_8C;
	int m_field_90;
	int m_field_94;
	bool m_haveVolume;
	CVector m_step;
	CVector m_stepRatio;
	float m_numSteps;
	bool m_flipAverage;
	char m_field_B9;
	bool m_isBoat;
	float m_volumeUnderWater;
	CVector m_impulsePoint;

	bool ProcessBuoyancy(CPhysical *phys, float buoyancy, CVector *point, CVector *impulse);
	void PreCalcSetup(CPhysical *phys, float buoyancy);
	void SimpleCalcBuoyancy(void);
	float SimpleSumBuoyancyData(CVector &waterLevel, tWaterLevel waterPosition);
	void FindWaterLevel(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition);
	bool CalcBuoyancyForce(CPhysical *phys, CVector *impulse, CVector *point);
};
extern cBuoyancy mod_Buoyancy;