//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"
#include "soundenvelope.h"
#include "Sprite.h"
#include "entitylist.h"
#include "ai_basenpc.h"
#include "soundent.h"
#include "explode.h"
#include "physics.h"
#include "physics_saverestore.h"
#include "combine_mine.h"
#include "movevars_shared.h"
#include "vphysics/constraints.h"
#include "ai_hint.h"

enum
{
	MINE_STATE_DORMANT = 0,
	MINE_STATE_DEPLOY,		// Try to lock down and arm
	MINE_STATE_CAPTIVE,		// Held in the physgun
	MINE_STATE_ARMED,		// Locked down and looking for targets
	MINE_STATE_TRIGGERED,	// No turning back. I'm going to explode when I touch something.
	MINE_STATE_LAUNCHED,	// Similar. Thrown from physgun.
};

// for the Modification keyfield
enum
{
	MINE_MODIFICATION_NORMAL  = 0,
	MINE_MODIFICATION_CAVERN,
};

// the citizen modified skins for the mine (inclusive):
#define MINE_CITIZEN_SKIN_MIN 1
#define MINE_CITIZEN_SKIN_MAX 2

char *pszMineStateNames[] =
{
	"Dormant",
	"Deploy",
	"Captive",
	"Armed",
	"Triggered",
	"Launched",
};

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

// After this many flips, seriously cut the frequency with which you try.
#define BOUNCEBOMB_MAX_FLIPS	5

// Approximate radius of the bomb's model
#define BOUNCEBOMB_RADIUS		24

BEGIN_DATADESC( CBounceBomb )
	DEFINE_THINKFUNC( ExplodeThink ),
	DEFINE_ENTITYFUNC( ExplodeTouch ),
	DEFINE_THINKFUNC( SearchThink ),
	DEFINE_THINKFUNC( BounceThink ),
	DEFINE_THINKFUNC( SettleThink ),
	DEFINE_THINKFUNC( CaptiveThink ),
	DEFINE_THINKFUNC( CavernBounceThink ),

	DEFINE_SOUNDPATCH( m_pWarnSound ),

	DEFINE_KEYFIELD( m_flExplosionDelay,	FIELD_FLOAT, "ExplosionDelay" ),
	DEFINE_KEYFIELD( m_bBounce,			FIELD_BOOLEAN, "Bounce" ),

	DEFINE_FIELD( m_bAwake, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_hNearestNPC, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hSprite, FIELD_EHANDLE ),
	DEFINE_FIELD( m_LastSpriteColor, FIELD_COLOR32 ),

	DEFINE_FIELD( m_flHookPositions, FIELD_FLOAT ),
	DEFINE_FIELD( m_iHookN, FIELD_INTEGER ),
	DEFINE_FIELD( m_iHookE, FIELD_INTEGER ),
	DEFINE_FIELD( m_iHookS, FIELD_INTEGER ),
	DEFINE_FIELD( m_iAllHooks, FIELD_INTEGER ),

	DEFINE_KEYFIELD( m_bLockSilently, FIELD_BOOLEAN, "LockSilently" ),
	DEFINE_FIELD( m_bFoeNearest, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flIgnoreWorldTime, FIELD_TIME ),
	DEFINE_KEYFIELD( m_bDisarmed, FIELD_BOOLEAN, "StartDisarmed" ),
	DEFINE_KEYFIELD( m_iModification, FIELD_INTEGER, "Modification" ),

	DEFINE_FIELD( m_bPlacedByPlayer, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bHeldByPhysgun, FIELD_BOOLEAN ),

	DEFINE_FIELD( m_iFlipAttempts, FIELD_INTEGER ),

	DEFINE_FIELD( m_flTimeGrabbed, FIELD_TIME ),
	DEFINE_FIELD( m_iMineState, FIELD_INTEGER ),

	// Physics Influence
	DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
	DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),

	DEFINE_PHYSPTR( m_pConstraint ),

	DEFINE_OUTPUT( m_OnPulledUp, "OnPulledUp" ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disarm", InputDisarm ),

END_DATADESC()

string_t CBounceBomb::gm_iszFloorTurretClassname;
string_t CBounceBomb::gm_iszGroundTurretClassname;

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::Precache()
{
	PrecacheModel("models/props_combine/combine_mine01.mdl");

	PrecacheScriptSound( "NPC_CombineMine.Hop" );
	PrecacheScriptSound( "NPC_CombineMine.FlipOver" );
	PrecacheScriptSound( "NPC_CombineMine.TurnOn" );
	PrecacheScriptSound( "NPC_CombineMine.TurnOff" );
	PrecacheScriptSound( "NPC_CombineMine.OpenHooks" );
	PrecacheScriptSound( "NPC_CombineMine.CloseHooks" );

	PrecacheScriptSound( "NPC_CombineMine.ActiveLoop" );

	PrecacheModel( "sprites/glow01.vmt" );

	gm_iszFloorTurretClassname = AllocPooledString( "npc_turret_floor" );
	gm_iszGroundTurretClassname = AllocPooledString( "npc_turret_ground" );
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::Spawn()
{
	Precache();

	Wake( false );

	SetModel("models/props_combine/combine_mine01.mdl");

	SetSolid( SOLID_VPHYSICS );

	m_hSprite.Set( NULL );
	m_takedamage = DAMAGE_EVENTS_ONLY;

	// Find my feet!
	m_iHookN = LookupPoseParameter( "blendnorth" );
	m_iHookE = LookupPoseParameter( "blendeast" );
	m_iHookS = LookupPoseParameter( "blendsouth" );
	m_iAllHooks = LookupPoseParameter( "blendstates" );
	m_flHookPositions = 0;

	SetHealth( 100 );

	m_bBounce = true;

	SetSequence( SelectWeightedSequence( ACT_IDLE ) );

	OpenHooks( true );

	m_bHeldByPhysgun = false;	

	m_iFlipAttempts = 0;

	if( !GetParent() )
	{
		// Create vphysics now if I'm not being carried.
		CreateVPhysics();
	}

	m_flTimeGrabbed = FLT_MAX;

	if( m_bDisarmed )
	{
		SetMineState( MINE_STATE_DORMANT );
	}
	else
	{
		SetMineState( MINE_STATE_DEPLOY );
	}

	// default to a different skin for cavern turrets (unless explicitly overridden)
	if ( m_iModification == MINE_MODIFICATION_CAVERN )
	{
		// look for this value in the first datamap
		// loop through the data description list, restoring each data desc block
		datamap_t *dmap = GetDataDescMap();

		bool bFoundSkin = false;
		// search through all the readable fields in the data description, looking for a match
		for ( int i = 0; i < dmap->dataNumFields; ++i )
		{
			if ( dmap->dataDesc[i].flags & (FTYPEDESC_OUTPUT | FTYPEDESC_KEY) )
			{
				if ( !Q_stricmp(dmap->dataDesc[i].externalName, "Skin") )
				{
					bFoundSkin = true; 
					break;
				}
			}
		}

		if (!bFoundSkin)
		{
			// select a random skin for the mine. Actually, we'll cycle through the available skins 
			// using a static variable to provide better distribution. The static isn't saved but
			// really it's only cosmetic.
			static unsigned int nextSkin = MINE_CITIZEN_SKIN_MIN;
			m_nSkin = nextSkin;
			// increment the skin for next time
			nextSkin = (nextSkin >= MINE_CITIZEN_SKIN_MAX) ? MINE_CITIZEN_SKIN_MIN : nextSkin + 1;
		}	

		// pretend like the player set me down.
		m_bPlacedByPlayer = true;
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::OnRestore()
{
	BaseClass::OnRestore();
	if ( gpGlobals->eLoadType == MapLoad_Transition && !m_hSprite && m_LastSpriteColor.GetRawColor() != 0 )
	{
		UpdateLight( true, m_LastSpriteColor.r(), m_LastSpriteColor.g(), m_LastSpriteColor.b(), m_LastSpriteColor.a() );
	}

	if( VPhysicsGetObject() )
	{
		VPhysicsGetObject()->Wake();
	}
}
	
//---------------------------------------------------------
//---------------------------------------------------------
int CBounceBomb::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();
	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		char tempstr[512];
		Q_snprintf(tempstr,sizeof(tempstr), "%s", pszMineStateNames[m_iMineState] );
		EntityText(text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::SetMineState( int iState )
{
	m_iMineState = iState;

	switch( iState )
	{
	case MINE_STATE_DORMANT:
		{
			CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
			controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.1 );
			UpdateLight( false, 0, 0, 0, 0 );
			SetThink( NULL );
		}
		break;

	case MINE_STATE_CAPTIVE:
		{
			CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
			controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.2 );

			// Unhook
			unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
			VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
			OpenHooks();
			physenv->DestroyConstraint( m_pConstraint );
			m_pConstraint = NULL;

			UpdateLight( true, 0, 0, 255, 190 );
			SetThink( &CBounceBomb::CaptiveThink );
			SetNextThink( gpGlobals->curtime + 0.1f );
			SetTouch( NULL );
		}
		break;

	case MINE_STATE_DEPLOY:
		OpenHooks( true );
		UpdateLight( true, 0, 0, 255, 190 );
		SetThink( &CBounceBomb::SettleThink );
		SetTouch( NULL );
		SetNextThink( gpGlobals->curtime + 0.1f );
		break;

	case MINE_STATE_ARMED:
		UpdateLight( false, 0, 0, 0, 0 );
		SetThink( &CBounceBomb::SearchThink );
		SetNextThink( gpGlobals->curtime + 0.1f );
		break;

	case MINE_STATE_TRIGGERED:
		{
			OpenHooks();

			if( m_pConstraint )
			{
				physenv->DestroyConstraint( m_pConstraint );
				m_pConstraint = NULL;
			}

			// Scare NPC's
			CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin(), 300, 1.0f, this );

			CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
			controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.2 );

			SetTouch( &CBounceBomb::ExplodeTouch );
			unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
			VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );

			Vector vecNudge;

			vecNudge.x = random->RandomFloat( -1, 1 );
			vecNudge.y = random->RandomFloat( -1, 1 );
			vecNudge.z = 1.5;
			vecNudge *= 350;

			VPhysicsGetObject()->Wake();
			VPhysicsGetObject()->ApplyForceCenter( vecNudge );

			float x, y;
			x = 10 + random->RandomFloat( 0, 20 );
			y = 10 + random->RandomFloat( 0, 20 );

			VPhysicsGetObject()->ApplyTorqueCenter( AngularImpulse( x, y, 0 ) );

			// Since we just nudged the mine, ignore collisions with the world until
			// the mine is in the air. We only want to explode if the player tries to 
			// run over the mine before it jumps up.
			m_flIgnoreWorldTime = gpGlobals->curtime + 1.0;
			UpdateLight( true, 255, 0, 0, 190 );

			// use the correct bounce behavior
			if (m_iModification == MINE_MODIFICATION_CAVERN)
			{
				SetThink ( &CBounceBomb::CavernBounceThink );
				SetNextThink( gpGlobals->curtime + 0.15 );
			}
			else
			{
				SetThink( &CBounceBomb::BounceThink );
				SetNextThink( gpGlobals->curtime + 0.5 );
			}
		}
		break;

	case MINE_STATE_LAUNCHED:
		{
			UpdateLight( true, 255, 0, 0, 190 );
			SetThink( NULL );
			SetNextThink( gpGlobals->curtime + 0.5 );

			SetTouch( &CBounceBomb::ExplodeTouch );
			unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
			VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
		}
		break;

	default:
		DevMsg("**Unknown Mine State: %d\n", iState );
		break;
	}
}

//---------------------------------------------------------
// Bouncbomb flips to try to right itself, try to get off
// of and object that it's not allowed to clamp to, or 
// to get away from a hint node that inhibits placement
// of mines.
//---------------------------------------------------------
void CBounceBomb::Flip( const Vector &vecForce, const AngularImpulse &torque )
{
	if( m_iFlipAttempts > BOUNCEBOMB_MAX_FLIPS )
	{
		// Not allowed to try anymore.
		SetThink(NULL);
		return;
	}

	EmitSound( "NPC_CombineMine.FlipOver" );
	VPhysicsGetObject()->ApplyForceCenter( vecForce );
	VPhysicsGetObject()->ApplyTorqueCenter( torque );
	m_iFlipAttempts++;
}

//---------------------------------------------------------
//---------------------------------------------------------
#define MINE_MIN_PROXIMITY_SQR	676 // 27 inches
bool CBounceBomb::IsValidLocation() 
{
	CBaseEntity *pAvoidObject = NULL;
	float flAvoidForce = 0.0f;
	CAI_Hint *pHint;
	CHintCriteria criteria;
	criteria.SetHintType( HINT_WORLD_INHIBIT_COMBINE_MINES );
	criteria.SetFlag( bits_HINT_NODE_NEAREST );
	criteria.AddIncludePosition( GetAbsOrigin(), 12.0f * 15.0f );
	pHint = CAI_HintManager::FindHint( GetAbsOrigin(), criteria );

	if( pHint )
	{
		pAvoidObject = pHint;
		flAvoidForce = 120.0f;
	}
	else
	{
		// Look for other mines that are too close to me.
		CBaseEntity *pEntity = gEntList.FirstEnt();
		Vector vecMyPosition = GetAbsOrigin();
		while( pEntity )
		{
			if( pEntity->m_iClassname == m_iClassname && pEntity != this )
			{
				// Don't lock down if I'm near a mine that's already locked down.
				if( vecMyPosition.DistToSqr(pEntity->GetAbsOrigin()) < MINE_MIN_PROXIMITY_SQR )
				{
					pAvoidObject = pEntity;
					flAvoidForce = 60.0f;
					break;
				}
			}

			pEntity = gEntList.NextEnt( pEntity );
		}
	}

	if( pAvoidObject )
	{
		// Build a force vector to push us away from the inhibitor.
		// Start by pushing upwards.
		Vector vecForce = Vector( 0, 0, VPhysicsGetObject()->GetMass() * 200.0f );

		// Now add some force in the direction that takes us away from the inhibitor.
		Vector vecDir = GetAbsOrigin() - pAvoidObject->GetAbsOrigin();
		vecDir.z = 0.0f;
		VectorNormalize( vecDir );
		vecForce += vecDir * VPhysicsGetObject()->GetMass() * flAvoidForce;

		Flip( vecForce, AngularImpulse( 100, 0, 0 ) );

		// Tell the code that asked that this position isn't valid.
		return false;
	}

	return true;
}

//---------------------------------------------------------
// Release the spikes
//---------------------------------------------------------
void CBounceBomb::BounceThink()
{
	SetNextThink( gpGlobals->curtime + 0.1 );
	StudioFrameAdvance();

	IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
	
	if ( pPhysicsObject != NULL )
	{
		const float MINE_MAX_JUMP_HEIGHT = 200;

		// Figure out how much headroom the mine has, and hop to within a few inches of that.
		trace_t tr;
		UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, MINE_MAX_JUMP_HEIGHT ), MASK_SHOT, this, COLLISION_GROUP_INTERACTIVE, &tr );

		float height;

		if( tr.m_pEnt && tr.m_pEnt->VPhysicsGetObject() )
		{
			// Physics object resting on me. Jump as hard as allowed to try to knock it away.
			height = MINE_MAX_JUMP_HEIGHT;
		}
		else
		{
			height = tr.endpos.z - GetAbsOrigin().z;
			height -= BOUNCEBOMB_RADIUS;
			if ( height < 0.1 )
				height = 0.1;
		}

		float time = sqrt( height / (0.5 * GetCurrentGravity()) );
		float velocity = GetCurrentGravity() * time;

		// or you can just AddVelocity to the object instead of ApplyForce
		float force = velocity * pPhysicsObject->GetMass();

		Vector up;

		GetVectors( NULL, NULL, &up );
		pPhysicsObject->Wake();
		pPhysicsObject->ApplyForceCenter( up * force );

		pPhysicsObject->ApplyTorqueCenter( AngularImpulse( random->RandomFloat( 5, 25 ), random->RandomFloat( 5, 25 ), 0 ) );
		

		if( m_hNearestNPC )
		{
			Vector vecPredict = m_hNearestNPC->GetSmoothedVelocity();

			pPhysicsObject->ApplyForceCenter( vecPredict * 10 );
		}

		EmitSound( "NPC_CombineMine.Hop" );
		SetThink( NULL );
	}
}


//---------------------------------------------------------
// A different bounce behavior for the citizen-modified mine. Detonates at the top of its apex, 
// and does not attempt to track enemies.
//---------------------------------------------------------
void CBounceBomb::CavernBounceThink()
{
	SetNextThink( gpGlobals->curtime + 0.1 );
	StudioFrameAdvance();

	IPhysicsObject *pPhysicsObject = VPhysicsGetObject();

	if ( pPhysicsObject != NULL )
	{
		const float MINE_MAX_JUMP_HEIGHT = 78;

		// Figure out how much headroom the mine has, and hop to within a few inches of that.
		trace_t tr;
		UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, MINE_MAX_JUMP_HEIGHT ), MASK_SHOT, this, COLLISION_GROUP_INTERACTIVE, &tr );

		float height;

		if( tr.m_pEnt && tr.m_pEnt->VPhysicsGetObject() )
		{
			// Physics object resting on me. Jump as hard as allowed to try to knock it away.
			height = MINE_MAX_JUMP_HEIGHT;
		}
		else
		{
			height = tr.endpos.z - GetAbsOrigin().z;
			height -= BOUNCEBOMB_RADIUS;
			if ( height < 0.1 )
				height = 0.1;
		}

		float time = sqrt( height / (0.5 * GetCurrentGravity()) );
		float velocity = GetCurrentGravity() * time;

		// or you can just AddVelocity to the object instead of ApplyForce
		float force = velocity * pPhysicsObject->GetMass();

		Vector up;

		GetVectors( NULL, NULL, &up );
		
		pPhysicsObject->Wake();
		pPhysicsObject->ApplyForceCenter( up * force );
		if( m_hNearestNPC )
		{
			Vector vecPredict = m_hNearestNPC->GetSmoothedVelocity();

			pPhysicsObject->ApplyForceCenter( vecPredict * (pPhysicsObject->GetMass() * 0.65f) );
		}

		pPhysicsObject->ApplyTorqueCenter( AngularImpulse( random->RandomFloat( 15, 40 ), random->RandomFloat( 15, 40 ), random->RandomFloat( 30, 60 ) ) );
		
		EmitSound( "NPC_CombineMine.Hop" );

		SetThink( &CBounceBomb::ExplodeThink );
		SetNextThink( gpGlobals->curtime + 0.33f );
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::CaptiveThink()
{
	SetNextThink( gpGlobals->curtime + 0.05 );
	StudioFrameAdvance();

	float phase = fabs( sin( gpGlobals->curtime * 4.0f ) );
	phase *= BOUNCEBOMB_HOOK_RANGE;
	SetPoseParameter( m_iAllHooks, phase );
	return;
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::SettleThink()
{
	SetNextThink( gpGlobals->curtime + 0.05 );
	StudioFrameAdvance();

	if( GetParent() )
	{
		// A scanner or something is carrying me. Just keep checking back.
		return;
	}

	// Not being carried.
	if( !VPhysicsGetObject() )
	{
		// Probably was just dropped. Get physics going.
		CreateVPhysics();

		if( !VPhysicsGetObject() )
		{
			Msg("**** Can't create vphysics for combine_mine!\n" );
			UTIL_Remove( this );
			return;
		}

		VPhysicsGetObject()->Wake();
		return;
	}

	if( !m_bDisarmed )
	{
		if( VPhysicsGetObject()->IsAsleep() && !(VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
		{
			// If i'm not resting on the world, jump randomly.
			trace_t tr;
			UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 1024 ), MASK_SHOT|CONTENTS_GRATE, this, COLLISION_GROUP_NONE, &tr );

			bool bHop = false;
			if( tr.m_pEnt )
			{
				IPhysicsObject *pPhysics = tr.m_pEnt->VPhysicsGetObject();

				if( pPhysics && pPhysics->GetMass() <= 1000 )
				{
					// Light physics objects can be moved out from under the mine.
					bHop = true;
				}
				else if( tr.m_pEnt->m_takedamage != DAMAGE_NO )
				{
					// Things that can be harmed can likely be broken.
					bHop = true;
				}

				if( bHop )
				{
					Vector vecForce;
					vecForce.x = random->RandomFloat( -1000, 1000 );
					vecForce.y = random->RandomFloat( -1000, 1000 );
					vecForce.z = 2500;

					AngularImpulse torque( 160, 0, 160 );

					Flip( vecForce, torque );
					return;
				}

				// Check for upside-down
				Vector vecUp;
				GetVectors( NULL, NULL, &vecUp );
				if( vecUp.z <= 0.8 )
				{
					// Landed upside down. Right self
					Vector vecForce( 0, 0, 2500 );
					Flip( vecForce, AngularImpulse( 60, 0, 0 ) );
					return;
				}
			}

			// Check to make sure I'm not in a forbidden location
			if( !IsValidLocation() )
			{
				return;
			}

			// Lock to what I'm resting on
			constraint_ballsocketparams_t ballsocket;
			ballsocket.Defaults();
			ballsocket.constraint.Defaults();
			ballsocket.constraint.forceLimit = lbs2kg(1000);
			ballsocket.constraint.torqueLimit = lbs2kg(1000);
			ballsocket.InitWithCurrentObjectState( g_PhysWorldObject, VPhysicsGetObject(), GetAbsOrigin() );
			m_pConstraint = physenv->CreateBallsocketConstraint( g_PhysWorldObject, VPhysicsGetObject(), NULL, ballsocket );
			CloseHooks();

			SetMineState( MINE_STATE_ARMED );
		}
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
int CBounceBomb::OnTakeDamage( const CTakeDamageInfo &info )
{
	if( m_pConstraint || !VPhysicsGetObject())
	{
		return false;
	}

	VPhysicsTakeDamage( info );
	return true;
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::UpdateLight( bool bTurnOn, unsigned int r, unsigned int g, unsigned int b, unsigned int a )
{
	if( bTurnOn )
	{
		Assert( a > 0 );

		// Throw the old sprite away
		if( m_hSprite )
		{
			UTIL_Remove( m_hSprite );
			m_hSprite.Set( NULL );
		}

		if( !m_hSprite.Get() )
		{
			Vector up;
			GetVectors( NULL, NULL, &up );

			// Light isn't on.
			m_hSprite = CSprite::SpriteCreate( "sprites/glow01.vmt", GetAbsOrigin() + up * 10.0f, false );
			CSprite *pSprite = (CSprite *)m_hSprite.Get();

			if( m_hSprite )
			{
				pSprite->SetParent( this );		
				pSprite->SetTransparency( kRenderTransAdd, r, g, b, a, kRenderFxNone );
				pSprite->SetScale( 0.35, 0.0 );
			}
		}
		else
		{
			// Update color
			CSprite *pSprite = (CSprite *)m_hSprite.Get();
			pSprite->SetTransparency( kRenderTransAdd, r, g, b, a, kRenderFxNone );
		}
	}

	if( !bTurnOn )
	{
		if( m_hSprite )
		{
			UTIL_Remove( m_hSprite );
			m_hSprite.Set( NULL );
		}
	}
	
	if ( !m_hSprite )
	{
		m_LastSpriteColor.SetRawColor( 0 );
	}
	else
	{
		m_LastSpriteColor.SetColor( r, g, b, a );
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::Wake( bool bAwake )
{
	CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();

	CReliableBroadcastRecipientFilter filter;
	
	if( !m_pWarnSound )
	{
		m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_CombineMine.ActiveLoop" );
		controller.Play( m_pWarnSound, 1.0, PITCH_NORM  );
	}

	if( bAwake )
	{
		// Turning on
		if( m_bFoeNearest )
		{
			EmitSound( "NPC_CombineMine.TurnOn" );
			controller.SoundChangeVolume( m_pWarnSound, 1.0, 0.1 );
		}

		unsigned char r, g, b;
		r = g = b = 0;

		if( m_bFoeNearest )
		{
			r = 255;
		}
		else
		{
			g = 255;
		}

		UpdateLight( true, r, g, b, 190 );
	}
	else
	{
		// Turning off
		if( m_bFoeNearest )
		{
			EmitSound( "NPC_CombineMine.TurnOff" );
		}

		SetNearestNPC( NULL );
		controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.1 );
		UpdateLight( false, 0, 0, 0, 0 );
	}

	m_bAwake = bAwake;
}

//---------------------------------------------------------
// Returns distance to the nearest BaseCombatCharacter.
//---------------------------------------------------------
float CBounceBomb::FindNearestNPC()
{
	float flNearest = (BOUNCEBOMB_WARN_RADIUS * BOUNCEBOMB_WARN_RADIUS) + 1.0;

	// Assume this search won't find anyone.
	SetNearestNPC( NULL );

	CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
	int nAIs = g_AI_Manager.NumAIs();

	for ( int i = 0; i < nAIs; i++ )
	{
		CAI_BaseNPC *pNPC = ppAIs[ i ];

		if( pNPC->IsAlive() )
		{
			// ignore hidden objects
			if ( pNPC->IsEffectActive( EF_NODRAW ) )
				continue;

			// Don't bother with NPC's that are below me.
			if( pNPC->EyePosition().z < GetAbsOrigin().z )
				continue;

			// Disregard things that want to be disregarded
			if( pNPC->Classify() == CLASS_NONE )
				continue; 

			// Disregard bullseyes
			if( pNPC->Classify() == CLASS_BULLSEYE )
				continue;

			// Disregard turrets
			if( pNPC->m_iClassname == gm_iszFloorTurretClassname || pNPC->m_iClassname == gm_iszGroundTurretClassname )
				continue;


			float flDist = (GetAbsOrigin() - pNPC->GetAbsOrigin()).LengthSqr();

			if( flDist < flNearest )
			{
				// Now do a visibility test.
				if( FVisible( pNPC, MASK_SOLID_BRUSHONLY ) )
				{
					flNearest = flDist;
					SetNearestNPC( pNPC );
				}
			}
		}
	}

	// finally, check the player.
	CBasePlayer *pPlayer = UTIL_GetLocalPlayer();

	if( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) )
	{
		float flDist = (pPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr();

		if( flDist < flNearest && FVisible( pPlayer, MASK_SOLID_BRUSHONLY ) )
		{
			flNearest = flDist;
			SetNearestNPC( pPlayer );
		}
	}

	if( m_hNearestNPC.Get() )
	{
		// If sprite is active, update its color to reflect who is nearest.
		if( IsFriend( m_hNearestNPC ) )
		{
			if( m_bFoeNearest )
			{
				// Changing state to where a friend is nearest.

				if( IsFriend( m_hNearestNPC ) )
				{
					// Friend
					UpdateLight( true, 0, 255, 0, 190 );
					m_bFoeNearest = false;
				}
			}
		}
		else // it's a foe
		{
			if( !m_bFoeNearest )
			{
				// Changing state to where a foe is nearest.
				UpdateLight( true, 255, 0, 0, 190 );
				m_bFoeNearest = true;
			}
		}
	}

	return sqrt( flNearest );
}

//---------------------------------------------------------
//---------------------------------------------------------
bool CBounceBomb::IsFriend( CBaseEntity *pEntity )
{
	int classify = pEntity->Classify();
	bool bIsCombine = false;

	// Unconditional enemies to combine and Player.
	if( classify == CLASS_ZOMBIE || classify == CLASS_HEADCRAB || classify == CLASS_ANTLION )
	{
		return false;
	}

  	if( classify == CLASS_METROPOLICE || 
  		classify == CLASS_COMBINE ||
  		classify == CLASS_MILITARY ||
  		classify == CLASS_COMBINE_HUNTER ||
  		classify == CLASS_SCANNER )
	{
		bIsCombine = true;
	}

	if( m_bPlacedByPlayer )
	{
		return !bIsCombine;
	}
	else
	{
		return bIsCombine;
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::SearchThink()
{
	if( !UTIL_FindClientInPVS(edict()) )
	{
		// Sleep!
		SetNextThink( gpGlobals->curtime + 0.5 );
		return;
	}

	if(	(CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI) )
	{
		if( IsAwake() )
		{
			Wake(false);
		}

		SetNextThink( gpGlobals->curtime + 0.5 );
		return;
	}

	SetNextThink( gpGlobals->curtime + 0.1 );
	StudioFrameAdvance();

	if( m_pConstraint && gpGlobals->curtime - m_flTimeGrabbed >= 1.0f )
	{
		m_OnPulledUp.FireOutput( this, this );
		SetMineState( MINE_STATE_CAPTIVE );
		return;
	}

	float flNearestNPCDist = FindNearestNPC();

	if( flNearestNPCDist <= BOUNCEBOMB_WARN_RADIUS )
	{
		if( !IsAwake() )
		{
			Wake( true );
		}
	}
	else
	{
 		if( IsAwake() )
		{
			Wake( false );
		}

		return;
	}

	if( flNearestNPCDist <= BOUNCEBOMB_DETONATE_RADIUS && !IsFriend( m_hNearestNPC ) )
	{
		if( m_bBounce )
		{
			SetMineState( MINE_STATE_TRIGGERED );
		}
		else
		{
			// Don't pop up in the air, just explode if the NPC gets closer than explode radius.
			SetThink( &CBounceBomb::ExplodeThink );
			SetNextThink( gpGlobals->curtime + m_flExplosionDelay );
		}
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::ExplodeTouch( CBaseEntity *pOther )
{
	// Don't touch anything if held by physgun.
	if( m_bHeldByPhysgun )
		return;

	// Don't touch triggers.
	if( pOther->IsSolidFlagSet(FSOLID_TRIGGER) )
		return;

	// Don't touch gibs and other debris
	if( pOther->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
	{
		if( hl2_episodic.GetBool() )
		{
			Vector vecVelocity;

			VPhysicsGetObject()->GetVelocity( &vecVelocity, NULL );

			if( vecVelocity == vec3_origin )
			{
				ExplodeThink();
			}
		}

		return;
	}

	// Don't detonate against the world if not allowed. Actually, don't
	// detonate against anything that's probably not an NPC (such as physics props)
	if( m_flIgnoreWorldTime > gpGlobals->curtime && !pOther->MyCombatCharacterPointer() )
	{
		return;
	}

	ExplodeThink();
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::ExplodeThink()
{
	SetSolid( SOLID_NONE );

	// Don't catch self in own explosion!
	m_takedamage = DAMAGE_NO;

	if( m_hSprite )
	{
		UpdateLight( false, 0, 0, 0, 0 );
	}

	if( m_pWarnSound )
	{
		CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
		controller.SoundDestroy( m_pWarnSound );
	}


	CBaseEntity *pThrower = HasPhysicsAttacker( 0.5 );

	if (m_iModification == MINE_MODIFICATION_CAVERN)
	{
		ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), (pThrower) ? pThrower : this, BOUNCEBOMB_EXPLODE_DAMAGE, BOUNCEBOMB_EXPLODE_RADIUS, true,
			NULL, CLASS_PLAYER_ALLY );
	}
	else
	{
		ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), (pThrower) ? pThrower : this, BOUNCEBOMB_EXPLODE_DAMAGE, BOUNCEBOMB_EXPLODE_RADIUS, true);
	}
	UTIL_Remove( this );
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::OpenHooks( bool bSilent )
{
	if( !bSilent )
	{
		EmitSound( "NPC_CombineMine.OpenHooks" );
	}

	if( VPhysicsGetObject() )
	{
		// It's possible to not have a valid physics object here, since this function doubles as an initialization function.
		PhysClearGameFlags( VPhysicsGetObject(), FVPHYSICS_CONSTRAINT_STATIC );

		VPhysicsGetObject()->EnableMotion( true );
	}

	SetPoseParameter( m_iAllHooks, BOUNCEBOMB_HOOK_RANGE );

#ifdef _XBOX 
	RemoveEffects( EF_NOSHADOW );
#endif

}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::CloseHooks()
{
	if( !m_bLockSilently )
	{
		EmitSound( "NPC_CombineMine.CloseHooks" );
	}

	if( VPhysicsGetObject() )
	{
		// It's possible to not have a valid physics object here, since this function doubles as an initialization function.
		PhysSetGameFlags( VPhysicsGetObject(), FVPHYSICS_CONSTRAINT_STATIC );
	}

	// Only lock silently the first time we call this.
	m_bLockSilently = false;

	SetPoseParameter( m_iAllHooks, 0 );

	VPhysicsGetObject()->EnableMotion( false );

	// Once I lock down, forget how many tries it took.
	m_iFlipAttempts = 0;

#ifdef _XBOX 
	AddEffects( EF_NOSHADOW );
#endif
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::InputDisarm( inputdata_t &inputdata )
{
	// Only affect a mine that's armed and not placed by player.
	if( !m_bPlacedByPlayer && m_iMineState == MINE_STATE_ARMED )
	{
		if( m_pConstraint )
		{
			physenv->DestroyConstraint( m_pConstraint );
			m_pConstraint = NULL;
		}

		m_bDisarmed = true;
		OpenHooks(false);

		SetMineState(MINE_STATE_DORMANT);
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
{
	m_hPhysicsAttacker = pPhysGunUser;
	m_flLastPhysicsInfluenceTime = gpGlobals->curtime;

	m_flTimeGrabbed = FLT_MAX;

	m_bHeldByPhysgun = false;

	if( m_iMineState == MINE_STATE_ARMED )
	{
		// Put the mine back to searching.
		Wake( false );
		return;
	}

	if( Reason == DROPPED_BY_CANNON )
	{
		// Set to lock down to ground again.
		m_bPlacedByPlayer = true;
		OpenHooks( true );
		SetMineState( MINE_STATE_DEPLOY );
	}
	else if ( Reason == LAUNCHED_BY_CANNON )
	{
		SetMineState( MINE_STATE_LAUNCHED );
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
CBasePlayer *CBounceBomb::HasPhysicsAttacker( float dt )
{
	if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime)
	{
		return m_hPhysicsAttacker;
	}
	return NULL;
}

//---------------------------------------------------------
//---------------------------------------------------------
void CBounceBomb::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
{
	m_hPhysicsAttacker = pPhysGunUser;
	m_flLastPhysicsInfluenceTime = gpGlobals->curtime;

	m_iFlipAttempts = 0;

	if( reason != PUNTED_BY_CANNON )
	{
		if( m_iMineState == MINE_STATE_ARMED )
		{
			// Yanking on a mine that is locked down, trying to rip it loose.
			UpdateLight( true, 255, 255, 0, 190 );
			m_flTimeGrabbed = gpGlobals->curtime;
			m_bHeldByPhysgun = true;

			VPhysicsGetObject()->EnableMotion( true );

			// Try to scatter NPCs without panicking them. Make a move away sound up around their 
			// ear level.
			CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin() + Vector( 0, 0, 60), 32.0f, 0.2f );
			return;
		}
		else
		{
			// Picked up a mine that was not locked down.
			m_bHeldByPhysgun = true;

			if( m_iMineState == MINE_STATE_TRIGGERED )
			{
				// This mine's already set to blow. Player can't place it.
				return;
			}
			else
			{
				m_bDisarmed = false;
				SetMineState( MINE_STATE_DEPLOY );
			}
		}
	}
	else
	{
		m_bHeldByPhysgun = false;
	}

	if( reason == PUNTED_BY_CANNON )
	{
		if( m_iMineState == MINE_STATE_TRIGGERED || m_iMineState == MINE_STATE_ARMED )
		{
			// Already set to blow
			return;
		}

		m_bDisarmed = false;
		m_bPlacedByPlayer = true;
		SetTouch( NULL );
		SetThink( &CBounceBomb::SettleThink );
		SetNextThink( gpGlobals->curtime + 0.1);

		// Since being punted causes the mine to flip, sometimes it 'catches an edge'
		// and ends up touching the ground from whence it came, exploding instantly. 
		// This little stunt prevents that by ignoring world collisions for a very short time.
		m_flIgnoreWorldTime = gpGlobals->curtime + 0.1;
	}
}


LINK_ENTITY_TO_CLASS( bounce_bomb, CBounceBomb );
LINK_ENTITY_TO_CLASS( combine_bouncemine, CBounceBomb );
LINK_ENTITY_TO_CLASS( combine_mine, CBounceBomb );

/*
*/