//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//

#include "cbase.h"
#include "basecombatcharacter.h"
#include "entityoutput.h"
#include "physics.h"
#include "explode.h"
#include "vphysics_interface.h"
#include "collisionutils.h"
#include "steamjet.h"
#include "eventqueue.h"
#include "soundflags.h"
#include "engine/IEngineSound.h"
#include "props.h"
#include "physics_cannister.h"
#include "globals.h"
#include "physics_saverestore.h"
#include "shareddefs.h"

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

#define SF_CANNISTER_ASLEEP		0x0001
#define SF_CANNISTER_EXPLODE	0x0002

BEGIN_SIMPLE_DATADESC( CThrustController )

	DEFINE_FIELD( m_thrustVector,	FIELD_VECTOR ),
	DEFINE_FIELD( m_torqueVector,	FIELD_VECTOR ),
	DEFINE_KEYFIELD( m_thrust,		FIELD_FLOAT, "thrust" ),

END_DATADESC()


LINK_ENTITY_TO_CLASS( physics_cannister, CPhysicsCannister );

BEGIN_DATADESC( CPhysicsCannister )

	DEFINE_OUTPUT( m_onActivate, "OnActivate" ),

	DEFINE_FIELD( m_thrustOrigin, FIELD_VECTOR ),	// this is a position, but in local space
	DEFINE_EMBEDDED( m_thruster ),
	DEFINE_PHYSPTR( m_pController ),
	DEFINE_FIELD( m_pJet, FIELD_CLASSPTR ),
	DEFINE_FIELD( m_active, FIELD_BOOLEAN ),
	DEFINE_KEYFIELD( m_thrustTime, FIELD_FLOAT, "fuel" ),
	DEFINE_KEYFIELD( m_damage, FIELD_FLOAT, "expdamage" ),
	DEFINE_KEYFIELD( m_damageRadius, FIELD_FLOAT, "expradius" ),
	DEFINE_FIELD( m_activateTime, FIELD_TIME ),
	DEFINE_KEYFIELD( m_gasSound, FIELD_SOUNDNAME, "gassound" ),
	DEFINE_FIELD( m_bFired, FIELD_BOOLEAN ),

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

	DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Explode", InputExplode ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ),

	DEFINE_THINKFUNC( BeginShutdownThink ),
	DEFINE_ENTITYFUNC( ExplodeTouch ),

END_DATADESC()

void CPhysicsCannister::Spawn( void )
{
	Precache();
	SetModel( STRING(GetModelName()) );
	SetBloodColor( DONT_BLEED );

	AddSolidFlags( FSOLID_CUSTOMRAYTEST );
	m_takedamage = DAMAGE_YES;
	SetNextThink( TICK_NEVER_THINK );

	if ( m_iHealth <= 0 )
		m_iHealth = 25;

	m_flAnimTime = gpGlobals->curtime;
	m_flPlaybackRate = 0.0;
	SetCycle( 0 );
	m_bFired = false;

	// not thrusting
	m_active = false;

	CreateVPhysics();
}

void CPhysicsCannister::OnRestore()
{
	BaseClass::OnRestore();
	if ( m_pController )
	{
		m_pController->SetEventHandler( &m_thruster );
	}
}

bool CPhysicsCannister::CreateVPhysics()
{
	bool asleep = HasSpawnFlags(SF_CANNISTER_ASLEEP);

	VPhysicsInitNormal( SOLID_VPHYSICS, 0, asleep );
	return true;
}

bool CPhysicsCannister::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
{
	Vector vecAbsMins, vecAbsMaxs;
	CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );

	if ( !IsBoxIntersectingRay( vecAbsMins, vecAbsMaxs, ray.m_Start, ray.m_Delta ) )
		return false;
	
	return BaseClass::TestCollision( ray, mask, trace );
}

Vector CPhysicsCannister::CalcLocalThrust( const Vector &offset )
{
	matrix3x4_t nozzleMatrix;
	Vector thrustDirection;

	GetAttachment( LookupAttachment("nozzle"), nozzleMatrix );
	MatrixGetColumn( nozzleMatrix, 2, thrustDirection );
	MatrixGetColumn( nozzleMatrix, 3, m_thrustOrigin );
	thrustDirection = -5*thrustDirection + offset;
	VectorNormalize( thrustDirection );
	return thrustDirection;
}


CPhysicsCannister::~CPhysicsCannister( void )
{
}

void CPhysicsCannister::Precache( void )
{
	PropBreakablePrecacheAll( GetModelName() );
	if ( m_gasSound != NULL_STRING )
	{
		PrecacheScriptSound( STRING(m_gasSound) );
	}
	BaseClass::Precache();
}

int CPhysicsCannister::OnTakeDamage( const CTakeDamageInfo &info )
{
	// HACKHACK: Shouldn't g_vecAttackDir be a parameter to this function?
	if ( !m_takedamage )
		return 0;

	if ( !m_active )
	{
		m_iHealth -= info.GetDamage();
		if ( m_iHealth < 0 )
		{
			Explode( info.GetAttacker() );
		}
		else
		{
			// explosions that don't destroy will activate
			// 50% of the time blunt damage will activate as well
			if ( (info.GetDamageType() & DMG_BLAST) ||
				( (info.GetDamageType() & (DMG_CLUB|DMG_SLASH|DMG_CRUSH) ) && random->RandomInt(1,100) < 50 ) )
			{
				CannisterActivate( info.GetAttacker(), g_vecAttackDir );
			}
		}
		return 1;
	}

	if ( (gpGlobals->curtime - m_activateTime) <= 0.1 )
		return 0;

	if ( info.GetDamageType() & (DMG_BULLET|DMG_BUCKSHOT|DMG_BURN|DMG_BLAST) )
	{
		Explode( info.GetAttacker() );
	}

	return 0;
}


void CPhysicsCannister::TraceAttack( const CTakeDamageInfo &info, const Vector &dir, trace_t *ptr )
{
	if ( !m_active && ptr->hitgroup != 0 )
	{
		Vector direction = -dir;
		direction.z -= 5;
		VectorNormalize( direction );
		CannisterActivate( info.GetAttacker(), direction );
	}
	BaseClass::TraceAttack( info, dir, ptr );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPhysicsCannister::CannisterActivate( CBaseEntity *pActivator, const Vector &thrustOffset )
{
	// already active or spent
	if ( m_active || !m_thrustTime )
	{
		return;
	}

	m_hLauncher = pActivator;

	Vector thrustDirection = CalcLocalThrust( thrustOffset );
	m_onActivate.FireOutput( pActivator, this, 0 );
	m_thruster.CalcThrust( m_thrustOrigin, thrustDirection, VPhysicsGetObject() );
	m_pController = physenv->CreateMotionController( &m_thruster );
	IPhysicsObject *pPhys = VPhysicsGetObject();
	m_pController->AttachObject( pPhys, true );
	// Make sure the object is simulated
	pPhys->Wake();

	m_active = true;
	m_activateTime = gpGlobals->curtime;
	SetNextThink( gpGlobals->curtime + m_thrustTime );
	SetThink( &CPhysicsCannister::BeginShutdownThink );

	QAngle angles;
	VectorAngles( -thrustDirection, angles );
	m_pJet = dynamic_cast<CSteamJet *>( CBaseEntity::Create( "env_steam", m_thrustOrigin, angles, this ) );
	m_pJet->SetParent( this );

	float extra = m_thruster.m_thrust * (1/5000.f);
	extra = clamp( extra, 0, 1 );

	m_pJet->m_SpreadSpeed = 15 * m_thruster.m_thrust * 0.001;
	m_pJet->m_Speed = 128 + 100 * extra;
	m_pJet->m_StartSize = 10;
	m_pJet->m_EndSize = 25;

	m_pJet->m_Rate = 52 + (int)extra*20;
	m_pJet->m_JetLength = 64;
	m_pJet->m_clrRender = m_clrRender;

	m_pJet->Use( this, this, USE_ON, 1 );
	if ( m_gasSound != NULL_STRING )
	{
		CPASAttenuationFilter filter( this );

		EmitSound_t ep;
		ep.m_nChannel = CHAN_ITEM;
		ep.m_pSoundName =  STRING(m_gasSound);
		ep.m_flVolume = 1.0f;
		ep.m_SoundLevel = SNDLVL_NORM;

		EmitSound( filter, entindex(), ep );
	}
}

//-----------------------------------------------------------------------------
// Purpose: The cannister's been fired by a weapon, so it should stay pretty accurate
//-----------------------------------------------------------------------------
void CPhysicsCannister::CannisterFire( CBaseEntity *pActivator )
{
	m_bFired = true;

	// Increase thrust
	m_thruster.m_thrust *= 4;

	// Only last a short time
	m_thrustTime = 10.0;

	// Explode on contact
	SetTouch( &CPhysicsCannister::ExplodeTouch );

	CannisterActivate( pActivator, vec3_origin );
}

//-----------------------------------------------------------------------------
// Purpose: Input handler for activating the cannister.
//-----------------------------------------------------------------------------
void CPhysicsCannister::InputActivate( inputdata_t &data )
{
	CannisterActivate( data.pActivator, Vector(0,0.1,-0.25) );
}

//-----------------------------------------------------------------------------
// Purpose: Input handler for deactivating the cannister.
//-----------------------------------------------------------------------------
void CPhysicsCannister::InputDeactivate(inputdata_t &data)
{
	Deactivate();
}


//-----------------------------------------------------------------------------
// Purpose: Input handler for making the cannister go boom.
//-----------------------------------------------------------------------------
void CPhysicsCannister::InputExplode(inputdata_t &data)
{
	Explode( data.pActivator );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler for waking up the cannister if it is sleeping.
//-----------------------------------------------------------------------------
void CPhysicsCannister::InputWake( inputdata_t &data )
{
	IPhysicsObject *pPhys = VPhysicsGetObject();
	if ( pPhys != NULL )
	{
		pPhys->Wake();
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPhysicsCannister::Deactivate(void)
{
	if ( !m_pController )
		return;

	m_pController->DetachObject( VPhysicsGetObject() );
	physenv->DestroyMotionController( m_pController );
	m_pController = NULL;
	SetNextThink( TICK_NEVER_THINK );
	m_thrustTime = 0;
	m_active = false;
	if ( m_pJet )
	{
		ShutdownJet();
	}
	if ( m_gasSound != NULL_STRING )
	{
		StopSound( entindex(), CHAN_ITEM, STRING(m_gasSound) );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPhysicsCannister::Explode( CBaseEntity *pAttacker )
{
	// don't recurse
	m_takedamage = 0;
	Deactivate();

	Vector velocity;
	AngularImpulse angVelocity;
	IPhysicsObject *pPhysics = VPhysicsGetObject();
	pPhysics->GetVelocity( &velocity, &angVelocity );
	PropBreakableCreateAll( GetModelIndex(), pPhysics, GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity, 1.0, 20, COLLISION_GROUP_DEBRIS );
	ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), pAttacker, m_damage, 0, true );
	UTIL_Remove( this );
}

//-----------------------------------------------------------------------------
// Purpose: Explode when I next hit a damageable entity
//-----------------------------------------------------------------------------
void CPhysicsCannister::ExplodeTouch( CBaseEntity *pOther )
{
	if ( !pOther->m_takedamage )
		return;

	Explode( m_hLauncher );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPhysicsCannister::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
	if ( m_bFired && m_active )
	{
		int otherIndex = !index;
		CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
		if ( pEvent->deltaCollisionTime < 0.5 && (pHitEntity == this) )
			return;

		// If we hit hard enough. explode
		if ( pEvent->collisionSpeed > 1000 )
		{
			Explode( m_hLauncher );
			return;
		}
	}

	BaseClass::VPhysicsCollision( index, pEvent );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPhysicsCannister::ShutdownJet( void )
{
	g_EventQueue.AddEvent( m_pJet, "kill", 5, NULL, NULL );

	m_pJet->m_bEmit = false;
	m_pJet->m_Rate = 0;
	m_pJet = NULL;
	SetNextThink( TICK_NEVER_THINK );
}

//-----------------------------------------------------------------------------
// Purpose: The think just shuts the cannister down
//-----------------------------------------------------------------------------
void CPhysicsCannister::BeginShutdownThink( void )
{
	Deactivate();
}

//-----------------------------------------------------------------------------
// Physics Attacker
//-----------------------------------------------------------------------------
void CPhysicsCannister::SetPhysicsAttacker( CBasePlayer *pEntity, float flTime )
{
	m_hPhysicsAttacker = pEntity;
	m_flLastPhysicsInfluenceTime = flTime;
}

	
//-----------------------------------------------------------------------------
// Purpose: Keep track of physgun influence
//-----------------------------------------------------------------------------
void CPhysicsCannister::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
{
	SetPhysicsAttacker( pPhysGunUser, gpGlobals->curtime );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CPhysicsCannister::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
{
	SetPhysicsAttacker( pPhysGunUser, gpGlobals->curtime );
}

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