//========= Copyright © 1996-2003, Valve LLC, All rights reserved. ============
//
// Purpose: Game-specific impact effect hooks
//
//=============================================================================
#include "cbase.h"
#include "fx_impact.h"
#include "decals.h"
#include "ieffects.h"
#include "c_breakableprop.h"
#include "tempent.h"
#include "c_te_legacytempents.h"
#include "tf_shareddefs.h"
#include "fx.h"

void ImpactCreateHurtShards( Vector &vecOrigin, trace_t &tr, Vector &shotDir, int iMaterial, bool bLarge, bool bBlood );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void MakeHurt( const CEffectData &data, bool bBlood )
{
	trace_t tr;
	Vector vecOrigin, vecStart, vecShotDir;
	int iMaterial, iDamageType, iHitbox, iEntIndex;
	ParseImpactData( data, &vecOrigin, &vecStart, &vecShotDir, &iMaterial, &iDamageType, &iHitbox, &iEntIndex );

	// If we hit, perform our custom effects and play the sound
	if ( Impact( vecOrigin, vecStart, iMaterial, iDamageType, iHitbox, iEntIndex, tr ) )
	{
		// Throw out shards to show the player he's hurting it
		ImpactCreateHurtShards( vecOrigin, tr, vecShotDir, iMaterial, false, bBlood );

		// Check for custom effects based on the Decal index
		PerformCustomEffects( vecOrigin, tr, vecShotDir, iMaterial, 1.0 );
		PlayImpactSound( tr );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Impact for:
//				Bullets hitting targets that CAN be hurt
//-----------------------------------------------------------------------------
void ImpactCallback( const CEffectData &data )
{
	MakeHurt( data, true );
}

DECLARE_CLIENT_EFFECT( "Impact", ImpactCallback );

//-----------------------------------------------------------------------------
// Purpose: Bloodless Impact for:
//				Bullets hitting targets that CAN be hurt
//-----------------------------------------------------------------------------
void ImpactNoBloodCallback( const CEffectData &data )
{
	MakeHurt( data, false );
}

DECLARE_CLIENT_EFFECT( "ImpactNoBlood", ImpactNoBloodCallback );

//-----------------------------------------------------------------------------
// Purpose: Impact for:
//				Bullets hitting targets that CANNOT be hurt
//-----------------------------------------------------------------------------
void ImpactUnhurtCallback( const CEffectData &data )
{
	trace_t tr;
	Vector vecOrigin, vecStart, vecShotDir;
	int iMaterial, iDamageType, iHitbox, iEntIndex;
	ParseImpactData( data, &vecOrigin, &vecStart, &vecShotDir, &iMaterial, &iDamageType, &iHitbox, &iEntIndex );

	// If we hit, perform our custom effects and play the sound
	// Don't decal team members, but decal everything else
	C_BaseEntity *pEntity = ClientEntityList().GetEnt( iEntIndex );
	bool bShouldDecal = !( pEntity && pEntity->IsPlayer() );
	if ( Impact( vecOrigin, vecStart, iMaterial, iDamageType, iHitbox, iEntIndex, tr, bShouldDecal ) )
	{
		// Check for custom effects based on the Decal index
		PerformCustomEffects( vecOrigin, tr, vecShotDir, iMaterial, 0 );
		PlayImpactSound( tr );
	}
}

DECLARE_CLIENT_EFFECT( "ImpactUnhurt", ImpactUnhurtCallback );

//-----------------------------------------------------------------------------
// Purpose: Impact for:
//				Bullets hitting player's handheld shields
//-----------------------------------------------------------------------------
void ImpactShieldCallback( const CEffectData &data )
{
	trace_t tr;
	Vector vecOrigin, vecStart, vecShotDir;
	int iMaterial, iDamageType, iHitbox, iEntIndex;
	ParseImpactData( data, &vecOrigin, &vecStart, &vecShotDir, &iMaterial, &iDamageType, &iHitbox, &iEntIndex );

	// Don't call Impact() because we don't want to decal the player
	Vector reflect = -vecShotDir;
	reflect[0] += random->RandomFloat( -0.5f, 0.5f );
	reflect[1] += random->RandomFloat( -0.5f, 0.5f );
	reflect[2] += random->RandomFloat( 0, 0.5f );
	FX_MetalSpark( vecOrigin, reflect, 3 );
}

DECLARE_CLIENT_EFFECT( "ImpactShield", ImpactShieldCallback );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void MakePlasmaHurt( const CEffectData &data, bool bBlood )
{
	trace_t tr;
	Vector vecOrigin, vecStart, vecShotDir;
	int iMaterial, iDamageType, iHitbox, iEntIndex;
	ParseImpactData( data, &vecOrigin, &vecStart, &vecShotDir, &iMaterial, &iDamageType, &iHitbox, &iEntIndex );

	// If we hit, play our splash
	if ( Impact( vecOrigin, vecStart, iMaterial, iDamageType, iHitbox, iEntIndex, tr ) )
	{
		// Throw out shards to show the player he's hurting it
		ImpactCreateHurtShards( vecOrigin, tr, vecShotDir, iMaterial, false, bBlood );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Impact for:
//				Plasma hitting targets that CAN be hurt
//-----------------------------------------------------------------------------
void ImpactPlasmaCallback( const CEffectData &data )
{
	MakePlasmaHurt( data, true );
}

DECLARE_CLIENT_EFFECT( "PlasmaHurt", ImpactPlasmaCallback );

//-----------------------------------------------------------------------------
// Purpose: Impact for:
//				Plasma hitting targets that CAN be hurt
//-----------------------------------------------------------------------------
void ImpactPlasmaNoBloodCallback( const CEffectData &data )
{
	MakePlasmaHurt( data, false );
}

DECLARE_CLIENT_EFFECT( "PlasmaHurtNoBlood", ImpactPlasmaNoBloodCallback );

//-----------------------------------------------------------------------------
// Purpose: Impact for:
//				Plasma hitting targets that CANNOT be hurt
//-----------------------------------------------------------------------------
void ImpactPlasmaUnhurtCallback( const CEffectData &data )
{
	trace_t tr;
	Vector vecOrigin, vecStart, vecShotDir;
	int iMaterial, iDamageType, iHitbox, iEntIndex;
	ParseImpactData( data, &vecOrigin, &vecStart, &vecShotDir, &iMaterial, &iDamageType, &iHitbox, &iEntIndex );

	// If we hit, play our splash
	C_BaseEntity *pEntity = ClientEntityList().GetEnt( iEntIndex );
	bool bShouldDecal = !( pEntity && pEntity->IsPlayer() );
	if ( Impact( vecOrigin, vecStart, iMaterial, iDamageType, iHitbox, iEntIndex, tr, bShouldDecal ) )
	{
		g_pEffects->EnergySplash( vecOrigin, tr.plane.normal, false );
	}
}

DECLARE_CLIENT_EFFECT( "PlasmaUnhurt", ImpactPlasmaUnhurtCallback );

//-----------------------------------------------------------------------------
// Purpose: Impact for:
//				Plasma hitting player's handheld shields
//-----------------------------------------------------------------------------
void ImpactPlasmaShieldCallback( const CEffectData &data )
{
	trace_t tr;
	Vector vecOrigin, vecStart, vecShotDir;
	int iMaterial, iDamageType, iHitbox, iEntIndex;
	ParseImpactData( data, &vecOrigin, &vecStart, &vecShotDir, &iMaterial, &iDamageType, &iHitbox, &iEntIndex );

	// Bounce sparks away from the shield
	// Don't call Impact() because we don't want to decal the player
	Vector offset = vecOrigin - ( vecShotDir * 1.0f );
	Vector vecDir = -vecShotDir + Vector(0,0,1.5);
	g_pEffects->Sparks( offset, 2, 2, &vecDir );
}

DECLARE_CLIENT_EFFECT( "PlasmaShield", ImpactPlasmaShieldCallback );

//-----------------------------------------------------------------------------
// Purpose: Impact for:
//				Strider hitting anything
//-----------------------------------------------------------------------------
void ImpactStriderCallback( const CEffectData &data )
{
	trace_t tr;
	Vector vecOrigin, vecStart, vecShotDir;
	int iMaterial, iDamageType, iHitbox, iEntIndex;
	ParseImpactData( data, &vecOrigin, &vecStart, &vecShotDir, &iMaterial, &iDamageType, &iHitbox, &iEntIndex );

	// If we hit, play our splash
	if ( Impact( vecOrigin, vecStart, iMaterial, iDamageType, iHitbox, iEntIndex, tr ) )
	{
		PerformCustomEffects( vecOrigin, tr, vecShotDir, iMaterial, 5 );
	}
}

DECLARE_CLIENT_EFFECT( "Strider", ImpactStriderCallback );


//=====================================================================================
// SHARDS. 
// Models for small impact tempents (not vphysics simulated)
//--------------------------------------------------------------------
// WOOD
//--------------------------------------------------------------------
#define WOOD_SHARDS		5
const char *ImpactHurtShards_Wood_Small_Models[ WOOD_SHARDS ] = 
{
"models/props_debris/wood_shard_001.mdl",
"models/props_debris/wood_shard_002.mdl",
"models/props_debris/wood_shard_003.mdl",
"models/props_debris/wood_shard_004.mdl",
"models/props_debris/wood_shard_005.mdl",
};
// Model pointers for the above
model_t *ImpactHurtShards_Wood_Small[ WOOD_SHARDS ];

//--------------------------------------------------------------------
// FOLIAGE
//--------------------------------------------------------------------
#define FOLIAGE_SHARDS		5
const char *ImpactHurtShards_Foliage_Small_Models[ FOLIAGE_SHARDS ] = 
{
"models/props_debris/foliage_shard_001.mdl",
"models/props_debris/foliage_shard_002.mdl",
"models/props_debris/foliage_shard_003.mdl",
"models/props_debris/foliage_shard_004.mdl",
"models/props_debris/foliage_shard_005.mdl",
};
// Model pointers for the above
model_t *ImpactHurtShards_Foliage_Small[ FOLIAGE_SHARDS ];

//--------------------------------------------------------------------
// CONCRETE
//--------------------------------------------------------------------
#define CONCRETE_SHARDS		1
const char *ImpactHurtShards_Concrete_Small_Models[ CONCRETE_SHARDS ] = 
{
"models/props_debris/metal_shard_001.mdl",
};
// Model pointers for the above
model_t *ImpactHurtShards_Concrete_Small[ CONCRETE_SHARDS ];

//--------------------------------------------------------------------
// METAL
//--------------------------------------------------------------------
#define METAL_SHARDS		6
const char *ImpactHurtShards_Metal_Small_Models[ METAL_SHARDS ] = 
{
"models/props_debris/metal_shard_001.mdl",
"models/props_debris/metal_shard_002.mdl",
"models/props_debris/metal_shard_003.mdl",
"models/props_debris/metal_shard_004.mdl",
"models/props_debris/metal_shard_005.mdl",
"models/props_debris/metal_shard_006.mdl",
};
// Model pointers for the above
model_t *ImpactHurtShards_Metal_Small[ METAL_SHARDS ];

//-----------------------------------------------------------------------------
// Purpose: Precache all our shards
//-----------------------------------------------------------------------------
void PrecacheImpactShards(void *pUser)
{
	int i;

	// Wood
	for ( i = 0; i < WOOD_SHARDS; i++ )
	{
		const char *sFile = ImpactHurtShards_Wood_Small_Models[i];
		ImpactHurtShards_Wood_Small[i] = (model_t *)engine->LoadModel( sFile );
	}

	// Foliage
	for ( i = 0; i < FOLIAGE_SHARDS; i++ )
	{
		const char *sFile = ImpactHurtShards_Foliage_Small_Models[i];
		ImpactHurtShards_Foliage_Small[i] = (model_t *)engine->LoadModel( sFile );
	}

	// Concrete
	for ( i = 0; i < CONCRETE_SHARDS; i++ )
	{
		const char *sFile = ImpactHurtShards_Concrete_Small_Models[i];
		ImpactHurtShards_Concrete_Small[i] = (model_t *)engine->LoadModel( sFile );
	}

	// Metal
	for ( i = 0; i < METAL_SHARDS; i++ )
	{
		const char *sFile = ImpactHurtShards_Metal_Small_Models[i];
		ImpactHurtShards_Metal_Small[i] = (model_t *)engine->LoadModel( sFile );
	}
}
PRECACHE_REGISTER_FN(PrecacheImpactShards);

//-----------------------------------------------------------------------------
// Purpose: Throw out shards from the impact point to show we can hurt the target
//-----------------------------------------------------------------------------
void ImpactCreateHurtShards( Vector &vecOrigin, trace_t &tr, Vector &shotDir, int iMaterial, bool bLarge, bool bBlood )
{
	// Throw out the effect if any of these are true
	if ( tr.surface.flags & (SURF_SKY|SURF_NODRAW|SURF_HINT|SURF_SKIP) )
		return;

	float flShardSpread = 0.5;

	Vector vecSpawnOrigin = vecOrigin;
	
	int iNumGibs = random->RandomInt( 1, 2 );
	for ( int i = 0; i < iNumGibs; i++ )
	{
		QAngle vecAngles;
		vecAngles[0] = random->RandomFloat(-255, 255);
		vecAngles[1] = random->RandomFloat(-255, 255);
		vecAngles[2] = random->RandomFloat(-255, 255);
		Vector vecForceDir;
		float spreadOfs = random->RandomFloat( 3.0f, 4.0f );
		vecForceDir[0] = -shotDir[0] + random->RandomFloat( -(flShardSpread*spreadOfs), (flShardSpread*spreadOfs) );
		vecForceDir[1] = -shotDir[1] + random->RandomFloat( -(flShardSpread*spreadOfs), (flShardSpread*spreadOfs) );
		vecForceDir[2] = -shotDir[2] + random->RandomFloat( -(flShardSpread*spreadOfs), (flShardSpread*spreadOfs) );
		// Add some extra vertical height
		vecForceDir[2] += random->RandomFloat( 0, 5 );
		vecForceDir *= random->RandomFloat( 30.0,60.0 );

		model_t *pModel = NULL;
		int iFlags = ( FTENT_COLLIDEWORLD | FTENT_FADEOUT | FTENT_GRAVITY );

		// Select the right chunk for the job
		switch ( iMaterial )
		{
		case CHAR_TEX_WOOD:
			iFlags |= FTENT_ROTATE;
			pModel = ImpactHurtShards_Wood_Small[ random->RandomInt(0,WOOD_SHARDS-1) ];
			break;

		case CHAR_TEX_FOLIAGE:
			pModel = ImpactHurtShards_Foliage_Small[ random->RandomInt(0,FOLIAGE_SHARDS-1) ];
			break;

		case CHAR_TEX_METAL:
		case CHAR_TEX_VENT:
		case CHAR_TEX_GRATE:

		case CHAR_TEX_CONCRETE:
			default:
			pModel = ImpactHurtShards_Metal_Small[ random->RandomInt(0,METAL_SHARDS-1) ];
			break;

		case CHAR_TEX_FLESH:
			// Spray some blood out
			if ( !bBlood )
				return;

			CEffectData	data;
			data.m_vOrigin = vecOrigin;
			data.m_vNormal = tr.plane.normal;
			data.m_flScale = 4;
			data.m_fFlags = FX_BLOODSPRAY_ALL;
			data.m_nEntIndex = tr.m_pEnt ?  tr.m_pEnt->entindex() : 0;
			DispatchEffect( "tf2blood", data );
			return;
			break;
		/*
		case CHAR_TEX_CONCRETE:
		default:
			pModel = ImpactHurtShards_Concrete_Small[ random->RandomInt(0,CONCRETE_SHARDS-1) ];
			break;
		*/
		}
		Assert( pModel );			

		// ROBIN: Removed until optimized
		return;

		// Throw it out
		tempents->SpawnTempModel( pModel, vecSpawnOrigin, vecAngles, vecForceDir, random->RandomFloat(0.5,1.5), iFlags );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Throw out breakable, vphysics gibs from the surface we hit
//-----------------------------------------------------------------------------
void ImpactCreateHurtGibs( Vector &vecOrigin, trace_t &tr, Vector &shotDir, int iMaterial, bool bLarge )
{
	// Throw out the effect if any of these are true
	if ( tr.surface.flags & (SURF_SKY|SURF_NODRAW|SURF_HINT|SURF_SKIP) )
		return;

	Vector vecSpawnOrigin = vecOrigin - (shotDir * 32);
	float flGibSpread = 0.8;

	// Custom TF2 impact effects
	if ( iMaterial == CHAR_TEX_FOLIAGE )
	{
		int iNumGibs = random->RandomInt( 1, 2 );
		for ( int i = 0; i < iNumGibs; i++ )
		{
			AngularImpulse angVel;
			angVel.Random( -400.0f, 400.0f );
			Vector vecForceDir;
			float spreadOfs = random->RandomFloat( 3.0f, 4.0f );
			vecForceDir[0] = -shotDir[0] + random->RandomFloat( -(flGibSpread*spreadOfs), (flGibSpread*spreadOfs) );
			vecForceDir[1] = -shotDir[1] + random->RandomFloat( -(flGibSpread*spreadOfs), (flGibSpread*spreadOfs) );
			vecForceDir[2] = -shotDir[2] + random->RandomFloat( -(flGibSpread*spreadOfs), (flGibSpread*spreadOfs) );
			// Add some extra vertical height
			vecForceDir[2] += 5;
			vecForceDir *= random->RandomFloat( 10.0,30.0 );

			//C_BreakableProp *pProp = new C_BreakableProp;
			//pProp->CreateClientsideProp( ImpactHurtGibs_Wood_Small[ random->RandomInt(0,NUM_WOOD_GIBS_SMALL-1) ], vecSpawnOrigin, vecForceDir, angVel );
		}
	}
	else 
	{
		PerformCustomEffects( vecOrigin, tr, shotDir, iMaterial, 1 );
	}
}
