text: /***
  *
  *	Copyright (c) 1996-2001, Valve LLC. All rights reserved.
  *	
  *	This product contains software technology licensed from Id 
  *	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
  *	All Rights Reserved.
  *
  *   Use, distribution, and modification of this source code and/or resulting
  *   object code is restricted to non-commercial enhancements to products from
  *   Valve LLC.  All other use, distribution, or modification is prohibited
  *   without written permission from Valve LLC.
  *
  ****/
  #if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD )
  
  #include "extdll.h"
  #include "util.h"
  #include "cbase.h"
  #include "monsters.h"
  #include "weapons.h"
  #include "nodes.h"
  #include "player.h"
  #include "gamerules.h"
  
  enum satchel_e {
  	SATCHEL_IDLE1 = 0,
  	SATCHEL_FIDGET1,
  	SATCHEL_DRAW,
  	SATCHEL_DROP
  };
  
  enum satchel_radio_e {
  	SATCHEL_RADIO_IDLE1 = 0,
  	SATCHEL_RADIO_FIDGET1,
  	SATCHEL_RADIO_DRAW,
  	SATCHEL_RADIO_FIRE,
  	SATCHEL_RADIO_HOLSTER
  };
  
  
  
  #ifndef CLIENT_DLL
  class CSatchelCharge : public CGrenade
  {
  public:
  	void Spawn( void );
  	void Precache( void );
  	void BounceSound( void );
  
  	void EXPORT SatchelSlide( CBaseEntity *pOther );
  	void EXPORT SatchelThink( void );
  
  	virtual int		Save(CSave &save);
  	virtual int		Restore(CRestore &restore);
  	static	TYPEDESCRIPTION m_SaveData[];
  
  	EHANDLE		m_hOwner;
  
  	BOOL AddSatchelAmmoToPlayer( CBasePlayer* pPlayer );
  	int GetNumSatchelDeployedByPlayer( CBasePlayer* pPlayer );
  
  public:
  	void Deactivate( void );
  };
  LINK_ENTITY_TO_CLASS( monster_satchel, CSatchelCharge );
  
  TYPEDESCRIPTION	CSatchelCharge::m_SaveData[] =
  {
  	DEFINE_FIELD(CSatchelCharge, m_hOwner, FIELD_EHANDLE),
  };
  IMPLEMENT_SAVERESTORE(CSatchelCharge, CGrenade);
  
  //=========================================================
  // Deactivate - do whatever it is we do to an orphaned 
  // satchel when we don't want it in the world anymore.
  //=========================================================
  void CSatchelCharge::Deactivate( void )
  {
  	pev->solid = SOLID_NOT;
  	UTIL_Remove( this );
  }
  
  
  void CSatchelCharge :: Spawn( void )
  {
  	Precache( );
  	// motor
  	pev->movetype = MOVETYPE_BOUNCE;
  	pev->solid = SOLID_BBOX;
  
  	SET_MODEL(ENT(pev), "models/w_pipebomb.mdl");
  	//UTIL_SetSize(pev, Vector( -16, -16, -4), Vector(16, 16, 32));	// Old box -- size of headcrab monsters/players get blocked by this
  	UTIL_SetSize(pev, Vector( -4, -4, -4), Vector(4, 4, 4));	// Uses point-sized, and can be stepped over
  	UTIL_SetOrigin( pev, pev->origin );
  
  	SetTouch( &CSatchelCharge::SatchelSlide );
  	SetUse( &CSatchelCharge::DetonateUse );
  	SetThink( &CSatchelCharge::SatchelThink );
  	pev->nextthink = gpGlobals->time + 0.1;
  
  	pev->gravity = 0.5;
  	pev->friction = 0.8;
  
  	pev->dmg = gSkillData.plrDmgSatchel;
  	// ResetSequenceInfo( );
  	pev->sequence = 0;
  }
  
  
  void CSatchelCharge::SatchelSlide( CBaseEntity *pOther )
  {
  	entvars_t	*pevOther = pOther->pev;
  
  	// don't hit the guy that launched this grenade
  	if ( pOther == m_hOwner )
  		return;
  
  	// pev->avelocity = Vector (300, 300, 300);
  	pev->gravity = 1;// normal gravity now
  
  	// HACKHACK - On ground isn't always set, so look for ground underneath
  	TraceResult tr;
  	UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,10), ignore_monsters, edict(), &tr );
  
  	if ( tr.flFraction < 1.0 )
  	{
  		// add a bit of static friction
  		pev->velocity = pev->velocity * 0.95;
  		pev->avelocity = pev->avelocity * 0.9;
  		// play sliding sound, volume based on velocity
  	}
  	if ( !(pev->flags & FL_ONGROUND) && pev->velocity.Length2D() > 10 )
  	{
  		BounceSound();
  	}
  	StudioFrameAdvance( );
  }
  
  
  void CSatchelCharge :: SatchelThink( void )
  {
  	StudioFrameAdvance( );
  	pev->nextthink = gpGlobals->time + 0.1;
  
  	if (!IsInWorld())
  	{
  		UTIL_Remove( this );
  		return;
  	}
  
  	if (pev->waterlevel == 3)
  	{
  		pev->movetype = MOVETYPE_FLY;
  		pev->velocity = pev->velocity * 0.8;
  		pev->avelocity = pev->avelocity * 0.9;
  		pev->velocity.z += 8;
  	}
  	else if (pev->waterlevel == 0)
  	{
  		pev->movetype = MOVETYPE_BOUNCE;
  	}
  	else
  	{
  		pev->velocity.z -= 8;
  	}	
  
  	if (m_hOwner != NULL)
  	{
  		if (pev->owner != NULL && pev->owner == m_hOwner->edict())
  		{
  			if (!Intersects(m_hOwner))
  			{
  				pev->owner = NULL;
  			}
  		}
  
  		if (std::abs(pev->velocity.x) <= 0 && std::abs(pev->velocity.y) <= 0)
  		{
  			if (Intersects(m_hOwner))
  			{
  				CBasePlayer* pPlayer = (CBasePlayer*)((CBaseEntity*)m_hOwner);
  				if (pPlayer)
  				{
  					if (AddSatchelAmmoToPlayer(pPlayer))
  					{
  						SetThink( &CSatchelCharge::SUB_Remove );
  						SetTouch( NULL );
  						pev->nextthink = gpGlobals->time;
  					}
  				}
  			}
  		}
  	}
  }
  
  void CSatchelCharge :: Precache( void )
  {
  	PRECACHE_MODEL("models/w_pipebomb.mdl");
  	PRECACHE_SOUND("weapons/pb_bounce1.wav");
  	PRECACHE_SOUND("weapons/pb_bounce2.wav");
  	PRECACHE_SOUND("weapons/pb_bounce3.wav");
  	PRECACHE_SOUND("items/9mmclip1.wav");
  }
  
  void CSatchelCharge :: BounceSound( void )
  {
  	switch ( RANDOM_LONG( 0, 2 ) )
  	{
  	case 0:	EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/pb_bounce1.wav", 1, ATTN_NORM);	break;
  	case 1:	EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/pb_bounce2.wav", 1, ATTN_NORM);	break;
  	case 2:	EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/pb_bounce3.wav", 1, ATTN_NORM);	break;
  	}
  }
  
  BOOL CSatchelCharge::AddSatchelAmmoToPlayer( CBasePlayer* pPlayer )
  {
  	if (!pPlayer)
  		return FALSE;
  
  	CBasePlayerItem *pItem = NULL;
  	int i;
  	BOOL found = FALSE;
   
  	for ( i = 0 ; i < MAX_ITEM_TYPES && !found; i++ )
  	{
  		pItem = pPlayer->m_rgpPlayerItems[ i ];
  		
  		while (pItem)
  		{
  			if ( pItem->m_iId == WEAPON_SATCHEL )
  			{
  				found = TRUE;
  
  				CSatchel* pSatchel = (CSatchel*)pItem;
  				if (pSatchel && pPlayer->GiveAmmo(1, "Satchel Charge", SATCHEL_MAX_CARRY) != -1)
  				{
  					EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM);
  
  					if (GetNumSatchelDeployedByPlayer(pPlayer) <= 1)
  					{
  						// Player has only 1 satchel deployed, which is the current instance,
  						// so make satchel weapon play redraw animation.
  						pSatchel->m_flTimeWeaponIdle = UTIL_WeaponTimeBase();
  						pSatchel->m_chargeReady = 2;
  					}
  					return TRUE;
  				}
  				break;
  			}
  			pItem = pItem->m_pNext;
  		}
  	}
  
  	return FALSE;
  }
  
  int CSatchelCharge::GetNumSatchelDeployedByPlayer( CBasePlayer* pPlayer )
  {
  	if (!pPlayer)
  		return 0;
  
  	int count = 0;
  	CBaseEntity *pSatchel = NULL;
  
  	while ((pSatchel = UTIL_FindEntityInSphere( pSatchel, pPlayer->pev->origin, 4096 )) != NULL)
  	{
  		if (FClassnameIs( pSatchel->pev, "monster_satchel"))
  		{
  			CSatchelCharge* pSatchelCharge = (CSatchelCharge*)pSatchel;
  			if ( pSatchelCharge && pSatchelCharge->m_hOwner == pPlayer )
  				count++;
  		}
  	}
  	return count;
  }
  #endif // !CLIENT_DLL
  
  
  LINK_ENTITY_TO_CLASS( weapon_pipebomb, CSatchel );
  
  
  //=========================================================
  // CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal
  //=========================================================
  int CSatchel::AddDuplicate( CBasePlayerItem *pOriginal )
  {
  	CSatchel *pSatchel;
  
  #ifdef CLIENT_DLL
  	if ( bIsMultiplayer() )
  #else
  	if ( g_pGameRules->IsMultiplayer() )
  #endif
  	{
  		pSatchel = (CSatchel *)pOriginal;
  
  		if ( pSatchel->m_chargeReady != 0 )
  		{
  			// player has some satchels deployed. Refuse to add more.
  			return FALSE;
  		}
  	}
  
  	return CBasePlayerWeapon::AddDuplicate ( pOriginal );
  }
  
  //=========================================================
  //=========================================================
  int CSatchel::AddToPlayer( CBasePlayer *pPlayer )
  {
  	int bResult = CBasePlayerItem::AddToPlayer( pPlayer );
  
  	pPlayer->pev->weapons |= (1<<m_iId);
  	m_chargeReady = 0;// this satchel charge weapon now forgets that any satchels are deployed by it.
  
  	if ( bResult )
  	{
  		return AddWeapon( );
  	}
  	return FALSE;
  }
  
  void CSatchel::Spawn( )
  {
  	Precache( );
  	m_iId = WEAPON_SATCHEL;
  	SET_MODEL(ENT(pev), "models/w_pipebomb.mdl");
  
  	m_iDefaultAmmo = SATCHEL_DEFAULT_GIVE;
  		
  	FallInit();// get ready to fall down.
  }
  
  
  void CSatchel::Precache( void )
  {
  	PRECACHE_MODEL("models/v_pipebomb.mdl");
  	PRECACHE_MODEL("models/v_pipebomb_watch.mdl");
  	PRECACHE_MODEL("models/w_pipebomb.mdl");
  	PRECACHE_MODEL("models/p_pipebomb.mdl");
  	PRECACHE_MODEL("models/p_pipebomb_watch.mdl");
  
  	UTIL_PrecacheOther( "monster_satchel" );
  }
  
  
  int CSatchel::GetItemInfo(ItemInfo *p)
  {
  	p->pszName = STRING(pev->classname);
  	p->pszAmmo1 = "Satchel Charge";
  	p->iMaxAmmo1 = SATCHEL_MAX_CARRY;
  	p->pszAmmo2 = NULL;
  	p->iMaxAmmo2 = -1;
  	p->iMaxClip = WEAPON_NOCLIP;
  	p->iSlot = 4;
  	p->iPosition = 0;
  	p->iFlags = ITEM_FLAG_SELECTONEMPTY | ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE;
  	p->iId = m_iId = WEAPON_SATCHEL;
  	p->iWeight = SATCHEL_WEIGHT;
  
  	return 1;
  }
  
  //=========================================================
  //=========================================================
  BOOL CSatchel::IsUseable( void )
  {
  	if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] > 0 ) 
  	{
  		// player is carrying some satchels
  		return TRUE;
  	}
  
  	if ( m_chargeReady != 0 )
  	{
  		// player isn't carrying any satchels, but has some out
  		return TRUE;
  	}
  
  	return FALSE;
  }
  
  BOOL CSatchel::CanDeploy( void )
  {
  	if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] > 0 ) 
  	{
  		// player is carrying some satchels
  		return TRUE;
  	}
  
  	if ( m_chargeReady != 0 )
  	{
  		// player isn't carrying any satchels, but has some out
  		return TRUE;
  	}
  
  	return FALSE;
  }
  
  BOOL CSatchel::Deploy( )
  {
  	if ( m_chargeReady == 2 )
  	{
  		// If we must play the redraw animation, reset the chargeReady status to
  		// prevent it playing again in WeaponIdle().
  		m_chargeReady = 0;
  	}
  
  	BOOL bResult;
  	if ( m_chargeReady )
  		bResult = DefaultDeploy( "models/v_pipebomb_watch.mdl", "models/p_pipebomb_watch.mdl", SATCHEL_RADIO_DRAW, "hive" );
  	else
  		bResult = DefaultDeploy( "models/v_pipebomb.mdl", "models/p_pipebomb.mdl", SATCHEL_DRAW, "trip" );
  
  	if ( bResult )
  	{
  		m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0;
  		m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );
  	}
  	
  	return bResult;
  }
  
  
  void CSatchel::Holster( int skiplocal /* = 0 */ )
  {
  	m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5;
  	
  	if ( m_chargeReady )
  	{
  		SendWeaponAnim( SATCHEL_RADIO_HOLSTER );
  	}
  	else
  	{
  		SendWeaponAnim( SATCHEL_DROP );
  	}
  	EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM);
  
  	if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] && !m_chargeReady )
  	{
  		m_pPlayer->pev->weapons &= ~(1<<WEAPON_SATCHEL);
  		SetThink( &CSatchel::DestroyItem );
  		pev->nextthink = gpGlobals->time + 0.1;
  	}
  }
  
  
  
  void CSatchel::PrimaryAttack()
  {
  	switch (m_chargeReady)
  	{
  	case 0:
  		{
  		Throw( );
  		}
  		break;
  	case 1:
  		{
  		SendWeaponAnim( SATCHEL_RADIO_FIRE );
  
  #ifndef CLIENT_DLL
  		CBaseEntity *pSatchel = NULL;
  
  		while ((pSatchel = UTIL_FindEntityInSphere( pSatchel, m_pPlayer->pev->origin, 4096 )) != NULL)
  		{
  			if (FClassnameIs( pSatchel->pev, "monster_satchel"))
  			{
  				CSatchelCharge* pSatchelCharge = (CSatchelCharge*)pSatchel;
  				if ( pSatchelCharge && pSatchelCharge->m_hOwner == m_pPlayer )
  				{
  					pSatchel->Use( m_pPlayer, m_pPlayer, USE_ON, 0 );
  					m_chargeReady = 2;
  				}
  			}
  		}
  #endif // !CLIENT_DLL
  
  		m_chargeReady = 2;
  		m_flNextPrimaryAttack = GetNextAttackDelay(0.5);
  		m_flNextSecondaryAttack = GetNextAttackDelay(0.5);
  		m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5;
  		break;
  		}
  
  	case 2:
  		// we're reloading, don't allow fire
  		{
  		}
  		break;
  	}
  }
  
  
  void CSatchel::SecondaryAttack( void )
  {
  	if ( m_chargeReady != 2 )
  	{
  		Throw( );
  	}
  }
  
  
  void CSatchel::Throw( void )
  {
  	if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] )
  	{
  		Vector vecSrc = m_pPlayer->pev->origin;
  
  		Vector vecThrow = gpGlobals->v_forward * 274 + m_pPlayer->pev->velocity;
  
  #ifndef CLIENT_DLL
  		CBaseEntity *pSatchel = Create( "monster_satchel", vecSrc, Vector( 0, 0, 0), m_pPlayer->edict() );
  		pSatchel->pev->velocity = vecThrow;
  		pSatchel->pev->avelocity.y = 400;
  
  		m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_pipebomb_watch.mdl");
  		m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_pipebomb_watch.mdl");
  
  		CSatchelCharge* pSatchelCharge = (CSatchelCharge*)pSatchel;
  		if (pSatchelCharge)
  		{
  			pSatchelCharge->m_hOwner = CBaseEntity::Instance(pSatchel->pev->owner);
  		}
  #else
  		LoadVModel ( "models/v_pipebomb_watch.mdl", m_pPlayer );
  #endif
  
  		SendWeaponAnim( SATCHEL_RADIO_DRAW );
  
  		// player "shoot" animation
  		m_pPlayer->SetAnimation( PLAYER_ATTACK1 );
  
  		m_chargeReady = 1;
  		
  		m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--;
  
  		m_flNextPrimaryAttack = GetNextAttackDelay(1.0);
  		m_flNextSecondaryAttack = GetNextAttackDelay(0.5);
  	}
  }
  
  
  void CSatchel::WeaponIdle( void )
  {
  	if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() )
  		return;
  
  	switch( m_chargeReady )
  	{
  	case 0:
  		SendWeaponAnim( SATCHEL_FIDGET1 );
  		// use tripmine animations
  		std::strcpy( m_pPlayer->m_szAnimExtention, "trip" );
  		break;
  	case 1:
  		SendWeaponAnim( SATCHEL_RADIO_FIDGET1 );
  		// use hivehand animations
  		std::strcpy( m_pPlayer->m_szAnimExtention, "hive" );
  		break;
  	case 2:
  		if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] )
  		{
  			m_chargeReady = 0;
  			RetireWeapon();
  			return;
  		}
  
  #ifndef CLIENT_DLL
  		m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_pipebomb.mdl");
  		m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_pipebomb.mdl");
  #else
  		LoadVModel ( "models/v_pipebomb.mdl", m_pPlayer );
  #endif
  
  		SendWeaponAnim( SATCHEL_DRAW );
  
  		// use tripmine animations
  		std::strcpy( m_pPlayer->m_szAnimExtention, "trip" );
  
  		m_flNextPrimaryAttack = GetNextAttackDelay(0.5);
  		m_flNextSecondaryAttack = GetNextAttackDelay(0.5);
  		m_chargeReady = 0;
  		break;
  	}
  	m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );// how long till we do this again.
  }
  
  //=========================================================
  // DeactivateSatchels - removes all satchels owned by
  // the provided player. Should only be used upon death.
  //
  // Made this global on purpose.
  //=========================================================
  void DeactivateSatchels( CBasePlayer *pOwner )
  {
  #ifndef CLIENT_DLL
  	edict_t *pFind; 
  
  	pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "monster_satchel" );
  
  	while ( !FNullEnt( pFind ) )
  	{
  		CBaseEntity *pEnt = CBaseEntity::Instance( pFind );
  		CSatchelCharge *pSatchel = (CSatchelCharge *)pEnt;
  
  		if ( pSatchel )
  		{
  			if ( pSatchel->m_hOwner == pOwner )
  			{
  				pSatchel->Deactivate();
  			}
  		}
  
  		pFind = FIND_ENTITY_BY_CLASSNAME( pFind, "monster_satchel" );
  	}
  #endif // !CLIENT_DLL
  }
  
  #endif
  