code:
#include "common.h"

#include "Automobile.h"
#include "Bike.h"
#include "Camera.h"
#include "Coronas.h"
#include "DMAudio.h"
#include "Entity.h"
#include "EventList.h"
#include "Explosion.h"
#include "General.h"
#include "Fire.h"
#include "Pad.h"
#include "Particle.h"
#include "PointLights.h"
#include "Shadows.h"
#include "Timer.h"
#include "Vehicle.h"
#include "WaterLevel.h"
#include "World.h"

CExplosion gaExplosion[NUM_EXPLOSIONS];

// these two were not initialised in original code, I'm really not sure what were they meant to be
RwRGBA colMedExpl = { 0, 0, 0, 0 };
RwRGBA colUpdate = { 0, 0, 0, 0 };

int AudioHandle = AEHANDLE_NONE;

void
CExplosion::Initialise()
{
	debug("Initialising CExplosion...\n");
	for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) {
		gaExplosion[i].m_ExplosionType = EXPLOSION_GRENADE;
		gaExplosion[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f);
		gaExplosion[i].m_fRadius = 1.0f;
		gaExplosion[i].m_fPropagationRate = 0.0f;
		gaExplosion[i].m_fZshift = 0.0f;
		gaExplosion[i].m_pCreatorEntity = nil;
		gaExplosion[i].m_pVictimEntity = nil;
		gaExplosion[i].m_fStopTime = 0.0f;
		gaExplosion[i].m_nIteration = 0;
		gaExplosion[i].m_fStartTime = 0.0f;
		gaExplosion[i].m_bIsBoat = false;
	}
	AudioHandle = DMAudio.CreateEntity(AUDIOTYPE_EXPLOSION, (void*)1);
	if (AudioHandle >= 0)
		DMAudio.SetEntityStatus(AudioHandle, TRUE);
	debug("CExplosion ready\n");
}

void
CExplosion::Shutdown()
{
	debug("Shutting down CExplosion...\n");
	if (AudioHandle >= 0) {
		DMAudio.DestroyEntity(AudioHandle);
		AudioHandle = AEHANDLE_NONE;
	}
	debug("CExplosion shut down\n");
}

int8
CExplosion::GetExplosionActiveCounter(uint8 id)
{
	return gaExplosion[id].m_nActiveCounter;
}

void
CExplosion::ResetExplosionActiveCounter(uint8 id)
{
	gaExplosion[id].m_nActiveCounter = 0;
}

uint8
CExplosion::GetExplosionType(uint8 id)
{
	return gaExplosion[id].m_ExplosionType;
}

CVector *
CExplosion::GetExplosionPosition(uint8 id)
{
	return &gaExplosion[id].m_vecPosition;
}

bool
CExplosion::AddExplosion(CEntity *explodingEntity, CEntity *culprit, eExplosionType type, const CVector &pos, uint32 lifetime)
{
	CVector pPosn;
	CVector posGround;

	RwRGBA colorMedium = colMedExpl;
	bool bDontExplode = false;
	const RwRGBA color = { 160, 160, 160, 255 };
	pPosn = pos;
	pPosn.z += 5.0f;
#ifdef FIX_BUGS
	CShadows::AddPermanentShadow(SHADOWTEX_CAR, gpShadowHeliTex, &pPosn, 8.0f, 0.0f, 0.0f, -8.0f, 200, 0, 0, 0, 10.0f, 30000, 1.0f);
#else
	// last two arguments are swapped resulting in no shadow
	CShadows::AddPermanentShadow(SHADOWTEX_CAR, gpShadowHeliTex, &pPosn, 8.0f, 0.0f, 0.0f, -8.0f, 200, 0, 0, 0, 10.0f, 1, 30000.0f);
#endif

	int n = 0;
#ifdef FIX_BUGS
	while (n < ARRAY_SIZE(gaExplosion) && gaExplosion[n].m_nIteration != 0)
#else
	// array overrun is UB
	while (gaExplosion[n].m_nIteration != 0 && n < ARRAY_SIZE(gaExplosion))
#endif
		n++;
	if (n == ARRAY_SIZE(gaExplosion))
		return false;

	CExplosion &explosion = gaExplosion[n];
	explosion.m_ExplosionType = type;
	explosion.m_vecPosition = pos;
	explosion.m_fRadius = 1.0f;
	explosion.m_fZshift = 0.0f;
	explosion.m_pCreatorEntity = culprit;
	if (culprit != nil)
		culprit->RegisterReference(&explosion.m_pCreatorEntity);
	explosion.m_pVictimEntity = explodingEntity;
	if (explodingEntity != nil)
		explodingEntity->RegisterReference(&explosion.m_pVictimEntity);
	explosion.m_nIteration = 1;
	explosion.m_nActiveCounter = 1;
	explosion.m_bIsBoat = false;
	explosion.m_nParticlesExpireTime = lifetime != 0 ? CTimer::GetTimeInMilliseconds() + lifetime : 0;
	switch (type)
	{
	case EXPLOSION_GRENADE:
		explosion.m_fRadius = 9.0f;
		explosion.m_fPower = 300.0f;
		explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750;
		explosion.m_fPropagationRate = 0.5f;
		posGround = pos;
		posGround.z = CWorld::FindGroundZFor3DCoord(posGround.x, posGround.y, posGround.z + 3.0f, nil);
		CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250);
		if (Distance(explosion.m_vecPosition, TheCamera.GetPosition()) < 40.0f)
			CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color);
		break;
	case EXPLOSION_MOLOTOV:
	{
		explosion.m_fRadius = 6.0f;
		explosion.m_fPower = 0.0f;
		explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 3000;
		explosion.m_fPropagationRate = 0.5f;
		posGround = pos;
		bool found;
		posGround.z = CWorld::FindGroundZFor3DCoord(posGround.x, posGround.y, posGround.z + 3.0f, &found);
		if (found) {
			float waterLevel;
			if (CWaterLevel::GetWaterLevelNoWaves(posGround.x, posGround.y, posGround.z, &waterLevel)
				&& posGround.z < waterLevel
				&& waterLevel - 6.0f < posGround.z) // some subway/tunnels check?
				bDontExplode = true;
			else
				gFireManager.StartFire(posGround, 1.8f, false);
		}
		else
			bDontExplode = true;
		break;
	}
	case EXPLOSION_ROCKET:
		explosion.m_fRadius = 10.0f;
		explosion.m_fPower = 300.0f;
		explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750;
		explosion.m_fPropagationRate = 0.5f;
		CEventList::RegisterEvent(EVENT_EXPLOSION, pos, 250);
		if (Distance(explosion.m_vecPosition, TheCamera.GetPosition()) < 40.0f)
			CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color);
		break;
	case EXPLOSION_CAR:
	case EXPLOSION_CAR_QUICK:
		explosion.m_fRadius = 9.0f;
		explosion.m_fPower = 300.0f;
		explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 4250;
		explosion.m_fPropagationRate = 0.5f;
		explosion.m_fStartTime = CTimer::GetTimeInMilliseconds();
		if (explosion.m_pVictimEntity != nil) {
			if (explosion.m_pVictimEntity->IsVehicle() && ((CVehicle*)explosion.m_pVictimEntity)->IsBoat())
				explosion.m_bIsBoat = true;
			CEventList::RegisterEvent(EVENT_EXPLOSION, EVENT_ENTITY_VEHICLE, explosion.m_pVictimEntity, nil, 1000);
		} else
			CEventList::RegisterEvent(EVENT_EXPLOSION, pos, 1000);

		if (explosion.m_pVictimEntity != nil && !explosion.m_bIsBoat) {
			int rn = (CGeneral::GetRandomNumber() & 1) + 2;
			for (int i = 0; i < rn; i++) {
				CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, explosion.m_pVictimEntity->GetPosition(), CVector(0.0f, 0.0f, 0.0f), nil, 3.5f, colMedExpl);
				CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, explosion.m_pVictimEntity->GetPosition(), CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color);
			}
			CVehicle *veh = (CVehicle*)explosion.m_pVictimEntity;
			int32 component = CAR_WING_LR;

			// miami leftover
			if (veh->IsBike())
				component = BIKE_FORKS_REAR;

			if (veh->IsComponentPresent(component)) {
				CVector componentPos;
				veh->GetComponentWorldPosition(component, componentPos);
				rn = (CGeneral::GetRandomNumber() & 1) + 1;
				for (int i = 0; i < rn; i++)
					CParticle::AddJetExplosion(componentPos, 1.4f, 0.0f);
			}
		}
		break;
	case EXPLOSION_HELI:
		explosion.m_fRadius = 6.0f;
		explosion.m_fPower = 300.0f;
		explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750;
		explosion.m_fPropagationRate = 0.5f;
		explosion.m_fStartTime = CTimer::GetTimeInMilliseconds();
		for (int i = 0; i < 10; i++) {
			CVector randpos;
			uint8 x, y, z;

			x = CGeneral::GetRandomNumber();
			y = CGeneral::GetRandomNumber();
			z = CGeneral::GetRandomNumber();
			randpos = pos + CVector(x - 128, y - 128, z - 128) / 20.0f;

			CParticle::AddParticle(PARTICLE_EXPLOSION_MFAST, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 2.5f, color);

			x = CGeneral::GetRandomNumber();
			y = CGeneral::GetRandomNumber();
			z = CGeneral::GetRandomNumber();
			randpos = pos + CVector(x - 128, y - 128, z - 128) / 20.0f;

			CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 5.0f, color);

			x = CGeneral::GetRandomNumber();
			y = CGeneral::GetRandomNumber();
			z = CGeneral::GetRandomNumber();
			randpos = pos + CVector(x - 128, y - 128, z - 128) / 20.0f;

			CParticle::AddJetExplosion(randpos, 1.4f, 3.0f);
		}
		CEventList::RegisterEvent(EVENT_EXPLOSION, pos, 1000);
		break;
	case EXPLOSION_MINE:
		explosion.m_fRadius = 10.0f;
		explosion.m_fPower = 150.0f;
		explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750;
		explosion.m_fPropagationRate = 0.5f;
		posGround = pos;
		//posGround.z =
			CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused
		CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250);
		break;
	case EXPLOSION_BARREL:
		explosion.m_fRadius = 7.0f;
		explosion.m_fPower = 150.0f;
		explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750;
		explosion.m_fPropagationRate = 0.5f;
		for (int i = 0; i < 6; i++) {
			CVector randpos;
			uint8 x, y, z;

			x = CGeneral::GetRandomNumber();
			y = CGeneral::GetRandomNumber();
			z = CGeneral::GetRandomNumber();
			randpos = CVector(x - 128, y - 128, z - 128);

			randpos.x /= 50.0f;
			randpos.y /= 50.0f;
			randpos.z /= 25.0f;
			randpos += pos;
			CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, colorMedium);
		}
		posGround = pos;
		//posGround.z =
			CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused
		CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250);
		break;
	case EXPLOSION_TANK_GRENADE:
		explosion.m_fRadius = 10.0f;
		explosion.m_fPower = 150.0f;
		explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750;
		explosion.m_fPropagationRate = 0.5f;
		posGround = pos;
		//posGround.z =
			CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused
		CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250);
		break;
	case EXPLOSION_HELI_BOMB:
		explosion.m_fRadius = 8.0f;
		explosion.m_fPower = 50.0f;
		explosion.m_fStopTime = lifetime + CTimer::GetTimeInMilliseconds() + 750;
		explosion.m_fPropagationRate = 0.5f;
		posGround = pos;
		//posGround.z =
			CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 4.0f, nil); // BUG? result is unused
		CEventList::RegisterEvent(EVENT_EXPLOSION, posGround, 250);
		break;
	}
	if (bDontExplode) {
		explosion.m_nIteration = 0;
		return false;
	}

	if (explosion.m_fPower != 0.0f && explosion.m_nParticlesExpireTime == 0)
		CWorld::TriggerExplosion(pos, explosion.m_fRadius, explosion.m_fPower, culprit, (type == EXPLOSION_ROCKET || type == EXPLOSION_CAR_QUICK || type == EXPLOSION_MINE || type == EXPLOSION_BARREL || type == EXPLOSION_TANK_GRENADE || type == EXPLOSION_HELI_BOMB));

	TheCamera.CamShake(0.6f, pos.x, pos.y, pos.z);
	CPad::GetPad(0)->StartShake_Distance(300, 128, pos.x, pos.y, pos.z);
	return true;
}

void
CExplosion::Update()
{
	RwRGBA color = colUpdate;
	for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) {
		CExplosion &explosion = gaExplosion[i];
		if (explosion.m_nIteration == 0) continue;

		if (explosion.m_nParticlesExpireTime != 0) {
			if (CTimer::GetTimeInMilliseconds() > explosion.m_nParticlesExpireTime) {
				explosion.m_nParticlesExpireTime = 0;
				if (explosion.m_fPower != 0.0f)
					CWorld::TriggerExplosion(explosion.m_vecPosition, explosion.m_fRadius, explosion.m_fPower, explosion.m_pCreatorEntity, (explosion.m_ExplosionType == EXPLOSION_ROCKET || explosion.m_ExplosionType == EXPLOSION_CAR_QUICK || explosion.m_ExplosionType == EXPLOSION_MINE || explosion.m_ExplosionType == EXPLOSION_BARREL || explosion.m_ExplosionType == EXPLOSION_TANK_GRENADE || explosion.m_ExplosionType == EXPLOSION_HELI_BOMB));
			}
		} else {
			explosion.m_fRadius += explosion.m_fPropagationRate * CTimer::GetTimeStep();
			int32 someTime = explosion.m_fStopTime - CTimer::GetTimeInMilliseconds();
			switch (explosion.m_ExplosionType)
			{
			case EXPLOSION_GRENADE:
			case EXPLOSION_ROCKET:
			case EXPLOSION_HELI:
			case EXPLOSION_MINE:
			case EXPLOSION_BARREL:
				if (CTimer::GetFrameCounter() & 1) {
					CPointLights::AddLight(CPointLights::LIGHT_POINT, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), 20.0f, 1.0f, 1.0f, 0.5f, CPointLights::FOG_NONE, true);
					CCoronas::RegisterCorona((uintptr)&explosion, 255, 255, 200, 255, explosion.m_vecPosition, 8.0f, 120.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
				} else
					CCoronas::RegisterCorona((uintptr)&explosion, 128, 128, 100, 255, explosion.m_vecPosition, 8.0f, 120.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
				CCoronas::RegisterCorona((uintptr)&explosion + 1, 30, 30, 25, 255, explosion.m_vecPosition, explosion.m_fRadius, 120.0f, gpCoronaTexture[7], CCoronas::TYPE_STAR, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
				break;
			case EXPLOSION_MOLOTOV:
				CWorld::SetPedsOnFire(explosion.m_vecPosition.x, explosion.m_vecPosition.y, explosion.m_vecPosition.z, 6.0f, explosion.m_pCreatorEntity);
				CWorld::SetCarsOnFire(explosion.m_vecPosition.x, explosion.m_vecPosition.y, explosion.m_vecPosition.z, 6.0f, explosion.m_pCreatorEntity);
				if (explosion.m_nIteration < 10) {
					if (explosion.m_nIteration == 1) {
						CVector point1 = explosion.m_vecPosition;
						point1.z += 5.0f;
						CColPoint colPoint;
						CEntity *pEntity;
						CWorld::ProcessVerticalLine(point1, -1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil);
						explosion.m_fZshift = colPoint.point.z;
					}
					float ff = ((float)explosion.m_nIteration * 0.55f);
					for (int i = 0; i < 5 * ff; i++) {
						float angle = CGeneral::GetRandomNumber() / 256.0f * 6.28f;

						CVector pos = explosion.m_vecPosition;
						pos.x += ff * Sin(angle);
						pos.y += ff * Cos(angle);
						pos.z += 5.0f; // what is the point of this?

						pos.z = explosion.m_fZshift + 0.5f;
						CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, color, CGeneral::GetRandomNumberInRange(-3.0f, 3.0f), CGeneral::GetRandomNumberInRange(-180.0f, 180.0f));
					}
				}
				break;
			case EXPLOSION_CAR:
			case EXPLOSION_CAR_QUICK:
				if (someTime >= 3500) {
					if (explosion.m_pVictimEntity != nil && !explosion.m_bIsBoat) {
						if ((CGeneral::GetRandomNumber() & 0xF) == 0) {
							CVehicle *veh = (CVehicle*)explosion.m_pVictimEntity;
							uint8 component = CAR_WING_LR;

							// miami leftover
							if (veh->IsBike())
								component = BIKE_FORKS_REAR;

							if (veh->IsComponentPresent(component)) {
								CVector componentPos;
								veh->GetComponentWorldPosition(component, componentPos);
								CParticle::AddJetExplosion(componentPos, 1.5f, 0.0f);
							}
						}
						if (CTimer::GetTimeInMilliseconds() > explosion.m_fStartTime) {
							explosion.m_fStartTime = CTimer::GetTimeInMilliseconds() + 125 + (CGeneral::GetRandomNumber() & 0x7F);
							CVector pos = explosion.m_pVictimEntity->GetPosition();
							for (int i = 0; i < (CGeneral::GetRandomNumber() & 1) + 1; i++) {
								CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, CVector(0.0f, 0.0f, 0.0f), nil, 3.5f, color);
								CParticle::AddParticle(PARTICLE_EXPLOSION_LARGE, pos, CVector(0.0f, 0.0f, 0.0f), nil, 5.5f, color);
							}
						}
					}
					if (CTimer::GetFrameCounter() & 1) {
						CPointLights::AddLight(CPointLights::LIGHT_POINT, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), 15.0f, 1.0f, 0.0f, 0.0f, CPointLights::FOG_NONE, true);
						CCoronas::RegisterCorona((uintptr)&explosion, 200, 100, 0, 255, explosion.m_vecPosition, 6.0f, 80.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
					} else
						CCoronas::RegisterCorona((uintptr)&explosion, 128, 0, 0, 255, explosion.m_vecPosition, 8.0f, 80.0f, gpCoronaTexture[0], CCoronas::TYPE_NORMAL, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);

					CCoronas::RegisterCorona((uintptr)&explosion + 1, 30, 15, 0, 255, explosion.m_vecPosition, explosion.m_fRadius, 80.0f, gpCoronaTexture[7], CCoronas::TYPE_STAR, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f);
				} else if (explosion.m_nIteration & 1) {
					if (explosion.m_pVictimEntity != nil)
						CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, explosion.m_pVictimEntity->GetPosition(), CVector(0.0f, 0.0f, 0.0f), nil, CGeneral::GetRandomNumberInRange(0.5f, 0.8f), color);
					CVector pos = explosion.m_vecPosition;
					pos.z += 1.0f;
					CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, pos, CVector(0.0f, 0.0f, 0.11f), nil, CGeneral::GetRandomNumberInRange(0.5f, 2.0f), color);
				}
				break;
			case EXPLOSION_TANK_GRENADE:
			case EXPLOSION_HELI_BOMB:
				if (explosion.m_nIteration < 5) {
					float ff = ((float)explosion.m_nIteration * 0.65f);
					for (int i = 0; i < 10 * ff; i++) {
						uint8 x = CGeneral::GetRandomNumber(), y = CGeneral::GetRandomNumber(), z = CGeneral::GetRandomNumber();
						CVector pos(x - 128, y - 128, (z % 128) + 1);

						pos.Normalise();
						pos *= ff / 5.0f;
						pos += explosion.m_vecPosition;
						pos.z += 0.5f;
						CParticle::AddParticle(PARTICLE_EXPLOSION_LARGE, pos, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, color, CGeneral::GetRandomNumberInRange(-3.0f, 3.0f), CGeneral::GetRandomNumberInRange(-180.0f, 180.0f));
					}
				}
				break;
			}
			if (someTime > 0)
				explosion.m_nIteration++;
			else
				explosion.m_nIteration = 0;
		}
	}
}

bool
CExplosion::TestForExplosionInArea(eExplosionType type, float x1, float x2, float y1, float y2, float z1, float z2)
{
	for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) {
		if (gaExplosion[i].m_nIteration != 0) {
			if (type == gaExplosion[i].m_ExplosionType) {
				if (gaExplosion[i].m_vecPosition.x >= x1 && gaExplosion[i].m_vecPosition.x <= x2) {
					if (gaExplosion[i].m_vecPosition.y >= y1 && gaExplosion[i].m_vecPosition.y <= y2) {
						if (gaExplosion[i].m_vecPosition.z >= z1 && gaExplosion[i].m_vecPosition.z <= z2)
							return true;
					}
				}
			}
		}
	}
	return false;
}

void
CExplosion::RemoveAllExplosionsInArea(CVector pos, float radius)
{
	for (int i = 0; i < ARRAY_SIZE(gaExplosion); i++) {
		if (gaExplosion[i].m_nIteration != 0) {
			if ((pos - gaExplosion[i].m_vecPosition).MagnitudeSqr() < SQR(radius))
				gaExplosion[i].m_nIteration = 0;
		}
	}
}


code:
#pragma once

class CEntity;
class CVector;

enum eExplosionType
{
	EXPLOSION_GRENADE,
	EXPLOSION_MOLOTOV,
	EXPLOSION_ROCKET,
	EXPLOSION_CAR,
	EXPLOSION_CAR_QUICK,
	EXPLOSION_HELI,
	EXPLOSION_MINE,
	EXPLOSION_BARREL,
	EXPLOSION_TANK_GRENADE,
	EXPLOSION_HELI_BOMB
};

class CExplosion
{
	eExplosionType m_ExplosionType;
	CVector m_vecPosition;
	float m_fRadius;
	float m_fPropagationRate;
	CEntity *m_pCreatorEntity;
	CEntity *m_pVictimEntity;
	float m_fStopTime;
	uint8 m_nIteration;
	uint8 m_nActiveCounter;
	float m_fStartTime;
	uint32 m_nParticlesExpireTime;
	float m_fPower;
	bool m_bIsBoat;
	float m_fZshift;
public:
	static void Initialise();
	static void Shutdown();
	static int8 GetExplosionActiveCounter(uint8 id);
	static void ResetExplosionActiveCounter(uint8 id);
	static uint8 GetExplosionType(uint8 id);
	static CVector *GetExplosionPosition(uint8 id);
	static bool AddExplosion(CEntity *explodingEntity, CEntity *culprit, eExplosionType type, const CVector &pos, uint32 lifetime);
	static void Update();
	static bool TestForExplosionInArea(eExplosionType type, float x1, float x2, float y1, float y2, float z1, float z2);
	static void RemoveAllExplosionsInArea(CVector pos, float radius);
};

extern CExplosion gaExplosion[NUM_EXPLOSIONS];

license: mit


code:
#include "common.h"

#include "Camera.h"
#include "General.h"
#include "Heli.h"
#include "ModelIndices.h"
#include "Particle.h"
#include "Ped.h"
#include "Plane.h"
#include "ProjectileInfo.h"
#include "Projectile.h"
#include "Explosion.h"
#include "Weapon.h"
#include "World.h"

#ifdef SQUEEZE_PERFORMANCE
uint32 projectileInUse;
#endif

CProjectileInfo gaProjectileInfo[NUM_PROJECTILES];
CProjectile *CProjectileInfo::ms_apProjectile[NUM_PROJECTILES];

void
CProjectileInfo::Initialise()
{
	debug("Initialising CProjectileInfo...\n");

	for (int i = 0; i < ARRAY_SIZE(ms_apProjectile); i++) {
		ms_apProjectile[i] = nil;
		gaProjectileInfo[i].m_eWeaponType = WEAPONTYPE_GRENADE;
		gaProjectileInfo[i].m_pSource = nil;
		gaProjectileInfo[i].m_nExplosionTime = 0;
		gaProjectileInfo[i].m_bInUse = false;
	}

	debug("CProjectileInfo ready\n");

#ifdef SQUEEZE_PERFORMANCE
	projectileInUse = 0;
#endif
}

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

CProjectileInfo*
CProjectileInfo::GetProjectileInfo(int32 id)
{
	return &gaProjectileInfo[id];
}

bool
CProjectileInfo::AddProjectile(CEntity *entity, eWeaponType weapon, CVector pos, float speed)
{
	int8 SpecialCollisionResponseCase = COLLRESPONSE_NONE;
	bool gravity = true;
	CMatrix matrix;
	float elasticity = 0.75f;
	CPed* ped = (CPed*)entity;
	int time;
	CVector velocity;

	switch (weapon)
	{
	case WEAPONTYPE_ROCKETLAUNCHER:
	{
		float vy = 1.25f;
		time = CTimer::GetTimeInMilliseconds() + 1400;
		if (ped->IsPlayer()) {
			matrix.GetForward() = TheCamera.Cams[TheCamera.ActiveCam].Front;
			matrix.GetUp() = TheCamera.Cams[TheCamera.ActiveCam].Up;
			matrix.GetRight() = CrossProduct(TheCamera.Cams[TheCamera.ActiveCam].Up, TheCamera.Cams[TheCamera.ActiveCam].Front);
			matrix.GetPosition() = pos;
		} else if (ped->m_pSeekTarget != nil) {
				float ry = CGeneral::GetRadianAngleBetweenPoints(1.0f, ped->m_pSeekTarget->GetPosition().z, 1.0f, pos.z);
				float rz = Atan2(-ped->GetForward().x, ped->GetForward().y);
				vy = 0.35f * speed + 0.15f;
				matrix.SetTranslate(0.0f, 1.0f, 1.0f);
				matrix.Rotate(0.0f, ry, rz);
				matrix.GetPosition() += pos;
		} else {
			matrix = ped->GetMatrix();
		}
		velocity = Multiply3x3(matrix, CVector(0.0f, vy, 0.0f));
		gravity = false;
		break;
	}
	case WEAPONTYPE_FLAMETHROWER:
		Error("Undefined projectile type, AddProjectile, ProjectileInfo.cpp");
		break;
	case WEAPONTYPE_MOLOTOV:
	{
		time = CTimer::GetTimeInMilliseconds() + 2000;
		float scale = 0.22f * speed + 0.15f;
		if (scale < 0.2f)
			scale = 0.2f;
		float angle = Atan2(-ped->GetForward().x, ped->GetForward().y);
		matrix.SetTranslate(0.0f, 0.0f, 0.0f);
		matrix.RotateZ(angle);
		matrix.GetPosition() += pos;
		velocity.x = -1.0f * scale * Sin(angle);
		velocity.y = scale * Cos(angle);
		velocity.z = (0.2f * speed + 0.4f) * scale;
		break;
	}
	case WEAPONTYPE_GRENADE:
	{
		time = CTimer::GetTimeInMilliseconds() + 2000;
		float scale = 0.0f;
		if (speed != 0.0f)
			scale = 0.22f * speed + 0.15f;
		float angle = Atan2(-ped->GetForward().x, ped->GetForward().y);
		matrix.SetTranslate(0.0f, 0.0f, 0.0f);
		matrix.RotateZ(angle);
		matrix.GetPosition() += pos;
		SpecialCollisionResponseCase = COLLRESPONSE_UNKNOWN5;
		velocity.x = -1.0f * scale * Sin(angle);
		velocity.y = scale * Cos(angle);
		velocity.z = (0.4f * speed + 0.4f) * scale;
		elasticity = 0.5f;
		break;
	}
	default: break;
	}

	int i = 0;
#ifdef FIX_BUGS
	while (i < ARRAY_SIZE(gaProjectileInfo) && gaProjectileInfo[i].m_bInUse) i++;
#else
	// array overrun is UB
	while (gaProjectileInfo[i].m_bInUse && i < ARRAY_SIZE(gaProjectileInfo)) i++;
#endif
	if (i == ARRAY_SIZE(gaProjectileInfo))
		return false;

	switch (weapon)
	{
	case WEAPONTYPE_ROCKETLAUNCHER:
		ms_apProjectile[i] = new CProjectile(MI_MISSILE);
		break;
	case WEAPONTYPE_FLAMETHROWER:
		break;
	case WEAPONTYPE_MOLOTOV:
		ms_apProjectile[i] = new CProjectile(MI_MOLOTOV);
		break;
	case WEAPONTYPE_GRENADE:
		ms_apProjectile[i] = new CProjectile(MI_GRENADE);
		break;
	default: break;
	}

	if (ms_apProjectile[i] == nil)
		return false;

	gaProjectileInfo[i].m_eWeaponType = weapon;
	gaProjectileInfo[i].m_pSource = ped;
	ms_apProjectile[i]->GetMatrix() = matrix;
	ms_apProjectile[i]->SetMoveSpeed(velocity);
	ms_apProjectile[i]->bAffectedByGravity = gravity;

	gaProjectileInfo[i].m_nExplosionTime = time;
	ms_apProjectile[i]->m_fElasticity = elasticity;
	ms_apProjectile[i]->m_nSpecialCollisionResponseCases = SpecialCollisionResponseCase;

#ifdef SQUEEZE_PERFORMANCE
	projectileInUse++;
#endif

	gaProjectileInfo[i].m_bInUse = true;
	CWorld::Add(ms_apProjectile[i]);

	gaProjectileInfo[i].m_vecPos = ms_apProjectile[i]->GetPosition();
	return true;
}

void
CProjectileInfo::RemoveProjectile(CProjectileInfo *info, CProjectile *projectile)
{
	RemoveNotAdd(info->m_pSource, info->m_eWeaponType, projectile->GetPosition());
#ifdef SQUEEZE_PERFORMANCE
	projectileInUse--;
#endif

	info->m_bInUse = false;
	CWorld::Remove(projectile);
	delete projectile;
}

void
CProjectileInfo::RemoveNotAdd(CEntity *entity, eWeaponType weaponType, CVector pos)
{
	switch (weaponType)
	{
	case WEAPONTYPE_GRENADE:
		CExplosion::AddExplosion(nil, entity, EXPLOSION_GRENADE, pos, 0);
		break;
	case WEAPONTYPE_MOLOTOV:
		CExplosion::AddExplosion(nil, entity, EXPLOSION_MOLOTOV, pos, 0);
		break;
	case WEAPONTYPE_ROCKETLAUNCHER:
		CExplosion::AddExplosion(nil, entity, EXPLOSION_ROCKET, pos, 0);
		break;
	default: break;
	}
}

void
CProjectileInfo::Update()
{
#ifdef SQUEEZE_PERFORMANCE
	if (projectileInUse == 0)
		return;
#endif

	for (int i = 0; i < ARRAY_SIZE(gaProjectileInfo); i++) {
		if (!gaProjectileInfo[i].m_bInUse) continue;

		CPed *ped = (CPed*)gaProjectileInfo[i].m_pSource;
		if (ped != nil && ped->IsPed() && !ped->IsPointerValid())
			gaProjectileInfo[i].m_pSource = nil;

		if (ms_apProjectile[i] == nil) {
#ifdef SQUEEZE_PERFORMANCE
			projectileInUse--;
#endif

			gaProjectileInfo[i].m_bInUse = false;
			continue;
		}

		if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) {
			CParticle::AddParticle(PARTICLE_SMOKE, ms_apProjectile[i]->GetPosition(), CVector(0.0f, 0.0f, 0.0f));
		}

		if (CTimer::GetTimeInMilliseconds() <= gaProjectileInfo[i].m_nExplosionTime) {
			if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) {
				CVector pos = ms_apProjectile[i]->GetPosition();
				CWorld::pIgnoreEntity = ms_apProjectile[i];
				if (ms_apProjectile[i]->bHasCollided
					|| !CWorld::GetIsLineOfSightClear(gaProjectileInfo[i].m_vecPos, pos, true, true, true, true, false, false)
					|| gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER && (CHeli::TestRocketCollision(&pos) || CPlane::TestRocketCollision(&pos))) {
					RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]);
				}
				CWorld::pIgnoreEntity = nil;
			} else if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_MOLOTOV) {
				CVector pos = ms_apProjectile[i]->GetPosition();
				CWorld::pIgnoreEntity = ms_apProjectile[i];

				if (gaProjectileInfo[i].m_pSource == nil
					|| ((gaProjectileInfo[i].m_vecPos - gaProjectileInfo[i].m_pSource->GetPosition()).MagnitudeSqr() >= 2.0f))
				{
					if (ms_apProjectile[i]->bHasCollided
						|| !CWorld::GetIsLineOfSightClear(gaProjectileInfo[i].m_vecPos, pos, true, true, true, true, false, false)
						|| gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER && (CHeli::TestRocketCollision(&pos) || CPlane::TestRocketCollision(&pos))) {
						RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]);
					}
				}
				CWorld::pIgnoreEntity = nil;
			}
		} else {
			RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]);
		}

		gaProjectileInfo[i].m_vecPos = ms_apProjectile[i]->GetPosition();
	}
}

bool
CProjectileInfo::IsProjectileInRange(float x1, float x2, float y1, float y2, float z1, float z2, bool remove)
{
	bool result = false;
	for (int i = 0; i < ARRAY_SIZE(ms_apProjectile); i++) {
		if (gaProjectileInfo[i].m_bInUse) {
			if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_MOLOTOV || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_GRENADE) {
				const CVector &pos = ms_apProjectile[i]->GetPosition();
				if (pos.x >= x1 && pos.x <= x2 && pos.y >= y1 && pos.y <= y2 && pos.z >= z1 && pos.z <= z2) {
					result = true;
					if (remove) {
#ifdef SQUEEZE_PERFORMANCE
						projectileInUse--;
#endif

						gaProjectileInfo[i].m_bInUse = false;
						CWorld::Remove(ms_apProjectile[i]);
						delete ms_apProjectile[i];
					}
				}
			}
		}
	}
	return result;
}

void
CProjectileInfo::RemoveAllProjectiles()
{
#ifdef SQUEEZE_PERFORMANCE
	if (projectileInUse == 0)
		return;
#endif

	for (int i = 0; i < ARRAY_SIZE(ms_apProjectile); i++) {
		if (gaProjectileInfo[i].m_bInUse) {
#ifdef SQUEEZE_PERFORMANCE
			projectileInUse--;
#endif

			gaProjectileInfo[i].m_bInUse = false;
			CWorld::Remove(ms_apProjectile[i]);
			delete ms_apProjectile[i];
		}
	}
}

bool
CProjectileInfo::RemoveIfThisIsAProjectile(CObject *object)
{
#ifdef SQUEEZE_PERFORMANCE
	if (projectileInUse == 0)
		return false;
#endif

	int i = 0;
	while (ms_apProjectile[i++] != object) {
		if (i >= ARRAY_SIZE(ms_apProjectile))
			return false;
	}

#ifdef SQUEEZE_PERFORMANCE
	projectileInUse--;
#endif

	gaProjectileInfo[i].m_bInUse = false;
	CWorld::Remove(ms_apProjectile[i]);
	delete ms_apProjectile[i];
	ms_apProjectile[i] = nil;
	return true;
}


code:
#pragma once

#include "WeaponType.h"

class CEntity;
class CObject;
class CProjectile;

class CProjectileInfo
{
public:
	eWeaponType m_eWeaponType;
	CEntity *m_pSource;
	uint32 m_nExplosionTime;
	bool m_bInUse;
	CVector m_vecPos;

public:
	static CProjectileInfo *GetProjectileInfo(int32 id);
	static CProjectile *ms_apProjectile[NUM_PROJECTILES];

	static void Initialise();
	static void Shutdown();
	static bool AddProjectile(CEntity *ped, eWeaponType weapon, CVector pos, float speed);
	static void RemoveProjectile(CProjectileInfo *info, CProjectile *projectile);
	static void RemoveNotAdd(CEntity *entity, eWeaponType weaponType, CVector pos);
	static bool RemoveIfThisIsAProjectile(CObject *pObject);
	static void RemoveAllProjectiles();
	static void Update();
	static bool IsProjectileInRange(float x1, float x2, float y1, float y2, float z1, float z2, bool remove);
};

extern CProjectileInfo gaProjectileInfo[NUM_PROJECTILES];


code:
#include "common.h"

#include "ShotInfo.h"
#include "Entity.h"
#include "Weapon.h"
#include "World.h"
#include "WeaponInfo.h"
#include "General.h"
#include "Timer.h"
#include "Ped.h"
#include "Fire.h"

CShotInfo gaShotInfo[NUMSHOTINFOS];
float CShotInfo::ms_afRandTable[20];

#ifdef SQUEEZE_PERFORMANCE
uint32 shotInfoInUse;
#endif

/*
	Used for flamethrower. I don't know why it's name is CShotInfo.
	Has no relation with any visual, just calculates the area fire affects
	(including spreading and slowing of fire) and make entities burn/flee.
*/

void
CShotInfo::Initialise()
{
	debug("Initialising CShotInfo...\n");
	for(int i=0; i<ARRAY_SIZE(gaShotInfo); i++) {
		gaShotInfo[i].m_inUse = false;
		gaShotInfo[i].m_weapon = WEAPONTYPE_COLT45;
		gaShotInfo[i].m_startPos = CVector(0.0f, 0.0f, 0.0f);
		gaShotInfo[i].m_areaAffected = CVector(0.0f, 0.0f, 0.0f);
		gaShotInfo[i].m_radius = 1.0f;
		gaShotInfo[i].m_sourceEntity = nil;
		gaShotInfo[i].m_timeout = 0;
	}

	// Not random for sure
	float nextVal = -0.05f;
	for (int i = 0; i < ARRAY_SIZE(ms_afRandTable); i++) {
		ms_afRandTable[i] = nextVal;
		nextVal += 0.005f;
	}
	debug("CShotInfo ready\n");
#ifdef SQUEEZE_PERFORMANCE
	shotInfoInUse = 0;
#endif
}

bool
CShotInfo::AddShot(CEntity *sourceEntity, eWeaponType weapon, CVector startPos, CVector endPos)
{
	CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(weapon);

	int slot;
	for (slot = 0; slot < ARRAY_SIZE(gaShotInfo) && gaShotInfo[slot].m_inUse; slot++);

	if (slot == ARRAY_SIZE(gaShotInfo))
		return false;

#ifdef SQUEEZE_PERFORMANCE
	shotInfoInUse++;
#endif

	gaShotInfo[slot].m_inUse = true;
	gaShotInfo[slot].m_weapon = weapon;
	gaShotInfo[slot].m_startPos = startPos;
	gaShotInfo[slot].m_areaAffected = endPos - startPos;
	gaShotInfo[slot].m_radius = weaponInfo->m_fRadius;

	if (weaponInfo->m_fSpread != 0.0f) {
		gaShotInfo[slot].m_areaAffected.x += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] * weaponInfo->m_fSpread;
		gaShotInfo[slot].m_areaAffected.y += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] * weaponInfo->m_fSpread;
		gaShotInfo[slot].m_areaAffected.z += CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)];
	}
	gaShotInfo[slot].m_areaAffected.Normalise();
	if (weaponInfo->IsFlagSet(WEAPONFLAG_RAND_SPEED))
		gaShotInfo[slot].m_areaAffected *= CShotInfo::ms_afRandTable[CGeneral::GetRandomNumber() % ARRAY_SIZE(ms_afRandTable)] + weaponInfo->m_fSpeed;
	else
		gaShotInfo[slot].m_areaAffected *= weaponInfo->m_fSpeed;

	gaShotInfo[slot].m_sourceEntity = sourceEntity;
	gaShotInfo[slot].m_timeout = CTimer::GetTimeInMilliseconds() + weaponInfo->m_fLifespan;

	return true;
}

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

void
CShotInfo::Update()
{
#ifdef SQUEEZE_PERFORMANCE
	if (shotInfoInUse == 0)
		return;
#endif
	for (int slot = 0; slot < ARRAY_SIZE(gaShotInfo); slot++) {
		CShotInfo &shot = gaShotInfo[slot];
		if (shot.m_sourceEntity && shot.m_sourceEntity->IsPed() && !((CPed*)shot.m_sourceEntity)->IsPointerValid())
			shot.m_sourceEntity = nil;

		if (!shot.m_inUse)
			continue;

		CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(shot.m_weapon);
		if (CTimer::GetTimeInMilliseconds() > shot.m_timeout) {
#ifdef SQUEEZE_PERFORMANCE
			shotInfoInUse--;
#endif
			shot.m_inUse = false;
		}

		if (weaponInfo->IsFlagSet(WEAPONFLAG_SLOWS_DOWN))
			shot.m_areaAffected *= pow(0.96, CTimer::GetTimeStep()); // FRAMERATE

		if (weaponInfo->IsFlagSet(WEAPONFLAG_EXPANDS))
			shot.m_radius += 0.075f * CTimer::GetTimeStep();

		shot.m_startPos += CTimer::GetTimeStep() * shot.m_areaAffected;
		if (shot.m_sourceEntity) {
			assert(shot.m_sourceEntity->IsPed());
			CPed *ped = (CPed*) shot.m_sourceEntity;
			float radius = Max(1.0f, shot.m_radius);

			for (int i = 0; i < ped->m_numNearPeds; ++i) {
				CPed *nearPed = ped->m_nearPeds[i];
				if (nearPed->IsPointerValid()) {
					if (nearPed->IsPedInControl() && (nearPed->GetPosition() - shot.m_startPos).MagnitudeSqr() < radius && !nearPed->bFireProof) {

						if (!nearPed->IsPlayer()) {
							nearPed->SetFindPathAndFlee(shot.m_sourceEntity, 10000);
							nearPed->SetMoveState(PEDMOVE_SPRINT);
						}
						gFireManager.StartFire(nearPed, shot.m_sourceEntity, 0.8f, true);
					}
				}
			}
		}
		if (!((CTimer::GetFrameCounter() + slot) & 3))
			CWorld::SetCarsOnFire(shot.m_startPos.x, shot.m_startPos.y, shot.m_startPos.z, 4.0f, shot.m_sourceEntity);
	}
}


code:
#pragma once

#include "WeaponType.h"

class CEntity;

class CShotInfo
{
public:
	eWeaponType m_weapon;
	CVector m_startPos;
	CVector m_areaAffected;
	float m_radius;
	CEntity *m_sourceEntity;
	float m_timeout;
	bool m_inUse;

	static float ms_afRandTable[20];

	static void Initialise(void);
	static bool AddShot(CEntity*, eWeaponType, CVector, CVector);
	static void Shutdown(void);
	static void Update(void);
};