code:
#include "common.h"

#include "ParticleObject.h"
#include "Timer.h"
#include "General.h"
#include "ParticleMgr.h"
#include "Particle.h"
#include "Camera.h"
#include "Game.h"
#include "DMAudio.h"
#include "screendroplets.h"

#ifdef COMPATIBLE_SAVES
#define PARTICLE_OBJECT_SIZEOF 0x88
#else
#define PARTICLE_OBJECT_SIZEOF sizeof(CParticleObject)
#endif

CParticleObject gPObjectArray[MAX_PARTICLEOBJECTS];

CParticleObject *CParticleObject::pCloseListHead;
CParticleObject *CParticleObject::pFarListHead;
CParticleObject *CParticleObject::pUnusedListHead;

CAudioHydrant List[MAX_AUDIOHYDRANTS];

CAudioHydrant *CAudioHydrant::Get(int n) { return &List[n]; }

bool
CAudioHydrant::Add(CParticleObject *particleobject)
{
	for ( int32 i = 0; i < MAX_AUDIOHYDRANTS; i++ )
	{
		if ( List[i].AudioEntity == AEHANDLE_NONE )
		{
			List[i].AudioEntity = DMAudio.CreateEntity(AUDIOTYPE_FIREHYDRANT, particleobject);

			if ( AEHANDLE_IS_FAILED(List[i].AudioEntity) )
				return false;

			DMAudio.SetEntityStatus(List[i].AudioEntity, TRUE);

			List[i].pParticleObject = particleobject;

			return true;
		}
	}

	return false;
}

void
CAudioHydrant::Remove(CParticleObject *particleobject)
{
	for ( int32 i = 0; i < MAX_AUDIOHYDRANTS; i++ )
	{
		if ( List[i].pParticleObject == particleobject )
		{
			DMAudio.DestroyEntity(List[i].AudioEntity);
			List[i].AudioEntity = AEHANDLE_NONE;
			List[i].pParticleObject = NULL;
		}
	}
}

CParticleObject::CParticleObject() :
	CPlaceable(),
	m_nFrameCounter(0),
	m_nState(POBJECTSTATE_INITIALISED),
	m_pNext(NULL),
	m_pPrev(NULL),
	m_nRemoveTimer(0)

{
	;
}

CParticleObject::~CParticleObject()
{

}

void
CParticleObject::Initialise()
{
	pCloseListHead = NULL;
	pFarListHead   = NULL;

	pUnusedListHead = &gPObjectArray[0];

	for ( int32 i = 0; i < MAX_PARTICLEOBJECTS; i++ )
	{
		if ( i == 0 )
			gPObjectArray[i].m_pPrev = NULL;
		else
			gPObjectArray[i].m_pPrev = &gPObjectArray[i - 1];

		if ( i == MAX_PARTICLEOBJECTS-1 )
			gPObjectArray[i].m_pNext = NULL;
		else
			gPObjectArray[i].m_pNext = &gPObjectArray[i + 1];

		gPObjectArray[i].m_nState = POBJECTSTATE_FREE;
	}
}

CParticleObject *
CParticleObject::AddObject(uint16 type, CVector const &pos, uint8 remove)
{
	CRGBA color(0, 0, 0, 0);
	CVector target(0.0f, 0.0f, 0.0f);
	return AddObject(type, pos, target, 0.0f, 0, color, remove);
}

CParticleObject *
CParticleObject::AddObject(uint16 type, CVector const &pos, float size, uint8 remove)
{
	CRGBA color(0, 0, 0, 0);
	CVector target(0.0f, 0.0f, 0.0f);
	return AddObject(type, pos, target, size, 0, color, remove);
}

CParticleObject *
CParticleObject::AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint8 remove)
{
	CRGBA color(0, 0, 0, 0);
	return AddObject(type, pos, target, size, 0, color, remove);
}

CParticleObject *
CParticleObject::AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, RwRGBA const &color, uint8 remove)
{
	CParticleObject *pobj = pUnusedListHead;

	ASSERT(pobj != NULL);

	if ( pobj == NULL )
	{
		printf("Error: No particle objects available!\n");
		return NULL;
	}

	MoveToList(&pUnusedListHead, &pCloseListHead, pobj);

	pobj->m_nState           = POBJECTSTATE_UPDATE_CLOSE;
	pobj->m_Type             = (eParticleObjectType)type;

	pobj->SetPosition(pos);
	pobj->m_vecTarget        = target;

	pobj->m_nNumEffectCycles = 1;
	pobj->m_nSkipFrames      = 1;
	pobj->m_nCreationChance  = 0;
	pobj->m_nFrameCounter    = 0;

	pobj->m_bRemove          = remove;

	pobj->m_pParticle        = NULL;

	if ( lifeTime != 0 )
		pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds() + lifeTime;
	else
		pobj->m_nRemoveTimer = 0;

	if ( color.alpha != 0 )
		pobj->m_Color = color;
	else
		pobj->m_Color.alpha  = 0;

	pobj->m_fSize            = size;
	pobj->m_fRandVal         = 0.0f;

	if ( type <= POBJECT_CATALINAS_SHOTGUNFLASH )
	{
		switch ( type )
		{
			case POBJECT_PAVEMENT_STEAM:
			{
				pobj->m_ParticleType     = PARTICLE_STEAM_NY;
				pobj->m_nNumEffectCycles = 1;
#ifdef PC_PARTICLE
				pobj->m_nSkipFrames      = 3;
#else
				pobj->m_nSkipFrames      = 1;
#endif
				pobj->m_nCreationChance  = 8;
				break;
			}

			case POBJECT_PAVEMENT_STEAM_SLOWMOTION:
			{
				pobj->m_ParticleType     = PARTICLE_STEAM_NY_SLOWMOTION;
				pobj->m_nNumEffectCycles = 1;
				pobj->m_nSkipFrames      = 1;
				pobj->m_nCreationChance  = 8;
				break;
			}

			case POBJECT_WALL_STEAM:
			{
				pobj->m_ParticleType     = PARTICLE_STEAM_NY;
				pobj->m_nNumEffectCycles = 1;
#ifdef PC_PARTICLE
				pobj->m_nSkipFrames      = 3;
#else
				pobj->m_nSkipFrames      = 1;
#endif
				pobj->m_nCreationChance  = 8;
				break;
			}

			case POBJECT_WALL_STEAM_SLOWMOTION:
			{
				pobj->m_ParticleType     = PARTICLE_STEAM_NY_SLOWMOTION;
				pobj->m_nNumEffectCycles = 1;
				pobj->m_nSkipFrames      = 1;
				pobj->m_nCreationChance  = 8;
				break;
			}

			case POBJECT_DARK_SMOKE:
			{
				pobj->m_ParticleType     = PARTICLE_STEAM_NY;
				pobj->m_nNumEffectCycles = 1;
#ifdef PC_PARTICLE
				pobj->m_nSkipFrames      = 3;
#else
				pobj->m_nSkipFrames      = 1;
#endif
				pobj->m_nCreationChance  = 8;
				pobj->m_Color            = CRGBA(16, 16, 16, 255);
				break;
			}

			case POBJECT_FIRE_HYDRANT:
			{
				pobj->m_ParticleType     = PARTICLE_WATER_HYDRANT;
				pobj->m_nNumEffectCycles = 4;
				pobj->m_nSkipFrames      = 1;
				pobj->m_nCreationChance  = 0;
				pobj->m_vecTarget        = CVector(0.0f, 0.0f, 0.3f);
				pobj->m_nRemoveTimer     = CTimer::GetTimeInMilliseconds() + 5000;
				CAudioHydrant::Add(pobj);
				break;
			}

			case POBJECT_CAR_WATER_SPLASH:
			case POBJECT_PED_WATER_SPLASH:
			{
				pobj->m_ParticleType     = PARTICLE_CAR_SPLASH;
				pobj->m_nNumEffectCycles = 0;
#ifdef PC_PARTICLE
				pobj->m_nSkipFrames      = 1;
#else
				pobj->m_nSkipFrames      = 3;
#endif
				pobj->m_nCreationChance  = 0;
#ifdef SCREEN_DROPLETS
				ScreenDroplets::RegisterSplash(pobj);
#endif
				break;
			}

			case POBJECT_SPLASHES_AROUND:
			{
				pobj->m_ParticleType     = PARTICLE_SPLASH;
#ifdef PC_PARTICLE
				pobj->m_nNumEffectCycles = 15;
#else
				pobj->m_nNumEffectCycles = 30;
#endif
				pobj->m_nSkipFrames      = 2;
				pobj->m_nCreationChance  = 0;
				break;
			}

			case POBJECT_SMALL_FIRE:
			{
				pobj->m_ParticleType     = PARTICLE_FLAME;
				pobj->m_nNumEffectCycles = 1;
#ifdef PC_PARTICLE
				pobj->m_nSkipFrames      = 2;
#else
				pobj->m_nSkipFrames      = 1;
#endif
				pobj->m_nCreationChance  = 2;
				pobj->m_vecTarget        = CVector(0.0f, 0.0f, 0.0f);
				break;
			}

			case POBJECT_BIG_FIRE:
			{
				pobj->m_ParticleType     = PARTICLE_FLAME;
				pobj->m_nNumEffectCycles = 1;
#ifdef PC_PARTICLE
				pobj->m_nSkipFrames      = 2;
#else
				pobj->m_nSkipFrames      = 1;
#endif
				pobj->m_nCreationChance  = 4;
				pobj->m_vecTarget        = CVector(0.0f, 0.0f, 0.0f);
				break;
			}

			case POBJECT_DRY_ICE:
			{
				pobj->m_ParticleType     = PARTICLE_SMOKE;
				pobj->m_nNumEffectCycles = 1;
				pobj->m_nSkipFrames      = 1;
				pobj->m_nCreationChance  = 0;
				pobj->m_vecTarget        = CVector(0.0f, 0.0f, 0.0f);
				break;
			}

			case POBJECT_DRY_ICE_SLOWMOTION:
			{
				pobj->m_ParticleType     = PARTICLE_SMOKE_SLOWMOTION;
				pobj->m_nNumEffectCycles = 1;
				pobj->m_nSkipFrames      = 1;
				pobj->m_nCreationChance  = 0;
				pobj->m_vecTarget        = CVector(0.0f, 0.0f, 0.0f);
				break;
			}

			case POBJECT_FIRE_TRAIL:
			{
				pobj->m_ParticleType     = PARTICLE_EXPLOSION_MEDIUM;
				pobj->m_nNumEffectCycles = 1;
#ifdef PC_PARTICLE
				pobj->m_nSkipFrames      = 3;
#else
				pobj->m_nSkipFrames      = 1;
#endif
				pobj->m_nCreationChance  = 2;
				pobj->m_fRandVal         = 0.01f;
				break;
			}

			case POBJECT_SMOKE_TRAIL:
			{
				pobj->m_ParticleType     = PARTICLE_FIREBALL_SMOKE;
				pobj->m_nNumEffectCycles = 1;
				pobj->m_nSkipFrames      = 1;
				pobj->m_nCreationChance  = 2;
				pobj->m_fRandVal         = 0.02f;
				break;
			}

			case POBJECT_FIREBALL_AND_SMOKE:
			{
				pobj->m_ParticleType     = PARTICLE_FLAME;
				pobj->m_nNumEffectCycles = 1;
				pobj->m_nSkipFrames      = 1;
				pobj->m_nCreationChance  = 2;
				pobj->m_fRandVal         = 0.1f;
				break;
			}

			case POBJECT_ROCKET_TRAIL:
			{
				pobj->m_ParticleType     = PARTICLE_FLAME;
				pobj->m_nNumEffectCycles = 1;
				pobj->m_nSkipFrames      = 2;
				pobj->m_nCreationChance  = 8;
				pobj->m_fRandVal         = 0.1f;
				break;
			}

			case POBJECT_EXPLOSION_ONCE:
			{
				pobj->m_ParticleType     = PARTICLE_EXPLOSION_LARGE;
				pobj->m_nNumEffectCycles = 1;
				pobj->m_nSkipFrames      = 1;
				pobj->m_nCreationChance  = 0;
				pobj->m_nRemoveTimer     = CTimer::GetTimeInMilliseconds();
				break;
			}

			case POBJECT_CATALINAS_GUNFLASH:
			case POBJECT_CATALINAS_SHOTGUNFLASH:
			{
				pobj->m_ParticleType     = PARTICLE_GUNFLASH_NOANIM;
				pobj->m_nNumEffectCycles = 1;
				pobj->m_nSkipFrames      = 1;
				pobj->m_nCreationChance  = 0;
				pobj->m_nRemoveTimer     = CTimer::GetTimeInMilliseconds();
				pobj->m_vecTarget.Normalise();
				break;
			}
		}
	}

	return pobj;
}

void
CParticleObject::RemoveObject(void)
{
	switch ( this->m_nState )
	{
		case POBJECTSTATE_UPDATE_CLOSE:
		{
			MoveToList(&pCloseListHead, &pUnusedListHead, this);
			this->m_nState = POBJECTSTATE_FREE;
			break;
		}
		case POBJECTSTATE_UPDATE_FAR:
		{
			MoveToList(&pFarListHead, &pUnusedListHead, this);
			this->m_nState = POBJECTSTATE_FREE;
			break;
		}
	}
}

void
CParticleObject::UpdateAll(void)
{
	{
		CParticleObject *pobj = pCloseListHead;
		CParticleObject *nextpobj;
		if ( pobj != NULL )
		{
			do
			{
				nextpobj = pobj->m_pNext;
				pobj->UpdateClose();
				pobj = nextpobj;
			}
			while ( nextpobj != NULL );
		}
	}

	{
		int32 frame = CTimer::GetFrameCounter() & 31;
		int32 counter = 0;

		CParticleObject *pobj = pFarListHead;
		CParticleObject *nextpobj;
		if ( pobj != NULL )
		{
			do
			{
				nextpobj = pobj->m_pNext;

				if ( counter == frame )
				{
					pobj->UpdateFar();
					frame += 32;
				}

				counter++;

				pobj = nextpobj;
			}
			while ( nextpobj != NULL );
		}
	}
}

void CParticleObject::UpdateClose(void)
{
	if ( !CGame::playingIntro )
	{
		if ( (this->GetPosition() - TheCamera.GetPosition()).MagnitudeSqr2D() > SQR(100.0f) )
		{
			if ( this->m_bRemove )
			{
				if ( this->m_Type == POBJECT_FIRE_HYDRANT )
					CAudioHydrant::Remove(this);

				MoveToList(&pCloseListHead, &pUnusedListHead, this);
				this->m_nState = POBJECTSTATE_FREE;
			}
			else
			{
				MoveToList(&pCloseListHead, &pFarListHead, this);
				this->m_nState = POBJECTSTATE_UPDATE_FAR;
			}

			return;
		}
	}

	if ( ++this->m_nFrameCounter >= this->m_nSkipFrames )
    {
		this->m_nFrameCounter = 0;

		int32 randVal;
		if ( this->m_nCreationChance != 0 )
			randVal = CGeneral::GetRandomNumber() % this->m_nCreationChance;

		if (   this->m_nCreationChance == 0
			|| randVal == 0 && this->m_nCreationChance < 0
			|| randVal != 0 && this->m_nCreationChance > 0)
		{
			switch ( this->m_Type )
			{
				case POBJECT_SMALL_FIRE:
				{
					CVector pos = this->GetPosition();
					CVector vel = this->m_vecTarget;
					float size  = this->m_fSize;

					CVector flamevel;

					flamevel.x = vel.x;
					flamevel.y = vel.y;
					flamevel.z = CGeneral::GetRandomNumberInRange(0.0125f*size, 0.1f*size);

					CParticle::AddParticle(PARTICLE_FLAME, pos, flamevel, NULL, size);

					CVector possmoke = pos;

					possmoke.x += CGeneral::GetRandomNumberInRange(0.625f*-size, size*0.625f);
					possmoke.y += CGeneral::GetRandomNumberInRange(0.625f*-size, size*0.625f);
					possmoke.z += CGeneral::GetRandomNumberInRange(0.625f* size, size*2.5f);

					CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, possmoke, vel);

					break;
				}

				case POBJECT_BIG_FIRE:
				{
					CVector pos = this->GetPosition();
					CVector vel = this->m_vecTarget;
					float size  = this->m_fSize;

					float s = 0.7f*size;

					CVector flamevel;

					flamevel.x = vel.x;
					flamevel.y = vel.y;
					flamevel.z = CGeneral::GetRandomNumberInRange(0.0125f*s, 0.1f*s);

					float flamesize = 0.8f*size;

					CParticle::AddParticle(PARTICLE_FLAME, pos, flamevel, NULL, flamesize);

					for ( int32 i = 0; i < 4; i++ )
					{
						CVector smokepos = pos;

						smokepos.x += CGeneral::GetRandomNumberInRange(0.625f*-size, 0.625f*size);
						smokepos.y += CGeneral::GetRandomNumberInRange(0.625f*-size, 0.625f*size);
						smokepos.z += CGeneral::GetRandomNumberInRange(0.625f* size, 3.5f  *size);

						CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, smokepos, vel);
					}

					break;
				}

				case POBJECT_FIREBALL_AND_SMOKE:
				{
					if ( this->m_pParticle == NULL )
					{
						CVector pos = this->GetPosition();
						CVector vel = this->m_vecTarget;
						float size  = this->m_fSize;

						CVector expvel = 1.2f*vel;
						float expsize  = 1.2f*size;
						this->m_pParticle = CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, expvel, NULL, expsize);
					}
					else
					{
						CVector pos = this->GetPosition(); // this->m_pParticle->m_vecPosition ?
						CVector vel = this->m_vecTarget;
						float size  = this->m_fSize;

						CVector veloffset   = 0.35f*vel;
						CVector fireballvel = vel;

						for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
						{
							fireballvel.x += CGeneral::GetRandomNumberInRange(-veloffset.x, veloffset.x);
							fireballvel.y += CGeneral::GetRandomNumberInRange(-veloffset.y, veloffset.y);
							fireballvel.z += CGeneral::GetRandomNumberInRange(-veloffset.z, veloffset.z);

							CParticle::AddParticle(PARTICLE_FIREBALL_SMOKE, pos, fireballvel, NULL, size);
						}
					}

					break;
				}

				case POBJECT_ROCKET_TRAIL:
				{
					if ( this->m_pParticle == NULL )
					{
						CVector pos = this->GetPosition();
						CVector vel = this->m_vecTarget;
						float size  = this->m_fSize;

						this->m_pParticle = CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, vel, NULL, size);
					}
					else
					{
						CVector pos = this->m_pParticle->m_vecPosition;
						CVector vel = this->m_vecTarget;
						float size  = this->m_fSize;

						float fireballsize  = size * 1.5f;
						CVector fireballvel = vel * -0.8f;

						for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
						{
							CParticle::AddParticle(PARTICLE_FIREBALL_SMOKE, pos, fireballvel, NULL, fireballsize);
						}
					}

					break;
				}

				case POBJECT_FIRE_TRAIL:
				{
					for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
					{
						CVector vel = this->m_vecTarget;

						if ( vel.x != 0.0f )
							vel.x += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);

						if ( vel.y != 0.0f )
							vel.y += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);

						if ( vel.z != 0.0f )
							vel.z += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);

						CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), vel, NULL, this->m_fSize,
							CGeneral::GetRandomNumberInRange(-6.0f, 6.0f));
					}

					break;
				}

				case POBJECT_PED_WATER_SPLASH:
				{
#ifdef PC_PARTICLE
					CRGBA colorsmoke(255, 255, 255, 196);

					CVector pos = this->GetPosition();
					CVector vel = this->m_vecTarget;

					for ( int32 i = 0; i < 3; i++ )
					{
						int32 angle = 90 * i;
						float fCos = CParticle::Cos(angle);
						float fSin = CParticle::Sin(angle);

						CVector splashpos;
						CVector splashvel;

						splashpos = pos + CVector(0.75f*fCos, 0.75f*fSin, 0.0f);
						splashvel = vel + CVector(0.05f*fCos, 0.05f*fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f));

						CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color);

						splashpos = pos + CVector(0.75f*fCos, 0.75f*-fSin, 0.0f);
						splashvel = vel + CVector(0.05f*fCos, 0.05f*-fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f));

						CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color);

						splashpos = pos + CVector(0.75f*-fCos, 0.75f*fSin, 0.0f);
						splashvel = vel + CVector(0.05f*-fCos, 0.05f*fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f));

						CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color);

						splashpos = pos + CVector(0.75f*-fCos, 0.75f*-fSin, 0.0f);
						splashvel = vel + CVector(0.05f*-fCos, 0.05f*-fSin, CGeneral::GetRandomNumberInRange(0.04f, 0.08f));

						CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.1f, 0.5f), this->m_Color);
					}

					for ( int32 i = 0; i < 1; i++ )
					{
						int32 angle = 180 * (i + 1);

						float fCos = CParticle::Cos(angle);
						float fSin = CParticle::Sin(angle);

						CVector splashpos;
						CVector splashvel;

						splashpos = pos + CVector(0.5f*fCos, 0.5f*fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f);

						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color);

						splashpos = pos + CVector(0.5f*fCos, 0.5f*-fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) *  fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f);

						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color);

						splashpos = pos + CVector(0.5f*-fCos, 0.5f*fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) *  fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f);

						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color);

						splashpos = pos + CVector(0.5f*-fCos, 0.5f*-fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.25f, 0.25f) * -fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.05f, 0.25f);

						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL,
							CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color);
					}
#else
					CVector pos;
					CVector vel;

					for ( int32 i = -2; i < 2; i++ )
					{
						pos = this->GetPosition();
						pos += CVector(-0.75f, 0.5f * float(i), 0.0f);

						vel = this->m_vecTarget;
						vel.x += -1.5     * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
						CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color);

						pos = this->GetPosition();
						pos += CVector(0.75f, 0.5f * float(i), 0.0f);

						vel = this->m_vecTarget;
						vel.x += 1.5f     * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
						CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color);

						pos = this->GetPosition();
						pos += CVector(0.5f * float(i), -0.75, 0.0f);

						vel = this->m_vecTarget;
						vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.y += -1.5f    * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
						CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color);

						pos = this->GetPosition();
						pos += CVector(0.5f * float(i), 0.75, 0.0f);

						vel = this->m_vecTarget;
						vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.y += 1.5f     * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
						CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color);
					}

					for ( int32 i = 0; i < 4; i++ )
					{
						pos = this->GetPosition();

						pos.x += CGeneral::GetRandomNumberInRange(-1.5f, 1.5f);
						pos.y += CGeneral::GetRandomNumberInRange(-1.5f, 1.5f);
						pos.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f);

						vel = this->m_vecTarget;
						CParticle::AddParticle(PARTICLE_PED_SPLASH, pos, vel, NULL, 0.8f, this->m_Color);
					}
#endif
					break;
				}

				case POBJECT_CAR_WATER_SPLASH:
				{
#ifdef PC_PARTICLE
					CRGBA colorsmoke(255, 255, 255, 196);

					CVector pos = this->GetPosition();
					CVector vel = this->m_vecTarget;

					float size = CGeneral::GetRandomNumberInRange(1.0f, 2.5f);

					for ( int32 i = 0; i < 3; i++ )
					{
						int32 angle = 90 * i;

						float fCos = CParticle::Cos(angle);
						float fSin = CParticle::Sin(angle);

						CVector splashpos;
						CVector splashvel;

						splashpos = pos + CVector(2.0f*fCos, 2.0f*fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f);

						CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color);

						splashpos = pos + CVector(2.0f*fCos, 2.0f*-fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f);

						CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color);

						splashpos = pos + CVector(2.0f*-fCos, 2.0f*fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f);

						CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color);

						splashpos = pos + CVector(2.0f*-fCos, 2.0f*-fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f) * -fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.01f, 0.03f);

						CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, splashpos, splashvel, NULL, size, colorsmoke);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color);
					}

					for ( int32 i = 0; i < 1; i++ )
					{
						int32 angle = 180 * (i + 1);

						float fCos = CParticle::Cos(angle);
						float fSin = CParticle::Sin(angle);

						CVector splashpos;
						CVector splashvel;

						splashpos = pos + CVector(1.25f*fCos, 1.25f*fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color);

						splashpos = pos + CVector(1.25f*fCos, 1.25f*-fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color);

						splashpos = pos + CVector(1.25f*-fCos, 1.25f*fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color);

						splashpos = pos + CVector(1.25f*-fCos, 1.25f*-fSin, 0.0f);
						splashvel = vel;
						splashvel.x += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fCos;
						splashvel.y += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * -fSin;
						splashvel.z += CGeneral::GetRandomNumberInRange(0.26f, 0.53f);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, NULL, 0.0f, this->m_Color);
					}
#else
					CVector pos;
					CVector vel;

					for ( int32 i = -3; i < 4; i++ )
					{
						pos = this->GetPosition();
						pos += CVector(-1.5f, 0.5f * float(i), 0.0f);

						vel = this->m_vecTarget;
						vel.x += -3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color);

						pos = this->GetPosition();
						pos += CVector(1.5f, 0.5f * float(i), 0.0f);

						vel = this->m_vecTarget;
						vel.x += 3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.y += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color);

						pos = this->GetPosition();
						pos += CVector(0.5f * float(i), -1.5f, 0.0f);

						vel = this->m_vecTarget;
						vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.y += -3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color);

						pos = this->GetPosition();
						pos += CVector(0.5f * float(i), 1.5f, 0.0f);

						vel = this->m_vecTarget;
						vel.x += float(i) * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.y += 3.0f * CGeneral::GetRandomNumberInRange(0.001f, 0.006f);
						vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color);
					}

					for ( int32 i = 0; i < 8; i++ )
					{
						pos = this->GetPosition();
						pos.x += CGeneral::GetRandomNumberInRange(-3.0f, 3.0f);
						pos.y += CGeneral::GetRandomNumberInRange(-3.0f, 3.0f);

						vel = this->m_vecTarget;
						vel.z += CGeneral::GetRandomNumberInRange(0.03f, 0.06f);
						CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, NULL, 0.0f, this->m_Color);
					}
#endif
					break;
				}

				case POBJECT_SPLASHES_AROUND:
				{
					CVector pos = this->GetPosition();
					float size  = this->m_fSize;

					for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
					{
						CVector splashpos = pos;

						splashpos.x += CGeneral::GetRandomNumberInRange(-size, size);
						splashpos.y += CGeneral::GetRandomNumberInRange(-size, size);

						if ( CGeneral::GetRandomNumber() & 1 )
						{
							CParticle::AddParticle(PARTICLE_RAIN_SPLASH,   splashpos, CVector(0.0f, 0.0f, 0.0f),
								NULL, 0.1f,  this->m_Color);
						}
						else
						{
							CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, splashpos, CVector(0.0f, 0.0f, 0.0f),
								NULL, 0.12f, this->m_Color);
						}
					}

					break;
				}

				case POBJECT_CATALINAS_GUNFLASH:
				{
					CRGBA flashcolor(120, 120, 120, 255);

					CVector vel = this->m_vecTarget;
					CVector pos = this->GetPosition();

					float size = 1.0f;
					if ( this->m_fSize != 0.0f )
						size = this->m_fSize;

					CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.12f*size, flashcolor);

					pos += size * (0.06f * vel);
					CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.08f*size, flashcolor);

					pos += size * (0.04f * vel);
					CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.04f*size, flashcolor);

					CVector smokepos = this->GetPosition();
					CVector smokevel = 0.1f * vel;
					CParticle::AddParticle(PARTICLE_GUNSMOKE2, smokepos, smokevel, NULL, 0.005f*size);

					break;
				}

				case POBJECT_CATALINAS_SHOTGUNFLASH:
				{
					CRGBA flashcolor(120, 120, 120, 255);

					CVector vel = this->m_vecTarget;

					float size = 1.0f;
					if ( this->m_fSize != 0.0f )
						size = this->m_fSize;

					CVector pos = this->GetPosition();

					CVector velstep  = size * (0.1f * vel);
					CVector flashpos = pos;

					flashpos += velstep;
					CParticle::AddParticle(PARTICLE_GUNFLASH, flashpos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.0f, flashcolor);

					flashpos += velstep;
					CParticle::AddParticle(PARTICLE_GUNFLASH, flashpos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.15f*size, flashcolor);

					flashpos += velstep;
					CParticle::AddParticle(PARTICLE_GUNFLASH, flashpos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.2f*size, flashcolor);

					CParticle::AddParticle(PARTICLE_GUNFLASH, pos, CVector(0.0f, 0.0f, 0.0f), NULL, 0.0f, flashcolor);

					CVector smokepos = this->GetPosition();
					CVector smokevel = 0.1f*vel;
					CParticle::AddParticle(PARTICLE_GUNSMOKE2, smokepos, smokevel, NULL, 0.1f*size);

					break;
				}

				default:
				{
					if ( this->m_fRandVal != 0.0f )
					{
						for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
						{
							CVector vel = this->m_vecTarget;

							if ( vel.x != 0.0f )
								vel.x += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);

							if ( vel.y != 0.0f )
								vel.y += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);

							if ( vel.z != 0.0f )
								vel.z += CGeneral::GetRandomNumberInRange(-this->m_fRandVal, this->m_fRandVal);

							CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), vel, NULL,
								this->m_fSize, this->m_Color);
						}
					}
					else
					{
						for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ )
						{
							CParticle::AddParticle(this->m_ParticleType, this->GetPosition(), this->m_vecTarget, NULL,
								this->m_fSize, this->m_Color);
						}
					}

					break;
				}
			}
		}
	}

	if ( this->m_nRemoveTimer != 0 && this->m_nRemoveTimer < CTimer::GetTimeInMilliseconds() )
	{
		MoveToList(&pCloseListHead, &pUnusedListHead, this);
		this->m_nState = POBJECTSTATE_FREE;

		if ( this->m_Type == POBJECT_FIRE_HYDRANT )
			CAudioHydrant::Remove(this);
	}
}

void
CParticleObject::UpdateFar(void)
{
	if ( this->m_nRemoveTimer != 0 && this->m_nRemoveTimer < CTimer::GetTimeInMilliseconds() )
	{
		MoveToList(&pFarListHead, &pUnusedListHead, this);
		this->m_nState = POBJECTSTATE_FREE;

		if ( this->m_Type == POBJECT_FIRE_HYDRANT )
			CAudioHydrant::Remove(this);
	}

	CVector2D dist = this->GetPosition() - TheCamera.GetPosition();
	if ( dist.MagnitudeSqr() < SQR(100.0f)/*10000.0f*/ )
	{
		MoveToList(&pFarListHead, &pCloseListHead, this);
		this->m_nState = POBJECTSTATE_UPDATE_CLOSE;
	}
}

#ifdef COMPATIBLE_SAVES
static inline void
SaveOneParticle(CParticleObject *p, uint8 *&buffer)
{
#define SkipBuf(buf, num) buf += num
#define ZeroBuf(buf, num) memset(buf, 0, num); SkipBuf(buf, num)
#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); SkipBuf(buf, sizeof(data))
	// CPlaceable
	{
		ZeroBuf(buffer, 4);
		CopyToBuf(buffer, p->GetMatrix().f);
		ZeroBuf(buffer, 4);
		CopyToBuf(buffer, p->GetMatrix().m_hasRwMatrix);
		ZeroBuf(buffer, 3);
	}

	// CParticleObject
	{
		ZeroBuf(buffer, 4);
		ZeroBuf(buffer, 4);
		ZeroBuf(buffer, 4);
		CopyToBuf(buffer, p->m_nRemoveTimer);
		CopyToBuf(buffer, p->m_Type);
		CopyToBuf(buffer, p->m_ParticleType);
		CopyToBuf(buffer, p->m_nNumEffectCycles);
		CopyToBuf(buffer, p->m_nSkipFrames);
		CopyToBuf(buffer, p->m_nFrameCounter);
		CopyToBuf(buffer, p->m_nState);
		ZeroBuf(buffer, 2);
		CopyToBuf(buffer, p->m_vecTarget);
		CopyToBuf(buffer, p->m_fRandVal);
		CopyToBuf(buffer, p->m_fSize);
		CopyToBuf(buffer, p->m_Color);
		CopyToBuf(buffer, p->m_bRemove);
		CopyToBuf(buffer, p->m_nCreationChance);
		ZeroBuf(buffer, 2);
	}
#undef SkipBuf
#undef ZeroBuf
#undef CopyToBuf
}
#endif

bool
CParticleObject::SaveParticle(uint8 *buffer, uint32 *length)
{
	ASSERT( buffer != NULL );
	ASSERT( length != NULL );

	int32 numObjects = 0;

	for ( CParticleObject *p = pCloseListHead; p != NULL; p = p->m_pNext )
		++numObjects;

	for ( CParticleObject *p = pFarListHead; p != NULL; p = p->m_pNext )
		++numObjects;

	*(int32 *)buffer = numObjects;
	buffer += sizeof(int32);

	int32 objectsLength = PARTICLE_OBJECT_SIZEOF * (numObjects + 1);
	int32 dataLength = objectsLength + sizeof(int32);

	for ( CParticleObject *p = pCloseListHead; p != NULL; p = p->m_pNext )
	{
#ifdef COMPATIBLE_SAVES
		SaveOneParticle(p, buffer);
#else
#ifdef THIS_IS_STUPID
		*(CParticleObject*)buffer = *p;
#else
		memcpy(buffer, p, sizeof(CParticleObject));
#endif
		buffer += sizeof(CParticleObject);
#endif
	}

	for ( CParticleObject *p = pFarListHead; p != NULL; p = p->m_pNext )
	{
#ifdef COMPATIBLE_SAVES
		SaveOneParticle(p, buffer);
#else
#ifdef THIS_IS_STUPID
		*(CParticleObject*)buffer = *p;
#else
		memcpy(buffer, p, sizeof(CParticleObject));
#endif
		buffer += sizeof(CParticleObject);
#endif
	}

	*length = dataLength;

	return true;
}

bool
CParticleObject::LoadParticle(uint8 *buffer, uint32  length)
{
	ASSERT( buffer != NULL );

	RemoveAllParticleObjects();

	int32 numObjects = *(int32 *)buffer;
	buffer += sizeof(int32);

	if ( length != PARTICLE_OBJECT_SIZEOF * (numObjects + 1) + sizeof(int32) )
		return false;

	if ( numObjects == 0 )
		return true;

	int32 i = 0;
	while ( i < numObjects )
	{
		CParticleObject *dst = pUnusedListHead;
#ifndef COMPATIBLE_SAVES
		CParticleObject *src = (CParticleObject *)buffer;
		buffer += sizeof(CParticleObject);
#endif

		if ( dst == NULL )
			return false;

		MoveToList(&pUnusedListHead, &pCloseListHead, dst);

#ifndef COMPATIBLE_SAVES
		dst->m_nState           = POBJECTSTATE_UPDATE_CLOSE;
		dst->m_Type             = src->m_Type;
		dst->m_ParticleType     = src->m_ParticleType;
		dst->SetPosition(src->GetPosition());
		dst->m_vecTarget        = src->m_vecTarget;
		dst->m_nFrameCounter    = src->m_nFrameCounter;
		dst->m_bRemove          = src->m_bRemove;
		dst->m_pParticle        = NULL;
		dst->m_nRemoveTimer     = src->m_nRemoveTimer;
		dst->m_Color            = src->m_Color;
		dst->m_fSize            = src->m_fSize;
		dst->m_fRandVal         = src->m_fRandVal;
		dst->m_nNumEffectCycles = src->m_nNumEffectCycles;
		dst->m_nSkipFrames      = src->m_nSkipFrames;
		dst->m_nCreationChance  = src->m_nCreationChance;
#else
		dst->m_nState = POBJECTSTATE_UPDATE_CLOSE;
		dst->m_pParticle = NULL;

#define SkipBuf(buf, num) buf += num
#define CopyFromBuf(buf, data) memcpy(&data, buf, sizeof(data)); SkipBuf(buf, sizeof(data))
		// CPlaceable
		{
			SkipBuf(buffer, 4);
			CMatrix matrix;
			CopyFromBuf(buffer, matrix.f);
			SkipBuf(buffer, 4);
			CopyFromBuf(buffer, matrix.m_hasRwMatrix);
			SkipBuf(buffer, 3);
			dst->SetPosition(matrix.GetPosition());
		}

		// CParticleObject
		{
			SkipBuf(buffer, 4);
			SkipBuf(buffer, 4);
			SkipBuf(buffer, 4);
			CopyFromBuf(buffer, dst->m_nRemoveTimer);
			CopyFromBuf(buffer, dst->m_Type);
			CopyFromBuf(buffer, dst->m_ParticleType);
			CopyFromBuf(buffer, dst->m_nNumEffectCycles);
			CopyFromBuf(buffer, dst->m_nSkipFrames);
			CopyFromBuf(buffer, dst->m_nFrameCounter);
			SkipBuf(buffer, 2);
			SkipBuf(buffer, 2);
			CopyFromBuf(buffer, dst->m_vecTarget);
			CopyFromBuf(buffer, dst->m_fRandVal);
			CopyFromBuf(buffer, dst->m_fSize);
			CopyFromBuf(buffer, dst->m_Color);
			CopyFromBuf(buffer, dst->m_bRemove);
			CopyFromBuf(buffer, dst->m_nCreationChance);
			SkipBuf(buffer, 2);
		}
#undef CopyFromBuf
#undef SkipBuf
#endif

		i++;
	}

	return true;
}

void
CParticleObject::RemoveAllParticleObjects(void)
{
	pUnusedListHead = &gPObjectArray[0];

	pCloseListHead = NULL;
	pFarListHead   = NULL;

	for ( int32 i = 0; i < MAX_PARTICLEOBJECTS; i++ )
	{
		if ( i == 0 )
			gPObjectArray[i].m_pPrev = NULL;
		else
			gPObjectArray[i].m_pPrev = &gPObjectArray[i - 1];

		if ( i == MAX_PARTICLEOBJECTS-1 )
			gPObjectArray[i].m_pNext = NULL;
		else
			gPObjectArray[i].m_pNext = &gPObjectArray[i + 1];

		gPObjectArray[i].m_nState = POBJECTSTATE_FREE;
	}
}

void
CParticleObject::MoveToList(CParticleObject **from, CParticleObject **to, CParticleObject *obj)
{
	ASSERT( from != NULL );
	ASSERT( to   != NULL );
	ASSERT( obj  != NULL );

	if ( obj->m_pPrev == NULL )
	{
		*from = obj->m_pNext;
		if ( *from )
			(*from)->m_pPrev = NULL;
	}
	else
	{
		if ( obj->m_pNext == NULL )
			obj->m_pPrev->m_pNext = NULL;
		else
		{
			obj->m_pNext->m_pPrev = obj->m_pPrev;
			obj->m_pPrev->m_pNext = obj->m_pNext;
		}
	}

	obj->m_pNext = *to;
	obj->m_pPrev = NULL;
	*to = obj;

	if ( obj->m_pNext )
		obj->m_pNext->m_pPrev = obj;
}


code:
#pragma once

#include "AudioManager.h"
#include "ParticleType.h"
#include "Placeable.h"

#define MAX_PARTICLEOBJECTS 100
#define MAX_AUDIOHYDRANTS   8

enum eParticleObjectType
{
	POBJECT_PAVEMENT_STEAM,
	POBJECT_PAVEMENT_STEAM_SLOWMOTION,
	POBJECT_WALL_STEAM,
	POBJECT_WALL_STEAM_SLOWMOTION,
	POBJECT_DARK_SMOKE,
	POBJECT_FIRE_HYDRANT,
	POBJECT_CAR_WATER_SPLASH,
	POBJECT_PED_WATER_SPLASH,
	POBJECT_SPLASHES_AROUND,
	POBJECT_SMALL_FIRE,
	POBJECT_BIG_FIRE,
	POBJECT_DRY_ICE,
	POBJECT_DRY_ICE_SLOWMOTION,
	POBJECT_FIRE_TRAIL,
	POBJECT_SMOKE_TRAIL,
	POBJECT_FIREBALL_AND_SMOKE,
	POBJECT_ROCKET_TRAIL,
	POBJECT_EXPLOSION_ONCE,
	POBJECT_CATALINAS_GUNFLASH,
	POBJECT_CATALINAS_SHOTGUNFLASH,
};

enum eParticleObjectState
{
	POBJECTSTATE_INITIALISED = 0,
	POBJECTSTATE_UPDATE_CLOSE,
	POBJECTSTATE_UPDATE_FAR,
	POBJECTSTATE_FREE,
};

class CParticle;

class CParticleObject : public CPlaceable
{
public:
	CParticleObject    *m_pNext;
	CParticleObject    *m_pPrev;
	CParticle          *m_pParticle;
	uint32               m_nRemoveTimer;
	eParticleObjectType m_Type;
	tParticleType       m_ParticleType;
	uint8               m_nNumEffectCycles;
	uint8               m_nSkipFrames;
	uint16              m_nFrameCounter;
	uint16              m_nState;
	CVector             m_vecTarget;
	float               m_fRandVal;
	float               m_fSize;
	CRGBA               m_Color;
	uint8               m_bRemove;
	int8                m_nCreationChance;

	static CParticleObject *pCloseListHead;
	static CParticleObject *pFarListHead;
	static CParticleObject *pUnusedListHead;

	CParticleObject();
	~CParticleObject();

	static void Initialise(void);

	static CParticleObject *AddObject(uint16 type, CVector const &pos,                                                                          uint8 remove);
	static CParticleObject *AddObject(uint16 type, CVector const &pos,                        float size,                                       uint8 remove);
	static CParticleObject *AddObject(uint16 type, CVector const &pos, CVector const &target, float size,                                       uint8 remove);
	static CParticleObject *AddObject(uint16 type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, RwRGBA const &color, uint8 remove);

	void RemoveObject(void);

	static void UpdateAll(void);
	void UpdateClose(void);
	void UpdateFar(void);

	static bool SaveParticle(uint8 *buffer, uint32 *length);
	static bool LoadParticle(uint8 *buffer, uint32  length);

	static void RemoveAllParticleObjects(void);
	static void MoveToList(CParticleObject **from, CParticleObject **to, CParticleObject *obj);
};

extern CParticleObject gPObjectArray[MAX_PARTICLEOBJECTS];

class CAudioHydrant
{
public:
	int32 AudioEntity;
	CParticleObject *pParticleObject;

	CAudioHydrant() :
		AudioEntity(AEHANDLE_NONE),
		pParticleObject(NULL)
	{ }

	static bool Add   (CParticleObject *particleobject);
	static void Remove(CParticleObject *particleobject);

	static CAudioHydrant *Get(int n);	// for neo screen droplets
};

license: mit


code:
#include "common.h"

#include "Projectile.h"

CProjectile::CProjectile(int32 model) : CObject()
{
	m_fMass = 1.0f;
	m_fTurnMass = 1.0f;
	m_fAirResistance = 0.99999f;
	m_fElasticity = 0.75f;
	m_fBuoyancy = GRAVITY * m_fMass * 0.1f;
	bExplosionProof = true;
	SetModelIndex(model);
	ObjectCreatedBy = MISSION_OBJECT;
}


code:
#pragma once

#include "Object.h"

class CProjectile : public CObject
{
public:
	CProjectile(int32);
};


code:
#include "common.h"

#include "CivilianPed.h"
#include "Phones.h"
#include "General.h"
#include "PlayerPed.h"
#include "Wanted.h"
#include "DMAudio.h"
#include "World.h"
#include "Vehicle.h"
#include "SurfaceTable.h"

#ifdef PEDS_REPORT_CRIMES_ON_PHONE
eCrimeType
EventTypeToCrimeType(eEventType event)
{
	eCrimeType crime;
	switch (event) {
	case EVENT_ASSAULT: crime = CRIME_HIT_PED; break;
	case EVENT_RUN_REDLIGHT: crime = CRIME_RUN_REDLIGHT; break;
	case EVENT_ASSAULT_POLICE: crime = CRIME_HIT_COP; break;
	case EVENT_GUNSHOT: crime = CRIME_POSSESSION_GUN; break;
	case EVENT_STEAL_CAR: crime = CRIME_STEAL_CAR; break;
	case EVENT_HIT_AND_RUN: crime = CRIME_RUNOVER_PED; break;
	case EVENT_HIT_AND_RUN_COP: crime = CRIME_RUNOVER_COP; break;
	case EVENT_SHOOT_PED: crime = CRIME_SHOOT_PED; break;
	case EVENT_SHOOT_COP: crime = CRIME_SHOOT_COP; break;
	case EVENT_PED_SET_ON_FIRE: crime = CRIME_PED_BURNED; break;
	case EVENT_COP_SET_ON_FIRE: crime = CRIME_COP_BURNED; break;
	case EVENT_CAR_SET_ON_FIRE: crime = CRIME_VEHICLE_BURNED; break;
	default: crime = CRIME_NONE; break;
	}
	return crime;
}
#endif

CCivilianPed::CCivilianPed(ePedType pedtype, uint32 mi) : CPed(pedtype)
{
	SetModelIndex(mi);
	for (int i = 0; i < ARRAY_SIZE(m_nearPeds); i++) {
		m_nearPeds[i] = nil;
	}
}

void
CCivilianPed::CivilianAI(void)
{
	if (CTimer::GetTimeInMilliseconds() <= m_fleeTimer || m_objective != OBJECTIVE_NONE && !bRespondsToThreats
		|| !IsPedInControl())  {

		if (m_objective == OBJECTIVE_GUARD_SPOT)
			return;

		if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) {
			if (m_pedInObjective && m_pedInObjective->IsPlayer())
				return;
		}
		if (CTimer::GetTimeInMilliseconds() <= m_lookTimer)
			return;

		uint32 closestThreatFlag = ScanForThreats();
		if (closestThreatFlag == PED_FLAG_EXPLOSION) {
			float angleToFace = CGeneral::GetRadianAngleBetweenPoints(
				m_eventOrThreat.x,  m_eventOrThreat.y,
				GetPosition().x, GetPosition().y);
			SetLookFlag(angleToFace, true);
			SetLookTimer(500);

		} else if (closestThreatFlag == PED_FLAG_GUN) {
			SetLookFlag(m_threatEntity, true);
			SetLookTimer(500);
		}
		return;
	}
	uint32 closestThreatFlag = ScanForThreats();
	if (closestThreatFlag == PED_FLAG_GUN) {
		if (!m_threatEntity || !m_threatEntity->IsPed())
			return;

		CPed *threatPed = (CPed*)m_threatEntity;
		float threatDistSqr = (m_threatEntity->GetPosition() - GetPosition()).MagnitudeSqr2D();
		if (m_pedStats->m_fear <= m_pedStats->m_lawfulness) {
			if (m_pedStats->m_temper <= m_pedStats->m_fear) {
				if (!threatPed->IsPlayer() || !RunToReportCrime(CRIME_POSSESSION_GUN)) {
					if (threatDistSqr < sq(10.0f)) {
						Say(SOUND_PED_FLEE_SPRINT);
						SetFindPathAndFlee(m_threatEntity, 10000);
					} else {
						SetFindPathAndFlee(m_threatEntity->GetPosition(), 5000, true);
					}
				}
			} else if (m_objective != OBJECTIVE_NONE || GetWeapon()->IsTypeMelee()) {
				SetFindPathAndFlee(m_threatEntity, 5000);
				if (threatDistSqr < sq(20.0f)) {
					SetMoveState(PEDMOVE_RUN);
					Say(SOUND_PED_FLEE_SPRINT);
				} else {
					SetMoveState(PEDMOVE_WALK);
				}
			} else if (threatPed->IsPlayer() && FindPlayerPed()->m_pWanted->m_CurrentCops != 0)  {
				SetFindPathAndFlee(m_threatEntity, 5000);
				if (threatDistSqr < sq(10.0f)) {
					SetMoveState(PEDMOVE_RUN);
				} else {
					SetMoveState(PEDMOVE_WALK);
				}
			} else {
				SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity);
			}
		} else {
			if (threatDistSqr < sq(10.0f)) {
				Say(SOUND_PED_FLEE_SPRINT);
				SetFindPathAndFlee(m_threatEntity, 10000);
				SetMoveState(PEDMOVE_SPRINT);
			} else {
				Say(SOUND_PED_FLEE_SPRINT);
				SetFindPathAndFlee(m_threatEntity, 5000);
				SetMoveState(PEDMOVE_RUN);
			}
		}
		SetLookFlag(m_threatEntity, false);
		SetLookTimer(500);
	} else if (closestThreatFlag == PED_FLAG_DEADPEDS) {
		float eventDistSqr = (m_pEventEntity->GetPosition() - GetPosition()).MagnitudeSqr2D();
		if (IsGangMember() && m_nPedType == ((CPed*)m_pEventEntity)->m_nPedType) {
			if (eventDistSqr < sq(5.0f)) {
				SetFindPathAndFlee(m_pEventEntity, 2000);
				SetMoveState(PEDMOVE_RUN);
			}
		} else if (IsGangMember() || eventDistSqr > sq(5.0f)) {
			bool investigateDeadPed = true;
			CEntity *killerOfDeadPed = ((CPed*)m_pEventEntity)->m_threatEntity;
			if (killerOfDeadPed && killerOfDeadPed->IsPed()) {
				CVector killerPos = killerOfDeadPed->GetPosition();
				CVector deadPedPos = m_pEventEntity->GetPosition();
				if (CVector2D(killerPos - deadPedPos).MagnitudeSqr() < sq(10.0f))
					investigateDeadPed = false;
			}

#ifdef PEDS_REPORT_CRIMES_ON_PHONE
			int32 eventId = CheckForPlayerCrimes((CPed*)m_pEventEntity);
			eCrimeType crime = (eventId == -1 ? CRIME_NONE : EventTypeToCrimeType(gaEvent[eventId].type));
			bool eligibleToReport = crime != CRIME_NONE && m_pedStats->m_fear <= m_pedStats->m_lawfulness && m_pedStats->m_temper <= m_pedStats->m_fear;
			if (IsGangMember() || !eligibleToReport || !RunToReportCrime(crime))
#endif
			if (investigateDeadPed)
				SetInvestigateEvent(EVENT_DEAD_PED, CVector2D(m_pEventEntity->GetPosition()), 1.0f, 20000, 0.0f);

		} else {
#ifdef PEDS_REPORT_CRIMES_ON_PHONE
			int32 eventId = CheckForPlayerCrimes((CPed*)m_pEventEntity);
			eCrimeType crime = (eventId == -1 ? CRIME_NONE : EventTypeToCrimeType(gaEvent[eventId].type));
			bool eligibleToReport = crime != CRIME_NONE && m_pedStats->m_fear <= m_pedStats->m_lawfulness && m_pedStats->m_temper <= m_pedStats->m_fear;
			if(!eligibleToReport || !RunToReportCrime(crime))
#endif
			{
				SetFindPathAndFlee(m_pEventEntity, 5000);
				SetMoveState(PEDMOVE_RUN);
			}
		}
	} else if (closestThreatFlag == PED_FLAG_EXPLOSION) {
		CVector2D eventDistVec = m_eventOrThreat - GetPosition();
		float eventDistSqr = eventDistVec.MagnitudeSqr();
		if (eventDistSqr < sq(20.0f)) {
			Say(SOUND_PED_FLEE_SPRINT);
			SetFlee(m_eventOrThreat, 2000);
			float angleToFace = CGeneral::GetRadianAngleBetweenPoints(
				m_eventOrThreat.x, m_eventOrThreat.y,
				GetPosition().x, GetPosition().y);
			SetLookFlag(angleToFace, true);
			SetLookTimer(500);
		} else if (eventDistSqr < sq(40.0f)) {
			if (bGonnaInvestigateEvent) {
				if (CharCreatedBy != MISSION_CHAR && !IsGangMember())
					SetInvestigateEvent(EVENT_EXPLOSION, m_eventOrThreat, 6.0f, 30000, 0.0f);

			} else {
				float eventHeading = CGeneral::GetRadianAngleBetweenPoints(eventDistVec.x, eventDistVec.y, 0.0f, 0.0f);
				eventHeading = CGeneral::LimitRadianAngle(eventHeading);
				if (eventHeading < 0.0f)
					eventHeading = eventHeading + TWOPI;

				SetWanderPath(eventHeading / 8.0f);
			}
		}
	} else {
#ifdef PEDS_REPORT_CRIMES_ON_PHONE
		bool youShouldRunEventually = false;
		bool dontGoToPhone = false;
#endif
		if (m_threatEntity && m_threatEntity->IsPed()) {
			CPed *threatPed = (CPed*)m_threatEntity;
			if (m_pedStats->m_fear <= 100 - threatPed->m_pedStats->m_temper && threatPed->m_nPedType != PEDTYPE_COP) {
				if (threatPed->GetWeapon(m_currentWeapon).IsTypeMelee() || !GetWeapon()->IsTypeMelee()) {
					if (threatPed->IsPlayer() && FindPlayerPed()->m_pWanted->m_CurrentCops != 0) {
						if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) {
#ifdef PEDS_REPORT_CRIMES_ON_PHONE
							dontGoToPhone = true;
#endif
							SetFindPathAndFlee(m_threatEntity, 10000);
						}
					} else {
#ifdef PEDS_REPORT_CRIMES_ON_PHONE
						dontGoToPhone = true;
#endif
						SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity);
					}
				}
			} else {
#ifdef PEDS_REPORT_CRIMES_ON_PHONE
				youShouldRunEventually = true;
#else
				SetFindPathAndFlee(m_threatEntity, 10000, true);
#endif
			}
		}

#ifdef PEDS_REPORT_CRIMES_ON_PHONE
		if (!dontGoToPhone) {
			int32 eventId = CheckForPlayerCrimes(nil);
			eCrimeType crime = (eventId == -1 ? CRIME_NONE : EventTypeToCrimeType(gaEvent[eventId].type));
			bool eligibleToReport = crime != CRIME_NONE && m_pedStats->m_fear <= m_pedStats->m_lawfulness;

			if ((!eligibleToReport || !RunToReportCrime(crime)) && youShouldRunEventually) {
				SetFindPathAndFlee(m_threatEntity, 10000, true);
			}
		}
#endif
	}
}

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

	CPed::ProcessControl();

	if (bWasPostponed)
		return;

	if (DyingOrDead())
		return;

	GetWeapon()->Update(m_audioEntityId);
	switch (m_nPedState) {
		case PED_WANDER_RANGE:
		case PED_WANDER_PATH:
			if (IsVisible())
				ScanForInterestingStuff();
			break;
		case PED_SEEK_ENTITY:
			if (!m_pSeekTarget) {
				RestorePreviousState();
				break;
			}
			m_vecSeekPos = m_pSeekTarget->GetPosition();

			// fall through
		case PED_SEEK_POS:
			if (Seek()) {
				if ((m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA) && m_pNextPathNode) {
					m_pNextPathNode = nil;
#ifdef PEDS_REPORT_CRIMES_ON_PHONE
				} else if (bRunningToPhone && m_objective < OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE) {
					if (crimeReporters[m_phoneId] != this) {
						RestorePreviousState();
						m_phoneId = -1;
						bRunningToPhone = false;
					} else {
						m_facePhoneStart = true;
						SetPedState(PED_FACE_PHONE);
					}
#else
				} else if (bRunningToPhone) {
					if (gPhoneInfo.m_aPhones[m_phoneId].m_nState != PHONE_STATE_FREE) {
						RestorePreviousState();
						m_phoneId = -1;
					} else {
						gPhoneInfo.m_aPhones[m_phoneId].m_nState = PHONE_STATE_REPORTING_CRIME;
						SetPedState(PED_FACE_PHONE);
					}
#endif
				} else if (m_objective != OBJECTIVE_KILL_CHAR_ANY_MEANS && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) {
					if (m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION) {
						if (m_moved.Magnitude() == 0.0f) {
							if (m_pedInObjective->m_nMoveState == PEDMOVE_STILL)
								m_fRotationDest = m_pedInObjective->m_fRotationCur;
						}
					} else if (m_objective == OBJECTIVE_GOTO_CHAR_ON_FOOT
						&& m_pedInObjective && m_pedInObjective->m_nMoveState != PEDMOVE_STILL) {
						SetMoveState(m_pedInObjective->m_nMoveState);
					} else if (m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA) {
						SetIdle();
					} else {
						RestorePreviousState();
					}
				}
			}
			break;
		case PED_FACE_PHONE:
			if (FacePhone())
				SetPedState(PED_MAKE_CALL);
			break;
		case PED_MAKE_CALL:
			if (MakePhonecall())
				SetWanderPath(CGeneral::GetRandomNumber() & 7);
			break;
		case PED_MUG:
			Mug();
			break;
		case PED_SOLICIT:
			Solicit();
			break;
		case PED_UNKNOWN:
		{
			int pedsInSameState = 0;
			Idle();
			for (int i = 0; i < m_numNearPeds; ++i) {
				CPed *nearPed = m_nearPeds[i];
				if (nearPed->m_nPedType == m_nPedType && nearPed->m_nPedState == PED_UNKNOWN) {
					++pedsInSameState;
				}
			}
			if (pedsInSameState < 5) {
				for (int j = 0; j < m_numNearPeds; ++j) {
					CPed *nearPed = m_nearPeds[j];
					if (nearPed->m_nPedType == m_nPedType && nearPed->m_nPedState == PED_WANDER_PATH) {
						nearPed->SetPedState(PED_UNKNOWN);
					}
				}
			}
			break;
		}
		case PED_DRIVING:
			if (m_nPedType != PEDTYPE_PROSTITUTE)
				break;

			if (CWorld::Players[CWorld::PlayerInFocus].m_pHooker != this)
				break;

			if (CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime) {
				if (m_nPedState == PED_DRIVING
					&& m_pMyVehicle->pDriver && m_pMyVehicle->pDriver->IsPlayer() && m_pMyVehicle->pDriver->m_nPedState == PED_DRIVING) {
					CColPoint foundCol;
					CEntity* foundEnt;

					CWorld::ProcessVerticalLine(m_pMyVehicle->GetPosition(), -100.0f,
						foundCol, foundEnt, true, false, false, false, false, false, nil);

					if (m_pMyVehicle->m_vecMoveSpeed.MagnitudeSqr() < sq(0.01f)
						&& foundCol.surfaceB != SURFACE_DEFAULT && foundCol.surfaceB != SURFACE_TARMAC && foundCol.surfaceB != SURFACE_PAVEMENT) {

						if (m_pMyVehicle->CarHasRoof()) {
							m_pMyVehicle->ApplyTurnForce(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(-0.8f, -1.2f) * m_fMass,
								GetPosition().x - m_pMyVehicle->GetPosition().x, GetPosition().y - m_pMyVehicle->GetPosition().y, 0.0f);

							DMAudio.PlayOneShot(m_pMyVehicle->m_audioEntityId, SOUND_CAR_JERK, 0.0f);

							int playerSexFrequency = CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency;
							if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney >= 10 && playerSexFrequency > 250) {
									CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + playerSexFrequency;
									if (playerSexFrequency >= 350) {
										CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = Max(250, playerSexFrequency - 30);
									} else {
										CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = Max(250, playerSexFrequency - 10);
									}

									m_pMyVehicle->pDriver->m_fHealth = Min(125.0f, 1.0f + m_pMyVehicle->pDriver->m_fHealth);
									if (CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency == 250)
										CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + 3000;
							} else {
								bWanderPathAfterExitingCar = true;
								CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil;
								SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle);
							}
						} else {
							bWanderPathAfterExitingCar = true;
							CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil;
							m_pMyVehicle->pDriver->m_fHealth = 125.0f;
							SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle);
						}
					} else {
						CWorld::Players[CWorld::PlayerInFocus].m_nNextSexFrequencyUpdateTime = CTimer::GetTimeInMilliseconds() + 3000;
						int playerSexFrequency = CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency;
						if (playerSexFrequency >= 1000 || playerSexFrequency <= 250)
							CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 1200;
						else
							CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 250;
					}
				} else {
					bWanderPathAfterExitingCar = true;
					CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil;
					SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle);
				}
			}

			if (CTimer::GetTimeInMilliseconds() > CWorld::Players[CWorld::PlayerInFocus].m_nNextSexMoneyUpdateTime) {
				int playerMoney = CWorld::Players[CWorld::PlayerInFocus].m_nMoney;
				if (playerMoney <= 1) {
					CWorld::Players[CWorld::PlayerInFocus].m_nSexFrequency = 250;
				} else {
					CWorld::Players[CWorld::PlayerInFocus].m_nMoney = Max(0, playerMoney - 1);
				}
				CWorld::Players[CWorld::PlayerInFocus].m_nNextSexMoneyUpdateTime = CTimer::GetTimeInMilliseconds() + 1000;
			}
			break;
		default:
			break;
	}
	if (IsPedInControl())
		CivilianAI();

	if (CTimer::GetTimeInMilliseconds() > m_timerUnused) {
		m_stateUnused = 0;
		m_timerUnused = 0;
	}

	if (m_moved.Magnitude() > 0.0f)
		Avoid();
}

// It's "CPhoneInfo::ProcessNearestFreePhone" in PC IDB but that's not true, someone made it up.
bool
CPed::RunToReportCrime(eCrimeType crimeToReport)
{
#ifdef PEDS_REPORT_CRIMES_ON_PHONE
	if (bRunningToPhone) {
		if (!isPhoneAvailable(m_phoneId) && crimeReporters[m_phoneId] != this) {
			crimeReporters[m_phoneId] = nil;
			m_phoneId = -1;
			bIsRunning = false;
			ClearSeek(); // clears bRunningToPhone
			return false;
		}

		return true;
	}
#else
	// They changed true into false to make this function unusable. So running to phone actually starts but first frame after that cancels it.
	if (m_nPedState == PED_SEEK_POS)
		return false;
#endif

	CVector pos = GetPosition();
	int phoneId = gPhoneInfo.FindNearestFreePhone(&pos);

	if (phoneId == -1)
		return false;

	CPhone *phone = &gPhoneInfo.m_aPhones[phoneId];
#ifndef PEDS_REPORT_CRIMES_ON_PHONE
	if (phone->m_nState != PHONE_STATE_FREE)
		return false;
#else
	crimeReporters[phoneId] = this;
#endif

	bRunningToPhone = true;
	SetSeek(phone->m_pEntity->GetMatrix() * -phone->m_pEntity->GetForward(), 1.0f); // original: phone.m_vecPos, 0.3f
	SetMoveState(PEDMOVE_RUN);
	bIsRunning = true; // not there in original
	m_phoneId = phoneId;
	m_crimeToReportOnPhone = crimeToReport;
	return true;
}


code:
#pragma once

#include "Ped.h"

class CCivilianPed : public CPed
{
public:
	CCivilianPed(ePedType, uint32);
	~CCivilianPed(void) { }

	void CivilianAI(void);
	void ProcessControl(void);
};
#ifndef PED_SKIN
VALIDATE_SIZE(CCivilianPed, 0x53C);
#endif


code:
#include "common.h"

#include "World.h"
#include "PlayerPed.h"
#include "CopPed.h"
#include "Wanted.h"
#include "DMAudio.h"
#include "ModelIndices.h"
#include "Vehicle.h"
#include "RpAnimBlend.h"
#include "AnimBlendAssociation.h"
#include "General.h"
#include "ZoneCull.h"
#include "PathFind.h"
#include "RoadBlocks.h"
#include "CarCtrl.h"
#include "Renderer.h"
#include "Camera.h"

CCopPed::CCopPed(eCopType copType) : CPed(PEDTYPE_COP)
{
	m_nCopType = copType;
	switch (copType) {
	case COP_STREET:
		SetModelIndex(MI_COP);
		GiveWeapon(WEAPONTYPE_COLT45, 1000);
		m_currentWeapon = WEAPONTYPE_UNARMED;
		m_fArmour = 0.0f;
		m_wepSkills = 208; /* TODO: what is this? seems unused */
		m_wepAccuracy = 60;
		break;
	case COP_FBI:
		SetModelIndex(MI_FBI);
		GiveWeapon(WEAPONTYPE_COLT45, 1000);
		GiveWeapon(WEAPONTYPE_AK47, 1000);
		SetCurrentWeapon(WEAPONTYPE_AK47);
		m_fArmour = 100.0f;
		m_wepSkills = 176; /* TODO: what is this? seems unused */
		m_wepAccuracy = 76;
		break;
	case COP_SWAT:
		SetModelIndex(MI_SWAT);
		GiveWeapon(WEAPONTYPE_COLT45, 1000);
		GiveWeapon(WEAPONTYPE_UZI, 1000);
		SetCurrentWeapon(WEAPONTYPE_UZI);
		m_fArmour = 50.0f;
		m_wepSkills = 32; /* TODO: what is this? seems unused */
		m_wepAccuracy = 68;
		break;
	case COP_ARMY:
		SetModelIndex(MI_ARMY);
		GiveWeapon(WEAPONTYPE_COLT45, 1000);
		GiveWeapon(WEAPONTYPE_M16, 1000);
		GiveWeapon(WEAPONTYPE_GRENADE, 10);
		SetCurrentWeapon(WEAPONTYPE_M16);
		m_fArmour = 100.0f;
		m_wepSkills = 32; /* TODO: what is this? seems unused */
		m_wepAccuracy = 84;
		break;
	default:
		break;
	}
	m_bIsInPursuit = false;
	field_1350 = 1;
	m_bIsDisabledCop = false;
	m_fAbseilPos = 0.0f;
	m_attackTimer = 0;
	m_bBeatingSuspect = false;
	m_bStopAndShootDisabledZone = false;
	m_bZoneDisabled = false;
	field_1364 = -1;
	SetWeaponLockOnTarget(nil);

	// VC also initializes in here, but as nil
#ifdef FIX_BUGS
	m_nRoadblockNode = -1;
#endif
}

CCopPed::~CCopPed()
{
	ClearPursuit();
}

// Parameter should always be CPlayerPed, but it seems they considered making civilians arrestable at some point
void
CCopPed::SetArrestPlayer(CPed *player)
{
	if (!IsPedInControl() || !player)
		return;

	switch (m_nCopType) {
		case COP_FBI:
			Say(SOUND_PED_ARREST_FBI);
			break;
		case COP_SWAT:
			Say(SOUND_PED_ARREST_SWAT);
			break;
		default:
			Say(SOUND_PED_ARREST_COP);
			break;
	}
	if (player->EnteringCar()) {
		if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer)
			return;

		// why?
		player->bGonnaKillTheCarJacker = true;

		// Genius
		FindPlayerPed()->m_bCanBeDamaged = false;
		((CPlayerPed*)player)->m_pArrestingCop = this;
		this->RegisterReference((CEntity**) &((CPlayerPed*)player)->m_pArrestingCop);

	} else if (player->m_nPedState != PED_DIE && player->m_nPedState != PED_DEAD && player->m_nPedState != PED_ARRESTED) {
		player->m_nLastPedState = player->m_nPedState;
		player->SetPedState(PED_ARRESTED);

		FindPlayerPed()->m_bCanBeDamaged = false;
		((CPlayerPed*)player)->m_pArrestingCop = this;
		this->RegisterReference((CEntity**) &((CPlayerPed*)player)->m_pArrestingCop);
	}

	SetPedState(PED_ARREST_PLAYER);
	SetObjective(OBJECTIVE_NONE);
	m_prevObjective = OBJECTIVE_NONE;
	bIsPointingGunAt = false;
	m_pSeekTarget = player;
	m_pSeekTarget->RegisterReference((CEntity**) &m_pSeekTarget);
	SetCurrentWeapon(WEAPONTYPE_COLT45);
	if (player->InVehicle()) {
		player->m_pMyVehicle->m_nNumGettingIn = 0;
		player->m_pMyVehicle->m_nGettingInFlags = 0;
		player->m_pMyVehicle->bIsHandbrakeOn = true;
		player->m_pMyVehicle->SetStatus(STATUS_PLAYER_DISABLED);
	}
	if (GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED)
		SetCurrentWeapon(WEAPONTYPE_COLT45);
}

void
CCopPed::ClearPursuit(void)
{
	CPlayerPed *player = FindPlayerPed();
	if (!player)
		return;

	CWanted *wanted = player->m_pWanted;
	int ourCopId = 0;
	bool foundMyself = false;
	int biggestCopId = 0;
	if (!m_bIsInPursuit)
		return;

	m_bIsInPursuit = false;
	for (int i = 0; i < Max(wanted->m_MaxCops, wanted->m_CurrentCops); ++i)  {
		if (!foundMyself && wanted->m_pCops[i] == this) {
			wanted->m_pCops[i] = nil;
			--wanted->m_CurrentCops;
			foundMyself = true;
			ourCopId = i;
			biggestCopId = i;
		} else {
			if (wanted->m_pCops[i])
				biggestCopId = i;
		}
	}
	if (foundMyself && biggestCopId > ourCopId) {
		wanted->m_pCops[ourCopId] = wanted->m_pCops[biggestCopId];
		wanted->m_pCops[biggestCopId] = nil;
	}
	m_objective = OBJECTIVE_NONE;
	m_prevObjective = OBJECTIVE_NONE;
	m_nLastPedState = PED_NONE;
	bIsRunning = false;
	bNotAllowedToDuck = false;
	bKindaStayInSamePlace = false;
	m_bStopAndShootDisabledZone = false;
	m_bZoneDisabled = false;
	ClearObjective();
	if (IsPedInControl()) {
		if (!m_pMyVehicle || wanted->GetWantedLevel() != 0)  {
			if (m_pMyVehicle && (m_pMyVehicle->GetPosition() - GetPosition()).MagnitudeSqr() < sq(5.0f)) {
				m_nLastPedState = PED_IDLE;
				SetSeek((CEntity*)m_pMyVehicle, 2.5f);
			} else {
				m_nLastPedState = PED_WANDER_PATH;
				SetFindPathAndFlee(FindPlayerPed()->GetPosition(), 10000, true);
			}
		} else {
			SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle);
		}
	}
}

// TODO: I don't know why they needed that parameter.
void
CCopPed::SetPursuit(bool ignoreCopLimit)
{
	CWanted *wanted = FindPlayerPed()->m_pWanted;
	if (m_bIsInPursuit || !IsPedInControl())
		return;

	if (wanted->m_CurrentCops < wanted->m_MaxCops || ignoreCopLimit) {
		for (int i = 0; i < wanted->m_MaxCops; ++i) {
			if (!wanted->m_pCops[i]) {
				m_bIsInPursuit = true;
				++wanted->m_CurrentCops;
				wanted->m_pCops[i] = this;
				break;
			}
		}
		if (m_bIsInPursuit) {
			ClearObjective();
			m_prevObjective = OBJECTIVE_NONE;
			SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, FindPlayerPed());
			SetObjectiveTimer(0);
			bNotAllowedToDuck = true;
			bIsRunning = true;
			m_bStopAndShootDisabledZone = false;
		}
	}
}

void
CCopPed::ArrestPlayer(void)
{
	m_pVehicleAnim = nil;
	CPed *suspect = (CPed*)m_pSeekTarget;
	if (suspect) {
		if (suspect->CanSetPedState())
			suspect->SetPedState(PED_ARRESTED);

		if (suspect->bInVehicle && m_pMyVehicle && suspect->m_pMyVehicle == m_pMyVehicle) {

			// BUG? I will never understand why they used LINE_UP_TO_CAR_2...
			LineUpPedWithCar(LINE_UP_TO_CAR_2);
		}

		if (suspect && (suspect->m_nPedState == PED_ARRESTED || suspect->DyingOrDead() || suspect->EnteringCar())) {

			CAnimBlendAssociation *arrestAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ARREST);
			if (!arrestAssoc || arrestAssoc->blendDelta < 0.0f)
				CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ARREST, 4.0f);

			CVector suspMidPos;
			suspect->m_pedIK.GetComponentPosition(suspMidPos, PED_MID);
			m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(suspMidPos.x, suspMidPos.y,
				GetPosition().x, GetPosition().y);

			m_fRotationCur = m_fRotationDest;
			SetOrientation(0.0f, 0.0f, m_fRotationCur);
		} else {
			ClearPursuit();
		}
	} else {
		ClearPursuit();
	}
}

void
CCopPed::ScanForCrimes(void)
{
	CVehicle *playerVeh = FindPlayerVehicle();

	// Look for car alarms
	if (playerVeh && playerVeh->IsCar()) {
		if (playerVeh->IsAlarmOn()) {
			if ((playerVeh->GetPosition() - GetPosition()).MagnitudeSqr() < sq(20.0f))
				FindPlayerPed()->SetWantedLevelNoDrop(1);
		}
	}

	// Look for stolen cop cars
	if (!m_bIsInPursuit) {
		CPlayerPed *player = FindPlayerPed();
		if ((m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER)
			&& player->m_pWanted->GetWantedLevel() == 0) {

			if (player->m_pMyVehicle
#ifdef FIX_BUGS
				&& m_pMyVehicle == player->m_pMyVehicle
#endif
				&& player->m_pMyVehicle->bIsLawEnforcer)
				player->SetWantedLevelNoDrop(1);
		}
	}
}

void
CCopPed::CopAI(void)
{
	CWanted *wanted = FindPlayerPed()->m_pWanted;
	int wantedLevel = wanted->GetWantedLevel();
	CPhysical *playerOrHisVeh = FindPlayerVehicle() ? (CPhysical*)FindPlayerVehicle() : (CPhysical*)FindPlayerPed();

	if (wanted->m_bIgnoredByEveryone || wanted->m_bIgnoredByCops) {
		if (m_nPedState != PED_ARREST_PLAYER)
			ClearPursuit();

		return;
	}
	if (CCullZones::NoPolice() && m_bIsInPursuit && !m_bIsDisabledCop) {
		if (bHitSomethingLastFrame) {
			m_bZoneDisabled = true;
			m_bIsDisabledCop = true;
#ifdef FIX_BUGS
			m_nRoadblockNode = -1;
#else
			m_nRoadblockNode = 0;
#endif
			bKindaStayInSamePlace = true;
			bIsRunning = false;
			bNotAllowedToDuck = false;
			bCrouchWhenShooting = false;
			SetIdle();
			ClearObjective();
			ClearPursuit();
			m_prevObjective = OBJECTIVE_NONE;
			m_nLastPedState = PED_NONE;
			SetAttackTimer(0);

			// Safe distance for disabled zone? Or to just make game easier?
			if (m_fDistanceToTarget > 15.0f)
				m_bStopAndShootDisabledZone = true;
		}
	} else if (m_bZoneDisabled && !CCullZones::NoPolice()) {
		m_bZoneDisabled = false;
		m_bIsDisabledCop = false;
		m_bStopAndShootDisabledZone = false;
		bKindaStayInSamePlace = false;
		bCrouchWhenShooting = false;
		bDuckAndCover = false;
		ClearPursuit();
	}
	if (wantedLevel > 0) {
		if (!m_bIsDisabledCop) {
			if (!m_bIsInPursuit || wanted->m_CurrentCops > wanted->m_MaxCops) {
				CCopPed *copFarthestToTarget = nil;
				float copFarthestToTargetDist = m_fDistanceToTarget;

				int oldCopNum = wanted->m_CurrentCops;
				int maxCops = wanted->m_MaxCops;

				for (int i = 0; i < Max(maxCops, oldCopNum); i++) {
					CCopPed *cop = wanted->m_pCops[i];
					if (cop && cop->m_fDistanceToTarget > copFarthestToTargetDist) {
						copFarthestToTargetDist = cop->m_fDistanceToTarget;
						copFarthestToTarget = wanted->m_pCops[i];
					}
				}

				if (m_bIsInPursuit) {
					if (copFarthestToTarget && oldCopNum > maxCops) {
						if (copFarthestToTarget == this && m_fDistanceToTarget > 10.0f) {
							ClearPursuit();
						} else if(copFarthestToTargetDist > 10.0f)
							copFarthestToTarget->ClearPursuit();
					}
				} else {
					if (oldCopNum < maxCops) {
						SetPursuit(true);
					} else {
						if (m_fDistanceToTarget <= 10.0f || copFarthestToTarget && m_fDistanceToTarget < copFarthestToTargetDist) {
							if (copFarthestToTarget && copFarthestToTargetDist > 10.0f)
								copFarthestToTarget->ClearPursuit();

							SetPursuit(true);
						}
					}
				}
			} else
				SetPursuit(false);

			if (!m_bIsInPursuit)
				return;

			if (wantedLevel > 1 && GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED)
				SetCurrentWeapon(WEAPONTYPE_COLT45);
			else if (wantedLevel == 1 && GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && !FindPlayerPed()->m_pCurrentPhysSurface) {
				// i.e. if player is on top of car, cop will still use colt45.
				SetCurrentWeapon(WEAPONTYPE_UNARMED);
			}

			if (FindPlayerVehicle()) {
				if (m_bBeatingSuspect) {
					--wanted->m_CopsBeatingSuspect;
					m_bBeatingSuspect = false;
				}
				if (m_fDistanceToTarget * FindPlayerSpeed().Magnitude() > 4.0f)
					ClearPursuit();
			}
			return;
		}
		float weaponRange = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_fRange;
		SetLookFlag(playerOrHisVeh, true);
		TurnBody();
		SetCurrentWeapon(WEAPONTYPE_COLT45);
		if (!bIsDucking) {
			if (m_attackTimer >= CTimer::GetTimeInMilliseconds()) {
				if (m_nPedState != PED_ATTACK && m_nPedState != PED_FIGHT && !m_bZoneDisabled) {
					CVector targetDist = playerOrHisVeh->GetPosition() - GetPosition();
					if (m_fDistanceToTarget > 30.0f) {
						CAnimBlendAssociation* crouchShootAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RBLOCK_SHOOT);
						if (crouchShootAssoc)
							crouchShootAssoc->blendDelta = -1000.0f;

						// Target is coming onto us
						if (DotProduct(playerOrHisVeh->m_vecMoveSpeed, targetDist) > 0.0f) {
							m_bIsDisabledCop = false;
							bKindaStayInSamePlace = false;
							bNotAllowedToDuck = false;
							bDuckAndCover = false;
							SetPursuit(false);
							SetObjective(OBJECTIVE_KILL_CHAR_ANY_MEANS, FindPlayerPed());
						}
					} else if (m_fDistanceToTarget < 5.0f
							&& (!FindPlayerVehicle() || FindPlayerVehicle()->m_vecMoveSpeed.MagnitudeSqr() < sq(1.f/200.f))) {
						m_bIsDisabledCop = false;
						bKindaStayInSamePlace = false;
						bNotAllowedToDuck = false;
						bDuckAndCover = false;
					} else {
						// VC checks for != nil compared to buggy behaviour of III. I check for != -1 here.
#ifdef VC_PED_PORTS
						float dotProd;
						if (m_nRoadblockNode != -1) {
							CTreadable *roadBlockRoad = ThePaths.m_mapObjects[CRoadBlocks::RoadBlockObjects[m_nRoadblockNode]];
							dotProd = DotProduct2D(playerOrHisVeh->GetPosition() - roadBlockRoad->GetPosition(), GetPosition() - roadBlockRoad->GetPosition());
						} else
							dotProd = -1.0f;

						if(dotProd >= 0.0f) {
#else

#ifndef FIX_BUGS
						float copRoadDotProd, targetRoadDotProd;
#else
						float copRoadDotProd = 1.0f, targetRoadDotProd = 1.0f;
						if (m_nRoadblockNode != -1)
#endif
						{
							CTreadable* roadBlockRoad = ThePaths.m_mapObjects[CRoadBlocks::RoadBlockObjects[m_nRoadblockNode]];
							CVector2D roadFwd = roadBlockRoad->GetForward();
							copRoadDotProd = DotProduct2D(GetPosition() - roadBlockRoad->GetPosition(), roadFwd);
							targetRoadDotProd = DotProduct2D(playerOrHisVeh->GetPosition() - roadBlockRoad->GetPosition(), roadFwd);
						}
						// Roadblock may be towards road's fwd or opposite, so check both
						if ((copRoadDotProd >= 0.0f || targetRoadDotProd >= 0.0f)
							&& (copRoadDotProd <= 0.0f || targetRoadDotProd <= 0.0f)) {
#endif
							bIsPointingGunAt = true;
						} else {
							m_bIsDisabledCop = false;
							bKindaStayInSamePlace = false;
							bNotAllowedToDuck = false;
							bCrouchWhenShooting = false;
							bIsDucking = false;
							bDuckAndCover = false;
							SetPursuit(false);
						}
					}
				}
			} else {
				if (m_fDistanceToTarget < weaponRange) {
					CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType);
					CVector gunPos = weaponInfo->m_vecFireOffset;
					TransformToNode(gunPos, PED_HANDR);

					CColPoint foundCol;
					CEntity *foundEnt;
					if (!CWorld::ProcessLineOfSight(gunPos, playerOrHisVeh->GetPosition(), foundCol, foundEnt,
						false, true, false, false, true, false, false)
						|| foundEnt && foundEnt == playerOrHisVeh) {
						SetWeaponLockOnTarget(playerOrHisVeh);
						SetAttack(playerOrHisVeh);
						SetShootTimer(CGeneral::GetRandomNumberInRange(500, 1000));
					}
					SetAttackTimer(CGeneral::GetRandomNumberInRange(200, 300));
				}
				SetMoveState(PEDMOVE_STILL);
			}
		}
	} else {
		if (!m_bIsDisabledCop || m_bZoneDisabled) {
			if (m_nPedState != PED_AIM_GUN) {
				if (m_bIsInPursuit)
					ClearPursuit();

				if (IsPedInControl()) {
					// Entering the vehicle
					if (m_pMyVehicle && !bInVehicle) {
						if (m_pMyVehicle->IsLawEnforcementVehicle()) {
							if (m_pMyVehicle->pDriver) {
								if (m_pMyVehicle->pDriver->m_nPedType == PEDTYPE_COP) {
									if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER)
										SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_pMyVehicle);
								} else if (m_pMyVehicle->pDriver->IsPlayer()) {
									FindPlayerPed()->SetWantedLevelNoDrop(1);
								}
							} else if (m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER) {
								SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle);
							}
						} else {
							m_pMyVehicle = nil;
							ClearObjective();
							SetWanderPath(CGeneral::GetRandomNumber() & 7);
						}
					}
#ifdef VC_PED_PORTS
					else {
						if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT && CharCreatedBy == RANDOM_CHAR) {
							for (int i = 0; i < m_numNearPeds; i++) {
								CPed *nearPed = m_nearPeds[i];
								if (nearPed->CharCreatedBy == RANDOM_CHAR) {
									if ((nearPed->m_nPedType == PEDTYPE_CRIMINAL || nearPed->IsGangMember())
										&& nearPed->IsPedInControl()) {

										bool anotherCopChasesHim = false;
										if (nearPed->m_nPedState == PED_FLEE_ENTITY) {
											if (nearPed->m_fleeFrom && nearPed->m_fleeFrom->IsPed() &&
												((CPed*)nearPed->m_fleeFrom)->m_nPedType == PEDTYPE_COP) {
												anotherCopChasesHim = true;
											}
										}
										if (!anotherCopChasesHim) {
											SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, nearPed);
											nearPed->SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE, this);
											nearPed->bBeingChasedByPolice = true;
											return;
										}
									}
								}
							}
						}
					}
#endif
				}
			}
		} else {
			if (m_bIsInPursuit && m_nPedState != PED_AIM_GUN)
				ClearPursuit();

			m_bIsDisabledCop = false;
			bKindaStayInSamePlace = false;
			bNotAllowedToDuck = false;
			bCrouchWhenShooting = false;
			bIsDucking = false;
			bDuckAndCover = false;
			if (m_pMyVehicle)
				SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle);
		}
	}
}

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

	CPed::ProcessControl();
	if (bWasPostponed)
		return;

	if (m_nPedState == PED_DEAD) {
		ClearPursuit();
		m_objective = OBJECTIVE_NONE;
		return;
	}
	if (m_nPedState == PED_DIE)
		return;

	if (m_nPedState == PED_ARREST_PLAYER) {
		ArrestPlayer();
		return;
	}
	GetWeapon()->Update(m_audioEntityId);
	if (m_moved.Magnitude() > 0.0f)
		Avoid();

	CPhysical *playerOrHisVeh = FindPlayerVehicle() ? (CPhysical*)FindPlayerVehicle() : (CPhysical*)FindPlayerPed();
	CPlayerPed *player = FindPlayerPed();

	m_fDistanceToTarget = (playerOrHisVeh->GetPosition() - GetPosition()).Magnitude();
	if (player->m_nPedState == PED_ARRESTED || player->DyingOrDead()) {
		if (m_fDistanceToTarget < 5.0f) {
			SetArrestPlayer(player);
			return;
		}
		if (IsPedInControl())
			SetIdle();
	}
	if (m_bIsInPursuit) {
		if (player->m_nPedState != PED_ARRESTED && !player->DyingOrDead()) {
			switch (m_nCopType) {
				case COP_FBI:
					Say(SOUND_PED_PURSUIT_FBI);
					break;
				case COP_SWAT:
					Say(SOUND_PED_PURSUIT_SWAT);
					break;
				case COP_ARMY:
					Say(SOUND_PED_PURSUIT_ARMY);
					break;
				default:
					Say(SOUND_PED_PURSUIT_COP);
					break;
			}
		}
	}

	if (IsPedInControl()) {
		CopAI();
		/* switch (m_nCopType)
		{
			case COP_FBI:
				CopAI();
				break;
			case COP_SWAT:
				CopAI();
				break;
			case COP_ARMY:
				CopAI();
				break;
			default:
				CopAI();
				break;
		} */
	} else if (InVehicle()) {
		if (m_pMyVehicle->pDriver == this && m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_NONE &&
			CanPedDriveOff() && m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE) {

			CCarCtrl::JoinCarWithRoadSystem(m_pMyVehicle);
			m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE;
			m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_STOP_FOR_CARS;
			m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 17;
		}
	}
	if (IsPedInControl() || m_nPedState == PED_DRIVING)
		ScanForCrimes();

	// They may have used goto to jump here in case of PED_ATTACK.
	if (m_nPedState == PED_IDLE || m_nPedState == PED_ATTACK) {
		if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT &&
			player && player->EnteringCar() && m_fDistanceToTarget < 1.3f) {
			SetArrestPlayer(player);
		}
	} else {
		if (m_nPedState == PED_SEEK_POS) {
			if (player->m_nPedState == PED_ARRESTED) {
				SetIdle();
				SetLookFlag(player, false);
				SetLookTimer(1000);
				RestorePreviousObjective();
			} else {
				if (player->m_pMyVehicle && player->m_pMyVehicle->m_nNumGettingIn != 0) {
					// This is 1.3f when arresting in car without seeking first (in above)
#if defined(VC_PED_PORTS) || defined(FIX_BUGS)
					m_distanceToCountSeekDone = 1.3f;
#else
					m_distanceToCountSeekDone = 2.0f;
#endif
				}

				if (bDuckAndCover) {
#if GTA_VERSION < GTA3_PC_11 && !defined(VC_PED_PORTS)
					if (!bNotAllowedToDuck && Seek()) {
						SetMoveState(PEDMOVE_STILL);
						SetMoveAnim();
						SetPointGunAt(m_pedInObjective);
					}
#endif
				} else if (Seek()) {
					CVehicle *playerVeh = FindPlayerVehicle();
					if (!playerVeh && player && player->EnteringCar()) {
						SetArrestPlayer(player);
					} else if (1.5f + GetPosition().z <= m_vecSeekPos.z || GetPosition().z - 0.3f >= m_vecSeekPos.z) {
						SetMoveState(PEDMOVE_STILL);
					} else if (playerVeh && playerVeh->CanPedEnterCar() && playerVeh->m_nNumGettingIn == 0) {
						SetCarJack(playerVeh);
					}
				}
			}
		} else if (m_nPedState == PED_SEEK_ENTITY) {
			if (!m_pSeekTarget) {
				RestorePreviousState();
			} else {
				m_vecSeekPos = m_pSeekTarget->GetPosition();
				if (Seek()) {
					if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && m_fDistanceToTarget < 2.5f && player) {
						if (player->m_nPedState == PED_ARRESTED || player->m_nPedState == PED_ENTER_CAR ||
							(player->m_nPedState == PED_CARJACK && m_fDistanceToTarget < 1.3f)) {
							SetArrestPlayer(player);
						} else
							RestorePreviousState();
					} else {
						RestorePreviousState();
					}

				}
			}
		}
	}
	if (!m_bStopAndShootDisabledZone)
		return;

	bool dontShoot = false;
	if (GetIsOnScreen() && CRenderer::IsEntityCullZoneVisible(this)) {
		if (((CTimer::GetFrameCounter() + m_randomSeed) & 0x1F) == 17) {
			CEntity *foundBuilding = nil;
			CColPoint foundCol;
			CVector lookPos = GetPosition() + CVector(0.0f, 0.0f, 0.7f);
			CVector camPos = TheCamera.GetGameCamPosition();
			CWorld::ProcessLineOfSight(camPos, lookPos, foundCol, foundBuilding,
				true, false, false, false, false, false, false);

			// He's at least 15.0 far, in disabled zone, collided into somewhere (that's why m_bStopAndShootDisabledZone set),
			// and now has building on front of him. He's stupid, we don't need him.
			if (foundBuilding) {
				FlagToDestroyWhenNextProcessed();
				dontShoot = true;
			}
		}
	} else {
		FlagToDestroyWhenNextProcessed();
		dontShoot = true;
	}

	if (!dontShoot) {
		bStopAndShoot = true;
		bKindaStayInSamePlace = true;
		bIsPointingGunAt = true;
		SetAttack(m_pedInObjective);
	}
}


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

enum eCopType
{
	COP_STREET = 0,
	COP_FBI = 1,
	COP_SWAT = 2,
	COP_ARMY = 3,
};

class CCopPed : public CPed
{
public:
	int16 m_nRoadblockNode;
	float m_fDistanceToTarget;
	bool m_bIsInPursuit;
	bool m_bIsDisabledCop;
	int8 field_1350;
	bool m_bBeatingSuspect;
	bool m_bStopAndShootDisabledZone;
	bool m_bZoneDisabled;
	float m_fAbseilPos; // VC leftover, unused
	eCopType m_nCopType;
	int8 field_1364;

	CCopPed(eCopType);
	~CCopPed();

	void ClearPursuit(void);
	void ProcessControl(void);
	void SetArrestPlayer(CPed*);
	void SetPursuit(bool);
	void ArrestPlayer(void);
	void ScanForCrimes(void);
	void CopAI(void);
};

#ifndef PED_SKIN
VALIDATE_SIZE(CCopPed, 0x558);
#endif


code:
#pragma once

#include "Dummy.h"

// actually unused
class CDummyPed : CDummy
{
	int32 pedType;
	int32 unknown;
};

VALIDATE_SIZE(CDummyPed, 0x70);


code:
#include "common.h"

#include "EmergencyPed.h"
#include "DMAudio.h"
#include "ModelIndices.h"
#include "Vehicle.h"
#include "Fire.h"
#include "General.h"
#include "CarCtrl.h"
#include "Accident.h"

CEmergencyPed::CEmergencyPed(uint32 type) : CPed(type)
{
	switch (type){
		case PEDTYPE_EMERGENCY:
			SetModelIndex(MI_MEDIC);
			m_pRevivedPed = nil;
			field_1360 = 0;
			break;
		case PEDTYPE_FIREMAN:
			SetModelIndex(MI_FIREMAN);
			m_pRevivedPed = nil;
			break;
		default:
			break;
	}
	m_nEmergencyPedState = EMERGENCY_PED_READY;
	m_pAttendedAccident = nil;
	m_bStartedToCPR = false;
}

bool
CEmergencyPed::InRange(CPed *victim)
{
	if (!m_pMyVehicle)
		return true;

	if ((m_pMyVehicle->GetPosition() - victim->GetPosition()).Magnitude() > 30.0f)
		return false;

	return true;
}

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

	CPed::ProcessControl();
	if (bWasPostponed)
		return;

	if(!DyingOrDead()) {
		GetWeapon()->Update(m_audioEntityId);

		if (IsPedInControl() && m_moved.Magnitude() > 0.0f)
			Avoid();

		switch (m_nPedState) {
			case PED_SEEK_POS:
				Seek();
				break;
			case PED_SEEK_ENTITY:
				if (m_pSeekTarget) {
					m_vecSeekPos = m_pSeekTarget->GetPosition();
					Seek();
				} else {
					ClearSeek();
				}
				break;
			default:
				break;
		}

		switch (m_nPedType) {
			case PEDTYPE_EMERGENCY:
				if (IsPedInControl() || m_nPedState == PED_DRIVING)
					MedicAI();
				break;
			case PEDTYPE_FIREMAN:
				if (IsPedInControl())
					FiremanAI();
				break;
			default:
				return;
		}
	}
}

// This function was buggy and incomplete in both III and VC, firemen had to be in 5.0m range of fire etc. etc.
// Copied some code from MedicAI to make it work.
void
CEmergencyPed::FiremanAI(void)
{
	float fireDist;
	CFire *nearestFire;

	switch (m_nEmergencyPedState) {
		case EMERGENCY_PED_READY:
			nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist);
			if (nearestFire) {
				SetPedState(PED_NONE);
				SetSeek(nearestFire->m_vecPos, 1.0f);
				SetMoveState(PEDMOVE_RUN);
				m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
				m_pAttendedFire = nearestFire;
#ifdef FIX_BUGS
				bIsRunning = true;
				++nearestFire->m_nFiremenPuttingOut;
#endif
			}
			break;
		case EMERGENCY_PED_DETERMINE_NEXT_STATE:
			nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist);
			if (nearestFire && nearestFire != m_pAttendedFire) {
				SetPedState(PED_NONE);
				SetSeek(nearestFire->m_vecPos, 1.0f);
				SetMoveState(PEDMOVE_RUN);
#ifdef FIX_BUGS
				bIsRunning = true;
				if (m_pAttendedFire) {
					--m_pAttendedFire->m_nFiremenPuttingOut;
				}
				++nearestFire->m_nFiremenPuttingOut;
				m_pAttendedFire = nearestFire;
			} else if (!nearestFire) {
#else
				m_pAttendedFire = nearestFire;
			} else {
#endif
				m_nEmergencyPedState = EMERGENCY_PED_STOP;
			}

			// "Extinguish" the fire (Will overwrite the stop decision above if the attended and nearest fires are same)
			if (fireDist < 5.0f) {
				SetIdle();
				m_nEmergencyPedState = EMERGENCY_PED_STAND_STILL;
			}
			break;
		case EMERGENCY_PED_STAND_STILL:
			if (!m_pAttendedFire->m_bIsOngoing)
				m_nEmergencyPedState = EMERGENCY_PED_STOP;

			// Leftover
			// fireDist = 30.0f;
			nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist);
			if (nearestFire) {
#ifdef FIX_BUGS
				if(nearestFire != m_pAttendedFire && (nearestFire->m_vecPos - GetPosition()).Magnitude() < 30.0f)
#endif
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
			}
			Say(SOUND_PED_EXTINGUISHING_FIRE);
			break;
		case EMERGENCY_PED_STOP:
#ifdef FIX_BUGS
			bIsRunning = false;
			if (m_pAttendedFire)
#endif
				--m_pAttendedFire->m_nFiremenPuttingOut;

			SetPedState(PED_NONE);
			SetWanderPath(CGeneral::GetRandomNumber() & 7);
			m_pAttendedFire = nil;
			m_nEmergencyPedState = EMERGENCY_PED_READY;
			SetMoveState(PEDMOVE_WALK);
			break;
		default: break;
	}
}

void
CEmergencyPed::MedicAI(void)
{
	float distToEmergency;
	if (!bInVehicle && IsPedInControl()) {
		ScanForThreats();
		if (m_threatEntity && m_threatEntity->IsPed() && ((CPed*)m_threatEntity)->IsPlayer()) {
			if (((CPed*)m_threatEntity)->GetWeapon()->IsTypeMelee()) {
				SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity);
			} else {
				SetFlee(m_threatEntity, 6000);
				Say(SOUND_PED_FLEE_SPRINT);
			}
			return;
		}
	}

	if (InVehicle()) {
		if (m_pMyVehicle->IsCar() && m_objective != OBJECTIVE_LEAVE_CAR) {
			if (gAccidentManager.FindNearestAccident(m_pMyVehicle->GetPosition(), &distToEmergency)
				&& distToEmergency < 25.0f && m_pMyVehicle->m_vecMoveSpeed.Magnitude() < 0.01f) {

				m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_NONE;
				SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle);
				Say(SOUND_PED_LEAVE_VEHICLE);
			} else if (m_pMyVehicle->pDriver == this && m_nPedState == PED_DRIVING
				&& m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_NONE && !(CGeneral::GetRandomNumber() & 31)) {

				bool waitUntilMedicEntersCar = false;
				for (int i = 0; i < m_numNearPeds; ++i) {
					CPed *nearPed = m_nearPeds[i];
					if (nearPed->m_nPedType == PEDTYPE_EMERGENCY) {
						if ((nearPed->m_nPedState == PED_SEEK_CAR || nearPed->m_nPedState == PED_ENTER_CAR)
							&& nearPed->m_pMyVehicle == m_pMyVehicle) {
							waitUntilMedicEntersCar = true;
							break;
						}
					}
				}
				if (!waitUntilMedicEntersCar) {
					CCarCtrl::JoinCarWithRoadSystem(m_pMyVehicle);
					m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE;
					m_pMyVehicle->m_bSirenOrAlarm = false;
					m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 12;
					m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_SLOW_DOWN_FOR_CARS;
					if (m_pMyVehicle->bIsAmbulanceOnDuty) {
						m_pMyVehicle->bIsAmbulanceOnDuty = false;
						--CCarCtrl::NumAmbulancesOnDuty;
					}
				}
			}
		}
	}

	CVector headPos, midPos;
	CAccident *nearestAccident;
	if (IsPedInControl()) {
		switch (m_nEmergencyPedState) {
			case EMERGENCY_PED_READY:
				nearestAccident = gAccidentManager.FindNearestAccident(GetPosition(), &distToEmergency);
				field_1360 = 0;
				if (nearestAccident) {
					m_pRevivedPed = nearestAccident->m_pVictim;
					m_pRevivedPed->RegisterReference((CEntity**)&m_pRevivedPed);
					m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID);
					m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD);
					SetSeek((headPos + midPos) * 0.5f, 1.0f);
					SetObjective(OBJECTIVE_NONE);
					bIsRunning = true;
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
					m_pAttendedAccident = nearestAccident;
					++m_pAttendedAccident->m_nMedicsAttending;
				} else {
					if (m_pMyVehicle) {
						if (!bInVehicle) {
							if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_pMyVehicle->pDriver || m_pMyVehicle->m_nGettingInFlags) {

								CPed* driver = m_pMyVehicle->pDriver;
								if (driver && driver->m_nPedType != PEDTYPE_EMERGENCY && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) {
									SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, driver);
								} else if (m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER
									&& m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER
									&& m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) {
									SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_pMyVehicle);
								}
							} else {
								SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle);
							}
						}
					} else if (m_nPedState != PED_WANDER_PATH) {
						SetWanderPath(CGeneral::GetRandomNumber() & 7);
					}
				}
				break;
			case EMERGENCY_PED_DETERMINE_NEXT_STATE:
				nearestAccident = gAccidentManager.FindNearestAccident(GetPosition(), &distToEmergency);
				if (nearestAccident) {
					if (nearestAccident != m_pAttendedAccident || m_nPedState != PED_SEEK_POS) {
						m_pRevivedPed = nearestAccident->m_pVictim;
						m_pRevivedPed->RegisterReference((CEntity**)&m_pRevivedPed);
						if (!InRange(m_pRevivedPed)) {
							m_nEmergencyPedState = EMERGENCY_PED_STOP;
							break;
						}
						m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID);
						m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD);
						SetSeek((headPos + midPos) * 0.5f, nearestAccident->m_nMedicsPerformingCPR * 0.5f + 1.0f);
						SetObjective(OBJECTIVE_NONE);
						bIsRunning = true;
						--m_pAttendedAccident->m_nMedicsAttending;
						++nearestAccident->m_nMedicsAttending;
						m_pAttendedAccident = nearestAccident;
					}
				} else {
					m_nEmergencyPedState = EMERGENCY_PED_STOP;
					bIsRunning = false;
				}
				if (distToEmergency < 5.0f) {
					if (m_pRevivedPed->m_pFire) {
						bIsRunning = false;
						SetMoveState(PEDMOVE_STILL);
					} else if (distToEmergency < 4.5f) {
						bIsRunning = false;
						SetMoveState(PEDMOVE_WALK);
						if (distToEmergency < 1.0f
							|| distToEmergency < 4.5f && m_pAttendedAccident->m_nMedicsPerformingCPR) {
							m_nEmergencyPedState = EMERGENCY_PED_START_CPR;
						}
					}
				}
				break;
			case EMERGENCY_PED_START_CPR:
				if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f || m_pRevivedPed->bFadeOut) {
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
				} else {
					m_pRevivedPed->m_bloodyFootprintCountOrDeathTime = CTimer::GetTimeInMilliseconds();
					SetMoveState(PEDMOVE_STILL);
					SetPedState(PED_CPR);
					m_nLastPedState = PED_CPR;
					SetLookFlag(m_pRevivedPed, 0);
					SetLookTimer(500);
					Say(SOUND_PED_HEALING);
					if (m_pAttendedAccident->m_nMedicsPerformingCPR) {
						SetIdle();
						m_nEmergencyPedState = EMERGENCY_PED_STAND_STILL;
					} else {
						m_nEmergencyPedState = EMERGENCY_PED_FACE_TO_PATIENT;
						m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_MEDIC_CPR, 4.0f);
						bIsDucking = true;
					}
					SetLookTimer(2000);
					++m_pAttendedAccident->m_nMedicsPerformingCPR;
					m_bStartedToCPR = true;
				}
				break;
			case EMERGENCY_PED_FACE_TO_PATIENT:
				if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f)
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
				else {
					m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID);
					m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD);
					midPos = (headPos + midPos) * 0.5f;
					m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(
						midPos.x, midPos.y,
						GetPosition().x, GetPosition().y);
					m_fRotationDest = CGeneral::LimitAngle(m_fRotationDest);
					m_pLookTarget = m_pRevivedPed;
					m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget);
					TurnBody();

					if (Abs(m_fRotationCur - m_fRotationDest) < DEGTORAD(45.0f))
						m_nEmergencyPedState = EMERGENCY_PED_PERFORM_CPR;
					else
						m_fRotationCur = (m_fRotationCur + m_fRotationDest) * 0.5f;
				}
				break;
			case EMERGENCY_PED_PERFORM_CPR:
				if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f) {
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
					break;
				}
				m_pRevivedPed->m_pedIK.GetComponentPosition(midPos, PED_MID);
				m_pRevivedPed->m_pedIK.GetComponentPosition(headPos, PED_HEAD);
				midPos = (headPos + midPos) * 0.5f;
				m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(
					midPos.x, midPos.y,
					GetPosition().x, GetPosition().y);
				m_fRotationDest = CGeneral::LimitAngle(m_fRotationDest);
				m_pLookTarget = m_pRevivedPed;
				m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget);
				TurnBody();
				if (CTimer::GetTimeInMilliseconds() <= m_lookTimer) {
					SetMoveState(PEDMOVE_STILL);
					break;
				}
				m_nEmergencyPedState = EMERGENCY_PED_STOP_CPR;
				SetPedState(PED_NONE);
				SetMoveState(PEDMOVE_WALK);
				m_pVehicleAnim = nil;
				if (!m_pRevivedPed->bBodyPartJustCameOff) {
					m_pRevivedPed->m_fHealth = 100.0f;
					m_pRevivedPed->SetPedState(PED_NONE);
					m_pRevivedPed->m_nLastPedState = PED_WANDER_PATH;
					m_pRevivedPed->SetGetUp();
					m_pRevivedPed->bUsesCollision = true;
					m_pRevivedPed->SetMoveState(PEDMOVE_WALK);
					m_pRevivedPed->RestartNonPartialAnims();
					m_pRevivedPed->bIsPedDieAnimPlaying = false;
					m_pRevivedPed->bKnockedUpIntoAir = false;
					m_pRevivedPed->m_pCollidingEntity = nil;
				}
				break;
			case EMERGENCY_PED_STOP_CPR:
				m_nEmergencyPedState = EMERGENCY_PED_STOP;
				bIsDucking = true;
				break;
			case EMERGENCY_PED_STAND_STILL:
				if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f)
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
				else {
					if (!m_pAttendedAccident->m_pVictim)
						m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
					if (!m_pAttendedAccident->m_nMedicsPerformingCPR)
						m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
					if (gAccidentManager.UnattendedAccidents())
						m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
				}
				break;
			case EMERGENCY_PED_STOP:
				m_bStartedToCPR = false;
				SetPedState(PED_NONE);
				if (m_pAttendedAccident) {
					m_pAttendedAccident->m_pVictim = nil;
					--m_pAttendedAccident->m_nMedicsAttending;
					m_pAttendedAccident = nil;
				}
				SetWanderPath(CGeneral::GetRandomNumber() & 7);
				m_pRevivedPed = nil;
				m_nEmergencyPedState = EMERGENCY_PED_READY;
				SetMoveState(PEDMOVE_WALK);
				break;
			default: break;
		}
	}
}


code:
#pragma once

#include "Ped.h"

class CAccident;
class CFire;

enum EmergencyPedState
{
	EMERGENCY_PED_READY = 0x0,
	EMERGENCY_PED_DETERMINE_NEXT_STATE = 0x1, // you can set that anytime you want
	EMERGENCY_PED_START_CPR = 0x2,
	EMERGENCY_PED_FLAG_4 = 0x4, // unused
	EMERGENCY_PED_FLAG_8 = 0x8, // unused
	EMERGENCY_PED_FACE_TO_PATIENT = 0x10, // for CPR
	EMERGENCY_PED_PERFORM_CPR = 0x20,
	EMERGENCY_PED_STOP_CPR = 0x40,
	EMERGENCY_PED_STAND_STILL = 0x80, // waiting colleagues for medics, "extinguishing" fire for firemen
	EMERGENCY_PED_STOP = 0x100,
};

class CEmergencyPed : public CPed
{
public:
	CPed *m_pRevivedPed;
	EmergencyPedState m_nEmergencyPedState;
	CAccident *m_pAttendedAccident;
	CFire *m_pAttendedFire;
	bool m_bStartedToCPR; // set but unused
	int32 field_1360; // set to 0 but unused

	CEmergencyPed(uint32);
	~CEmergencyPed() { }
	bool InRange(CPed*);
	void ProcessControl(void);
	void FiremanAI(void);
	void MedicAI(void);
};
#ifndef PED_SKIN
VALIDATE_SIZE(CEmergencyPed, 0x554);
#endif


code:
#include "common.h"

#include "ModelIndices.h"
#include "Gangs.h"
#include "Weapon.h"
#include "SaveBuf.h"

CGangInfo CGangs::Gang[NUM_GANGS];

CGangInfo::CGangInfo() :
	m_nVehicleMI(MI_BUS),
	m_nPedModelOverride(-1),
	m_Weapon1(WEAPONTYPE_UNARMED),
	m_Weapon2(WEAPONTYPE_UNARMED)
{}

void CGangs::Initialise(void)
{
	Gang[GANG_MAFIA].m_nVehicleMI = MI_MAFIA;
	Gang[GANG_TRIAD].m_nVehicleMI = MI_BELLYUP;
	Gang[GANG_DIABLOS].m_nVehicleMI = MI_DIABLOS;
	Gang[GANG_YAKUZA].m_nVehicleMI = MI_YAKUZA;
	Gang[GANG_YARDIE].m_nVehicleMI = MI_YARDIE;
	Gang[GANG_COLUMB].m_nVehicleMI = MI_COLUMB;
	Gang[GANG_HOODS].m_nVehicleMI = MI_HOODS;
	Gang[GANG_7].m_nVehicleMI = -1;
	Gang[GANG_8].m_nVehicleMI = -1;
#ifdef FIX_BUGS
	for (int i = 0; i < NUM_GANGS; i++)
		Gang[i].m_nPedModelOverride = -1;
#endif
}

void CGangs::SetGangVehicleModel(int16 gang, int32 model)
{
	GetGangInfo(gang)->m_nVehicleMI = model;
}

void CGangs::SetGangWeapons(int16 gang, int32 weapon1, int32 weapon2)
{
	CGangInfo *gi = GetGangInfo(gang);
	gi->m_Weapon1 = weapon1;
	gi->m_Weapon2 = weapon2;
}

void CGangs::SetGangPedModelOverride(int16 gang, int8 ovrd)
{
	GetGangInfo(gang)->m_nPedModelOverride = ovrd;
}

int8 CGangs::GetGangPedModelOverride(int16 gang)
{
	return GetGangInfo(gang)->m_nPedModelOverride;
}

void CGangs::SaveAllGangData(uint8 *buf, uint32 *size)
{
INITSAVEBUF

	*size = SAVE_HEADER_SIZE + sizeof(Gang);
	WriteSaveHeader(buf, 'G','N','G','\0', *size - SAVE_HEADER_SIZE);
	for (int i = 0; i < NUM_GANGS; i++)
		WriteSaveBuf(buf, Gang[i]);

VALIDATESAVEBUF(*size);
}

void CGangs::LoadAllGangData(uint8 *buf, uint32 size)
{
	Initialise();

INITSAVEBUF
	CheckSaveHeader(buf, 'G','N','G','\0', size - SAVE_HEADER_SIZE);

	for (int i = 0; i < NUM_GANGS; i++)
		ReadSaveBuf(&Gang[i], buf);
VALIDATESAVEBUF(size);
}


code:
#pragma once

struct CGangInfo
{
	int32 m_nVehicleMI;
	int8 m_nPedModelOverride;
	int32 m_Weapon1;
	int32 m_Weapon2;

	CGangInfo();
};

VALIDATE_SIZE(CGangInfo, 0x10);

enum {
	GANG_MAFIA = 0,
	GANG_TRIAD,
	GANG_DIABLOS,
	GANG_YAKUZA,
	GANG_YARDIE,
	GANG_COLUMB,
	GANG_HOODS,
	GANG_7,
	GANG_8,
	NUM_GANGS
};

class CGangs
{
public:
	static void Initialise(void);
	static void SetGangVehicleModel(int16, int32);
	static void SetGangWeapons(int16, int32, int32);
	static void SetGangPedModelOverride(int16, int8);
	static int8 GetGangPedModelOverride(int16);
	static void SaveAllGangData(uint8 *, uint32 *);
	static void LoadAllGangData(uint8 *, uint32);

	static int32 GetGangVehicleModel(int16 gang) { return Gang[gang].m_nVehicleMI; }
	static CGangInfo *GetGangInfo(int16 gang) { return &Gang[gang]; }

private:
	static CGangInfo Gang[NUM_GANGS];
};