code:
#include "common.h"

#include "main.h"
#include "General.h"
#include "Timer.h"
#include "TxdStore.h"
#include "Entity.h"
#include "Sprite.h"
#include "Camera.h"
#include "Collision.h"
#include "World.h"
#include "Shadows.h"
#include "AudioScriptObject.h"
#include "ParticleObject.h"
#include "Particle.h"
#include "soundlist.h"
#include "debugmenu.h"

#define MAX_PARTICLES_ON_SCREEN   (1000)

//(5)
#define MAX_SMOKE_FILES           ARRAY_SIZE(SmokeFiles)

//(5)
#define MAX_SMOKE2_FILES          ARRAY_SIZE(Smoke2Files)
//(5)
#define MAX_RUBBER_FILES          ARRAY_SIZE(RubberFiles)
//(5)
#define MAX_RAINSPLASH_FILES      ARRAY_SIZE(RainSplashFiles)
//(3)
#define MAX_WATERSPRAY_FILES      ARRAY_SIZE(WatersprayFiles)
//(6)
#define MAX_EXPLOSIONMEDIUM_FILES ARRAY_SIZE(ExplosionMediumFiles)
//(4)
#define MAX_GUNFLASH_FILES        ARRAY_SIZE(GunFlashFiles)
//(2)
#define MAX_RAINSPLASHUP_FILES    ARRAY_SIZE(RainSplashupFiles)
//(4)
#define MAX_BIRDFRONT_FILES       ARRAY_SIZE(BirdfrontFiles)
//(4)
#define MAX_CARDEBRIS_FILES       ARRAY_SIZE(CardebrisFiles)
//(4)
#define MAX_CARSPLASH_FILES       ARRAY_SIZE(CarsplashFiles)

//(4)
#define MAX_RAINDROP_FILES        ARRAY_SIZE(RaindropFiles)

const char SmokeFiles[][6+1] =
{
	"smoke1",
	"smoke2",
	"smoke3",
	"smoke4",
	"smoke5"
};

const char Smoke2Files[][9+1] =
{
	"smokeII_1",
	"smokeII_2",
	"smokeII_3",
	"smokeII_4",
	"smokeII_5"
};

const char RubberFiles[][7+1] =
{
	"rubber1",
	"rubber2",
	"rubber3",
	"rubber4",
	"rubber5"
};

const char RainSplashFiles[][7+1] =
{
	"splash1",
	"splash2",
	"splash3",
	"splash4",
	"splash5"
};

const char WatersprayFiles[][11+1] =
{
	"waterspray1",
	"waterspray2",
	"waterspray3"
};

const char ExplosionMediumFiles[][7+1] =
{
	"explo01",
	"explo02",
	"explo03",
	"explo04",
	"explo05",
	"explo06"
};

const char GunFlashFiles[][9+1] =
{
	"gunflash1",
	"gunflash2",
	"gunflash3",
	"gunflash4"
};

const char RaindropFiles[][9+1] =
{
	"raindrop1",
	"raindrop2",
	"raindrop3",
	"raindrop4"
};

const char RainSplashupFiles[][10+1] =
{
	"splash_up1",
	"splash_up2"
};

const char BirdfrontFiles[][8+1] =
{
	"birdf_01",
	"birdf_02",
	"birdf_03",
	"birdf_04"
};

const char CardebrisFiles[][12+1] =
{
	"cardebris_01",
	"cardebris_02",
	"cardebris_03",
	"cardebris_04"
};

const char CarsplashFiles[][12+1] =
{
	"carsplash_01",
	"carsplash_02",
	"carsplash_03",
	"carsplash_04"
};

CParticle gParticleArray[MAX_PARTICLES_ON_SCREEN];

RwTexture *gpSmokeTex[MAX_SMOKE_FILES];
RwTexture *gpSmoke2Tex[MAX_SMOKE2_FILES];
RwTexture *gpRubberTex[MAX_RUBBER_FILES];
RwTexture *gpRainSplashTex[MAX_RAINSPLASH_FILES];
RwTexture *gpWatersprayTex[MAX_WATERSPRAY_FILES];
RwTexture *gpExplosionMediumTex[MAX_EXPLOSIONMEDIUM_FILES];
RwTexture *gpGunFlashTex[MAX_GUNFLASH_FILES];
RwTexture *gpRainSplashupTex[MAX_RAINSPLASHUP_FILES];
RwTexture *gpBirdfrontTex[MAX_BIRDFRONT_FILES];
RwTexture *gpCarDebrisTex[MAX_CARDEBRIS_FILES];
RwTexture *gpCarSplashTex[MAX_CARSPLASH_FILES];

RwTexture *gpFlame1Tex;
RwTexture *gpFlame5Tex;
RwTexture *gpRainDropSmallTex;
RwTexture *gpBloodTex;
RwTexture *gpLeafTex;
RwTexture *gpCloudTex1; // unused
RwTexture *gpCloudTex4;
RwTexture *gpBloodSmallTex;
RwTexture *gpGungeTex;
RwTexture *gpCollisionSmokeTex;
RwTexture *gpBulletHitTex;
RwTexture *gpGunShellTex;
RwTexture *gpWakeOldTex;
RwTexture *gpPointlightTex;

RwRaster  *gpSmokeRaster[MAX_SMOKE_FILES];
RwRaster  *gpSmoke2Raster[MAX_SMOKE2_FILES];
RwRaster  *gpRubberRaster[MAX_RUBBER_FILES];
RwRaster  *gpRainSplashRaster[MAX_RAINSPLASH_FILES];
RwRaster  *gpWatersprayRaster[MAX_WATERSPRAY_FILES];
RwRaster  *gpExplosionMediumRaster[MAX_EXPLOSIONMEDIUM_FILES];
RwRaster  *gpGunFlashRaster[MAX_GUNFLASH_FILES];
RwRaster  *gpRainSplashupRaster[MAX_RAINSPLASHUP_FILES];
RwRaster  *gpBirdfrontRaster[MAX_BIRDFRONT_FILES];
RwRaster  *gpCarDebrisRaster[MAX_CARDEBRIS_FILES];
RwRaster  *gpCarSplashRaster[MAX_CARSPLASH_FILES];

RwRaster  *gpFlame1Raster;
RwRaster  *gpFlame5Raster;
RwRaster  *gpRainDropSmallRaster;
RwRaster  *gpBloodRaster;
RwRaster  *gpLeafRaster;
RwRaster  *gpCloudRaster1; // unused
RwRaster  *gpCloudRaster4;
RwRaster  *gpBloodSmallRaster;
RwRaster  *gpGungeRaster;
RwRaster  *gpCollisionSmokeRaster;
RwRaster  *gpBulletHitRaster;
RwRaster  *gpGunShellRaster;
RwRaster  *gpWakeOldRaster;

RwRaster  *gpPointlightRaster;	// CPointLights::RenderFogEffect

RwTexture *gpRainDropTex[MAX_RAINDROP_FILES]; // CWeather::RenderRainStreaks

RwRaster  *gpRainDropRaster[MAX_RAINDROP_FILES];

float      CParticle::ms_afRandTable[CParticle::RAND_TABLE_SIZE];

CParticle *CParticle::m_pUnusedListHead;

float      CParticle::m_SinTable[CParticle::SIN_COS_TABLE_SIZE];
float      CParticle::m_CosTable[CParticle::SIN_COS_TABLE_SIZE];

int32 Randomizer;

int32 nParticleCreationInterval = 1;
float fParticleScaleLimit       = 0.5f;

#ifdef DEBUGMENU
SETTWEAKPATH("Particle");
TWEAKINT32(nParticleCreationInterval, 0, 5, 1);
TWEAKFLOAT(fParticleScaleLimit, 0.0f, 1.0f, 0.1f);
TWEAKFUNC(CParticle::ReloadConfig);
#endif

void CParticle::ReloadConfig()
{
	debug("Initialising CParticleMgr...");

	mod_ParticleSystemManager.Initialise();

	debug("Initialising CParticle...");

	m_pUnusedListHead = gParticleArray;

	for ( int32 i = 0; i < MAX_PARTICLES_ON_SCREEN; i++ )
	{
		if ( i == MAX_PARTICLES_ON_SCREEN - 1 )
			gParticleArray[i].m_pNext = nil;
		else
			gParticleArray[i].m_pNext = &gParticleArray[i + 1];

		gParticleArray[i].m_vecPosition = CVector(0.0f, 0.0f, 0.0f);

		gParticleArray[i].m_vecVelocity = CVector(0.0f, 0.0f, 0.0f);

		gParticleArray[i].m_nTimeWhenWillBeDestroyed = 0;

		gParticleArray[i].m_nTimeWhenColorWillBeChanged = 0;

		gParticleArray[i].m_fSize = 0.2f;

		gParticleArray[i].m_fExpansionRate = 0.0f;

		gParticleArray[i].m_nColorIntensity = 255;

		gParticleArray[i].m_nFadeToBlackTimer = 0;

		gParticleArray[i].m_nAlpha = 255;

		gParticleArray[i].m_nFadeAlphaTimer = 0;

		gParticleArray[i].m_nCurrentZRotation = 0;

		gParticleArray[i].m_nZRotationTimer = 0;

		gParticleArray[i].m_fCurrentZRadius = 0.0f;

		gParticleArray[i].m_nZRadiusTimer = 0;

		gParticleArray[i].m_nCurrentFrame = 0;

		gParticleArray[i].m_nAnimationSpeedTimer = 0;

		gParticleArray[i].m_nRotation = 0;

		gParticleArray[i].m_nRotationStep = 0;
	}
}

void CParticle::Initialise()
{
	ReloadConfig();

	CParticleObject::Initialise();

	float randVal = -1.0f;
	for ( int32 i = 0; i < RAND_TABLE_SIZE; i++ )
	{
		ms_afRandTable[i] = randVal;
		randVal += 0.1f;
	}

	for ( int32 i = 0; i < SIN_COS_TABLE_SIZE; i++ )
	{
		float angle = DEGTORAD(float(i) * float(360.0f / SIN_COS_TABLE_SIZE));

		m_SinTable[i] = ::Sin(angle);
		m_CosTable[i] = ::Cos(angle);
	}

	int32 slot = CTxdStore::FindTxdSlot("particle");

	CTxdStore::PushCurrentTxd();
	CTxdStore::SetCurrentTxd(slot);

	for ( int32 i = 0; i < MAX_SMOKE_FILES; i++ )
	{
		gpSmokeTex[i] = RwTextureRead(SmokeFiles[i], nil);
		gpSmokeRaster[i] = RwTextureGetRaster(gpSmokeTex[i]);
	}

	for ( int32 i = 0; i < MAX_SMOKE2_FILES; i++ )
	{
		gpSmoke2Tex[i] = RwTextureRead(Smoke2Files[i], nil);
		gpSmoke2Raster[i] = RwTextureGetRaster(gpSmoke2Tex[i]);
	}

	for ( int32 i = 0; i < MAX_RUBBER_FILES; i++ )
	{
		gpRubberTex[i] = RwTextureRead(RubberFiles[i], nil);
		gpRubberRaster[i] = RwTextureGetRaster(gpRubberTex[i]);
	}

	for ( int32 i = 0; i < MAX_RAINSPLASH_FILES; i++ )
	{
		gpRainSplashTex[i] = RwTextureRead(RainSplashFiles[i], nil);
		gpRainSplashRaster[i] = RwTextureGetRaster(gpRainSplashTex[i]);
	}

	for ( int32 i = 0; i < MAX_WATERSPRAY_FILES; i++ )
	{
		gpWatersprayTex[i] = RwTextureRead(WatersprayFiles[i], nil);
		gpWatersprayRaster[i] = RwTextureGetRaster(gpWatersprayTex[i]);
	}

	for ( int32 i = 0; i < MAX_EXPLOSIONMEDIUM_FILES; i++ )
	{
		gpExplosionMediumTex[i] = RwTextureRead(ExplosionMediumFiles[i], nil);
		gpExplosionMediumRaster[i] = RwTextureGetRaster(gpExplosionMediumTex[i]);
	}

	for ( int32 i = 0; i < MAX_GUNFLASH_FILES; i++ )
	{
		gpGunFlashTex[i] = RwTextureRead(GunFlashFiles[i], NULL);
		gpGunFlashRaster[i] = RwTextureGetRaster(gpGunFlashTex[i]);
	}

	for ( int32 i = 0; i < MAX_RAINDROP_FILES; i++ )
	{
		gpRainDropTex[i] = RwTextureRead(RaindropFiles[i], nil);
		gpRainDropRaster[i] = RwTextureGetRaster(gpRainDropTex[i]);
	}

	for ( int32 i = 0; i < MAX_RAINSPLASHUP_FILES; i++ )
	{
		gpRainSplashupTex[i] = RwTextureRead(RainSplashupFiles[i], nil);
		gpRainSplashupRaster[i] = RwTextureGetRaster(gpRainSplashupTex[i]);
	}

	for ( int32 i = 0; i < MAX_BIRDFRONT_FILES; i++ )
	{
		gpBirdfrontTex[i] = RwTextureRead(BirdfrontFiles[i], NULL);
		gpBirdfrontRaster[i] = RwTextureGetRaster(gpBirdfrontTex[i]);
	}

	for ( int32 i = 0; i < MAX_CARDEBRIS_FILES; i++ )
	{
		gpCarDebrisTex[i] = RwTextureRead(CardebrisFiles[i], nil);
		gpCarDebrisRaster[i] = RwTextureGetRaster(gpCarDebrisTex[i]);
	}

	for ( int32 i = 0; i < MAX_CARSPLASH_FILES; i++ )
	{
		gpCarSplashTex[i] = RwTextureRead(CarsplashFiles[i], nil);
		gpCarSplashRaster[i] = RwTextureGetRaster(gpCarSplashTex[i]);
	}

	gpFlame1Tex = RwTextureRead("flame1", NULL);
	gpFlame1Raster = RwTextureGetRaster(gpFlame1Tex);

	gpFlame5Tex = RwTextureRead("flame5", nil);

//#ifdef FIX_BUGS
#if 0
	gpFlame5Raster = RwTextureGetRaster(gpFlame5Tex);
#else
	// this seems to have become more of a design choice
	gpFlame5Raster = RwTextureGetRaster(gpFlame1Tex);	// copy-paste bug ?
#endif

	gpRainDropSmallTex = RwTextureRead("rainsmall", nil);
	gpRainDropSmallRaster = RwTextureGetRaster(gpRainDropSmallTex);

	gpBloodTex = RwTextureRead("blood", nil);
	gpBloodRaster = RwTextureGetRaster(gpBloodTex);

	gpLeafTex = RwTextureRead("gameleaf01_64", nil);
	gpLeafRaster = RwTextureGetRaster(gpLeafTex);

	gpCloudTex1 = RwTextureRead("cloud3", nil);
	gpCloudRaster1 = RwTextureGetRaster(gpCloudTex1);

	gpCloudTex4 = RwTextureRead("cloudmasked", nil);
	gpCloudRaster4 = RwTextureGetRaster(gpCloudTex4);

	gpBloodSmallTex = RwTextureRead("bloodsplat2", nil);
	gpBloodSmallRaster = RwTextureGetRaster(gpBloodSmallTex);

	gpGungeTex = RwTextureRead("gunge", nil);
	gpGungeRaster = RwTextureGetRaster(gpGungeTex);

	gpCollisionSmokeTex = RwTextureRead("collisionsmoke", nil);
	gpCollisionSmokeRaster = RwTextureGetRaster(gpCollisionSmokeTex);

	gpBulletHitTex = RwTextureRead("bullethitsmoke", nil);
	gpBulletHitRaster = RwTextureGetRaster(gpBulletHitTex);

	gpGunShellTex = RwTextureRead("gunshell", nil);
	gpGunShellRaster = RwTextureGetRaster(gpGunShellTex);

	gpWakeOldTex = RwTextureRead("wake_old", nil);
	gpWakeOldRaster = RwTextureGetRaster(gpWakeOldTex);

	gpPointlightTex = RwTextureRead("pointlight", nil);
	gpPointlightRaster = RwTextureGetRaster(gpPointlightTex);

	CTxdStore::PopCurrentTxd();

	for ( int32 i = 0; i < MAX_PARTICLES; i++ )
	{
		tParticleSystemData *entry = &mod_ParticleSystemManager.m_aParticles[i];

		switch ( i )
		{
			case PARTICLE_BLOOD:
				entry->m_ppRaster = &gpBloodRaster;
				break;

			case PARTICLE_BLOOD_SMALL:
			case PARTICLE_BLOOD_SPURT:
				entry->m_ppRaster = &gpBloodSmallRaster;
				break;

			case PARTICLE_DEBRIS2:
				entry->m_ppRaster = &gpGungeRaster;
				break;

			case PARTICLE_GUNFLASH:
			case PARTICLE_GUNFLASH_NOANIM:
				entry->m_ppRaster = gpGunFlashRaster;
				break;

			case PARTICLE_GUNSMOKE:
			case PARTICLE_SPLASH:
				entry->m_ppRaster = nil;
				break;

			case PARTICLE_FLAME:
			case PARTICLE_CARFLAME:
				entry->m_ppRaster = &gpFlame1Raster;
				break;

			case PARTICLE_FIREBALL:
				entry->m_ppRaster = &gpFlame5Raster;
				break;

			case PARTICLE_RAIN_SPLASH:
			case PARTICLE_RAIN_SPLASH_BIGGROW:
				entry->m_ppRaster = gpRainSplashRaster;
				break;

			case PARTICLE_RAIN_SPLASHUP:
				entry->m_ppRaster = gpRainSplashupRaster;
				break;

			case PARTICLE_WATERSPRAY:
				entry->m_ppRaster = gpWatersprayRaster;
				break;

			case PARTICLE_SHARD:
			case PARTICLE_RAINDROP:
			case PARTICLE_RAINDROP_2D:
				entry->m_ppRaster = gpRainDropRaster;
				break;

			case PARTICLE_EXPLOSION_MEDIUM:
			case PARTICLE_EXPLOSION_LARGE:
			case PARTICLE_EXPLOSION_MFAST:
			case PARTICLE_EXPLOSION_LFAST:
				entry->m_ppRaster = gpExplosionMediumRaster;
				break;

			case PARTICLE_BOAT_WAKE:
				entry->m_ppRaster = &gpWakeOldRaster;
				break;

			case PARTICLE_CAR_SPLASH:
			case PARTICLE_WATER_HYDRANT:
			case PARTICLE_PED_SPLASH:
				entry->m_ppRaster = gpCarSplashRaster;
				break;

			case PARTICLE_SPARK:
			case PARTICLE_SPARK_SMALL:
			case PARTICLE_RAINDROP_SMALL:
			case PARTICLE_HELI_ATTACK:
				entry->m_ppRaster = &gpRainDropSmallRaster;
				break;

			case PARTICLE_DEBRIS:
			case PARTICLE_TREE_LEAVES:
				entry->m_ppRaster = &gpLeafRaster;
				break;

			case PARTICLE_CAR_DEBRIS:
			case PARTICLE_HELI_DEBRIS:
				entry->m_ppRaster = gpCarDebrisRaster;
				break;

			case PARTICLE_WHEEL_DIRT:
			case PARTICLE_STEAM2:
			case PARTICLE_STEAM_NY:
			case PARTICLE_STEAM_NY_SLOWMOTION:
			case PARTICLE_ENGINE_STEAM:
			case PARTICLE_BOAT_THRUSTJET:
			case PARTICLE_PEDFOOT_DUST:
			case PARTICLE_EXHAUST_FUMES:
				entry->m_ppRaster = gpSmoke2Raster;
				break;

			case PARTICLE_GUNSMOKE2:
			case PARTICLE_RUBBER_SMOKE:
				entry->m_ppRaster = gpRubberRaster;
				break;

			case PARTICLE_CARCOLLISION_DUST:
			case PARTICLE_BURNINGRUBBER_SMOKE:
				entry->m_ppRaster = &gpCollisionSmokeRaster;
				break;

			case PARTICLE_WHEEL_WATER:
			case PARTICLE_WATER:
			case PARTICLE_SMOKE:
			case PARTICLE_SMOKE_SLOWMOTION:
			case PARTICLE_GARAGEPAINT_SPRAY:
			case PARTICLE_STEAM:
			case PARTICLE_BOAT_SPLASH:
			case PARTICLE_WATER_CANNON:
			case PARTICLE_EXTINGUISH_STEAM:
			case PARTICLE_HELI_DUST:
			case PARTICLE_PAINT_SMOKE:
			case PARTICLE_BULLETHIT_SMOKE:
				entry->m_ppRaster = gpSmokeRaster;
				break;

			case PARTICLE_GUNSHELL_FIRST:
			case PARTICLE_GUNSHELL:
			case PARTICLE_GUNSHELL_BUMP1:
			case PARTICLE_GUNSHELL_BUMP2:
				entry->m_ppRaster = &gpGunShellRaster;
				break;

			case PARTICLE_ENGINE_SMOKE:
			case PARTICLE_ENGINE_SMOKE2:
			case PARTICLE_CARFLAME_SMOKE:
			case PARTICLE_FIREBALL_SMOKE:
			case PARTICLE_TEST:
				entry->m_ppRaster = &gpCloudRaster4;
				break;

			case PARTICLE_BIRD_FRONT:
				entry->m_ppRaster = gpBirdfrontRaster;
				break;
		}
	}

	debug("CParticle ready");
}

void
CEntity::AddSteamsFromGround(CVector *unused)
{
	int i, n;
	C2dEffect *effect;
	CVector pos;

	n = CModelInfo::GetModelInfo(GetModelIndex())->GetNum2dEffects();
	for(i = 0; i < n; i++){
		effect = CModelInfo::GetModelInfo(GetModelIndex())->Get2dEffect(i);
		if(effect->type != EFFECT_PARTICLE)
			continue;

		pos = GetMatrix() * effect->pos;
		switch(effect->particle.particleType){
		case 0:
			CParticleObject::AddObject(POBJECT_PAVEMENT_STEAM, pos, effect->particle.dir, effect->particle.scale, false);
			break;
		case 1:
			CParticleObject::AddObject(POBJECT_WALL_STEAM, pos, effect->particle.dir, effect->particle.scale, false);
			break;
		case 2:
			CParticleObject::AddObject(POBJECT_DRY_ICE, pos, effect->particle.scale, false);
			break;
		case 3:
			CParticleObject::AddObject(POBJECT_SMALL_FIRE, pos, effect->particle.dir, effect->particle.scale, false);
			break;
		case 4:
			CParticleObject::AddObject(POBJECT_DARK_SMOKE, pos, effect->particle.dir, effect->particle.scale, false);
			break;
		}
	}
}

void CParticle::Shutdown()
{
	debug("Shutting down CParticle...");

	for ( int32 i = 0; i < MAX_SMOKE_FILES; i++ )
	{
		RwTextureDestroy(gpSmokeTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpSmokeTex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_SMOKE2_FILES; i++ )
	{
		RwTextureDestroy(gpSmoke2Tex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpSmoke2Tex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_RUBBER_FILES; i++ )
	{
		RwTextureDestroy(gpRubberTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpRubberTex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_RAINSPLASH_FILES; i++ )
	{
		RwTextureDestroy(gpRainSplashTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpRainSplashTex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_WATERSPRAY_FILES; i++ )
	{
		RwTextureDestroy(gpWatersprayTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpWatersprayTex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_EXPLOSIONMEDIUM_FILES; i++ )
	{
		RwTextureDestroy(gpExplosionMediumTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpExplosionMediumTex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_GUNFLASH_FILES; i++ )
	{
		RwTextureDestroy(gpGunFlashTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpGunFlashTex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_RAINDROP_FILES; i++ )
	{
		RwTextureDestroy(gpRainDropTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpRainDropTex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_RAINSPLASHUP_FILES; i++ )
	{
		RwTextureDestroy(gpRainSplashupTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpRainSplashupTex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_BIRDFRONT_FILES; i++ )
	{
		RwTextureDestroy(gpBirdfrontTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpBirdfrontTex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_CARDEBRIS_FILES; i++ )
	{
		RwTextureDestroy(gpCarDebrisTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpCarDebrisTex[i] = nil;
#endif
	}

	for ( int32 i = 0; i < MAX_CARSPLASH_FILES; i++ )
	{
		RwTextureDestroy(gpCarSplashTex[i]);
#if GTA_VERSION >= GTA3_PC_11
		gpCarSplashTex[i] = nil;
#endif
	}

	RwTextureDestroy(gpFlame1Tex);
#if GTA_VERSION >= GTA3_PC_11
	gpFlame1Tex = nil;
#endif

	RwTextureDestroy(gpFlame5Tex);
#if GTA_VERSION >= GTA3_PC_11
	gpFlame5Tex = nil;
#endif

	RwTextureDestroy(gpRainDropSmallTex);
#if GTA_VERSION >= GTA3_PC_11
	gpRainDropSmallTex = nil;
#endif

	RwTextureDestroy(gpBloodTex);
#if GTA_VERSION >= GTA3_PC_11
	gpBloodTex = nil;
#endif

	RwTextureDestroy(gpLeafTex);
#if GTA_VERSION >= GTA3_PC_11
	gpLeafTex = nil;
#endif

	RwTextureDestroy(gpCloudTex1);
#if GTA_VERSION >= GTA3_PC_11
	gpCloudTex1 = nil;
#endif

	RwTextureDestroy(gpCloudTex4);
#if GTA_VERSION >= GTA3_PC_11
	gpCloudTex4 = nil;
#endif

	RwTextureDestroy(gpBloodSmallTex);
#if GTA_VERSION >= GTA3_PC_11
	gpBloodSmallTex = nil;
#endif

	RwTextureDestroy(gpGungeTex);
#if GTA_VERSION >= GTA3_PC_11
	gpGungeTex = nil;
#endif

	RwTextureDestroy(gpCollisionSmokeTex);
#if GTA_VERSION >= GTA3_PC_11
	gpCollisionSmokeTex = nil;
#endif

	RwTextureDestroy(gpBulletHitTex);
#if GTA_VERSION >= GTA3_PC_11
	gpBulletHitTex = nil;
#endif

	RwTextureDestroy(gpGunShellTex);
#if GTA_VERSION >= GTA3_PC_11
	gpGunShellTex = nil;
#endif

	RwTextureDestroy(gpWakeOldTex);
#if GTA_VERSION >= GTA3_PC_11
	gpWakeOldTex = nil;
#endif

	RwTextureDestroy(gpPointlightTex);
#if GTA_VERSION >= GTA3_PC_11
	gpPointlightTex = nil;
#endif

	int32 slot;

	slot = CTxdStore::FindTxdSlot("particle");
	CTxdStore::RemoveTxdSlot(slot);

	debug("CParticle shut down");
}

CParticle *CParticle::AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity, float fSize, int32 nRotationSpeed, int32 nRotation, int32 nCurFrame, int32 nLifeSpan)
{
	CRGBA color(0, 0, 0, 0);
	return AddParticle(type, vecPos, vecDir, pEntity, fSize, color, nRotationSpeed, nRotation, nCurFrame, nLifeSpan);
}

CParticle *CParticle::AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity, float fSize, RwRGBA const &color, int32 nRotationSpeed, int32 nRotation, int32 nCurFrame, int32 nLifeSpan)
{
	if ( CTimer::GetIsPaused() )
		return NULL;

#ifdef PC_PARTICLE
	if ( ( type == PARTICLE_ENGINE_SMOKE
		|| type == PARTICLE_ENGINE_SMOKE2
		|| type == PARTICLE_ENGINE_STEAM
		|| type == PARTICLE_CARFLAME_SMOKE
		|| type == PARTICLE_RUBBER_SMOKE
		|| type == PARTICLE_BURNINGRUBBER_SMOKE
		|| type == PARTICLE_EXHAUST_FUMES
		|| type == PARTICLE_CARCOLLISION_DUST )
		&& nParticleCreationInterval & CTimer::GetFrameCounter() )
	{
		return nil;
	}
#endif

	CParticle *pParticle = m_pUnusedListHead;

	if ( pParticle == nil )
		return nil;

	tParticleSystemData *psystem = &mod_ParticleSystemManager.m_aParticles[type];

	if ( psystem->m_fCreateRange != 0.0f && psystem->m_fCreateRange < ( TheCamera.GetPosition() - vecPos ).MagnitudeSqr() )
		return nil;

	pParticle->m_fSize = psystem->m_fDefaultInitialRadius;
	pParticle->m_fExpansionRate = psystem->m_fExpansionRate;

	if ( nLifeSpan != 0 )
		pParticle->m_nTimeWhenWillBeDestroyed = CTimer::GetTimeInMilliseconds() + nLifeSpan;
	else
		pParticle->m_nTimeWhenWillBeDestroyed = CTimer::GetTimeInMilliseconds() + psystem->m_nLifeSpan;

	pParticle->m_nColorIntensity = psystem->m_nFadeToBlackInitialIntensity;
	pParticle->m_nAlpha = psystem->m_nFadeAlphaInitialIntensity;
	pParticle->m_nCurrentZRotation = psystem->m_nZRotationInitialAngle;
	pParticle->m_fCurrentZRadius = psystem->m_fInitialZRadius;

	if ( nCurFrame != 0 )
		pParticle->m_nCurrentFrame = nCurFrame;
	else
		pParticle->m_nCurrentFrame = psystem->m_nStartAnimationFrame;

	pParticle->m_nFadeToBlackTimer = 0;
	pParticle->m_nFadeAlphaTimer = 0;
	pParticle->m_nZRotationTimer = 0;
	pParticle->m_nZRadiusTimer = 0;
	pParticle->m_nAnimationSpeedTimer = 0;
	pParticle->m_fZGround = 0.0f;
	pParticle->m_vecPosition = vecPos;
	pParticle->m_vecVelocity = vecDir;
	pParticle->m_vecParticleMovementOffset = CVector(0.0f, 0.0f, 0.0f);
	pParticle->m_nTimeWhenColorWillBeChanged = 0;

	if ( color.alpha != 0 )
		RwRGBAAssign(&pParticle->m_Color, &color);
	else
	{
		RwRGBAAssign(&pParticle->m_Color, &psystem->m_RenderColouring);

		if ( psystem->m_ColorFadeTime != 0 )
			pParticle->m_nTimeWhenColorWillBeChanged = CTimer::GetTimeInMilliseconds() + psystem->m_ColorFadeTime;

		if ( psystem->m_InitialColorVariation != 0 )
		{
			int32 ColorVariation = CGeneral::GetRandomNumberInRange(-psystem->m_InitialColorVariation, psystem->m_InitialColorVariation);
			//Float ColorVariation = CGeneral::GetRandomNumberInRange((float)-psystem->m_InitialColorVariation, (float)psystem->m_InitialColorVariation);

			pParticle->m_Color.red   = Clamp(pParticle->m_Color.red +
				PERCENT(pParticle->m_Color.red, ColorVariation),
				0, 255);

			pParticle->m_Color.green = Clamp(pParticle->m_Color.green +
				PERCENT(pParticle->m_Color.green, ColorVariation),
				0, 255);

			pParticle->m_Color.blue  = Clamp(pParticle->m_Color.blue +
				PERCENT(pParticle->m_Color.blue, ColorVariation),
				0, 255);
		}
	}

	pParticle->m_nRotation = nRotation;

// PC only
	if ( pParticle->m_nRotation >= 360 )
		pParticle->m_nRotation -= 360;
	else if ( pParticle->m_nRotation < 0 )
		pParticle->m_nRotation += 360;

	if ( nRotationSpeed != 0 )
		pParticle->m_nRotationStep = nRotationSpeed;
	else
		pParticle->m_nRotationStep = psystem->m_nRotationSpeed;

	if ( CGeneral::GetRandomNumber() & 1 )
		pParticle->m_nRotationStep = -pParticle->m_nRotationStep;

	pParticle->m_vecScreenPosition.x = 0.0f; // bug ?

	if ( psystem->m_fPositionRandomError != 0.0f )
	{
		pParticle->m_vecPosition.x += psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE];
		pParticle->m_vecPosition.y += psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE];

		if ( psystem->Flags & RAND_VERT_V )
			pParticle->m_vecPosition.z += psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE];
	}

	if ( psystem->m_fVelocityRandomError != 0.0f )
	{
		pParticle->m_vecVelocity.x += psystem->m_fVelocityRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE];
		pParticle->m_vecVelocity.y += psystem->m_fVelocityRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE];

		if ( psystem->Flags & RAND_VERT_V )
			pParticle->m_vecVelocity.z += psystem->m_fVelocityRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE];
	}

	if ( psystem->m_fExpansionRateError != 0.0f )
		pParticle->m_fExpansionRate += psystem->m_fExpansionRateError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE] + psystem->m_fExpansionRateError;

	if ( psystem->m_nRotationRateError != 0 )
		pParticle->m_nRotationStep += CGeneral::GetRandomNumberInRange(-psystem->m_nRotationRateError, psystem->m_nRotationRateError);

	if ( psystem->m_nLifeSpanErrorShape != 0 )
	{
		float randVal = ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE];
		if ( randVal > 0.0f )
			pParticle->m_nTimeWhenWillBeDestroyed += int32(float(psystem->m_nLifeSpan) * randVal * float(psystem->m_nLifeSpanErrorShape));
		else
			pParticle->m_nTimeWhenWillBeDestroyed += int32(float(psystem->m_nLifeSpan) * randVal / float(psystem->m_nLifeSpanErrorShape));
	}

	if ( psystem->Flags & ZCHECK_FIRST )
	{
		static bool bValidGroundFound = false;
		static CVector LastTestCoors;
		static float LastTestGroundZ;

		if ( bValidGroundFound
			&& vecPos.x == LastTestCoors.x
			&& vecPos.y == LastTestCoors.y
			&& vecPos.z == LastTestCoors.z )
		{
			pParticle->m_fZGround = LastTestGroundZ;
		}
		else
		{
			bValidGroundFound = false;

			CColPoint point;
			CEntity *entity;

			if ( !CWorld::ProcessVerticalLine(
						pParticle->m_vecPosition + CVector(0.0f, 0.0f, 0.5f),
						-100.0f, point, entity, true, true, false, false, true, false, nil) )
			{
				return nil;
			}

			if ( point.point.z >= pParticle->m_vecPosition.z )
				return nil;

			pParticle->m_fZGround = point.point.z;
			bValidGroundFound = true;
			LastTestCoors = vecPos;
			LastTestGroundZ = point.point.z;
		}
	}

	if ( psystem->Flags & ZCHECK_BUMP )
	{
		static float Z_Ground = 0.0f;

		if ( psystem->Flags & ZCHECK_BUMP_FIRST )
		{
			bool bZFound = false;

			Z_Ground = CWorld::FindGroundZFor3DCoord(vecPos.x, vecPos.y, vecPos.z, (bool *)&bZFound);

			if ( bZFound == false )
				return nil;

			pParticle->m_fZGround = Z_Ground;
		}

		pParticle->m_fZGround = Z_Ground;
	}

	switch ( type )
	{
		case PARTICLE_DEBRIS:
			pParticle->m_vecVelocity.z *= CGeneral::GetRandomNumberInRange(0.5f, 3.0f);
			break;

		case PARTICLE_EXPLOSION_MEDIUM:
			pParticle->m_nColorIntensity -= 30 * (CGeneral::GetRandomNumber() & 1); // mb "+= -30 * rand" here ?
			pParticle->m_nAnimationSpeedTimer = CGeneral::GetRandomNumber() & 7;
			pParticle->m_fSize = CGeneral::GetRandomNumberInRange(0.3f, 0.8f);
			pParticle->m_vecPosition.z -= CGeneral::GetRandomNumberInRange(-0.1f, 0.1f);
			break;

		case PARTICLE_EXPLOSION_LARGE:
			pParticle->m_nColorIntensity -= 30 * (CGeneral::GetRandomNumber() & 1); // mb "+= -30 * rand" here ?
			pParticle->m_nAnimationSpeedTimer = CGeneral::GetRandomNumber() & 7;
			pParticle->m_fSize = CGeneral::GetRandomNumberInRange(0.8f, 1.4f);
			pParticle->m_vecPosition.z -= CGeneral::GetRandomNumberInRange(-0.3f, 0.3f);
			break;

		case PARTICLE_WATER_HYDRANT:
			pParticle->m_vecPosition.z += 20.0f * psystem->m_fPositionRandomError * ms_afRandTable[CGeneral::GetRandomNumber() % RAND_TABLE_SIZE];
			break;
		default: break;
	}

	if ( fSize != 0.0f )
		pParticle->m_fSize = fSize;

	m_pUnusedListHead = pParticle->m_pNext;

	pParticle->m_pNext = psystem->m_pParticles;

	psystem->m_pParticles = pParticle;

	return pParticle;
}

void CParticle::Update()
{
	if ( CTimer::GetIsPaused() )
		return;

	CRGBA color(0, 0, 0, 0);

	float fFricDeccel50 = pow(0.50f, CTimer::GetTimeStep());
	float fFricDeccel80 = pow(0.80f, CTimer::GetTimeStep());
	float fFricDeccel90 = pow(0.90f, CTimer::GetTimeStep());
	float fFricDeccel95 = pow(0.95f, CTimer::GetTimeStep());
	float fFricDeccel96 = pow(0.96f, CTimer::GetTimeStep());
	float fFricDeccel99 = pow(0.99f, CTimer::GetTimeStep());

	CParticleObject::UpdateAll();

	for ( int32 i = 0; i < MAX_PARTICLES; i++ )
	{
		tParticleSystemData *psystem = &mod_ParticleSystemManager.m_aParticles[i];
		CParticle *particle = psystem->m_pParticles;
		CParticle *prevParticle = nil;
		bool bRemoveParticle;

		if ( particle == nil )
			continue;

		for ( ; particle != nil; _Next(particle, prevParticle, psystem, bRemoveParticle) )
		{
			bRemoveParticle = false;

			CVector moveStep = particle->m_vecPosition + ( particle->m_vecVelocity * CTimer::GetTimeStep() );

			if (  CTimer::GetTimeInMilliseconds() > particle->m_nTimeWhenWillBeDestroyed || particle->m_nAlpha == 0 )
			{
				bRemoveParticle = true;
				continue;
			}

			if ( particle->m_nTimeWhenColorWillBeChanged != 0 )
			{
				if ( particle->m_nTimeWhenColorWillBeChanged > CTimer::GetTimeInMilliseconds() )
				{
					float colorMul = 1.0f - float(particle->m_nTimeWhenColorWillBeChanged - CTimer::GetTimeInMilliseconds()) / float(psystem->m_ColorFadeTime);

					particle->m_Color.red = Clamp(
						psystem->m_RenderColouring.red + int32(float(psystem->m_FadeDestinationColor.red - psystem->m_RenderColouring.red) * colorMul),
						0, 255);

					particle->m_Color.green = Clamp(
						psystem->m_RenderColouring.green + int32(float(psystem->m_FadeDestinationColor.green - psystem->m_RenderColouring.green) * colorMul),
						0, 255);

					particle->m_Color.blue = Clamp(
						psystem->m_RenderColouring.blue + int32(float(psystem->m_FadeDestinationColor.blue - psystem->m_RenderColouring.blue) * colorMul),
						0, 255);
				}
				else
					RwRGBAAssign(&particle->m_Color, &psystem->m_FadeDestinationColor);
			}

			if ( psystem->Flags & CLIPOUT2D )
			{
				if ( particle->m_vecPosition.x < -10.0f || particle->m_vecPosition.x > SCREEN_WIDTH + 10.0f
					|| particle->m_vecPosition.y < -10.0f || particle->m_vecPosition.y > SCREEN_HEIGHT + 10.0f )
				{
					bRemoveParticle = true;
					continue;
				}
			}

			float size = particle->m_fSize + particle->m_fExpansionRate;

			if ( size < 0.0f )
			{
				bRemoveParticle = true;
				continue;
			}

			particle->m_fSize = size;

			switch ( psystem->m_nFrictionDecceleration )
			{
				case 50:
					particle->m_vecVelocity *= fFricDeccel50;
					break;

				case 80:
					particle->m_vecVelocity *= fFricDeccel80;
					break;

				case 90:
					particle->m_vecVelocity *= fFricDeccel90;
					break;

				case 95:
					particle->m_vecVelocity *= fFricDeccel95;
					break;

				case 96:
					particle->m_vecVelocity *= fFricDeccel96;
					break;

				case 99:
					particle->m_vecVelocity *= fFricDeccel99;
					break;
			}

			if ( psystem->m_fGravitationalAcceleration > 0.0f )
			{
				if ( -50.0f * psystem->m_fGravitationalAcceleration < particle->m_vecVelocity.z )
					particle->m_vecVelocity.z -= psystem->m_fGravitationalAcceleration * CTimer::GetTimeStep();

				if ( psystem->Flags & ZCHECK_FIRST )
				{
					if ( particle->m_vecPosition.z < particle->m_fZGround )
					{
						switch ( psystem->m_Type )
						{
							case PARTICLE_RAINDROP:
							case PARTICLE_RAINDROP_SMALL:
								{
									bRemoveParticle = true;

									if ( CGeneral::GetRandomNumber() & 1 )
									{
										AddParticle(PARTICLE_RAIN_SPLASH,
													CVector
													(
														particle->m_vecPosition.x,
														particle->m_vecPosition.y,
														0.05f + particle->m_fZGround
													),
													CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0);
									}
									else
									{
										AddParticle(PARTICLE_RAIN_SPLASHUP,
													CVector
													(
														particle->m_vecPosition.x,
														particle->m_vecPosition.y,
														0.05f + particle->m_fZGround
													),
													CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0);
									}

									continue;
								}
								break;

							case PARTICLE_WHEEL_WATER:
								{
									bRemoveParticle = true;

									int32 randVal = CGeneral::GetRandomNumber();

									if ( randVal & 1 )
									{
										if ( (randVal % 5) == 0 )
										{
											AddParticle(PARTICLE_RAIN_SPLASH,
														CVector
														(
															particle->m_vecPosition.x,
															particle->m_vecPosition.y,
															0.05f + particle->m_fZGround
														),
														CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0);
										}
										else
										{
											AddParticle(PARTICLE_RAIN_SPLASHUP,
														CVector
														(
															particle->m_vecPosition.x,
															particle->m_vecPosition.y,
															0.05f + particle->m_fZGround
														),
														CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, 0, 0, 0, 0);
										}

									}
									continue;
								}
								break;

							case PARTICLE_BLOOD:
							case PARTICLE_BLOOD_SMALL:
								{
									bRemoveParticle = true;

									CVector vecPosn = particle->m_vecPosition;
									vecPosn.z += 1.0f;

									Randomizer++;
									int32 randVal = int32(Randomizer & 7);

									if ( randVal == 5 )
									{
										CShadows::AddPermanentShadow(1, gpBloodPoolTex, &vecPosn,
												0.1f, 0.0f, 0.0f, -0.1f,
												255,
												255, 0, 0,
												4.0f, (CGeneral::GetRandomNumber() & 4095) + 2000, 1.0f);
									}
									else if ( randVal == 2 )
									{
										CShadows::AddPermanentShadow(1, gpBloodPoolTex, &vecPosn,
												0.2f, 0.0f, 0.0f, -0.2f,
												255,
												255, 0, 0,
												4.0f, (CGeneral::GetRandomNumber() & 4095) + 8000, 1.0f);
									}
									continue;
								}
								break;
							default: break;
						}
					}
				}
				else if ( psystem->Flags & ZCHECK_STEP )
				{
					CColPoint point;
					CEntity *entity;

					if ( CWorld::ProcessVerticalLine(particle->m_vecPosition, moveStep.z, point, entity,
														true, true, false, false, true, false, nil) )
					{
						if ( moveStep.z <= point.point.z )
						{
							moveStep.z = point.point.z;
							if ( psystem->m_Type == PARTICLE_DEBRIS2 )
							{
								particle->m_vecVelocity.x *= 0.8f;
								particle->m_vecVelocity.y *= 0.8f;
								particle->m_vecVelocity.z *= -0.4f;
								if ( particle->m_vecVelocity.z < 0.005f )
									particle->m_vecVelocity.z = 0.0f;
							}
						}
					}
				}
				else if ( psystem->Flags & ZCHECK_BUMP )
				{
					if ( particle->m_vecPosition.z < particle->m_fZGround )
					{
						switch ( psystem->m_Type )
						{
							case PARTICLE_GUNSHELL_FIRST:
							case PARTICLE_GUNSHELL:
								{
									bRemoveParticle = true;

									AddParticle(PARTICLE_GUNSHELL_BUMP1,
												CVector
												(
													particle->m_vecPosition.x,
													particle->m_vecPosition.y,
													0.05f + particle->m_fZGround
												),
												CVector
												(
													CGeneral::GetRandomNumberInRange(-0.02f, 0.02f),
													CGeneral::GetRandomNumberInRange(-0.02f, 0.02f),
													CGeneral::GetRandomNumberInRange(0.05f, 0.1f)
												),
												nil,
												particle->m_fSize, color, particle->m_nRotationStep, 0, 0, 0);

									PlayOneShotScriptObject(SCRIPT_SOUND_GUNSHELL_DROP, particle->m_vecPosition);
								}
								break;

							case PARTICLE_GUNSHELL_BUMP1:
								{
									bRemoveParticle = true;

									AddParticle(PARTICLE_GUNSHELL_BUMP2,
												CVector
												(
													particle->m_vecPosition.x,
													particle->m_vecPosition.y,
													0.05f + particle->m_fZGround
												),
												CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.03f, 0.06f)),
												nil,
												particle->m_fSize, color, 0, 0, 0, 0);

									PlayOneShotScriptObject(SCRIPT_SOUND_GUNSHELL_DROP_SOFT, particle->m_vecPosition);
								}
								break;

							case PARTICLE_GUNSHELL_BUMP2:
								{
									bRemoveParticle = true;
									continue;
								}
								break;
							default: break;
						}
					}
				}
			}
			else
			{
				if ( psystem->m_fGravitationalAcceleration < 0.0f )
				{
					if ( -5.0f * psystem->m_fGravitationalAcceleration > particle->m_vecVelocity.z )
						particle->m_vecVelocity.z -= psystem->m_fGravitationalAcceleration * CTimer::GetTimeStep();
				}
				else
				{
					if ( psystem->Flags & ZCHECK_STEP )
					{
						CColPoint point;
						CEntity *entity;

						if ( CWorld::ProcessVerticalLine(particle->m_vecPosition, moveStep.z, point, entity,
														true, false, false, false, true, false, nil) )
						{
							if ( moveStep.z <= point.point.z )
							{
								moveStep.z = point.point.z;
								if ( psystem->m_Type == PARTICLE_HELI_ATTACK )
								{
									bRemoveParticle = true;
									AddParticle(PARTICLE_STEAM, moveStep, CVector(0.0f, 0.0f, 0.05f), nil, 0.2f, 0, 0, 0, 0);
									continue;
								}
							}
						}
					}
				}
			}

			if ( psystem->m_nFadeToBlackAmount != 0 )
			{
				if ( particle->m_nFadeToBlackTimer >= psystem->m_nFadeToBlackTime )
				{
					particle->m_nFadeToBlackTimer = 0;

					particle->m_nColorIntensity = Clamp(particle->m_nColorIntensity - psystem->m_nFadeToBlackAmount,
														0, 255);
				}
				else
					++particle->m_nFadeToBlackTimer;
			}

			if ( psystem->m_nFadeAlphaAmount != 0 )
			{
				if ( particle->m_nFadeAlphaTimer >= psystem->m_nFadeAlphaTime )
				{
					particle->m_nFadeAlphaTimer = 0;

					particle->m_nAlpha = Clamp(particle->m_nAlpha - psystem->m_nFadeAlphaAmount,
														0, 255);
#ifdef PC_PARTICLE
					if ( particle->m_nAlpha == 0 )
					{
						bRemoveParticle = true;
						continue;
					}
#endif
				}
				else
					++particle->m_nFadeAlphaTimer;
			}

			if ( psystem->m_nZRotationAngleChangeAmount != 0 )
			{
				if ( particle->m_nZRotationTimer >= psystem->m_nZRotationChangeTime )
				{
					particle->m_nZRotationTimer = 0;
					particle->m_nCurrentZRotation += psystem->m_nZRotationAngleChangeAmount;
				}
				else
					++particle->m_nZRotationTimer;
			}

			if ( psystem->m_fZRadiusChangeAmount != 0.0f )
			{
				if ( particle->m_nZRadiusTimer >= psystem->m_nZRadiusChangeTime )
				{
					particle->m_nZRadiusTimer = 0;
					particle->m_fCurrentZRadius += psystem->m_fZRadiusChangeAmount;
				}
				else
					++particle->m_nZRadiusTimer;
			}

			if ( psystem->m_nAnimationSpeed != 0 )
			{
				if ( particle->m_nAnimationSpeedTimer > psystem->m_nAnimationSpeed )
				{
					particle->m_nAnimationSpeedTimer = 0;

					if ( ++particle->m_nCurrentFrame > psystem->m_nFinalAnimationFrame )
					{
						if ( psystem->Flags & CYCLE_ANIM )
							particle->m_nCurrentFrame = psystem->m_nStartAnimationFrame;
						else
							--particle->m_nCurrentFrame;
					}
				}
				else
					++particle->m_nAnimationSpeedTimer;
			}

			if ( particle->m_nRotationStep != 0 )
			{
				particle->m_nRotation += particle->m_nRotationStep;

				if ( particle->m_nRotation >= 360 )
					particle->m_nRotation -= 360;
				else if ( particle->m_nRotation < 0 )
					particle->m_nRotation += 360;
			}

			if ( particle->m_fCurrentZRadius != 0.0f )
			{
				int32 nRot = particle->m_nCurrentZRotation % (SIN_COS_TABLE_SIZE - 1);

				float fX = (Cos(nRot) - Sin(nRot)) * particle->m_fCurrentZRadius;

				float fY = (Sin(nRot) + Cos(nRot)) * particle->m_fCurrentZRadius;

				moveStep -= particle->m_vecParticleMovementOffset;

				moveStep += CVector(fX, fY, 0.0f);

				particle->m_vecParticleMovementOffset = CVector(fX, fY, 0.0f);
			}

			particle->m_vecPosition = moveStep;
		}
	}
}

void CParticle::Render()
{
	PUSH_RENDERGROUP("CParticle::Render");

	RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void *)rwTEXTUREADDRESSWRAP);
	RwRenderStateSet(rwRENDERSTATETEXTUREPERSPECTIVE, (void *)TRUE);
	RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)FALSE);
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE);
	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA);

	CSprite::InitSpriteBuffer2D();

	uint32 flags = DRAW_OPAQUE;

	RwRaster *prevFrame = nil;

	for ( int32 i = 0; i < MAX_PARTICLES; i++ )
	{
		tParticleSystemData *psystem = &mod_ParticleSystemManager.m_aParticles[i];
#ifdef PC_PARTICLE
		bool particleBanned = false;
#endif
		CParticle *particle = psystem->m_pParticles;

		RwRaster **frames = psystem->m_ppRaster;
#ifdef PC_PARTICLE
		tParticleType type = psystem->m_Type;

		if ( type == PARTICLE_ENGINE_SMOKE
			|| type == PARTICLE_ENGINE_SMOKE2
			|| type == PARTICLE_ENGINE_STEAM
			|| type == PARTICLE_CARFLAME_SMOKE
			|| type == PARTICLE_RUBBER_SMOKE
			|| type == PARTICLE_BURNINGRUBBER_SMOKE
			|| type == PARTICLE_EXHAUST_FUMES
			|| type == PARTICLE_CARCOLLISION_DUST )
		{
			particleBanned = true;
		}
#endif

		if ( particle )
		{
			if ( (flags & DRAW_OPAQUE) != (psystem->Flags & DRAW_OPAQUE)
				|| (flags & DRAW_DARK) != (psystem->Flags & DRAW_DARK) )
			{
				CSprite::FlushSpriteBuffer();

				if ( psystem->Flags & DRAW_OPAQUE )
				{
					RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA);
					RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA);
				}
				else
				{
					if ( psystem->Flags & DRAW_DARK )
						RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA);
					else
						RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE);

					RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE);
				}

				flags = psystem->Flags;
			}

			if ( frames != nil )
			{
				RwRaster *curFrame = *frames;
				if ( curFrame != prevFrame )
				{
					CSprite::FlushSpriteBuffer();
					RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)curFrame);
					prevFrame = curFrame;
				}
			}
		}

		while ( particle != nil )
		{
			bool canDraw = true;
#ifdef PC_PARTICLE

			if ( particle->m_nAlpha == 0 )
				canDraw = false;
#endif
			if ( canDraw && psystem->m_nFinalAnimationFrame != 0 && frames != nil )
			{
				RwRaster *curFrame = frames[particle->m_nCurrentFrame];
				if ( prevFrame != curFrame )
				{
					CSprite::FlushSpriteBuffer();
					RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)curFrame);
					prevFrame = curFrame;
				}
			}

			if ( canDraw && psystem->Flags & DRAWTOP2D )
			{
				if ( particle->m_nRotation != 0 )
				{
					 CSprite::RenderBufferedOneXLUSprite2D_Rotate_Dimension(
							particle->m_vecPosition.x,
							particle->m_vecPosition.y,
							particle->m_fSize * 63.0f,
							particle->m_fSize * 63.0f,
							particle->m_Color,
							particle->m_nColorIntensity,
							(float)particle->m_nRotation, //DEGTORAD((float)particle->m_nRotation) ps2
							particle->m_nAlpha);
				}
				else
				{
					CSprite::RenderBufferedOneXLUSprite2D(
							particle->m_vecPosition.x,
							particle->m_vecPosition.y,
							particle->m_fSize * 63.0f,
							particle->m_fSize * 63.0f,
							particle->m_Color,
							particle->m_nColorIntensity,
							particle->m_nAlpha);
				}

				canDraw = false;
			}

			if ( canDraw )
			{
				CVector coors;
				float w;
				float h;

				if ( CSprite::CalcScreenCoors(particle->m_vecPosition, &coors, &w, &h, true) )
				{
#ifdef PC_PARTICLE
					if ( (!particleBanned || SCREEN_WIDTH * fParticleScaleLimit >= w)
											&& SCREEN_HEIGHT * fParticleScaleLimit >= h )
#endif
					{
						if ( particle->m_nRotation != 0 )
						{
							CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(coors.x, coors.y, coors.z,
									particle->m_fSize * w, particle->m_fSize * h,
									particle->m_Color.red,
									particle->m_Color.green,
									particle->m_Color.blue,
									particle->m_nColorIntensity,
									1.0f / coors.z,
									float(particle->m_nRotation), // DEGTORAD((float)particle->m_nRotation) ps2
									particle->m_nAlpha);
						}
						else if ( psystem->Flags & SCREEN_TRAIL )
						{
							float fRotation;
							float fTrailLength;

							if ( particle->m_vecScreenPosition.x == 0.0f )
							{
								fTrailLength = 0.0f;
								fRotation = 0.0f;
							}
							else
							{
								CVector2D vecDist
								(
									coors.x - particle->m_vecScreenPosition.x,
									coors.y - particle->m_vecScreenPosition.y
								);

								float fDist = vecDist.Magnitude();

								fTrailLength = fDist;

								float fRot = Asin(vecDist.x / fDist);

								fRotation = fRot;

								if ( vecDist.y < 0.0f )
									fRotation = -1.0f * fRot + DEGTORAD(180.0f);

								fRotation = RADTODEG(fRotation);

								if ( fRotation < 0.0f )
									fRotation += 360.0f;

								float fSpeed = particle->m_vecVelocity.Magnitude();

								float fNewTrailLength = fSpeed * CTimer::GetTimeStep() * w * 2.0f;

								if ( fDist > fNewTrailLength )
									fTrailLength = fNewTrailLength;
							}

							CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(coors.x, coors.y, coors.z,
									particle->m_fSize * w,
									particle->m_fSize * h + fTrailLength * psystem->m_fTrailLengthMultiplier,
									particle->m_Color.red,
									particle->m_Color.green,
									particle->m_Color.blue,
									particle->m_nColorIntensity,
									1.0f / coors.z,
									fRotation,
									particle->m_nAlpha);

							particle->m_vecScreenPosition = coors;
						}
						else if ( psystem->Flags & SPEED_TRAIL )
						{
							CVector vecPrevPos = particle->m_vecPosition - particle->m_vecVelocity;
							float fRotation;
							float fTrailLength;

							if ( CSprite::CalcScreenCoors(vecPrevPos, &particle->m_vecScreenPosition, &fTrailLength, &fRotation, true) )
							{
								CVector2D vecDist
								(
									coors.x - particle->m_vecScreenPosition.x,
									coors.y - particle->m_vecScreenPosition.y
								);

								float fDist = vecDist.Magnitude();

								fTrailLength = fDist;

								float fRot = Asin(vecDist.x / fDist);

								fRotation = fRot;

								if ( vecDist.y < 0.0f )
									fRotation = -1.0f * fRot + DEGTORAD(180.0f);

								fRotation = RADTODEG(fRotation);

								if ( fRotation < 0.0f )
									fRotation += 360.0f;
							}
							else
							{
								fRotation = 0.0f;
								fTrailLength = 0.0f;
							}

							CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(coors.x, coors.y, coors.z,
									particle->m_fSize * w,
									particle->m_fSize * h + fTrailLength * psystem->m_fTrailLengthMultiplier,
									particle->m_Color.red,
									particle->m_Color.green,
									particle->m_Color.blue,
									particle->m_nColorIntensity,
									1.0f / coors.z,
									fRotation,
									particle->m_nAlpha);
						}
						else if ( psystem->Flags & VERT_TRAIL )
						{
							float fTrailLength = fabsf(particle->m_vecVelocity.z * 10.0f);

							CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z,
									particle->m_fSize * w,
									(particle->m_fSize + fTrailLength * psystem->m_fTrailLengthMultiplier) * h,
									particle->m_Color.red,
									particle->m_Color.green,
									particle->m_Color.blue,
									particle->m_nColorIntensity,
									1.0f / coors.z,
									particle->m_nAlpha);
						}
						else if ( i == PARTICLE_RAINDROP_SMALL )
						{
							CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z,
									particle->m_fSize * w * 0.05f,
									particle->m_fSize * h,
									particle->m_Color.red,
									particle->m_Color.green,
									particle->m_Color.blue,
									particle->m_nColorIntensity,
									1.0f / coors.z,
									particle->m_nAlpha);
						}
						else if ( i == PARTICLE_BOAT_WAKE )
						{
							CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z,
									particle->m_fSize * w,
									psystem->m_fDefaultInitialRadius * h,
									particle->m_Color.red,
									particle->m_Color.green,
									particle->m_Color.blue,
									particle->m_nColorIntensity,
									1.0f / coors.z,
									particle->m_nAlpha);
						}
						else
						{
							CSprite::RenderBufferedOneXLUSprite(coors.x, coors.y, coors.z,
									particle->m_fSize * w,
									particle->m_fSize * h,
									particle->m_Color.red,
									particle->m_Color.green,
									particle->m_Color.blue,
									particle->m_nColorIntensity,
									1.0f / coors.z,
									particle->m_nAlpha);
						}
					}
				}
			}

			particle = particle->m_pNext;
		}

		CSprite::FlushSpriteBuffer();

	}

	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE);
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE);
	RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA);

	POP_RENDERGROUP();
}

void CParticle::RemovePSystem(tParticleType type)
{
	tParticleSystemData *psystemdata = &mod_ParticleSystemManager.m_aParticles[type];

	for ( CParticle *particle = psystemdata->m_pParticles; particle; particle = psystemdata->m_pParticles )
		RemoveParticle(particle, nil, psystemdata);
}

void CParticle::RemoveParticle(CParticle *pParticle, CParticle *pPrevParticle, tParticleSystemData *pPSystemData)
{
	if ( pPrevParticle )
		pPrevParticle->m_pNext = pParticle->m_pNext;
	else
		pPSystemData->m_pParticles = pParticle->m_pNext;

	pParticle->m_pNext = m_pUnusedListHead;
	m_pUnusedListHead = pParticle;
}

void CParticle::AddJetExplosion(CVector const &vecPos, float fPower, float fSize)
{
	CRGBA color(240, 240, 240, 255);

	if ( fPower < 1.0f )
		fPower = 1.0f;

	CVector vecRandOffset
	(
		CGeneral::GetRandomNumberInRange(-0.4f, 0.4f),
		CGeneral::GetRandomNumberInRange(-0.4f, 0.4f),
		CGeneral::GetRandomNumberInRange(0.1f, 0.3f)
	);

	vecRandOffset *= 2.0f;

	CVector vecStepPos = vecPos;

	for ( int32 i = 0; i < int32(fPower * 4.0f); i++ )
	{
		AddParticle(PARTICLE_EXPLOSION_MFAST,
					vecStepPos,
					CVector
					(
						CGeneral::GetRandomNumberInRange(-0.02f, 0.02f),
						CGeneral::GetRandomNumberInRange(-0.02f, 0.02f),
						CGeneral::GetRandomNumberInRange(-0.02f, 0.0f)
					),
					nil,
					fSize, color, 0, 0, 0, 0);

		AddParticle(PARTICLE_EXPLOSION_MFAST,
					vecStepPos,
					CVector
					(
						CGeneral::GetRandomNumberInRange(-0.04f, 0.04f),
						CGeneral::GetRandomNumberInRange(-0.04f, 0.04f),
						CGeneral::GetRandomNumberInRange(0.0f, 0.07f)
					),
					nil,
					fSize, color, 0, 0, 0, 0);

		AddParticle(PARTICLE_EXPLOSION_MFAST,
					vecStepPos,
					CVector
					(
						CGeneral::GetRandomNumberInRange(-0.04f, 0.04f),
						CGeneral::GetRandomNumberInRange(-0.04f, 0.04f),
						CGeneral::GetRandomNumberInRange(0.0f, 0.07f)
					),
					nil,
					fSize, color, 0, 0, 0, 0);

		vecStepPos += vecRandOffset;
	}
}

void CParticle::AddYardieDoorSmoke(CVector const &vecPos, CMatrix const &matMatrix)
{
	CRGBA color(0, 0, 0, 0);

	CMatrix invMat(Invert(matMatrix));

	CVector vecBasePos = matMatrix * (invMat * vecPos + CVector(0.0f, -1.0f, 0.5f));

	for ( int32 i = 0; i < 5; i++ )
	{
		CVector pos = vecBasePos;

		pos.x += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f);
		pos.y += CGeneral::GetRandomNumberInRange(-0.5f, 0.5f);

		AddParticle(PARTICLE_CARCOLLISION_DUST,
					pos,
					CVector(0.0f, 0.0f, 0.0f),
					nil,
					0.3f, color, 0, 0, 0, 0);
	}
}


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

class CEntity;

class CParticle
{
public:
	enum
	{
		RAND_TABLE_SIZE    = 20,
		SIN_COS_TABLE_SIZE = 1024
	};

	CVector   m_vecPosition;
	CVector   m_vecVelocity;
	CVector   m_vecScreenPosition;
	uint32     m_nTimeWhenWillBeDestroyed;
	uint32     m_nTimeWhenColorWillBeChanged;
	float     m_fZGround;
	CVector   m_vecParticleMovementOffset;
	int16     m_nCurrentZRotation;
	uint16     m_nZRotationTimer;
	float     m_fCurrentZRadius;
	uint16     m_nZRadiusTimer;
	float     m_fSize;
	float     m_fExpansionRate;
	uint16     m_nFadeToBlackTimer;
	uint16     m_nFadeAlphaTimer;
	uint8     m_nColorIntensity;
	uint8     m_nAlpha;
	uint16    m_nCurrentFrame;
	int16     m_nAnimationSpeedTimer;
	int16     m_nRotationStep;
	int16     m_nRotation;
	RwRGBA    m_Color;
	CParticle *m_pNext;

	CParticle()
	{
		;
	}

	~CParticle()
	{
		;
	}

	static float      ms_afRandTable[RAND_TABLE_SIZE];
	static CParticle *m_pUnusedListHead;

	static float      m_SinTable[SIN_COS_TABLE_SIZE];
	static float      m_CosTable[SIN_COS_TABLE_SIZE];

	static float Sin(int32 value) { return m_SinTable[value]; }
	static float Cos(int32 value) { return m_CosTable[value]; }

	static void ReloadConfig();
	static void Initialise();
	static void Shutdown();

	static CParticle *AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity = nil, float fSize = 0.0f, int32 nRotationSpeed = 0, int32 nRotation = 0, int32 nCurFrame = 0, int32 nLifeSpan = 0);
	static CParticle *AddParticle(tParticleType type, CVector const &vecPos, CVector const &vecDir, CEntity *pEntity, float fSize, RwRGBA const &color, int32 nRotationSpeed = 0, int32 nRotation = 0, int32 nCurFrame = 0, int32 nLifeSpan = 0);

	static void Update();
	static void Render();

	static void RemovePSystem(tParticleType type);
	static void RemoveParticle(CParticle *pParticle, CParticle *pPrevParticle, tParticleSystemData *pPSystemData);

	static void _Next(CParticle *&pParticle, CParticle *&pPrevParticle, tParticleSystemData *pPSystemData, bool bRemoveParticle)
	{
		if ( bRemoveParticle )
		{
			RemoveParticle(pParticle, pPrevParticle, pPSystemData);

			if ( pPrevParticle )
				pParticle = pPrevParticle->m_pNext;
			else
				pParticle = pPSystemData->m_pParticles;
		}
		else
		{
			pPrevParticle = pParticle;
			pParticle = pParticle->m_pNext;
		}
	}

	static void AddJetExplosion(CVector const &vecPos, float fPower, float fSize);
	static void AddYardieDoorSmoke(CVector const &vecPos, CMatrix const &matMatrix);
};

VALIDATE_SIZE(CParticle, 0x68);

license: mit


code:
#include "common.h"

#include "main.h"
#include "FileMgr.h"
#include "ParticleMgr.h"

cParticleSystemMgr mod_ParticleSystemManager;

const char *ParticleFilename = "PARTICLE.CFG";

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

void cParticleSystemMgr::Initialise()
{
	LoadParticleData();

	for ( int32 i = 0; i < MAX_PARTICLES; i++ )
		m_aParticles[i].m_pParticles = nil;
}

void cParticleSystemMgr::LoadParticleData()
{
	CFileMgr::SetDir("DATA");
	CFileMgr::LoadFile(ParticleFilename, work_buff, ARRAY_SIZE(work_buff), "r");
	CFileMgr::SetDir("");

	tParticleSystemData *entry = nil;
	int32 type = PARTICLE_FIRST;

	char *lineStart = (char *)work_buff;
	char *lineEnd = lineStart + 1;

	char line[500];
	char delims[4];

	while ( true )
	{
		ASSERT(lineStart != nil);
		ASSERT(lineEnd != nil);

		while ( *lineEnd != '\n' )
			++lineEnd;

		int32 lineLength = lineEnd - lineStart;

		ASSERT(lineLength < 500);

		strncpy(line, lineStart, lineLength);

		line[lineLength] = '\0';

		if ( !strcmp(line, ";the end") )
			break;

		if ( *line != ';' )
		{
			int32 param = CFG_PARAM_FIRST;

			strcpy(delims, " \t");

			char *value = strtok(line, delims);

			ASSERT(value != nil);

			do
			{
				switch ( param )
				{
					case CFG_PARAM_PARTICLE_TYPE_NAME:
						ASSERT(type < MAX_PARTICLES);
						entry = &m_aParticles[type];
						ASSERT(entry != nil);
						entry->m_Type = (tParticleType)type++;
						strcpy(entry->m_aName, value);
						break;

					case CFG_PARAM_RENDER_COLOURING_R:
						entry->m_RenderColouring.red = atoi(value);
						break;

					case CFG_PARAM_RENDER_COLOURING_G:
						entry->m_RenderColouring.green = atoi(value);
						break;

					case CFG_PARAM_RENDER_COLOURING_B:
						entry->m_RenderColouring.blue = atoi(value);
						break;

					case CFG_PARAM_INITIAL_COLOR_VARIATION:
						entry->m_InitialColorVariation = Min(atoi(value), 100);
						break;

					case CFG_PARAM_FADE_DESTINATION_COLOR_R:
						entry->m_FadeDestinationColor.red = atoi(value);
						break;

					case CFG_PARAM_FADE_DESTINATION_COLOR_G:
						entry->m_FadeDestinationColor.green = atoi(value);
						break;

					case CFG_PARAM_FADE_DESTINATION_COLOR_B:
						entry->m_FadeDestinationColor.blue = atoi(value);
						break;

					case CFG_PARAM_COLOR_FADE_TIME:
						entry->m_ColorFadeTime = atoi(value);
						break;

					case CFG_PARAM_DEFAULT_INITIAL_RADIUS:
						entry->m_fDefaultInitialRadius = atof(value);
						break;

					case CFG_PARAM_EXPANSION_RATE:
						entry->m_fExpansionRate = atof(value);
						break;

					case CFG_PARAM_INITIAL_INTENSITY:
						entry->m_nFadeToBlackInitialIntensity = atoi(value);
						break;

					case CFG_PARAM_FADE_TIME:
						entry->m_nFadeToBlackTime = atoi(value);
						break;

					case CFG_PARAM_FADE_AMOUNT:
						entry->m_nFadeToBlackAmount = atoi(value);
						break;

					case CFG_PARAM_INITIAL_ALPHA_INTENSITY:
						entry->m_nFadeAlphaInitialIntensity = atoi(value);
						break;

					case CFG_PARAM_FADE_ALPHA_TIME:
						entry->m_nFadeAlphaTime = atoi(value);
						break;

					case CFG_PARAM_FADE_ALPHA_AMOUNT:
						entry->m_nFadeAlphaAmount = atoi(value);
						break;

					case CFG_PARAM_INITIAL_ANGLE:
						entry->m_nZRotationInitialAngle = atoi(value);
						break;

					case CFG_PARAM_CHANGE_TIME:
						entry->m_nZRotationChangeTime = atoi(value);
						break;

					case CFG_PARAM_ANGLE_CHANGE_AMOUNT:
						entry->m_nZRotationAngleChangeAmount = atoi(value);
						break;

					case CFG_PARAM_INITIAL_Z_RADIUS:
						entry->m_fInitialZRadius = atof(value);
						break;

					case CFG_PARAM_Z_RADIUS_CHANGE_TIME:
						entry->m_nZRadiusChangeTime = atoi(value);
						break;

					case CFG_PARAM_Z_RADIUS_CHANGE_AMOUNT:
						entry->m_fZRadiusChangeAmount = atof(value);
						break;

					case CFG_PARAM_ANIMATION_SPEED:
						entry->m_nAnimationSpeed = atoi(value);
						break;

					case CFG_PARAM_START_ANIMATION_FRAME:
						entry->m_nStartAnimationFrame = atoi(value);
						break;

					case CFG_PARAM_FINAL_ANIMATION_FRAME:
						entry->m_nFinalAnimationFrame = atoi(value);
						break;

					case CFG_PARAM_ROTATION_SPEED:
						entry->m_nRotationSpeed = atoi(value);
						break;

					case CFG_PARAM_GRAVITATIONAL_ACCELERATION:
						entry->m_fGravitationalAcceleration = atof(value);
						break;

					case CFG_PARAM_FRICTION_DECCELERATION:
						entry->m_nFrictionDecceleration = atoi(value);
						break;

					case CFG_PARAM_LIFE_SPAN:
						entry->m_nLifeSpan = atoi(value);
						break;

					case CFG_PARAM_POSITION_RANDOM_ERROR:
						entry->m_fPositionRandomError = atof(value);
						break;

					case CFG_PARAM_VELOCITY_RANDOM_ERROR:
						entry->m_fVelocityRandomError = atof(value);
						break;

					case CFG_PARAM_EXPANSION_RATE_ERROR:
						entry->m_fExpansionRateError = atof(value);
						break;

					case CFG_PARAM_ROTATION_RATE_ERROR:
						entry->m_nRotationRateError = atoi(value);
						break;

					case CFG_PARAM_LIFE_SPAN_ERROR_SHAPE:
						entry->m_nLifeSpanErrorShape = atoi(value);
						break;

					case CFG_PARAM_TRAIL_LENGTH_MULTIPLIER:
						entry->m_fTrailLengthMultiplier = atof(value);
						break;

					case CFG_PARAM_PARTICLE_CREATE_RANGE:
						entry->m_fCreateRange = SQR(atof(value));
						break;

					case CFG_PARAM_FLAGS:
						entry->Flags = atoi(value);
						break;
				}

				value = strtok(nil, delims);

				param++;

				if ( param > CFG_PARAM_LAST )
					param = CFG_PARAM_FIRST;

			} while ( value != nil );
		}

		lineEnd++;
		lineStart = lineEnd;
		lineEnd++;
	}
}


code:
#pragma once

#include "ParticleType.h"

class CParticle;

enum
{
	ZCHECK_FIRST      = BIT(0),
	ZCHECK_STEP       = BIT(1),
	DRAW_OPAQUE       = BIT(2),
	SCREEN_TRAIL      = BIT(3),
	SPEED_TRAIL       = BIT(4),
	RAND_VERT_V       = BIT(5),
	CYCLE_ANIM        = BIT(6),
	DRAW_DARK         = BIT(7),
	VERT_TRAIL        = BIT(8),
	_FLAG9            = BIT(9),		// unused
	DRAWTOP2D         = BIT(10),
	CLIPOUT2D         = BIT(11),
	ZCHECK_BUMP       = BIT(12),
	ZCHECK_BUMP_FIRST = BIT(13)
};

struct tParticleSystemData
{
	tParticleType m_Type;
	char          m_aName[20];
	float         m_fCreateRange;
	float         m_fDefaultInitialRadius;
	float         m_fExpansionRate;
	uint16        m_nZRotationInitialAngle;
	int16         m_nZRotationAngleChangeAmount;
	uint16        m_nZRotationChangeTime;
	uint16        m_nZRadiusChangeTime;
	float         m_fInitialZRadius;
	float         m_fZRadiusChangeAmount;
	uint16        m_nFadeToBlackTime;
	int16         m_nFadeToBlackAmount;
	uint8         m_nFadeToBlackInitialIntensity;
	uint8         m_nFadeAlphaInitialIntensity;
	uint16        m_nFadeAlphaTime;
	int16         m_nFadeAlphaAmount;
	uint16        m_nStartAnimationFrame;
	uint16        m_nFinalAnimationFrame;
	uint16        m_nAnimationSpeed;
	uint16        m_nRotationSpeed;
	float         m_fGravitationalAcceleration;
	int32         m_nFrictionDecceleration;
	int32         m_nLifeSpan;
	float         m_fPositionRandomError;
	float         m_fVelocityRandomError;
	float         m_fExpansionRateError;
	int32         m_nRotationRateError;
	uint32        m_nLifeSpanErrorShape;
	float         m_fTrailLengthMultiplier;
	uint32        Flags;
	RwRGBA        m_RenderColouring;
	uint8         m_InitialColorVariation;
	RwRGBA        m_FadeDestinationColor;
	uint32        m_ColorFadeTime;

	RwRaster      **m_ppRaster;
	CParticle     *m_pParticles;
};

VALIDATE_SIZE(tParticleSystemData, 0x88);

class cParticleSystemMgr
{
	enum
	{
		CFG_PARAM_PARTICLE_TYPE_NAME = 0,
		CFG_PARAM_RENDER_COLOURING_R,
		CFG_PARAM_RENDER_COLOURING_G,
		CFG_PARAM_RENDER_COLOURING_B,
		CFG_PARAM_INITIAL_COLOR_VARIATION,
		CFG_PARAM_FADE_DESTINATION_COLOR_R,
		CFG_PARAM_FADE_DESTINATION_COLOR_G,
		CFG_PARAM_FADE_DESTINATION_COLOR_B,
		CFG_PARAM_COLOR_FADE_TIME,
		CFG_PARAM_DEFAULT_INITIAL_RADIUS,
		CFG_PARAM_EXPANSION_RATE,
		CFG_PARAM_INITIAL_INTENSITY,
		CFG_PARAM_FADE_TIME,
		CFG_PARAM_FADE_AMOUNT,
		CFG_PARAM_INITIAL_ALPHA_INTENSITY,
		CFG_PARAM_FADE_ALPHA_TIME,
		CFG_PARAM_FADE_ALPHA_AMOUNT,
		CFG_PARAM_INITIAL_ANGLE,
		CFG_PARAM_CHANGE_TIME,
		CFG_PARAM_ANGLE_CHANGE_AMOUNT,
		CFG_PARAM_INITIAL_Z_RADIUS,
		CFG_PARAM_Z_RADIUS_CHANGE_TIME,
		CFG_PARAM_Z_RADIUS_CHANGE_AMOUNT,
		CFG_PARAM_ANIMATION_SPEED,
		CFG_PARAM_START_ANIMATION_FRAME,
		CFG_PARAM_FINAL_ANIMATION_FRAME,
		CFG_PARAM_ROTATION_SPEED,
		CFG_PARAM_GRAVITATIONAL_ACCELERATION,
		CFG_PARAM_FRICTION_DECCELERATION,
		CFG_PARAM_LIFE_SPAN,
		CFG_PARAM_POSITION_RANDOM_ERROR,
		CFG_PARAM_VELOCITY_RANDOM_ERROR,
		CFG_PARAM_EXPANSION_RATE_ERROR,
		CFG_PARAM_ROTATION_RATE_ERROR,
		CFG_PARAM_LIFE_SPAN_ERROR_SHAPE,
		CFG_PARAM_TRAIL_LENGTH_MULTIPLIER,
		CFG_PARAM_PARTICLE_CREATE_RANGE,
		CFG_PARAM_FLAGS,

		MAX_CFG_PARAMS,
		CFG_PARAM_FIRST = CFG_PARAM_PARTICLE_TYPE_NAME,
		CFG_PARAM_LAST = CFG_PARAM_FLAGS
	};

public:
	tParticleSystemData m_aParticles[MAX_PARTICLES];

	cParticleSystemMgr();

	void Initialise();
	void LoadParticleData();
	void RangeCheck(tParticleSystemData *pData) { }
};

VALIDATE_SIZE(cParticleSystemMgr, 0x2420);

extern cParticleSystemMgr mod_ParticleSystemManager;


code:
#pragma once

enum tParticleType
{
	PARTICLE_SPARK = 0,
	PARTICLE_SPARK_SMALL,
	PARTICLE_WHEEL_DIRT,
	PARTICLE_WHEEL_WATER,
	PARTICLE_BLOOD,
	PARTICLE_BLOOD_SMALL,
	PARTICLE_BLOOD_SPURT,
	PARTICLE_DEBRIS,
	PARTICLE_DEBRIS2,
	PARTICLE_WATER,
	PARTICLE_FLAME,
	PARTICLE_FIREBALL,
	PARTICLE_GUNFLASH,
	PARTICLE_GUNFLASH_NOANIM,
	PARTICLE_GUNSMOKE,
	PARTICLE_GUNSMOKE2,
	PARTICLE_SMOKE,
	PARTICLE_SMOKE_SLOWMOTION,
	PARTICLE_GARAGEPAINT_SPRAY,
	PARTICLE_SHARD,
	PARTICLE_SPLASH,
	PARTICLE_CARFLAME,
	PARTICLE_STEAM,
	PARTICLE_STEAM2,
	PARTICLE_STEAM_NY,
	PARTICLE_STEAM_NY_SLOWMOTION,
	PARTICLE_ENGINE_STEAM,
	PARTICLE_RAINDROP,
	PARTICLE_RAINDROP_SMALL,
	PARTICLE_RAIN_SPLASH,
	PARTICLE_RAIN_SPLASH_BIGGROW,
	PARTICLE_RAIN_SPLASHUP,
	PARTICLE_WATERSPRAY,
	PARTICLE_EXPLOSION_MEDIUM,
	PARTICLE_EXPLOSION_LARGE,
	PARTICLE_EXPLOSION_MFAST,
	PARTICLE_EXPLOSION_LFAST,
	PARTICLE_CAR_SPLASH,
	PARTICLE_BOAT_SPLASH,
	PARTICLE_BOAT_THRUSTJET,
	PARTICLE_BOAT_WAKE,
	PARTICLE_WATER_HYDRANT,
	PARTICLE_WATER_CANNON,
	PARTICLE_EXTINGUISH_STEAM,
	PARTICLE_PED_SPLASH,
	PARTICLE_PEDFOOT_DUST,
	PARTICLE_HELI_DUST,
	PARTICLE_HELI_ATTACK,
	PARTICLE_ENGINE_SMOKE,
	PARTICLE_ENGINE_SMOKE2,
	PARTICLE_CARFLAME_SMOKE,
	PARTICLE_FIREBALL_SMOKE,
	PARTICLE_PAINT_SMOKE,
	PARTICLE_TREE_LEAVES,
	PARTICLE_CARCOLLISION_DUST,
	PARTICLE_CAR_DEBRIS,
	PARTICLE_HELI_DEBRIS,
	PARTICLE_EXHAUST_FUMES,
	PARTICLE_RUBBER_SMOKE,
	PARTICLE_BURNINGRUBBER_SMOKE,
	PARTICLE_BULLETHIT_SMOKE,
	PARTICLE_GUNSHELL_FIRST,
	PARTICLE_GUNSHELL,
	PARTICLE_GUNSHELL_BUMP1,
	PARTICLE_GUNSHELL_BUMP2,
	PARTICLE_TEST,
	PARTICLE_BIRD_FRONT,
	PARTICLE_RAINDROP_2D,

	MAX_PARTICLES,
	PARTICLE_FIRST = PARTICLE_SPARK,
	PARTICLE_LAST = PARTICLE_RAINDROP_2D
};


code:
#include "common.h"

#include "main.h"
#include "PlayerSkin.h"
#include "TxdStore.h"
#include "rtbmp.h"
#include "ClumpModelInfo.h"
#include "VisibilityPlugins.h"
#include "World.h"
#include "PlayerInfo.h"
#include "CdStream.h"
#include "FileMgr.h"
#include "Directory.h"
#include "RwHelper.h"
#include "Timer.h"
#include "Lights.h"
#include "MemoryMgr.h"

RpClump *gpPlayerClump;
float gOldFov;

int CPlayerSkin::m_txdSlot;

void
FindPlayerDff(uint32 &offset, uint32 &size)
{
	int file;
	CDirectory::DirectoryInfo info;

	file = CFileMgr::OpenFile("models\\gta3.dir", "rb");

	do {
		if (!CFileMgr::Read(file, (char*)&info, sizeof(CDirectory::DirectoryInfo)))
			return;
	} while (strcasecmp("player.dff", info.name) != 0);

	offset = info.offset;
	size = info.size;
}

void
LoadPlayerDff(void)
{
	RwStream *stream;
	RwMemory mem;
	uint32 offset, size;
	uint8 *buffer;
	bool streamWasAdded = false;

	if (CdStreamGetNumImages() == 0) {
		CdStreamAddImage("models\\gta3.img");
		streamWasAdded = true;
	}

	FindPlayerDff(offset, size);
	buffer = (uint8*)RwMallocAlign(size << 11, 2048);
	CdStreamRead(0, buffer, offset, size);
	CdStreamSync(0);

	mem.start = buffer;
	mem.length = size << 11;
	stream = RwStreamOpen(rwSTREAMMEMORY, rwSTREAMREAD, &mem);

	if (RwStreamFindChunk(stream, rwID_CLUMP, nil, nil))
		gpPlayerClump = RpClumpStreamRead(stream);

	RwStreamClose(stream, &mem);
	RwFreeAlign(buffer);

	if (streamWasAdded)
		CdStreamRemoveImages();
}

void
CPlayerSkin::Initialise(void)
{
	m_txdSlot = CTxdStore::AddTxdSlot("skin");
	CTxdStore::Create(m_txdSlot);
	CTxdStore::AddRef(m_txdSlot);
}

void
CPlayerSkin::Shutdown(void)
{
	CTxdStore::RemoveTxdSlot(m_txdSlot);
}

RwTexture *
CPlayerSkin::GetSkinTexture(const char *texName)
{
	RwTexture *tex;
	RwRaster *raster;
	int32 width, height, depth, format;

	CTxdStore::PushCurrentTxd();
	CTxdStore::SetCurrentTxd(m_txdSlot);
	tex = RwTextureRead(texName, NULL);
	CTxdStore::PopCurrentTxd();
	if (tex != nil) return tex;

	if (strcmp(DEFAULT_SKIN_NAME, texName) == 0)
		sprintf(gString, "models\\generic\\player.bmp");
	else
		sprintf(gString, "skins\\%s.bmp", texName);

	if (RwImage *image = RtBMPImageRead(gString)) {
		RwImageFindRasterFormat(image, rwRASTERTYPETEXTURE, &width, &height, &depth, &format);
		raster = RwRasterCreate(width, height, depth, format);
		RwRasterSetFromImage(raster, image);

		tex = RwTextureCreate(raster);
		RwTextureSetName(tex, texName);
#ifdef FIX_BUGS
		RwTextureSetFilterMode(tex, rwFILTERLINEAR); // filtering bugfix from VC
#endif
		RwTexDictionaryAddTexture(CTxdStore::GetSlot(m_txdSlot)->texDict, tex);

		RwImageDestroy(image);
	}
	return tex;
}

void
CPlayerSkin::BeginFrontendSkinEdit(void)
{
	LoadPlayerDff();
	RpClumpForAllAtomics(gpPlayerClump, CClumpModelInfo::SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB);
	CWorld::Players[0].LoadPlayerSkin();
	gOldFov = CDraw::GetFOV();
	CDraw::SetFOV(30.0f);
}

void
CPlayerSkin::EndFrontendSkinEdit(void)
{
	RpClumpDestroy(gpPlayerClump);
	gpPlayerClump = NULL;
	CDraw::SetFOV(gOldFov);
}

void
CPlayerSkin::RenderFrontendSkinEdit(void)
{
	static float rotation = 0.0f;
	RwRGBAReal AmbientColor = { 0.65f, 0.65f, 0.65f, 1.0f };
	const RwV3d pos = { 1.35f, 0.35f, 7.725f };
	const RwV3d axis1 = { 1.0f, 0.0f, 0.0f };
	const RwV3d axis2 = { 0.0f, 0.0f, 1.0f };
	static uint32 LastFlash = 0;

	RwFrame *frame = RpClumpGetFrame(gpPlayerClump);

	if (CTimer::GetTimeInMillisecondsPauseMode() - LastFlash > 7) {
		rotation += 2.0f;
		if (rotation > 360.0f)
			rotation -= 360.0f;
		LastFlash = CTimer::GetTimeInMillisecondsPauseMode();
	}
	RwFrameTransform(frame, RwFrameGetMatrix(RwCameraGetFrame(Scene.camera)), rwCOMBINEREPLACE);
	RwFrameTranslate(frame, &pos, rwCOMBINEPRECONCAT);
	RwFrameRotate(frame, &axis1, -90.0f, rwCOMBINEPRECONCAT);
	RwFrameRotate(frame, &axis2, rotation, rwCOMBINEPRECONCAT);
	RwFrameUpdateObjects(frame);
	SetAmbientColours(&AmbientColor);
	RpClumpRender(gpPlayerClump);
}


code:
#pragma once

#define DEFAULT_SKIN_NAME "$$\"\""

class CPlayerSkin
{
	static int m_txdSlot;
public:
	static void Initialise();
	static void Shutdown();
	static RwTexture *GetSkinTexture(const char *texName);
	static void BeginFrontendSkinEdit();
	static void EndFrontendSkinEdit();
	static void RenderFrontendSkinEdit();
};


code:
#include "common.h"

#include "main.h"
#include "Lights.h"
#include "Camera.h"
#include "Weather.h"
#include "World.h"
#include "Collision.h"
#include "Sprite.h"
#include "Timer.h"
#include "PointLights.h"

int16 CPointLights::NumLights;
CRegisteredPointLight CPointLights::aLights[NUMPOINTLIGHTS];

void
CPointLights::InitPerFrame(void)
{
	NumLights = 0;
}

#define MAX_DIST 22.0f

void
CPointLights::AddLight(uint8 type, CVector coors, CVector dir, float radius, float red, float green, float blue, uint8 fogType, bool castExtraShadows)
{
	CVector dist;
	float distance;

	// The check is done in some weird way in the game
	// we're doing it a bit better here
	if(NumLights >= NUMPOINTLIGHTS)
		return;

	dist = coors - TheCamera.GetPosition();
	if(Abs(dist.x) < MAX_DIST && Abs(dist.y) < MAX_DIST){
		distance = dist.Magnitude();
		if(distance < MAX_DIST){
			aLights[NumLights].type = type;
			aLights[NumLights].fogType = fogType;
			aLights[NumLights].coors = coors;
			aLights[NumLights].dir = dir;
			aLights[NumLights].radius = radius;
			aLights[NumLights].castExtraShadows = castExtraShadows;
			if(distance < MAX_DIST*0.75f){
				aLights[NumLights].red = red;
				aLights[NumLights].green = green;
				aLights[NumLights].blue = blue;
			}else{
				float fade = 1.0f - (distance/MAX_DIST - 0.75f)*4.0f;
				aLights[NumLights].red = red * fade;
				aLights[NumLights].green = green * fade;
				aLights[NumLights].blue = blue * fade;
			}
			NumLights++;
		}
	}
}

float
CPointLights::GenerateLightsAffectingObject(Const CVector *objCoors)
{
	int i;
	float ret;
	CVector dist;
	float radius, distance;

	ret = 1.0f;
	for(i = 0; i < NumLights; i++){
		if(aLights[i].type == LIGHT_FOGONLY || aLights[i].type == LIGHT_FOGONLY_ALWAYS)
			continue;

		// same weird distance calculation. simplified here
		dist = aLights[i].coors - *objCoors;
		radius = aLights[i].radius;
		if(Abs(dist.x) < radius &&
		   Abs(dist.y) < radius &&
		   Abs(dist.z) < radius){

			distance = dist.Magnitude();
			if(distance < radius){

				float distNorm = distance/radius;
				if(aLights[i].type == LIGHT_DARKEN){
					// darken the object the closer it is
					ret *= distNorm;
				}else{
					float intensity;
					// distance fade
					if(distNorm < 0.5f)
						intensity = 1.0f;
					else
						intensity = 1.0f - (distNorm - 0.5f)/(1.0f - 0.5f);

					if(distance != 0.0f){
						CVector dir = dist / distance;

						if(aLights[i].type == LIGHT_DIRECTIONAL){
							float dot = -DotProduct(dir, aLights[i].dir);
							intensity *= Max((dot-0.5f)*2.0f, 0.0f);
						}

						if(intensity > 0.0f)
							AddAnExtraDirectionalLight(Scene.world,
								dir.x, dir.y, dir.z,
								aLights[i].red*intensity, aLights[i].green*intensity, aLights[i].blue*intensity);
					}
				}
			}
		}
	}

	return ret;
}

extern RwRaster *gpPointlightRaster;

void
CPointLights::RemoveLightsAffectingObject(void)
{
	RemoveExtraDirectionalLights(Scene.world);
}

// for directional fog
#define FOG_AREA_LENGTH 12.0f
#define FOG_AREA_WIDTH 5.0f
// for pointlight fog
#define FOG_AREA_RADIUS 9.0f

float FogSizes[8] = { 1.3f, 2.0f, 1.7f, 2.0f, 1.4f, 2.1f, 1.5f, 2.3f };

void
CPointLights::RenderFogEffect(void)
{
	int i;
	float fogginess;
	CColPoint point;
	CEntity *entity;
	float xmin, ymin;
	float xmax, ymax;
	int16 xi, yi;
	CVector spriteCoors;
	float spritew, spriteh;

	PUSH_RENDERGROUP("CPointLights::RenderFogEffect");

	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE);
	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE);
	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpPointlightRaster);

	for(i = 0; i < NumLights; i++){
		if(aLights[i].fogType != FOG_NORMAL && aLights[i].fogType != FOG_ALWAYS)
			continue;

		fogginess = aLights[i].fogType == FOG_NORMAL ? CWeather::Foggyness : 1.0f;
		if(fogginess == 0.0f)
			continue;

		if(aLights[i].type == LIGHT_DIRECTIONAL){

			// TODO: test this. haven't found directional fog so far

			float coors2X = aLights[i].coors.x + FOG_AREA_LENGTH*aLights[i].dir.x;
			float coors2Y = aLights[i].coors.y + FOG_AREA_LENGTH*aLights[i].dir.y;

			if(coors2X < aLights[i].coors.x){
				xmin = coors2X;
				xmax = aLights[i].coors.x;
			}else{
				xmax = coors2X;
				xmin = aLights[i].coors.x;
			}
			if(coors2Y < aLights[i].coors.y){
				ymin = coors2Y;
				ymax = aLights[i].coors.y;
			}else{
				ymax = coors2Y;
				ymin = aLights[i].coors.y;
			}

			xmin -= 5.0f;
			ymin -= 5.0f;
			xmax += 5.0f;
			ymax += 5.0f;

			for(xi = (int16)xmin - (int16)xmin % 4; xi <= (int16)xmax + 4; xi += 4){
				for(yi = (int16)ymin - (int16)ymin % 4; yi <= (int16)ymax + 4; yi += 4){
					// Some kind of pseudo random number?
					int r = (xi ^ yi)>>2 & 0xF;
					if((r & 1) == 0)
						continue;

					// Check if fog effect is close enough to directional line in x and y
					float dx = xi - aLights[i].coors.x;
					float dy = yi - aLights[i].coors.y;
					float dot = dx*aLights[i].dir.x + dy*aLights[i].dir.y;
					float distsq = sq(dx) + sq(dy);
					float linedistsq = distsq - sq(dot);
					if(dot > 0.0f && dot < FOG_AREA_LENGTH && linedistsq < sq(FOG_AREA_WIDTH)){
						CVector fogcoors(xi, yi, aLights[i].coors.z + 10.0f);
						if(CWorld::ProcessVerticalLine(fogcoors, fogcoors.z - 20.0f,
								point, entity, true, false, false, false, true, false, nil)){
							// Now same check again in xyz
							fogcoors.z = point.point.z + 1.3f;
							// actually we don't have to recalculate x and y, but the game does it that way
							dx = xi - aLights[i].coors.x;
							dy = yi - aLights[i].coors.y;
							float dz = fogcoors.z - aLights[i].coors.z;
							dot = dx*aLights[i].dir.x + dy*aLights[i].dir.y + dz*aLights[i].dir.z;
							distsq = sq(dx) + sq(dy) + sq(dz);
							linedistsq = distsq - sq(dot);
							if(dot > 0.0f && dot < FOG_AREA_LENGTH && linedistsq < sq(FOG_AREA_WIDTH)){
								float intensity = 158.0f * fogginess;
								// more intensity the smaller the angle
								intensity *= dot/Sqrt(distsq);
								// more intensity the closer to light source
								intensity *= 1.0f - sq(dot/FOG_AREA_LENGTH);
								// more intensity the closer to line
								intensity *= 1.0f - sq(Sqrt(linedistsq) / FOG_AREA_WIDTH);

								if(CSprite::CalcScreenCoors(fogcoors, &spriteCoors, &spritew, &spriteh, true)){
									float rotation = (CTimer::GetTimeInMilliseconds()&0x1FFF) * 2*3.14f / 0x2000;
									float size = FogSizes[r>>1];
									CSprite::RenderOneXLUSprite_Rotate_Aspect(spriteCoors.x, spriteCoors.y, spriteCoors.z,
										spritew * size, spriteh * size,
										aLights[i].red * intensity, aLights[i].green * intensity, aLights[i].blue * intensity,
										intensity, 1/spriteCoors.z, rotation, 255);
								}
							}
						}
					}
				}
			}

		}else if(aLights[i].type == LIGHT_POINT || aLights[i].type == LIGHT_FOGONLY || aLights[i].type == LIGHT_FOGONLY_ALWAYS){
			if(CWorld::ProcessVerticalLine(aLights[i].coors, aLights[i].coors.z - 20.0f,
					point, entity, true, false, false, false, true, false, nil)){

				xmin = aLights[i].coors.x - FOG_AREA_RADIUS;
				ymin = aLights[i].coors.y - FOG_AREA_RADIUS;
				xmax = aLights[i].coors.x + FOG_AREA_RADIUS;
				ymax = aLights[i].coors.y + FOG_AREA_RADIUS;

				for(xi = (int16)xmin - (int16)xmin % 2; xi <= (int16)xmax + 2; xi += 2){
					for(yi = (int16)ymin - (int16)ymin % 2; yi <= (int16)ymax + 2; yi += 2){
						// Some kind of pseudo random number?
						int r = (xi ^ yi)>>1 & 0xF;
						if((r & 1) == 0)
							continue;

						float dx = xi - aLights[i].coors.x;
						float dy = yi - aLights[i].coors.y;
						float lightdist = Sqrt(sq(dx) + sq(dy));
						if(lightdist < FOG_AREA_RADIUS){
							dx = xi - TheCamera.GetPosition().x;
							dy = yi - TheCamera.GetPosition().y;
							float camdist = Sqrt(sq(dx) + sq(dy));
							if(camdist < MAX_DIST){
								float intensity;
								// distance fade
								if(camdist < MAX_DIST/2)
									intensity = 1.0f;
								else
									intensity = 1.0f - (camdist - MAX_DIST/2) / (MAX_DIST/2);
								intensity *= 132.0f * fogginess;
								// more intensity the closer to light source
								intensity *= 1.0f - sq(lightdist / FOG_AREA_RADIUS);

								CVector fogcoors(xi, yi, point.point.z + 1.6f);
								if(CSprite::CalcScreenCoors(fogcoors, &spriteCoors, &spritew, &spriteh, true)){
									float rotation = (CTimer::GetTimeInMilliseconds()&0x3FFF) * 2*3.14f / 0x4000;
									float size = FogSizes[r>>1];
									CSprite::RenderOneXLUSprite_Rotate_Aspect(spriteCoors.x, spriteCoors.y, spriteCoors.z,
										spritew * size, spriteh * size,
										aLights[i].red * intensity, aLights[i].green * intensity, aLights[i].blue * intensity,
										intensity, 1/spriteCoors.z, rotation, 255);
								}
							}
						}
					}
				}
			}
		}
	}

	POP_RENDERGROUP();
}


code:
#pragma once

class CRegisteredPointLight
{
public:
	CVector coors;
	CVector dir;
	float radius;
	float red;
	float green;
	float blue;
	int8 type;
	int8 fogType;
	bool castExtraShadows;
};
VALIDATE_SIZE(CRegisteredPointLight, 0x2C);

class CPointLights
{
public:
	static int16 NumLights;
	static CRegisteredPointLight aLights[NUMPOINTLIGHTS];

	enum {
		LIGHT_POINT,
		LIGHT_DIRECTIONAL,
		LIGHT_DARKEN,	// no effects at all
		// these have only fog, otherwise no difference?
		// only used by CEntity::ProcessLightsForEntity it seems
		// and there used together with fog type
		LIGHT_FOGONLY_ALWAYS,
		LIGHT_FOGONLY,
	};
	enum {
		FOG_NONE,
		FOG_NORMAL,	// taken from Foggyness
		FOG_ALWAYS
	};

	static void InitPerFrame(void);
	static void AddLight(uint8 type, CVector coors, CVector dir, float radius, float red, float green, float blue, uint8 fogType, bool castExtraShadows);
	static float GenerateLightsAffectingObject(Const CVector *objCoors);
	static void RemoveLightsAffectingObject(void);
	static void RenderFogEffect(void);
};


code:
#include "common.h"

#include "RenderBuffer.h"

int32 TempBufferVerticesStored;
int32 TempBufferIndicesStored;

RwIm3DVertex TempBufferRenderVertices[TEMPBUFFERVERTSIZE];
RwImVertexIndex TempBufferRenderIndexList[TEMPBUFFERINDEXSIZE];

int RenderBuffer::VerticesToBeStored;
int RenderBuffer::IndicesToBeStored;

void
RenderBuffer::ClearRenderBuffer(void)
{
	TempBufferVerticesStored = 0;
	TempBufferIndicesStored = 0;
}

void
RenderBuffer::StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart)
{
	if(TempBufferIndicesStored + numIndices >= TEMPBUFFERINDEXSIZE)
		RenderStuffInBuffer();
	if(TempBufferVerticesStored + numVertices >= TEMPBUFFERVERTSIZE)
		RenderStuffInBuffer();
        *indexStart = &TempBufferRenderIndexList[TempBufferIndicesStored];
        *vertexStart = &TempBufferRenderVertices[TempBufferVerticesStored];
        IndicesToBeStored = numIndices;
        VerticesToBeStored = numVertices;
}

void
RenderBuffer::StopStoring(void)
{
	int i;
	for(i = TempBufferIndicesStored; i < TempBufferIndicesStored+IndicesToBeStored; i++)
		TempBufferRenderIndexList[i] += TempBufferVerticesStored;
	TempBufferIndicesStored += IndicesToBeStored;
	TempBufferVerticesStored += VerticesToBeStored;
}

void
RenderBuffer::RenderStuffInBuffer(void)
{
	if(TempBufferVerticesStored && RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){
		RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored);
		RwIm3DEnd();
	}
	ClearRenderBuffer();
}


code:
class RenderBuffer
{
public:
	static int VerticesToBeStored;
	static int IndicesToBeStored;
	static void ClearRenderBuffer(void);
	static void StartStoring(int numIndices, int numVertices, RwImVertexIndex **indexStart, RwIm3DVertex **vertexStart);
	static void StopStoring(void);
	static void RenderStuffInBuffer(void);
};

#define TEMPBUFFERVERTSIZE 256
#define TEMPBUFFERINDEXSIZE 1024

extern int32 TempBufferVerticesStored;
extern int32 TempBufferIndicesStored;
extern RwIm3DVertex TempBufferRenderVertices[TEMPBUFFERVERTSIZE];
extern RwImVertexIndex TempBufferRenderIndexList[TEMPBUFFERINDEXSIZE];