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

#include "cbase.h"
#include "tf_obj_barbed_wire.h"
#include "tf_player.h"
#include "basetfvehicle.h"
#include "engine/IEngineSound.h"
#include "te_effect_dispatch.h"
#include "ndebugoverlay.h"

#define BARBED_WIRE_MINS	Vector(-5, -5, 0)
#define BARBED_WIRE_MAXS	Vector( 5,  5, 40)
#define BARBED_WIRE_MODEL	"models/objects/obj_barbed_wire.mdl"

#define MAX_BARBED_WIRE_DISTANCE	768
#define BARBED_WIRE_THINK_CONTEXT	"BarbedWireThinkContext"

#define BARBED_WIRE_THINK_INTERVAL	0.2

ConVar obj_barbed_wire_damage( "obj_barbed_wire_damage", "80" );
ConVar obj_barbed_wire_health( "obj_barbed_wire_health", "100" );


IMPLEMENT_SERVERCLASS_ST( CObjectBarbedWire, DT_ObjectBarbedWire )
	SendPropEHandle( SENDINFO( m_hConnectedTo ) )
END_SEND_TABLE()


LINK_ENTITY_TO_CLASS( obj_barbed_wire, CObjectBarbedWire );
PRECACHE_REGISTER( obj_barbed_wire );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CObjectBarbedWire::CObjectBarbedWire()
{
	m_iHealth = obj_barbed_wire_health.GetInt();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBarbedWire::Precache()
{
	PrecacheModel( BARBED_WIRE_MODEL );
	BaseClass::Precache();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBarbedWire::Spawn()
{
	Precache();

	SetModel( BARBED_WIRE_MODEL );
	SetSolid( SOLID_BBOX );
	SetType( OBJ_BARBED_WIRE );
	UTIL_SetSize(this, BARBED_WIRE_MINS, BARBED_WIRE_MAXS );

	// Set our flags.
	m_fObjectFlags |= OF_DOESNT_NEED_POWER | OF_SUPPRESS_APPEAR_ON_MINIMAP | OF_SUPPRESS_NOTIFY_UNDER_ATTACK | OF_ALLOW_REPEAT_PLACEMENT;

	// Get the ball rolling here.
	BarbedWireThink();

	BaseClass::Spawn();
}

//-----------------------------------------------------------------------------
// Purpose: Enumerator 
//-----------------------------------------------------------------------------
class CBarbedWireEnumerator : public IEntityEnumerator
{
public:
	CBarbedWireEnumerator( CObjectBarbedWire *pWire, Ray_t *pRay, int contentsMask )
	{
		m_pWire = pWire;
		m_pRay = pRay;
		m_ContentsMask = contentsMask;
		m_aDamagedEntities.Purge();
	}

	virtual bool EnumEntity( IHandleEntity *pHandleEntity )
	{
		CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() );
		if ( pEntity && !m_pWire->InSameTeam( pEntity ) )
		{
			trace_t tr;
			enginetrace->ClipRayToEntity( *m_pRay, m_ContentsMask, pHandleEntity, &tr );
			if ( tr.fraction < 1.0f )
			{
				// Add them to the list & damage them later.
				// Done this way so entities don't remove themselves from leaves while we're tracing
				if ( m_aDamagedEntities.Find( pEntity ) == m_aDamagedEntities.InvalidIndex() )
				{
					m_aDamagedEntities.AddToTail( pEntity );
				}
			}
		}

		return true;
	}

	void DamageEntities( void )
	{
		int iSize = m_aDamagedEntities.Count();
		for ( int i = iSize-1; i >= 0; i-- )
		{
			CBaseEntity *pEntity = m_aDamagedEntities[i].Get();
			if ( pEntity )
			{
				// DMG_CRUSH added so there's no physics force generated
				CTakeDamageInfo info( m_pWire, m_pWire->GetBuilder(), obj_barbed_wire_damage.GetFloat() * BARBED_WIRE_THINK_INTERVAL, DMG_SLASH | DMG_CRUSH );
				pEntity->TakeDamage( info );

				// Bloodspray
				CEffectData	data;
				data.m_vOrigin = pEntity->WorldSpaceCenter();
				data.m_vNormal = Vector(0,0,1);
				data.m_flScale = 4;
				data.m_fFlags = FX_BLOODSPRAY_ALL;
				data.m_nEntIndex = pEntity->entindex();
				DispatchEffect( "tf2blood", data );
			}
		}
	}

private:
	CObjectBarbedWire		*m_pWire;
	int						m_ContentsMask;
	Ray_t		
		*m_pRay;
	CUtlVector< EHANDLE >	m_aDamagedEntities;
};

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBarbedWire::BarbedWireThink( void )
{
	// Cut the rope if we're too far from the entity it's attached to.
	if ( m_hConnectedTo.Get() )
	{
		if ( (WorldSpaceCenter() - m_hConnectedTo->WorldSpaceCenter()).Length() > MAX_BARBED_WIRE_DISTANCE )
		{
			m_hConnectedTo = NULL;
		}

		if ( m_hConnectedTo )
		{		
			Ray_t ray;
			ray.Init( WorldSpaceCenter(), m_hConnectedTo->WorldSpaceCenter() );
			
			//NDebugOverlay::Line( WorldSpaceCenter(), m_hConnectedTo->WorldSpaceCenter(), 255,255,255, false, 0.1 );
			//NDebugOverlay::Box( WorldSpaceCenter(), -Vector(5,5,5), Vector(5,5,5), 0,255,0,8, 0.1 );
			//NDebugOverlay::Box( m_hConnectedTo->WorldSpaceCenter(), -Vector(6,6,6), Vector(6,6,6), 255,255,255,8, 0.1 );

			CBarbedWireEnumerator bwEnum( this, &ray, MASK_SHOT_HULL );
			enginetrace->EnumerateEntities( ray, false, &bwEnum );
			bwEnum.DamageEntities();
		}
	}
	
	SetContextThink( BarbedWireThink, gpGlobals->curtime + BARBED_WIRE_THINK_INTERVAL, BARBED_WIRE_THINK_CONTEXT );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBarbedWire::StartPlacement( CBaseTFPlayer *pPlayer )
{
	if ( pPlayer && !m_hConnectedTo )
	{
		// Automatically connect to the nearest barbed wire on our team.
		float flClosest = 1e24;
		CObjectBarbedWire *pClosest = NULL;
		
		CBaseEntity *pCur = gEntList.FirstEnt();
		while ( pCur )
		{
			CObjectBarbedWire *pWire = dynamic_cast< CObjectBarbedWire* >( pCur );
			if ( pWire )
			{
				if ( pWire->GetTeamNumber() == pPlayer->GetTeamNumber() )
				{
					float flDist = (pWire->WorldSpaceCenter() - pPlayer->WorldSpaceCenter()).Length();
					if ( flDist < flClosest )
					{
						flClosest = flDist;
						pClosest = pWire;
					}
				}
			}

			pCur = gEntList.NextEnt( pCur );
		}

		if ( pClosest )
		{
			m_hConnectedTo = pClosest;
		}
	}

	BaseClass::StartPlacement( pPlayer );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CObjectBarbedWire::PreStartBuilding()
{
	const CObjectBarbedWire *pWire = dynamic_cast< const CObjectBarbedWire* >( m_hBuiltOnEntity.Get() );
	if ( pWire )
	{
		// Reconnect the wire to this entity and don't build yet.
		m_hConnectedTo = pWire;

		return false;
	}
	else
	{
		// Go ahead and build.
		return true;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectBarbedWire::FinishedBuilding()
{
	BaseClass::FinishedBuilding();
}