// Kelp Heavy Weaponry, Feb 2001
// smoke grenade


#include "extdll.h"
#include "mod_utilities.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "soundent.h"
#include "nodes.h"
#include "player.h"
#include "decals.h"

#define WEAPON_SMOKEGRENADE	19
#define SMOKETIME 16	// how long it spews

#define SMOKEG_SLOT 4
#define SMOKEG_POSITION 4
#define SMOKEG_WEIGHT 25

#define SMOKEG_MODEL_1STPERSON "models/v_grenade.mdl"
#define SMOKEG_MODEL_3RDPERSON "models/p_grenade.mdl" 
#define SMOKEG_MODEL_WORLD "models/w_grenade.mdl" 
#define SMOKEG_SOUND_VOLUME 0.25
#define SMOKEG_BODYHIT_VOLUME 128
#define SMOKEG_WALLHIT_VOLUME 512
#define SMOKEG_FIRE_DELAY 2.0 
#define SMOKEG_DEFAULT_GIVE 5
#define SMOKEG_CLIPSIZE 1
#define SMOKEG_MAX_AMMO 10

enum handgrenade_e {
	HANDGRENADE_IDLE = 0,
	HANDGRENADE_FIDGET,
	HANDGRENADE_PINPULL,
	HANDGRENADE_THROW1,	// toss
	HANDGRENADE_THROW2,	// medium
	HANDGRENADE_THROW3,	// hard
	HANDGRENADE_HOLSTER,
	HANDGRENADE_DRAW
};

// definition of smoke grenade
// this is the weapon the player holds and uses
class CSmokeGrenade : public CBasePlayerWeapon
	{
	public:
	void Spawn( void );
	void Precache( void );
	int iItemSlot( void ) { return 5; }
	int GetItemInfo(ItemInfo *p);

	void PrimaryAttack( void );
	BOOL Deploy( void );
	BOOL CanHolster( void );
	void Holster( void );
	void WeaponIdle( void );

	float m_flStartThrow;
	float m_flReleaseThrow;
	};
LINK_ENTITY_TO_CLASS( weapon_smokegrenade, CSmokeGrenade );

// the annoying smoking grenade monster!
// this guy is spawned when the player throws the grenade
class CSmoker : public CGrenade
{
public:
	void Spawn( void );

	static CSmoker *ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time );

	void BounceTouch( CBaseEntity *pOther );
	void TumbleThink( void );
	void SmokeThink(void);
	void SputterOut(void);
	
	virtual void BounceSound( void );
	virtual int	BloodColor( void ) { return DONT_BLEED; }
	virtual void Killed( entvars_t *pevAttacker, int iGib );
	
	BOOL m_Detonated;
	BOOL m_fRegisteredSound;
	float m_SmokeTime;
};
LINK_ENTITY_TO_CLASS( smoking_grenade, CSmoker);


//---------------------------------------------------------
// implementation
void CSmokeGrenade::Spawn( )
	{
	Precache();
	m_iId = WEAPON_SMOKEGRENADE;
	SET_MODEL(ENT(pev), SMOKEG_MODEL_WORLD);

	pev->dmg = gSkillData.plrDmgHandGrenade;
	m_iDefaultAmmo = SMOKEG_DEFAULT_GIVE;
	FallInit(); // get ready to fall down.
	}

void CSmokeGrenade::Precache( void )
	{
	PRECACHE_MODEL(SMOKEG_MODEL_WORLD);
	PRECACHE_MODEL(SMOKEG_MODEL_1STPERSON);
	PRECACHE_MODEL(SMOKEG_MODEL_3RDPERSON);
	PRECACHE_SOUND ("weapons/xbow_fly1.wav"); // yeah yeah
	PRECACHE_SOUND ("common/null.wav");
	}

int CSmokeGrenade::GetItemInfo(ItemInfo *p)
	{
	p->pszName = STRING(pev->classname);
	p->pszAmmo1 = "Smoke grenade";
	p->iMaxAmmo1 = SMOKEG_MAX_AMMO;
	p->pszAmmo2 = NULL;
	p->iMaxAmmo2 = -1;
	p->iMaxClip = WEAPON_NOCLIP;
	p->iSlot = SMOKEG_SLOT;
	p->iPosition = SMOKEG_POSITION;
	p->iId = m_iId = WEAPON_SMOKEGRENADE;
	p->iWeight = 10;
	p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE;

	return 1;
	}

BOOL CSmokeGrenade::Deploy( )
	{
	m_flReleaseThrow = -1;
	return DefaultDeploy( SMOKEG_MODEL_1STPERSON, SMOKEG_MODEL_3RDPERSON, HANDGRENADE_DRAW, "gren" );
	} 

BOOL CSmokeGrenade::CanHolster( void )
	{
	// can only holster hand grenades when not primed!
	return ( m_flStartThrow == 0 );
	}

void CSmokeGrenade::Holster( )
	{
	m_pPlayer->m_flNextAttack = gpGlobals->time + 0.5;

	if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType])
		SendWeaponAnim( HANDGRENADE_HOLSTER );
	else
		{
		// no more grenades!
		m_pPlayer->pev->weapons &= ~(1<<WEAPON_SMOKEGRENADE);
		SetThink(&CSmokeGrenade::DestroyItem );
		pev->nextthink = gpGlobals->time + 0.1;
		}

	EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM);
	}
 

void CSmokeGrenade::PrimaryAttack()
	{
	if (!m_flStartThrow && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0)
		{
		m_flStartThrow = gpGlobals->time;
		m_flReleaseThrow = 0;

		SendWeaponAnim( HANDGRENADE_PINPULL );
		m_flTimeWeaponIdle = gpGlobals->time + 0.5;
		}
	}
 

void CSmokeGrenade::WeaponIdle( void )
	{
	if (m_flReleaseThrow == 0)
		m_flReleaseThrow = gpGlobals->time;

	if (m_flTimeWeaponIdle > gpGlobals->time)
		return;

	if (m_flStartThrow)
		{
		Vector angThrow = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle;

		if (angThrow.x < 0)
			angThrow.x = -10 + angThrow.x * ((90 - 10) / 90.0);
		else
			angThrow.x = -10 + angThrow.x * ((90 + 10) / 90.0);

		float flVel = (90 - angThrow.x) * 4;
		if (flVel > 500)
			flVel = 500;

		UTIL_MakeVectors( angThrow );
		Vector vecSrc = m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16;
		Vector vecThrow = gpGlobals->v_forward * flVel + m_pPlayer->pev->velocity;

		// alway explode 2 seconds after the pin was pulled
		float time = m_flStartThrow - gpGlobals->time + 2.0;
		if (time < 0)
			time = 0;

		CSmoker::ShootTimed( m_pPlayer->pev, vecSrc, vecThrow, time);

		if (flVel < 500)
			{
			SendWeaponAnim( HANDGRENADE_THROW1 );
			}
		else if (flVel < 1000)
			{
			SendWeaponAnim( HANDGRENADE_THROW2 );
			}
		else
			{
			SendWeaponAnim( HANDGRENADE_THROW3 );
			}

		// player "shoot" animation
		m_pPlayer->SetAnimation( PLAYER_ATTACK1 );

		m_flStartThrow = 0;
		m_flNextPrimaryAttack = gpGlobals->time + 0.5;
		m_flTimeWeaponIdle = gpGlobals->time + 0.5;

		m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--;

		if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] )
			{
			// just threw last grenade
			// set attack times in the future, and weapon idle in the future so we can see the whole throw
			// animation, weapon idle will automatically retire the weapon for us.
			m_flTimeWeaponIdle = m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->time + 0.5;// ensure that the animation can finish playing
			}
		return;
		}
	else if (m_flReleaseThrow > 0)
		{
		// we've finished the throw, restart.
		m_flStartThrow = 0;

		if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType])
			{
			SendWeaponAnim( HANDGRENADE_DRAW );
			}
		else
			{
			RetireWeapon();
			return;
			}

		m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15);
		m_flReleaseThrow = -1;
	return;
	}

	if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType])
		{
		int iAnim;
		float flRand = RANDOM_FLOAT(0, 1);
		if (flRand <= 0.75)
			{
			iAnim = HANDGRENADE_IDLE;
			m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 );// how long till we do this again.
			}
		else
			{
			iAnim = HANDGRENADE_FIDGET;
			m_flTimeWeaponIdle = gpGlobals->time + 75.0 / 30.0;
			}
		SendWeaponAnim( iAnim );
		}
	}

//--------------------------------------------------------------

// timed explosion
CSmoker* CSmoker::ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time )
{
	CSmoker *pGrenade = GetClassPtr((CSmoker *)NULL);
	pGrenade->Spawn();
	UTIL_SetOrigin( pGrenade->pev, vecStart);
	pGrenade->pev->velocity = vecVelocity;
	pGrenade->pev->angles = UTIL_VecToAngles(pGrenade->pev->velocity);
	pGrenade->pev->owner = ENT(pevOwner);
	
	pGrenade->SetTouch(&CSmoker::BounceTouch);	// Bounce if touched
	
	pGrenade->pev->dmgtime = gpGlobals->time + time;
	pGrenade->m_SmokeTime = pGrenade->pev->dmgtime + SMOKETIME; // smoke for 12 seconds
	pGrenade->SetThink(&CSmoker::TumbleThink);
	pGrenade->pev->nextthink = gpGlobals->time + 0.1;
	if (time < 0.1)
	{
		pGrenade->pev->nextthink = gpGlobals->time;
		pGrenade->pev->velocity = Vector( 0, 0, 0);
	}
		
	pGrenade->pev->sequence = RANDOM_LONG( 3, 6);
	pGrenade->pev->framerate = 1.0;

	pGrenade->pev->gravity = 0.5;
	pGrenade->pev->friction = 0.8;

	SET_MODEL(ENT(pGrenade->pev), "models/w_grenade.mdl");
	pGrenade->pev->dmg = 100;

	return pGrenade;
}

// bounce ominously
void CSmoker::TumbleThink( void )
{
	if (!IsInWorld())
		{
		UTIL_Remove( this);
		return;
		}

	StudioFrameAdvance();
	pev->nextthink = gpGlobals->time + 0.1;

	if (pev->dmgtime - 1 < gpGlobals->time)
		{
		CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * (pev->dmgtime - gpGlobals->time), 400, 0.1);
		}

	if (pev->dmgtime <= gpGlobals->time)
		{
		SetThink(&CSmoker::SmokeThink);
		}
	if (pev->waterlevel != 0)
		{
		pev->velocity = pev->velocity * 0.5;
		pev->framerate = 0.2;
		}
}


void CSmoker::SmokeThink( void )
	{
	// keep velocity from our throw, but start spewing smoke
	if (m_SmokeTime < gpGlobals->time)
		{
		EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/null.wav", 1 , ATTN_NORM);
		pev->effects |= EF_NODRAW;
		SetThink(&CSmoker::SputterOut);
		pev->velocity = g_vecZero;
		pev->nextthink = gpGlobals->time + 0.3;

		return;
		}
	else
		{
		EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/xbow_fly1.wav", .3 , ATTN_NORM);
		}

	// Make some smoke
	MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin );
		WRITE_BYTE( TE_SMOKE );
		WRITE_COORD( pev->origin.x );
		WRITE_COORD( pev->origin.y );
		WRITE_COORD( pev->origin.z );
		WRITE_SHORT( g_sModelIndexSmoke );
		WRITE_BYTE( (int)((pev->dmg - 50) * 1.80) ); // a nice big cloud
		WRITE_BYTE( 12 ); // framerate
	MESSAGE_END();

	pev->nextthink = gpGlobals->time + 0.3;
	}

// we've finished smoking. Leave a nasty stain.
void CSmoker::SputterOut(void)
	{
	TraceResult tr;
	UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ),  ignore_monsters, ENT(pev), & tr);

	if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 )
		UTIL_DecalTrace( &tr, DECAL_SCORCH1);
	else
		UTIL_DecalTrace( &tr, DECAL_SCORCH2);

	// make a much smaller little puff
	if (UTIL_PointContents ( pev->origin ) == CONTENTS_WATER)
		{
		UTIL_Bubbles( pev->origin - Vector( 64, 64, 64 ), pev->origin + Vector( 64, 64, 64 ), 100);
		}
	else
		{
		MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin);
			WRITE_BYTE( TE_SMOKE);
			WRITE_COORD( pev->origin.x);
			WRITE_COORD( pev->origin.y);
			WRITE_COORD( pev->origin.z);
			WRITE_SHORT( g_sModelIndexSmoke);
			WRITE_BYTE( (int) ((pev->dmg - 50) * 0.40)); // scale * 10
			WRITE_BYTE( 12 ); // framerate
		MESSAGE_END();
		}
	UTIL_Remove( this);
	}


void CSmoker::BounceTouch( CBaseEntity *pOther )
	{
	// don't hit the guy that launched this grenade
	if ( pOther->edict() == pev->owner )
		return;

	// only do damage if we're moving fairly fast
	if (m_flNextAttack < gpGlobals->time && pev->velocity.Length() > 100)
		{
		entvars_t *pevOwner = VARS( pev->owner);
		if (pevOwner)
			{
			TraceResult tr = UTIL_GetGlobalTrace();
			ClearMultiDamage();
			pOther->TraceAttack(pevOwner, 1, gpGlobals->v_forward, &tr, DMG_CLUB); 
			ApplyMultiDamage( pev, pevOwner);
			}
		m_flNextAttack = gpGlobals->time + 1.0; // debounce
		}

	Vector vecTestVelocity;
	vecTestVelocity = pev->velocity; 
	vecTestVelocity.z *= 0.45;

	if ( !m_fRegisteredSound && vecTestVelocity.Length() <= 60 )
		{
		// grenade is moving really slow. It's probably very close to where it will ultimately stop moving. 
		// go ahead and emit the danger sound.
		// register a radius louder than the explosion, so we make sure everyone gets out of the way
		CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, pev->dmg / 0.4, 0.3);
		m_fRegisteredSound = TRUE;
		}

	if (pev->flags & FL_ONGROUND)
		{
		pev->velocity = pev->velocity * 0.8;
		pev->sequence = RANDOM_LONG( 1, 1);
		}
	else
		{
		BounceSound();
		}
	pev->framerate = pev->velocity.Length() / 200.0;
	if (pev->framerate > 1.0)
		pev->framerate = 1;
	else if (pev->framerate < 0.5)
		pev->framerate = 0;

	}
	
void CSmoker::Killed( entvars_t *pevAttacker, int iGib )
{
	SputterOut();
}


void CSmoker::BounceSound( void )
{
	switch ( RANDOM_LONG( 0, 2 ) )
	{
	case 0:	EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit1.wav", 0.25, ATTN_NORM);	break;
	case 1:	EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit2.wav", 0.25, ATTN_NORM);	break;
	case 2:	EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit3.wav", 0.25, ATTN_NORM);	break;
	}
}

void CSmoker::Spawn( void )
{
	pev->movetype = MOVETYPE_BOUNCE;
	pev->classname = MAKE_STRING( "grenade");
	
	pev->solid = SOLID_BBOX;

	SET_MODEL(ENT(pev), "models/grenade.mdl");
	UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0));

	pev->dmg = 100;
	m_fRegisteredSound = FALSE;
	m_Detonated = FALSE;
}




