//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Revive
//
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
#include "tf_revive.h"
#include "tf_gamerules.h"
#ifdef CLIENT_DLL
#include "tf_hud_target_id.h"
#include "view.h"
#include "tf_hud_mediccallers.h"
#else
#include "tf_gamestats.h"
#include "particle_parse.h"
#include "world.h"
#include "collisionutils.h"
#include "triggers.h"
#endif // CLIENT_DLL

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

#define MARKER_MODEL	"models/props_mvm/mvm_revive_tombstone.mdl"

static const int REVIVE_EASY_LIMIT = 4;
static const int REVIVE_MEDIUM_LIMIT = 8;

#ifdef GAME_DLL
#ifdef STAGING_ONLY
CON_COMMAND_F ( tf_test_revive_spawnmarker, "Crude way to spawn a marker for testing", FCVAR_CHEAT )
{
	CBasePlayer *pLocalPlayer = UTIL_PlayerByIndex( 1 );
	if ( !pLocalPlayer )
		return;

	CTFPlayer *pPlayer = ToTFPlayer( pLocalPlayer );
	if ( !pPlayer )
		return;

	CTFReviveMarker::Create( pPlayer );
}
#endif // STAGING_ONLY
extern void HandleRageGain( CTFPlayer *pPlayer, unsigned int iRequiredBuffFlags, float flDamage, float fInverseRageGainScale );
#else
extern void AddMedicCaller( C_BaseEntity *pEntity, float flDuration, Vector &vecOffset, bool bAutoCaller = false );
#endif // GAME_DLL


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
IMPLEMENT_NETWORKCLASS_ALIASED( TFReviveMarker, DT_TFReviveMarker )

BEGIN_NETWORK_TABLE( CTFReviveMarker, DT_TFReviveMarker )
#ifdef GAME_DLL
	SendPropEHandle( SENDINFO( m_hOwner ) ),
	SendPropInt( SENDINFO( m_iHealth ), -1, SPROP_VARINT | SPROP_CHANGES_OFTEN ),
	SendPropInt( SENDINFO( m_iMaxHealth ), -1, SPROP_VARINT ),
	SendPropInt( SENDINFO( m_nRevives ), -1, SPROP_VARINT | SPROP_UNSIGNED ),
#else
	RecvPropEHandle( RECVINFO( m_hOwner ) ),
	RecvPropInt( RECVINFO( m_iHealth ) ),
	RecvPropInt( RECVINFO( m_iMaxHealth ) ),
	RecvPropInt( RECVINFO( m_nRevives ) ),
#endif
END_NETWORK_TABLE()

LINK_ENTITY_TO_CLASS( entity_revive_marker, CTFReviveMarker );
PRECACHE_REGISTER( entity_revive_marker );

BEGIN_DATADESC( CTFReviveMarker )
#ifdef GAME_DLL
	DEFINE_THINKFUNC( ReviveThink ),
#endif // GAME_DLL
END_DATADESC()

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CTFReviveMarker::CTFReviveMarker()
{
#ifdef GAME_DLL
	m_flHealAccumulator = 0.f;
	m_flLastHealTime = 0.f;
	m_bOwnerPromptedToRevive = false;
	m_bOnGround = false;
#else
	m_iMaxHealth = 1;
	m_bCalledForMedic = false;
#endif // GAME_DLL
	m_nRevives = 0;

	UseClientSideAnimation();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFReviveMarker::Precache()
{
	BaseClass::Precache();

	PrecacheModel( MARKER_MODEL );
	PrecacheScriptSound( "MVM.PlayerRevived" );
	PrecacheParticleSystem( "speech_revivecall" );
	PrecacheParticleSystem( "speech_revivecall_medium" );
	PrecacheParticleSystem( "speech_revivecall_hard" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFReviveMarker::Spawn( void )
{
	Precache();
	
	BaseClass::Spawn();

	SetHealth( 1 );
	SetModel( MARKER_MODEL );
	SetSolid( SOLID_BBOX );
	SetSolidFlags( FSOLID_TRIGGER );
	SetCollisionGroup( COLLISION_GROUP_DEBRIS );
	SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
	// SetCollisionBounds( VEC_HULL_MIN, VEC_HULL_MAX );
	SetBlocksLOS( false );
	AddEffects( EF_NOSHADOW );
	ResetSequence( LookupSequence( "idle" ) );

#ifdef GAME_DLL
	m_takedamage = DAMAGE_NO;

	SetThink( &CTFReviveMarker::ReviveThink );
	SetNextThink( gpGlobals->curtime );
#endif // GAME_DLL
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : collisionGroup - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CTFReviveMarker::ShouldCollide( int collisionGroup, int contentsMask ) const
{
	if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT )
		return false;

	if ( collisionGroup == COLLISION_GROUP_PROJECTILE )
		return false;

	if ( collisionGroup == TFCOLLISION_GROUP_ROCKETS )
		return false;
	
	return BaseClass::ShouldCollide( collisionGroup, contentsMask );
}

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFReviveMarker::OnDataChanged( DataUpdateType_t updateType )
{
	// Call for medic once the server's set maxhealth
	if ( !m_bCalledForMedic && m_iMaxHealth > 1 )
	{
		MedicCallerType nType = CALLER_TYPE_REVIVE_EASY;
		if ( m_nRevives >= REVIVE_EASY_LIMIT && m_nRevives < REVIVE_MEDIUM_LIMIT )
		{
			nType = CALLER_TYPE_REVIVE_MEDIUM;
		}
		else if ( m_nRevives >= REVIVE_MEDIUM_LIMIT )
		{
			nType = CALLER_TYPE_REVIVE_HARD;
		}
		
		Vector vecPos;
		if ( GetAttachmentLocal( LookupAttachment( "mediccall" ), vecPos ) )
		{
			CTFMedicCallerPanel::AddMedicCaller( this, 5.0, vecPos, nType );
		}
		
		m_bCalledForMedic = true;
	}

	BaseClass::OnDataChanged( updateType );
}
#endif // CLIENT_DLL

#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFReviveMarker *CTFReviveMarker::Create( CTFPlayer *pOwner )
{
	if ( pOwner )
	{
		CTFReviveMarker *pMarker = static_cast< CTFReviveMarker* >( CBaseEntity::Create( "entity_revive_marker", pOwner->GetAbsOrigin() + Vector( 0, 0, 50 ), pOwner->GetAbsAngles() ) );		
		if ( pMarker )
		{
			pMarker->SetOwner( pOwner );
			pMarker->ChangeTeam( pOwner->GetTeamNumber() );

			return pMarker;
		}
	}

	return NULL;
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int CTFReviveMarker::UpdateTransmitState( void )
{
	return SetTransmitState( FL_EDICT_FULLCHECK );
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int CTFReviveMarker::ShouldTransmit( const CCheckTransmitInfo *pInfo )
{
	return FL_EDICT_ALWAYS;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFReviveMarker::ReviveThink( void )
{
	if ( !m_hOwner || !InSameTeam( m_hOwner ) )
	{
		UTIL_Remove( this );
		return;
	}

	if ( !GetMaxHealth() )
	{
		// Set health of marker based on class, and number of previous revives
		float flHealth = m_hOwner->GetMaxHealth() / 2;
		Assert( flHealth > 0.f );
		PlayerStats_t *pPlayerStats = CTF_GameStats.FindPlayerStats( m_hOwner );
		if ( pPlayerStats ) 
		{
			m_nRevives.Set( pPlayerStats->statsCurrentRound.m_iStat[TFSTAT_REVIVED] );
			flHealth += ( (float)m_nRevives * 10.f );
		}
		SetMaxHealth( flHealth );
	}

	// At rest?
	if ( !m_bOnGround && ( GetFlags() & FL_ONGROUND ) )
	{
		SetMoveType( MOVETYPE_NONE );
		m_bOnGround = true;

		// See if we've in a trigger_hurt
		for ( int i = 0; i < ITriggerHurtAutoList::AutoList().Count(); i++ )
		{
			CTriggerHurt *pTrigger = static_cast<CTriggerHurt*>( ITriggerHurtAutoList::AutoList()[i] );
			if ( !pTrigger->m_bDisabled )
			{
				Vector vecMins, vecMaxs;
				pTrigger->GetCollideable()->WorldSpaceSurroundingBounds( &vecMins, &vecMaxs );
				if ( IsPointInBox( GetCollideable()->GetCollisionOrigin(), vecMins, vecMaxs ) )
				{
					UTIL_Remove( this );
					return;
				}
			}
		}

		// Different particle based on difficulty of this revive
		const char *pszParticle = NULL;
		if ( m_nRevives < REVIVE_EASY_LIMIT )
		{
			pszParticle = "speech_revivecall";
		}
		else if ( m_nRevives < REVIVE_MEDIUM_LIMIT )
		{
			pszParticle = "speech_revivecall_medium";
		}
		else
		{
			pszParticle = "speech_revivecall_hard";
		}

		// DispatchParticleEffect( pszParticle, GetAbsOrigin() + Vector( 0, 0, 80 ), vec3_angle );
		DispatchParticleEffect( pszParticle, PATTACH_POINT_FOLLOW, this, "mediccall" );
		EmitSound( "Medic.AutoCallerAnnounce" );
	}

	// Close revive prompt if no longer being revived
	if ( HasOwnerBeenPrompted() && !IsReviveInProgress() )
	{
		IGameEvent *event = gameeventmanager->CreateEvent( "revive_player_stopped" );
		if ( event )
		{
			event->SetInt( "entindex", m_hOwner->entindex() );
			gameeventmanager->FireEvent( event );

			SetOwnerHasBeenPrompted( false );
		}
	}

	SetNextThink( gpGlobals->curtime + 0.1f );
}
#endif // GAME_DLL

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CTFReviveMarker::SetOwner( CTFPlayer *pPlayer )
{
#ifdef GAME_DLL
	if ( !pPlayer )
		return;

	m_hOwner = pPlayer;
	ChangeTeam( m_hOwner->GetTeamNumber() );

	// Determine bodygroup based on class
	SetBodygroup( 1, m_hOwner->GetPlayerClass()->GetClassIndex() - 1 );
	
	SetAbsAngles( m_hOwner->GetAbsAngles() );
#endif // GAME_DLL
}

#ifdef GAME_DLL
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CTFReviveMarker::AddMarkerHealth( float flAmount )
{
	CTFPlayer *pReviver = GetReviver();
	if ( !pReviver )
		return;

	CTFPlayer *pOwner = GetOwner();
	if ( !pOwner )
		return;

	if ( !GetMaxHealth() )
		return;

	HandleRageGain( pReviver, kRageBuffFlag_OnHeal, flAmount * 2, 1.f );

	m_flHealAccumulator += flAmount;
	if ( m_flHealAccumulator >= 1.f )
	{
		float flHealthToAdd = floor( m_flHealAccumulator );
		m_flHealAccumulator -= flHealthToAdd;
		m_iHealth += flHealthToAdd;
		m_flLastHealTime = gpGlobals->curtime;
	}

	if ( m_iHealth >= GetMaxHealth() )
	{
		ReviveOwner();
	
		// Give points
		CTF_GameStats.Event_PlayerAwardBonusPoints( pReviver, pOwner, 50 );
	}
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
bool CTFReviveMarker::IsReviveInProgress( void )
{
	float flTimeSinceHeal = gpGlobals->curtime - m_flLastHealTime;
	return ( m_flLastHealTime && flTimeSinceHeal <= 2.f );
}

//-----------------------------------------------------------------------------
// Returns true if the player was spawned at their marker
//-----------------------------------------------------------------------------
bool CTFReviveMarker::ReviveOwner( void )
{
	if ( !m_hOwner )
		return false;

	m_hOwner->ForceRespawn();

	// Increment stat
	CTF_GameStats.Event_PlayerRevived( m_hOwner );

	// If the medic's gone, or dead, stay in the spawn room
	if ( !m_pReviver || !m_pReviver->IsAlive() )
		return false;

	// See if their marker is clear
	Vector vecTeleportPos = GetAbsOrigin();			
	trace_t tr;
	CTraceFilterIgnoreTeammatesAndTeamObjects filter( m_hOwner, COLLISION_GROUP_NONE, m_hOwner->GetTeamNumber() );
	UTIL_TraceHull( vecTeleportPos, vecTeleportPos, VEC_HULL_MIN_SCALED( m_hOwner ), VEC_HULL_MAX_SCALED( m_hOwner ), ( MASK_SOLID | CONTENTS_PLAYERCLIP ), &filter, &tr );
		
	// If not, try the medic's location
	if ( tr.fraction < 1.f )
	{
		if ( !m_pReviver )
			// They'll appear in their spawn room.
			return false;
			
		vecTeleportPos = m_pReviver->GetAbsOrigin();
	}
	else
	{
		// Use the angles that were stored when the marker was spawned
		m_hOwner->SetAbsAngles( GetAbsAngles() );
	}

	// Magic
	color32 fadeColor = { 50, 50, 50, 200 };
	UTIL_ScreenFade( m_hOwner, fadeColor, 0.5, 0.4, FFADE_IN );

	m_hOwner->Teleport( &vecTeleportPos, &m_hOwner->GetAbsAngles(), &vec3_origin  );
	m_hOwner->EmitSound( "MVM.PlayerRevived" );

	if ( m_pReviver )
	{
		IGameEvent *event = gameeventmanager->CreateEvent( "revive_player_complete" );
		if ( event )
		{
			event->SetInt( "entindex", m_pReviver->entindex() );
			gameeventmanager->FireEvent( event );
		}
	}

	m_hOwner->SpeakConceptIfAllowed( MP_CONCEPT_RESURRECTED );

	return true;
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CTFReviveMarker::PromptOwner( void )
{
	if ( !m_hOwner )
	{
		UTIL_Remove( this );
		return;
	}

	if ( HasOwnerBeenPrompted() )
		return;

	IGameEvent *event = gameeventmanager->CreateEvent( "revive_player_notify" );
	if ( event )
	{
		event->SetInt( "entindex", m_hOwner->entindex() );
		event->SetInt( "marker_entindex", entindex() );
		gameeventmanager->FireEvent( event );

		SetOwnerHasBeenPrompted( true );
	}
}
#endif // GAME_DLL