/**
* Particle system
* @version: 6-6-02
* @author Joseph 'Pcjoe' Florencio
*/

#include "hud.h"
#include "parsemsg.h"
#include "const.h"
#include "cl_util.h"
#include "r_efx.h"
#include "pm_defs.h"
#include "pmtrace.h"
#include "event_api.h"
#include "vrml.h"

// Added by Pcjoe, needed for ParticleTrace function to see if it hits water
extern "C"
{

// HLDM
bool UTIL_GetWaterSurface( Vector vecStart, Vector vecEnd, float* vecReturn);

}

// Used to see if particles are being generated underwater
#define ENG_PM_POINT_CONTENTS gEngfuncs.PM_PointContents

// TraceLines
enum
{
	DOWN,
	UP,
	BACK,
	FRONT,
};

DECLARE_MESSAGE(m_Powerup, Powerup);
DECLARE_MESSAGE(m_Powerup, StopPowerup);

// Added/Modified by Rick
powerupfx *g_PoweringupList = NULL;
powerupfx *g_LastPoweringupEntry = NULL;

powerupfx *GetPowerUpFXEntry(int EntIndex);
powerupfx *GetPrevPowerUpFXEntry(powerupfx *pEntry);
void DeleteFromPowerUpFXList(powerupfx *pEntry);
void CleanPowerUpFXList(void);
//End add by Rick

// Global Sprites

// Used for the TriApi dome
HSPRITE g_hsprPowerupFX;
// Dust
HSPRITE g_hsprPowerupDust1;
HSPRITE g_hsprPowerupDust2;
// Water Splashes
HSPRITE g_hsprPowerupWater1;
HSPRITE g_hsprPowerupWater2;
// Ripples
HSPRITE g_hsprPowerupRipple1;
HSPRITE g_hsprPowerupRipple2;
// Rocks
HSPRITE g_hsprPowerupRock1;
HSPRITE g_hsprPowerupRock2;
HSPRITE g_hsprPowerupRock3;
HSPRITE g_hsprPowerupRock4;
HSPRITE g_hsprPowerupRock5;
// Aura
HSPRITE g_hsprPowerupAura;
// NOTE - Not used in power up, needed to place it somewere
// Used for triangle over team-mates head
HSPRITE g_hsprWhite;
HSPRITE g_hsprHalo;
// Powerup VRML model
VRMLData gVRMLPowerupAura;


// Maxium amount of particles in each ring
int g_MaxParticles = 25;
// Maxium amount of rings on the screen.
int g_MaxParticleGroup = 10;
// Max amount of ripples
int g_MaxRipples = 10;
// Max amount of rocks
int g_MaxRocks = 50;
// Max amount of aura particles
int g_MaxAura = 80;

// Radius pulsate speed
#define RADIUS_PULSATE_SPEED 100
#define RADIUS_PULSATE_MIN 100
#define RADIUS_PULSATE_MAX 160

int CHudPowerup::Init(void)
{	HOOK_MESSAGE(Powerup);
	HOOK_MESSAGE(StopPowerup);

	InitHUDMap();

	gHUD.AddHudElem(this);
	m_iFlags |= HUD_ACTIVE;
	return 1;
}

void CHudPowerup::InitHUDMap(void)
{	// Time Difference
	m_flDrawTime = 0;

	// Modified by Rick
	while(g_PoweringupList)
	{
		powerupfx *temp = g_PoweringupList;
		g_PoweringupList = g_PoweringupList->next;
		delete temp;
	}

	g_PoweringupList = g_LastPoweringupEntry = NULL;
}

// Lets load our sprites up
int CHudPowerup::VidInit(void)
{	// Power up Sprites
	g_hsprPowerupFX = LoadSprite("sprites/powerup.spr");
	g_hsprPowerupDust1 = LoadSprite("sprites/dust1.spr");
	g_hsprPowerupDust2 = LoadSprite("sprites/dust2.spr");
	g_hsprPowerupWater1 = LoadSprite("sprites/watersplash1.spr");
	g_hsprPowerupWater2 = LoadSprite("sprites/watersplash2.spr");
	// Ripple Sprites
	g_hsprPowerupRipple1 = LoadSprite("sprites/ripplea.spr");
	g_hsprPowerupRipple2 = LoadSprite("sprites/rippleb.spr");
	// Rock Sprites
	g_hsprPowerupRock1 = LoadSprite("sprites/rock1.spr");
	g_hsprPowerupRock2 = LoadSprite("sprites/rock2.spr");
	g_hsprPowerupRock3 = LoadSprite("sprites/rock3.spr");
	g_hsprPowerupRock4 = LoadSprite("sprites/rock4.spr");
	g_hsprPowerupRock5 = LoadSprite("sprites/rock5.spr");
	// Aura Sprites
	g_hsprPowerupAura = LoadSprite("sprites/flare.spr");
	// Triangle over team-mates head
	g_hsprWhite = LoadSprite("sprites/white.spr");
	g_hsprHalo = LoadSprite("sprites/halo.spr");
	// 3d powerup aura
	ParseVRML("models/powerupaura.wrl", gVRMLPowerupAura);

	return 1;
}

// Collision Detection, predetermined collide point.
void CHudPowerup::ParticleCollisionSetup( group_particle &p, int iCollisionsize)
{
	// Set trace origin to particle origin
	vec3_t vecTrace = p.origin;

	// add 5 to the trace so it wont be "in the ground"
	vecTrace.z = vecTrace.z + 5;

	// Velocity for trace, multiplied by some insaine number
	vec3_t vecVeloctyTrace = vecTrace + 8192 * p.direction;

	// Trace Pointer
	pmtrace_t tr;

	// Trace line
	tr = *(gEngfuncs.PM_TraceLine((float *)&vecTrace, (float *)&vecVeloctyTrace , PM_TRACELINE_PHYSENTSONLY ,2, -1));


	// Has the trace hit something? If not, Disable Particle collision
	if(tr.fraction == 1.0)
	{	p.m_blCollision = 0;
	}

	// if so, enable particle collision and store particle collision spot
	else
	{	// If particle is generated inside the level, kill the particle immediantly
		if(tr.startsolid)
		{	p.m_blLiving = false;
			return;
		}
		else
			p.m_blLiving = true;

		// Collision detection is enabled
		p.m_blCollision = 1;
		// Add the endpos of the trace to our particle pointer
		p.endpos = tr.endpos;

		// If particle is generated close to the wall
		// (looks retarted bunched up agenst the wall) kill it
		if(Length(p.origin - p.endpos) <= iCollisionsize && iCollisionsize != 0)
		{	p.m_blLiving = false;
		}
		else
		{	p.m_blLiving = true;
		}
	}
}

vec3_t CHudPowerup::ParticleTrace(vec3_t origin, bool &blwater, int direction)
{
	vec3_t vecDirTrace;
	vec3_t endpos;

	// Angle Vectors
	vec3_t v_up, v_right, v_forward;
	vec3_t vecViewAngles;
	// Only calculate the angle vectors if were going to be using front or back (only ones that need them)
	if(direction == FRONT || direction == BACK)
	{	gEngfuncs.GetViewAngles( (float *)vecViewAngles);
		AngleVectors( vecViewAngles, v_forward, v_right, v_up );
	}

	// Set to 0 for the up and down trace so it just has to set the z direction, overwritten by the front + back trace
	vecDirTrace.x = 0;
	vecDirTrace.y = 0;

	// What direction shall we send the traceline in? Down, up, front, or back?
	if(direction == DOWN)
	{	vecDirTrace.z = -1;
	}
	else if(direction == UP)
	{	vecDirTrace.z = 1;
	}
	else if(direction == FRONT)
	{	VectorCopy(v_forward, vecDirTrace);
	}
	else if(direction == BACK)
	{	VectorCopy(v_forward * -1, vecDirTrace);
	}

	VectorNormalize(vecDirTrace);

	// Using some insainly high value to make the traceline go... far
	vec3_t veltraceOrigin = origin + 8192 * vecDirTrace;

	pmtrace_t trOrigin;
	// Lets trace directly down until we hit something
	trOrigin = *(gEngfuncs.PM_TraceLine((float *)&origin, (float *)&veltraceOrigin , PM_TRACELINE_PHYSENTSONLY ,2, -1));
	vec3_t endpos_water;

	// If water is present, use the surface as the origin. If not, just use the trace line result
	if(UTIL_GetWaterSurface(origin, trOrigin.endpos, endpos_water))
	{	endpos = endpos_water;
		blwater = true;
	}
	else
	{	endpos = trOrigin.endpos;
		blwater = false;
	}

	// Return position of trace
	return endpos;
}

void CHudPowerup::SubLife(float flTime, float flSubLife, float &flm_flSubTime, parGroup group[MAX_DUST_GROUP], float flAddTime, int MaxParticleGroup)
{
	// Time Controller to Subtract Particle Life
	if(flm_flSubTime < flTime)
	{
		flm_flSubTime = flTime + flAddTime;
		for(int i = 0; i < MaxParticleGroup; i++)
		{
			group[i].m_flLife -= flSubLife;
			// If life goes below zero, set back to zero
			if(group[i].m_flLife < 0)
				group[i].m_flLife = 0;
		}
	}
}

/*
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
Ripple - Water Particle System
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
By Pcjoe
*/

void CHudPowerup::AddSize(float flTime, powerupfx *pFXEntry, float &flm_flSizeTime, float flTimeInc, float flSizeIncrease, parGroup &group, int iMaxGroup)
{
	// Time Controller to Add Size
	if(flm_flSizeTime < flTime)
	{
		flm_flSizeTime = flTime + flTimeInc;
		
		for(int i = 0; i < iMaxGroup; i++)
		{
			pFXEntry->ripplegroup[i].m_iSize += flSizeIncrease;
		}
	}
}

void CHudPowerup::CreateRippleGroupParticle(powerupfx *pFXEntry, int group)
{
	// Player Pointer
	cl_entity_t *pClient = gEngfuncs.GetEntityByIndex(pFXEntry->m_iEntIndex);
	
	// Copy vectors
	vec3_t vecWaterOrigin;
	VectorCopy(pClient->origin, vecWaterOrigin);

	// Is the player under water?
	int contentPlayer = ENG_PM_POINT_CONTENTS(vecWaterOrigin,NULL);

	// If underwater, trace up, if in the air/ground, trace down
	int TraceDir;

	if(contentPlayer == CONTENT_WATER)
		TraceDir = UP;
	else
		TraceDir = DOWN;

	// bool used to see if dust is drawn or water(returns true if water is under player)
	bool water = NULL;

	// Vector to Store Velocity of vector trace
	vec3_t endpos = ParticleTrace(pClient->origin, water, TraceDir);

	// Is it being drawn ontop of water? If so, set the colors to "water" colors. If not, draw dust:)
	if(water)
	{	pFXEntry->ripplegroup[group].r = 0;
		pFXEntry->ripplegroup[group].g = 61;
		pFXEntry->ripplegroup[group].b = 173;
	}
	// Not over water
	else
	{	pFXEntry->ripplegroup[group].m_flLife = 0;
		return;
	}

	// Set particles life and size
	// - Not randomized since dust + water waves go out in even rings
	pFXEntry->ripplegroup[group].m_flLife = 1.45;
	pFXEntry->ripplegroup[group].m_iSize = 35.0;

	// Set the particle Origin, add one so it wont be in the water surface
	endpos.z += 1;

	// Random Sprite for Ripples
	pFXEntry->ripplegroup[group].dust[0].origin = endpos;
	if(gEngfuncs.pfnRandomLong(0, 2))
	{	pFXEntry->ripplegroup[group].m_iSprite = 1;
	}
	else
	{	pFXEntry->ripplegroup[group].m_iSprite = 0;
	}

}

void CHudPowerup::ParticleRippleThink(float flTime)
{
	powerupfx *pEntry = g_PoweringupList;

	while(pEntry)
	{	// If particle is enabled, or dying out
		if(pEntry->enabled || pEntry->m_flDyingTime != 0)
		{	
			// Time Controller to Subtract Particle Life
			SubLife(flTime, 0.1, pEntry->ParRippleTime.m_flSubTime, pEntry->ripplegroup, 0.05, g_MaxRipples);

			// Time Controller to Add Size
			AddSize(flTime, pEntry, pEntry->ParRippleTime.m_flSizeTime, 0.05, 22.25, pEntry->ripplegroup[g_MaxRipples], g_MaxRipples);

			// Let Rings out evenly
			// Make sure if particles are dying out, don't generate more!
			if(pEntry->ParRippleTime.m_flRingTime < flTime && pEntry->enabled)
			{
				pEntry->ParRippleTime.m_flRingTime = flTime + 0.475;

				// If particle ring is dead(don't draw particle rings that are still alive, looks gay...
				if(!pEntry->ripplegroup[pEntry->ParRippleTime.m_iCurrentRing].m_flLife)
				{	if(pEntry->enabled)
					{	CreateRippleGroupParticle(pEntry, pEntry->ParRippleTime.m_iCurrentRing);
					}
				}

				// Render next ring in the next loop
				pEntry->ParRippleTime.m_iCurrentRing += 1;
				if(pEntry->ParRippleTime.m_iCurrentRing == g_MaxRipples-1)
				{	pEntry->ParRippleTime.m_iCurrentRing = 0;
				}
			}

		}
		pEntry = pEntry->next;
	}
}

/*
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
Ripple - Water Particle System
End
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
*/

// Particle System 2, Dust/Water

/*
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
Powerup - Dust Particle System
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
By Pcjoe
*/

void CHudPowerup::CreateDustGroupParticle( powerupfx *pFXEntry, int group )
{	// Player Pointer
	cl_entity_t *pClient = gEngfuncs.GetEntityByIndex(pFXEntry->m_iEntIndex);

	// Used to locate the origin Above the players head
	vec3_t vecWater;

	// Make the trace start from the head, so the water particles
	// wont stop drawing until the body is submerged into the water
	VectorCopy(pClient->origin, vecWater);
	vecWater.z += 12;

	// If player is underwater, don't draw particles
	int contentPlayer = ENG_PM_POINT_CONTENTS(vecWater,NULL);
	if(contentPlayer == CONTENT_WATER)
		return;
	
	// bool used to see if dust is draw ontop of water or land(returns true if drawn over water)
	bool water = NULL;

	// Vector to Store Velocity of vector trace
	vec3_t endpos = ParticleTrace(vecWater, water, DOWN);

	// Add some so the sprites wont be drawing into the ground
	endpos.z += 1;

	// Is it being drawn ontop of water? If so, set the colors to "water" colors. If not, draw dust:)
	if(water)
	{
		pFXEntry->dustgroup[group].r = 0;
		pFXEntry->dustgroup[group].g = 61;
		pFXEntry->dustgroup[group].b = 173;
		pFXEntry->dustgroup[group].m_iSprite = gEngfuncs.pfnRandomLong(1, 2);

	}
	// Dust
	else
	{
		pFXEntry->dustgroup[group].r = 108;
		pFXEntry->dustgroup[group].g = 71;
		pFXEntry->dustgroup[group].b = 0;
		pFXEntry->dustgroup[group].m_iSprite = gEngfuncs.pfnRandomLong(-1, 0);
	}

	// Used for increasment of dust/water
	// Note, max distance dust will draw is 400
	float flLength = Length(vecWater - endpos);

	
	//  Max dist
	int iMaxDist = 1200;
	
	int iAlmostMaxDist = iMaxDist / 1.3;
	// Set particles life and size
	// - Not randomized since dust + water waves go out in even rings
	if(flLength <= iAlmostMaxDist)
	{	pFXEntry->dustgroup[group].m_flLife = 1.65 * (iMaxDist-flLength)/iMaxDist;
		pFXEntry->dustgroup[group].m_iSize = 9.0 * (iMaxDist-flLength)/iMaxDist;
		pFXEntry->dustgroup[group].m_flSpeed = 100 * (iMaxDist-flLength)/iMaxDist;
	}
	else if(flLength > iAlmostMaxDist && flLength < iMaxDist)
	{	pFXEntry->dustgroup[group].m_flLife = 1.65 * (iMaxDist-iAlmostMaxDist)/iMaxDist;
		pFXEntry->dustgroup[group].m_iSize = 9.0 * (iMaxDist-iAlmostMaxDist)/iMaxDist;
		pFXEntry->dustgroup[group].m_flSpeed = 100 * (iMaxDist-iAlmostMaxDist)/iMaxDist;
	}
	else
	{	pFXEntry->dustgroup[group].m_flLife = 0;
		pFXEntry->dustgroup[group].m_iSize = 0;
		pFXEntry->dustgroup[group].m_flSpeed = 0;
	}

	// Generate particles in specified ring...
	for(int i = 0; i < g_MaxParticles; i++)
	{	CreateDustParticle(pFXEntry, i, group, endpos);
	}
}

// Used to (re)create a particle
void CHudPowerup::CreateDustParticle( powerupfx *pFXEntry, int particle, int group, vec3_t origin )
{
	// Create Particle, Lets start by setting the origin
	pFXEntry->dustgroup[group].dust[particle].origin = origin;

	// --- Start of Velocity Calculations
	vec3_t vecDir;
	vecDir.x = gEngfuncs.pfnRandomFloat(-10, 10);
	vecDir.y = gEngfuncs.pfnRandomFloat(-10, 10);
	vecDir.z = 1.025;
	VectorNormalize(vecDir);
	pFXEntry->dustgroup[group].dust[particle].direction = vecDir * 4.75;
	// --- End of Velocity Cacluations

	// Move particle acouple units to in its specified direction, makes aura look more relistic
	pFXEntry->dustgroup[group].dust[particle].origin = pFXEntry->dustgroup[group].dust[particle].origin + pFXEntry->dustgroup[group].dust[particle].direction * 2;

	// Collision Detection
	ParticleCollisionSetup(pFXEntry->dustgroup[group].dust[particle], 32);
}

void CHudPowerup::ParticleDustThink(float flTime )
{
	powerupfx *pEntry = g_PoweringupList;
	while(pEntry)
	{	
		// If Particle system disabled, add one extra second onto all particle life
		// Used for if the user disables powerup, the particles still "fade" away,
		// instead of popping out of existance
		if(pEntry->enabled)
			pEntry->m_flDyingTime = flTime + 6.0;
		if(pEntry->m_flDyingTime < flTime)
			pEntry->m_flDyingTime = 0;

		// If particle is enabled, or dying out
		if(pEntry->enabled || pEntry->m_flDyingTime != 0)
		{
			// Time Controller to Add Direction
			// -- And
			// Collision Detection
			for(int i = 0; i < g_MaxParticleGroup; i++)
			{
				for(int s = 0; s < g_MaxParticles; s++)
				{
					// No collisions, Just add velocity
					if(pEntry->dustgroup[i].dust[s].m_blCollision = false)
					{	// Add Velocity to each of the Particles
						// Formula = Origin + ( Direction Normalized * speed * time difference )
						pEntry->dustgroup[i].dust[s].origin = pEntry->dustgroup[i].dust[s].origin + pEntry->dustgroup[i].dust[s].direction.Normalize() * pEntry->dustgroup[i].m_flSpeed * m_flTimeDifference;
					}
					// If particle has a collision position
					else
					{
						// If Particle is close its collision position(a wall or something), stop it in its tracks
						if(Length(pEntry->dustgroup[i].dust[s].origin - pEntry->dustgroup[i].dust[s].endpos) < 20)
						{	pEntry->dustgroup[i].dust[s].direction.x = 0.0;
							pEntry->dustgroup[i].dust[s].direction.y = 0.0;
						}
						else
						{	pEntry->dustgroup[i].dust[s].origin = pEntry->dustgroup[i].dust[s].origin + pEntry->dustgroup[i].dust[s].direction.Normalize() * pEntry->dustgroup[i].m_flSpeed * m_flTimeDifference;
						}
					}
				}
			}

			// Time Controller to Subtract Particle Life
			SubLife(flTime, 0.15, pEntry->ParDustTime.m_flSubTime, pEntry->dustgroup, 0.1, g_MaxParticleGroup);

			// Time Controller to Add Size
			if(pEntry->ParDustTime.m_flSizeTime < flTime)
			{
				pEntry->ParDustTime.m_flSizeTime = flTime + 0.2;
				for(int i = 0; i < g_MaxParticleGroup; i++)
				{
					pEntry->dustgroup[i].m_iSize += 8.5;
				}
			}	

			// Let Rings out evenly
			// Make sure if particles are dying out, don't generate more!
			if(pEntry->ParDustTime.m_flRingTime < flTime && pEntry->enabled)
			{
				pEntry->ParDustTime.m_flRingTime = flTime + 0.2;

				// If particle ring is dead(don't draw particle rings that are still alive, looks gay...
				if(!pEntry->dustgroup[pEntry->ParDustTime.m_iCurrentRing].m_flLife
						&& !(pEntry->ParDustTime.m_iCurrentRing >= g_MaxParticleGroup-1))
				{
					CreateDustGroupParticle(pEntry, pEntry->ParDustTime.m_iCurrentRing);
				}

				// Render next ring in the next loop
				pEntry->ParDustTime.m_iCurrentRing += 1;
				if(pEntry->ParDustTime.m_iCurrentRing >= g_MaxParticleGroup-1)
				{	pEntry->ParDustTime.m_iCurrentRing = 0;
				}

			}

		}
		pEntry = pEntry->next;
	}
}


/*
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
Dust Particle System
End
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
*/

// Particle System 3, Floating rocks

/*
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
Powerup - Rock Particle System
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
By Pcjoe
*/

void CHudPowerup::CreateRockGroupParticle( powerupfx *pFXEntry, int group )
{	// Player Pointer
	cl_entity_t *pClient = gEngfuncs.GetEntityByIndex(pFXEntry->m_iEntIndex);

	// bool used to see if Rock is draw ontop of water or land(returns true if drawn over water)
	bool water = NULL;

	// Vector to Store Velocity of vector trace
	vec3_t endpos = ParticleTrace(pClient->origin, water, DOWN);

	// Is it being drawn ontop of water? If so, set the colors to "water" colors. If not, draw Rock:)
	if(water)
	{	return;
	}
	// Rock transparency...
	else
	{	pFXEntry->rockgroup[group].r = 167;
		pFXEntry->rockgroup[group].g = 149;
		pFXEntry->rockgroup[group].b = 145;
	}

	// is there water under the rocks? If so, kill it
	int contentPlayer = ENG_PM_POINT_CONTENTS(endpos,NULL);
	if(contentPlayer == CONTENTS_WATER)
	{	pFXEntry->rockgroup[group].m_flLife = 0.0;
		pFXEntry->rockgroup[group].rock[0].m_blLiving = false;
		return;
	}
	pFXEntry->rockgroup[group].m_flLife = gEngfuncs.pfnRandomFloat(0.6, 1.1);
	pFXEntry->rockgroup[group].m_iSize = gEngfuncs.pfnRandomLong(1, 3);
	pFXEntry->rockgroup[group].m_iSprite = gEngfuncs.pfnRandomLong(0, 5);
	pFXEntry->rockgroup[group].m_flSpeed = 180;



	// Generate particle
	CreateRockParticle(pFXEntry, 0, group, endpos);

}

// Used to (re)create a particle
void CHudPowerup::CreateRockParticle( powerupfx *pFXEntry, int particle, int group, vec3_t origin )
{
	// Player Pointer
	cl_entity_t *pClient = gEngfuncs.GetEntityByIndex(pFXEntry->m_iEntIndex);

	// Create Particle, Lets start by setting the origin
	pFXEntry->rockgroup[group].rock[particle].origin = origin;

	// See if particle was geenrated deep in water
	{
		vec3_t vecTemp;
		VectorCopy(origin, vecTemp);
		vecTemp.z += 5;

		// If it was spawned out of this world, kill it...
		int contentPlayer = ENG_PM_POINT_CONTENTS(vecTemp,NULL);
		if(contentPlayer == CONTENTS_WATER)
		{	pFXEntry->rockgroup[group].rock[particle].m_blLiving = false;
			return;
		}
		else
		{	pFXEntry->rockgroup[group].rock[particle].m_blLiving = true;
		}
	}

	// If far up from the ground, dont draw.
	if(Length(pFXEntry->rockgroup[group].rock[particle].origin - pClient->origin) >= 94)
	{	pFXEntry->rockgroup[group].rock[particle].m_blLiving = false;
		return;
	}

	// --- Start of Velocity Calculations
	vec3_t vecDir;
	vecDir.x = 0;
	vecDir.y = 0;
	vecDir.z = 1;
	// --- End of Velocity Cacluations
	
	// Randomize rock spawning points
	vec3_t RockOrigin;
	VectorCopy(pFXEntry->rockgroup[group].rock[particle].origin, RockOrigin);
	
	RockOrigin.x += gEngfuncs.pfnRandomLong(-64, 64);
	RockOrigin.y += gEngfuncs.pfnRandomLong(-64, 64);

	// Set Rock origin
	VectorCopy(RockOrigin, pFXEntry->rockgroup[group].rock[particle].origin);

	// Do some random rotations for 3d rocks
	pFXEntry->rockgroup[group].rock[particle].vecRotation =
		vec3_t(gEngfuncs.pfnRandomLong(0, 360), gEngfuncs.pfnRandomLong(0, 360), gEngfuncs.pfnRandomLong(0, 360));

	// Used for collision detection
	// Disable Regular collision detection
	pFXEntry->rockgroup[group].rock[particle].m_blCollision = false;
	// Used for bouncing collision detection
	pFXEntry->rockgroup[group].m_blAddDyingLife = false;
	pFXEntry->rockgroup[group].m_iBounce = 0;
}

void CHudPowerup::ParticleRockThink(float flTime)
{
	powerupfx *pEntry = g_PoweringupList;
	while(pEntry)
	{
		// If particle is enabled, or dying out
		if(pEntry->enabled || pEntry->m_flDyingTime != 0)
		{
			// Time Controller to Add Direction
			// -- And
			// Bouncing
			
			for(int i = 0; i < g_MaxRocks; i++)
			{	// Formula = Origin + ( Direction Normalized * speed * time difference )
				if(pEntry->enabled && !pEntry->rockgroup[i].m_blAddDyingLife)
				{	pEntry->rockgroup[i].rock[0].origin = pEntry->rockgroup[i].rock[0].origin + (pEntry->rockgroup[i].rock[0].direction.Normalize() * pEntry->rockgroup[i].m_flSpeed * m_flTimeDifference);
				}

				// If dying out, set life to max, and make particles drop+bounce
				else if(!pEntry->enabled || pEntry->rockgroup[i].m_blAddDyingLife)
				{
					// If rock particle is still alive, restore its life.
					if(pEntry->rockgroup[i].m_flLife && pEntry->rockgroup[i].m_iBounce != 4)
					{	
						if(!pEntry->rockgroup[i].m_blAddDyingLife)
						{	pEntry->rockgroup[i].m_flLife = 4.8;
							pEntry->rockgroup[i].m_blAddDyingLife = true;
						}

						// Add Velocity + Speed
						pEntry->rockgroup[i].rock[0].origin = pEntry->rockgroup[i].rock[0].origin + (pEntry->rockgroup[i].rock[0].direction.Normalize() * pEntry->rockgroup[i].m_flSpeed * m_flTimeDifference);

						// This s basically so we detect when the particle hits a surface before the rock touches it(making it look better)
						vec3_t vecOriginAdvanced = pEntry->rockgroup[i].rock[0].origin;
						vecOriginAdvanced.z -= 2;

						// If speed is negative(going upwards), slowly reverse it, do the oppisite if not
						if(pEntry->rockgroup[i].m_flSpeed < 0)
						{	pEntry->rockgroup[i].m_flSpeed *= 1.1;
						}
						else if(pEntry->rockgroup[i].m_flSpeed > 0)
						{	pEntry->rockgroup[i].m_flSpeed *= 0.65;
						}

						// If rock is up in the air on its climax point, turn it
						if(pEntry->rockgroup[i].m_flSpeed > -6 && pEntry->rockgroup[i].m_flSpeed < 0)
						{	pEntry->rockgroup[i].m_flSpeed = 7;
						}
						if(pEntry->rockgroup[i].m_flSpeed < 6 && pEntry->rockgroup[i].m_flSpeed > 0)
						{	pEntry->rockgroup[i].m_flSpeed = -7;
						}

						// If rock hits the floor, reverse Z axis, so it will "bounce"
						int contentPlayer = ENG_PM_POINT_CONTENTS(vecOriginAdvanced,NULL);
						if(contentPlayer == CONTENTS_SOLID)
						{	pEntry->rockgroup[i].m_flSpeed *= -0.75;
							// Move it up/down a bit so it wont be in the ground still
							pEntry->rockgroup[i].rock[0].origin.z += (pEntry->rockgroup[i].rock[0].direction.z * pEntry->rockgroup[i].m_flSpeed * m_flTimeDifference)*1.5;

							pEntry->rockgroup[i].m_iBounce++;
							// If particle bounced 3 times, stop it
							if(pEntry->rockgroup[i].m_iBounce == 3)
							{	pEntry->rockgroup[i].m_flSpeed = 0;
							}
						}

						// Speed Checks, don't let the rocks go down at 90 mph:-)
						if(pEntry->rockgroup[i].m_flSpeed > 500)
						{	pEntry->rockgroup[i].m_flSpeed = 500;
						}
						else if(pEntry->rockgroup[i].m_flSpeed < -500)
						{	pEntry->rockgroup[i].m_flSpeed = -500;
						}

						// If particle fading time is almost complete, just kill off all rocks
						if(pEntry->m_flDyingTime <= flTime + 0.3)
						{	pEntry->rockgroup[i].m_flLife = 0;
						}
					}
				}
			}

			// Time Controller to Subtract Particle Life
			SubLife(flTime, 0.15, pEntry->ParRockTime.m_flSubTime, pEntry->rockgroup, 0.1, g_MaxRocks);

			// Generate Rocks at their own pace
			// Make sure if particles are dying out, don't generate more!
			if(pEntry->ParRockTime.m_flRingTime < flTime && pEntry->enabled)
			{
				pEntry->ParRockTime.m_flRingTime = flTime + 0.05;

				// Don't generate particles if particle system is off/fading away
				if(pEntry->enabled)
				{
					int iRockLoop = 2;
			
					// Spawn 2 particles per loop, more rocks this way
					for(int c = 0; c < iRockLoop; c ++)
					{	// If particle is dead(don't draw particle rings that are still alive, looks gay...
						if(!pEntry->rockgroup[pEntry->ParRockTime.m_iCurrentRing].m_flLife)
						{	CreateRockGroupParticle(pEntry, pEntry->ParRockTime.m_iCurrentRing);
						}
		
						// Set up next dust particle
						pEntry->ParRockTime.m_iCurrentRing++;
						if(pEntry->ParRockTime.m_iCurrentRing >= g_MaxRocks-1)
						{	pEntry->ParRockTime.m_iCurrentRing = 0;
						}
					}
				}
			}
		}
		pEntry = pEntry->next;
	}
}

/*
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
Rock Particle System
End
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
*/

// Particle system 4, Aura FX

/*
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
Aura Particle System
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
By Pcjoe
*/

void CHudPowerup::CreateAuraParticle( powerupfx *pFXEntry, int particle )
{
	// Player Pointer
	cl_entity_t *pClient = gEngfuncs.GetEntityByIndex(pFXEntry->m_iEntIndex);

	// Appear at feet level
	vec3_t origin;
	VectorCopy(pClient->origin, origin);

	// Create Particle, Lets start by setting the origin
	origin.x += gEngfuncs.pfnRandomFloat(-3, 3);
	origin.y += gEngfuncs.pfnRandomFloat(-3, 3);
	origin.z -= 15;
	VectorCopy(origin, pFXEntry->aura[particle].vecParticleOrigin);

	// --- Start of Velocity Calculations
	vec3_t vecDir;
	vecDir.x = gEngfuncs.pfnRandomFloat(-0.8, 0.8);
	vecDir.y = gEngfuncs.pfnRandomFloat(-0.8, 0.8);
	vecDir.z = 1;
	VectorNormalize(vecDir);
	pFXEntry->aura[particle].vecDirection = vecDir;
	pFXEntry->aura[particle].vecParticleOrigin = pFXEntry->aura[particle].vecParticleOrigin + (pFXEntry->aura[particle].vecDirection * 6);
	// --- End of Velocity Cacluations

	// Set Speed...
	pFXEntry->aura[particle].m_flSpeed = gEngfuncs.pfnRandomFloat(45, 55);
	// Set Size...
	pFXEntry->aura[particle].m_flSize = 16;
	// Set Life
	pFXEntry->aura[particle].m_flLife = 0.65;
	// Set colors
	pFXEntry->aura[particle].r = pFXEntry->r;
	pFXEntry->aura[particle].g = pFXEntry->g;
	pFXEntry->aura[particle].b = pFXEntry->b;
}


void CHudPowerup::ParticleAuraThink( float flTime )
{
	powerupfx *pEntry = g_PoweringupList;
	while(pEntry)
	{	// If particle is enabled, or dying out
		if(pEntry->enabled || pEntry->m_flDyingTime != 0)
		{	// Time Controller to Add Direction
			// -- And
			// Collision Detection

			// Used for moving the particle, equal movment at any FPS
			for(int i = 0; i < g_MaxAura; i++)
			{	vec3_t vecReverseDirection = pEntry->aura[i].vecDirection;
				vecReverseDirection = vecReverseDirection * (-0.5 * m_flTimeDifference);
				pEntry->aura[i].vecDirection.x *= vecReverseDirection.x;
				pEntry->aura[i].vecDirection.y *= vecReverseDirection.y;

				pEntry->aura[i].vecParticleOrigin = pEntry->aura[i].vecParticleOrigin + (pEntry->aura[i].vecDirection * pEntry->aura[i].m_flSpeed * m_flTimeDifference);
			}

			// Time Controller to Subtract Particle Life + Size
			if(pEntry->ParAuraTime.m_flSubTime < flTime)
			{
				pEntry->ParAuraTime.m_flSubTime = flTime + 0.075;
				for(int i = 0; i < g_MaxAura; i++)
				{
					pEntry->aura[i].m_flLife -= 0.1;
					// If life goes below zero, set back to zero
					if(pEntry->aura[i].m_flLife < 0)
					{	pEntry->aura[i].m_flLife = 0;
					}
				}
			}

			// Time Controller to Decrease Size
			if(pEntry->ParAuraTime.m_flSizeTime < flTime)
			{
				pEntry->ParAuraTime.m_flSizeTime = flTime + 0.2;
				for(int i = 0; i < g_MaxAura; i++)
				{
					pEntry->aura[i].m_flSize -= 0.2;
					if(pEntry->aura[i].m_flSize <= 0)
					{	pEntry->aura[i].m_flSize = 0;
						pEntry->aura[i].m_flLife = 0;
					}
				}
			}

			// Generate aura particles at their own pace
			// Make sure if particles are dying out, don't generate more!
			if(pEntry->ParAuraTime.m_flRingTime < flTime && pEntry->enabled)
			{
				pEntry->ParAuraTime.m_flRingTime = flTime + (g_MaxAura / 80) * 0.01;
				// Set 2 particles out per loop
				for(int c = 0; c < 2; c++)
				{	// If particle is dead, don't draw particle rings that are still alive.
					if(!pEntry->aura[pEntry->ParAuraTime.m_iCurrentRing].m_flLife && pEntry->enabled)
					{	CreateAuraParticle( pEntry, pEntry->ParAuraTime.m_iCurrentRing);
					}	

					// Render next ring in the next loop
					pEntry->ParAuraTime.m_iCurrentRing++;
					if(pEntry->ParAuraTime.m_iCurrentRing >= g_MaxAura-1)
					{	pEntry->ParAuraTime.m_iCurrentRing = 0;
					}
				}
			}

			// Update light position and life
			if(pEntry->dl)
			{	cl_entity_t *pClient = gEngfuncs.GetEntityByIndex(pEntry->m_iEntIndex);
				VectorCopy(pClient->origin, pEntry->dl->origin);
				pEntry->dl->die = gEngfuncs.GetClientTime() + 99999;

				// Pulsate radius
				if(pEntry->m_bRadiusPulsate)
				{	pEntry->dl->radius += RADIUS_PULSATE_SPEED * m_flTimeDifference;
					if(pEntry->dl->radius > RADIUS_PULSATE_MAX)
					{	pEntry->dl->radius = RADIUS_PULSATE_MAX;
						pEntry->m_bRadiusPulsate = false;
					}
				}
				else
				{	pEntry->dl->radius -= RADIUS_PULSATE_SPEED * m_flTimeDifference;
					if(pEntry->dl->radius < RADIUS_PULSATE_MIN)
					{	pEntry->dl->radius = RADIUS_PULSATE_MIN;
						pEntry->m_bRadiusPulsate = true;
					}
				}
			}
		}
		pEntry = pEntry->next;
	}
}

/*
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
Aura Particle System
End
/-/-/-/-/-/-/-/--/-/-/-/-/-/-/
*/

void CHudPowerup::Think( void )
{
	// Set Time Difference
	SetTimeDifference( gEngfuncs.GetClientTime() );

	// Clean entries from list which shouldn't drawed anymore
	CleanPowerUpFXList();

	// Particle Thinks
	ParticleDustThink( gEngfuncs.GetClientTime() );
	ParticleRippleThink( gEngfuncs.GetClientTime() );
	ParticleRockThink( gEngfuncs.GetClientTime() );
	ParticleAuraThink( gEngfuncs.GetClientTime() );

	// Reset it for next loop
	ResetTimeDifference( gEngfuncs.GetClientTime() );
}

int CHudPowerup::MsgFunc_Powerup(const char *pszName,  int iSize, void *pbuf )
{	BEGIN_READ( pbuf, iSize );
	int index = READ_BYTE();
	int r = READ_BYTE();
	int g = READ_BYTE();
	int b = READ_BYTE();

	powerupfx *pEntry = GetPowerUpFXEntry(index);
	dlight_t *dl;

	if (!pEntry)
	{
		if (!g_PoweringupList)
		{
			g_PoweringupList = g_LastPoweringupEntry = new powerupfx(index);
			g_PoweringupList->enabled = true;
			g_PoweringupList->m_bRadiusPulsate = true;
			g_PoweringupList->r = r;
			g_PoweringupList->g = g;
			g_PoweringupList->b = b;
			dl = g_PoweringupList->dl = gEngfuncs.pEfxAPI->CL_AllocDlight (0);
		}
		else
		{
			g_LastPoweringupEntry->next = new powerupfx(index);
			g_LastPoweringupEntry = g_LastPoweringupEntry->next;
			g_LastPoweringupEntry->enabled = true;
			g_LastPoweringupEntry->m_bRadiusPulsate = true;
			g_LastPoweringupEntry->r = r;
			g_LastPoweringupEntry->g = g;
			g_LastPoweringupEntry->b = b;
			dl = g_LastPoweringupEntry->dl = gEngfuncs.pEfxAPI->CL_AllocDlight (0);
		}
	}
	else
	{
		pEntry->r = r;
		pEntry->g = g;
		pEntry->b = b;
		pEntry->enabled = true;
		pEntry->m_bRadiusPulsate = true;
		dl = pEntry->dl = gEngfuncs.pEfxAPI->CL_AllocDlight (0);
	}


	// Entity Pointer
	cl_entity_t *pClient = gEngfuncs.GetEntityByIndex(index);
	gEngfuncs.pEventAPI->EV_PlaySound( index, pClient->origin, CHAN_WEAPON, "weapons/powerup.wav", 1, ATTN_NORM, 0, PITCH_NORM );

	// setup light
	VectorCopy (pClient->origin, dl->origin);
	dl->radius = RADIUS_PULSATE_MIN;
	dl->color.r = r;
	dl->color.g = g;
	dl->color.b = b;
	dl->die = gEngfuncs.GetClientTime() + 99999;

	return 1;
}

int CHudPowerup::MsgFunc_StopPowerup(const char *pszName,  int iSize, void *pbuf )
{	BEGIN_READ( pbuf, iSize );
	int index = READ_BYTE();
	// Kill entities powerup aura/dust/whatever
	powerupfx *pEntry = GetPowerUpFXEntry(index);
	if (pEntry)
	{	//DeleteFromPowerUpFXList(pEntry);
		pEntry->enabled = false;
		cl_entity_t *pClient = gEngfuncs.GetEntityByIndex(index);
		gEngfuncs.pEventAPI->EV_PlaySound( index, pClient->origin, CHAN_WEAPON, "common/null.wav", 1, ATTN_NORM, 0, PITCH_NORM );
		// Kill light
		pEntry->dl->die = gEngfuncs.GetClientTime() + 3;
		pEntry->dl->decay = 128;
		pEntry->dl = NULL;
	}
	return 1;
}

// Added by Rick
powerupfx::powerupfx(int EntIndex) : m_iEntIndex(EntIndex)
{
	// Dust/Water
	ParDustTime.m_flSubTime = 0;
	ParDustTime.m_flSizeTime = 0;
	ParDustTime.m_flRingTime = 0;
	ParDustTime.m_iCurrentRing = 0;

		// Ripples
	ParRippleTime.m_flSubTime = 0;
	ParRippleTime.m_flSizeTime = 0;
	ParRippleTime.m_flRingTime = 0;
	ParRippleTime.m_iCurrentRing = 0;

	// Rocks
	ParRockTime.m_flSubTime = 0;
	ParRockTime.m_flSizeTime = 0;
	ParRockTime.m_flRingTime = 0;
	ParRockTime.m_iCurrentRing = 0;

	// Aura
	ParAuraTime.m_flSubTime = 0;
	ParAuraTime.m_flSizeTime = 0;
	ParAuraTime.m_flRingTime = 0;
	ParAuraTime.m_iCurrentRing = 0;

	// Make sure all particlesystems are off/dead

	enabled = false;

	// Dust
	int n = 0;
	for(n = 0; n < g_MaxParticleGroup; n++)
	{
		memset(&dustgroup[n], 0, sizeof(dustgroup[0]));
	}

	// Ripples
	for(n = 0; n < g_MaxRipples; n++)
	{	memset(&ripplegroup[n], 0, sizeof(ripplegroup[0]));
	}

	// Rocks
	for(n = 0; n < g_MaxRocks; n++)
	{	memset(&rockgroup[n], 0, sizeof(rockgroup[0]));
	}

	// Aura
	for(n = 0; n < g_MaxAura; n++)
	{	memset(&aura[n], 0, sizeof(aura[0]));
	}

	next = NULL;
	dl = NULL;
}

powerupfx *GetPowerUpFXEntry(int EntIndex)
{
	powerupfx *pEntry = g_PoweringupList;
	while(pEntry)
	{
		if (pEntry->m_iEntIndex == EntIndex)
			return pEntry;

		pEntry = pEntry->next;
	}

	return NULL;
}

void DeleteFromPowerUpFXList(powerupfx *pEntry)
{
	powerupfx *pTemp;
	if (!g_PoweringupList || !pEntry)
		return;

	if (g_PoweringupList == pEntry) // first node
	{
		if (g_PoweringupList == g_LastPoweringupEntry)
			g_LastPoweringupEntry = NULL;

		g_PoweringupList = g_PoweringupList->next;

		pEntry->next = NULL;
		delete pEntry;
		return;
	}

	if (pEntry == g_LastPoweringupEntry) // last node
	{
		pTemp = g_LastPoweringupEntry;
		g_LastPoweringupEntry = GetPrevPowerUpFXEntry(pEntry);
		g_LastPoweringupEntry->next = NULL;

		pTemp->next = NULL;
		delete pTemp;
		return;
	}

	// the node is somewhere in the middle
	pTemp = GetPrevPowerUpFXEntry(pEntry);

	if (!pTemp)
		return;

	pTemp->next = pEntry->next; // unlink node
	pEntry->next = NULL;
	delete pEntry;
}

powerupfx *GetPrevPowerUpFXEntry(powerupfx *pEntry)
{
	powerupfx *pNode = g_PoweringupList;
	while(pNode)
	{
		if (pNode->next && (pNode->next == pEntry))
			return pNode;

		pNode = pNode->next;
	}
	return NULL;
}

void CleanPowerUpFXList()
{
	powerupfx *pEntry = g_PoweringupList;
	powerupfx *pTemp;

	while(pEntry)
	{
		pTemp = pEntry->next;

		if (!pEntry->enabled && !pEntry->m_flDyingTime)
			DeleteFromPowerUpFXList(pEntry);

		pEntry = pTemp;
	}
}

// End add by Rick
