//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: A moving vehicle that is used as a battering ram
//
// $NoKeywords: $
//=============================================================================

#include "cbase.h"
#include "tf_vehicle_battering_ram.h"
#include "engine/IEngineSound.h"
#include "VGuiScreen.h"
#include "ammodef.h"
#include "in_buttons.h"
#include "shake.h"

#define BATTERING_RAM_MINS			Vector(-30, -50, -10)
#define BATTERING_RAM_MAXS			Vector( 30,  50, 55)
#define BATTERING_RAM_MODEL			"models/objects/vehicle_battering_ram.mdl"
#define BATTERING_RAM_START_SOUND	"vehicles/diesel_start1.wav"
#define BATTERING_RAM_RUNNING_SOUND "vehicles/diesel_loop2.wav"
#define BATTERING_RAM_TURBO_SOUND	"vehicles/diesel_nitroboost.wav"

IMPLEMENT_SERVERCLASS_ST(CVehicleBatteringRam, DT_VehicleBatteringRam)
END_SEND_TABLE();

LINK_ENTITY_TO_CLASS(vehicle_battering_ram, CVehicleBatteringRam);
PRECACHE_REGISTER(vehicle_battering_ram);

// CVars
ConVar	vehicle_battering_ram_health( "vehicle_battering_ram_health","800", FCVAR_NONE, "Battering ram health" );
ConVar	vehicle_battering_ram_damage( "vehicle_battering_ram_damage","300", FCVAR_NONE, "Battering ram damage" );
ConVar	vehicle_battering_ram_damage_boostmod( "vehicle_battering_ram_damage_boostmod", "1.5", FCVAR_NONE, "Battering ram boosted damage modifier" );
ConVar	vehicle_battering_ram_mindamagevel( "vehicle_battering_ram_mindamagevel","100", FCVAR_NONE, "Battering ram velocity for min damage" );
ConVar	vehicle_battering_ram_maxdamagevel( "vehicle_battering_ram_maxdamagevel","260", FCVAR_NONE, "Battering ram velocity for max damage" );
ConVar  vehicle_battering_ram_impact_time( "vehicle_battering_ram_impact_time", "1.0", FCVAR_NONE, "Battering ram impact wait time." );

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CVehicleBatteringRam::CVehicleBatteringRam()
{
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CVehicleBatteringRam::Precache()
{
	engine->PrecacheModel( BATTERING_RAM_MODEL );
	enginesound->PrecacheSound( BATTERING_RAM_START_SOUND );
	enginesound->PrecacheSound( BATTERING_RAM_RUNNING_SOUND );
	enginesound->PrecacheSound( BATTERING_RAM_TURBO_SOUND );

	PrecacheVGuiScreen( "screen_vehicle_battering_ram" );
	PrecacheVGuiScreen( "screen_vulnerable_point");
	
	BaseClass::Precache();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CVehicleBatteringRam::Spawn()
{
	SetVehicleSounds( BATTERING_RAM_START_SOUND, BATTERING_RAM_RUNNING_SOUND, NULL, BATTERING_RAM_TURBO_SOUND );

	SetModel( BATTERING_RAM_MODEL );
	
	// This size is used for placement only...
	UTIL_SetSize(this, BATTERING_RAM_MINS, BATTERING_RAM_MAXS);
	m_takedamage = DAMAGE_YES;
	m_iHealth = vehicle_battering_ram_health.GetInt();
	m_nBarrelAttachment = LookupAttachment( "barrel" );

	SetType( OBJ_BATTERING_RAM );
	SetMaxPassengerCount( 4 );
//	SetTouch( BashTouch );
	
	m_flNextBashTime = 0.0f;

	BaseClass::Spawn();
}


//-----------------------------------------------------------------------------
// Does the player use his normal weapons while in this mode?
//-----------------------------------------------------------------------------
bool CVehicleBatteringRam::IsPassengerUsingStandardWeapons( int nRole )
{
	return (nRole > 1);
}


//-----------------------------------------------------------------------------
// Purpose: Gets info about the control panels
//-----------------------------------------------------------------------------
void CVehicleBatteringRam::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
{
	pPanelName = "screen_vulnerable_point";
}


//-----------------------------------------------------------------------------
// Purpose: Ram!!!
//-----------------------------------------------------------------------------
void CVehicleBatteringRam::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
	int otherIndex = !index;
	CBaseEntity *pEntity = pEvent->pEntities[otherIndex];

    // We only damage objects...
	// And only if we're travelling fast enough...
	if ( !pEntity->IsSolid( ) )
		return;

	// Ignore shields..
	if ( pEntity->GetCollisionGroup() == TFCOLLISION_GROUP_SHIELD )
		return;

	// Ignore anything that's not an object
	if ( pEntity->Classify() != CLASS_MILITARY && !pEntity->IsPlayer() )
		return;

	// Ignore teammates
	if ( InSameTeam( pEntity ))
		return;

	// Ignore invulnerable stuff
	if ( pEntity->m_takedamage == DAMAGE_NO )
		return;

	// See if we can damage again? (Time-based)
	if ( m_flNextBashTime > gpGlobals->curtime )
		return;

	// Use the attachment point to make sure we damage stuff that hit our front
	// FIXME: Should we be using hitboxes here?
	// And damage them all
    // We only damage objects...
 	QAngle vecAng;
	Vector vecSrc, vecAim;
	GetAttachment( m_nBarrelAttachment, vecSrc, vecAng );

	// Check the forward direction
	Vector forward;
	AngleVectors( vecAng, &forward );

	// Did we hit it in front of the vehicle?
	Vector vecDelta;
	VectorSubtract( pEntity->WorldSpaceCenter(), vecSrc, vecDelta );
	if ( ( DotProduct( vecDelta, forward ) <= 0 ) || pEntity->IsPlayer() )
	{
		BaseClass::VPhysicsCollision( index, pEvent );
		return;
	}

	// Call our parents base class.
	BaseClass::BaseClass::VPhysicsCollision( index, pEvent );

	// Do damage based on velocity (and only if we were heading forward)
	Vector vecVelocity = pEvent->preVelocity[index];
	if (DotProduct( vecVelocity, forward ) <= 0)
		return;

	CTakeDamageInfo info;
	info.SetInflictor( this );
	info.SetAttacker( GetDriverPlayer() );
	info.SetDamageType( DMG_CLUB );

	float flMaxDamage = vehicle_battering_ram_damage.GetFloat();
	float flMaxDamageVel = vehicle_battering_ram_maxdamagevel.GetFloat();
	float flMinDamageVel = vehicle_battering_ram_mindamagevel.GetFloat();

	float flVel = vecVelocity.Length();
	if (flVel < flMinDamageVel)
		return;

	// FIXME: Play a sound here...
	EmitSound( "VehicleBatteringRam.BashSound" );

	// Apply the damage
	float flDamageFactor = flMaxDamage;
	if ( IsBoosting() )
	{
		flDamageFactor *= vehicle_battering_ram_damage_boostmod.GetFloat();
	}

	if ((flMaxDamageVel > flMinDamageVel) && (flVel < flMaxDamageVel))
	{
		// Use less damage when we're not moving fast enough
		float flVelocityFactor = (flVel - flMinDamageVel) / (flMaxDamageVel - flMinDamageVel);
		flVelocityFactor *= flVelocityFactor;
		flDamageFactor *= flVelocityFactor;
	}

	UTIL_ScreenShakeObject( pEntity, vecSrc, 45.0f * flDamageFactor / flMaxDamage, 30.0f, 0.5f, 250.0f, SHAKE_START );

	info.SetDamage( flDamageFactor );

	Vector damagePos;
	pEvent->pInternalData->GetContactPoint( damagePos );
	Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
	info.SetDamageForce( damageForce );
	info.SetDamagePosition( damagePos );

	PhysCallbackDamage( pEntity, info );

	// Set next time ram time
	m_flNextBashTime = gpGlobals->curtime + vehicle_battering_ram_impact_time.GetFloat();
}

//-----------------------------------------------------------------------------
// Here's where we deal with weapons, ladders, etc.
//-----------------------------------------------------------------------------
void CVehicleBatteringRam::OnItemPostFrame( CBaseTFPlayer *pDriver )
{
	// I can't do anything if I'm not active
	if ( !ShouldBeActive() )
		return;

	if ( GetPassengerRole( pDriver ) != VEHICLE_DRIVER )
		return;

	if ( pDriver->m_nButtons & (IN_ATTACK2 | IN_SPEED) )
	{
		StartBoost();
	}

	BaseClass::OnItemPostFrame( pDriver );
}
