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

#include "cbase.h"
#include "dod_area_capture.h"
#include "dod_player.h"
#include "dod_gamerules.h"
#include "dod_control_point.h"
#include "dod_team.h"
#include "dod_objective_resource.h"


//-------------------------------------------------------------------
// CAreaCapture
// An area entity that players must remain in in order to active another entity
// Triggers are fired on start of capture, on end of capture and on broken capture
// Can either be capped by both teams at once, or just by one
// Time to capture and number of people required to capture are both passed by the mapper



BEGIN_DATADESC(CAreaCapture)

	// Touch functions
	DEFINE_FUNCTION( AreaTouch ),

	// Think functions
	DEFINE_THINKFUNC( Think ),

	// Keyfields
	DEFINE_KEYFIELD( m_iszCapPointName,			FIELD_STRING,	"area_cap_point" ),

	DEFINE_KEYFIELD( m_nAlliesNumCap,			FIELD_INTEGER,	"area_allies_numcap" ),
	DEFINE_KEYFIELD( m_nAxisNumCap,				FIELD_INTEGER,	"area_axis_numcap" ),
	DEFINE_KEYFIELD( m_flCapTime,				FIELD_FLOAT,	"area_time_to_cap" ),

	DEFINE_KEYFIELD( m_bAlliesCanCap,			FIELD_BOOLEAN,	"area_allies_cancap" ),
	DEFINE_KEYFIELD( m_bAxisCanCap,				FIELD_BOOLEAN,	"area_axis_cancap" ),

	DEFINE_KEYFIELD( m_bDisabled,	FIELD_BOOLEAN,	"StartDisabled" ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "RoundInit", InputRoundInit ),

	// Outputs
	DEFINE_OUTPUT( m_AlliesStartOutput, "OnAlliesStartCap" ),
	DEFINE_OUTPUT( m_AlliesBreakOutput, "OnAlliesBreakCap" ),
	DEFINE_OUTPUT( m_AlliesCapOutput,	"OnAlliesEndCap" ),

	DEFINE_OUTPUT( m_AxisStartOutput,	"OnAxisStartCap" ),
	DEFINE_OUTPUT( m_AxisBreakOutput,	"OnAxisBreakCap" ),
	DEFINE_OUTPUT( m_AxisCapOutput,		"OnAxisEndCap" ),

	DEFINE_OUTPUT( m_StartOutput,	"OnStartCap" ),
	DEFINE_OUTPUT( m_BreakOutput,	"OnBreakCap" ),
	DEFINE_OUTPUT( m_CapOutput,		"OnEndCap" ),

//	DEFINE_OUTPUT( m_OnStartCap, "OnStartCap" );
//	DEFINE_OUTPUT( m_OnBreakCap, 

//	DEFINE_OUTPUT( m_OnEnterNoObj, "OnEnterNoObj" );

END_DATADESC();

LINK_ENTITY_TO_CLASS( dod_capture_area, CAreaCapture );

void CAreaCapture::Spawn( void )
{
	BaseClass::Spawn();
	
	InitTrigger();
	
	Precache();

	m_iAreaIndex = -1;
		
	SetTouch ( &CAreaCapture::AreaTouch );		

	m_bCapturing = false;
	m_nCapturingTeam = TEAM_UNASSIGNED;
	m_nOwningTeam = TEAM_UNASSIGNED;
	m_fTimeRemaining = 0.0f;
	
	SetNextThink( gpGlobals->curtime + AREA_THINK_TIME );

	if( m_nAlliesNumCap < 1 )
		m_nAlliesNumCap = 1;

	if( m_nAxisNumCap < 1 )
		m_nAxisNumCap = 1;

	m_bDisabled = false;

	m_iCapAttemptNumber = 0;
}

void CAreaCapture::Precache( void )
{
}

bool CAreaCapture::KeyValue( const char *szKeyName, const char *szValue )
{
	return BaseClass::KeyValue( szKeyName, szValue );
}

//sends to all players at the start of the round
//needed?
void CAreaCapture::area_SetIndex( int index )
{
	m_iAreaIndex = index;
}

bool CAreaCapture::IsActive( void )
{
	return !m_bDisabled;
}

void CAreaCapture::AreaTouch( CBaseEntity *pOther )
{
	//if they are touching, set their SIGNAL flag on, and their m_iCapAreaNum to ours
	//then in think do all the scoring

	if( !IsActive() )
		return;

	//Don't cap areas unless the round is running
	if( DODGameRules()->State_Get() != STATE_RND_RUNNING || DODGameRules()->IsInWarmup() )
		return;

	Assert( m_iAreaIndex != -1 );

	if( m_pPoint )
	{
		m_nOwningTeam = m_pPoint->GetOwner();
	}
	
	//dont touch for non-alive or non-players
	if( !pOther->IsPlayer() )
		return;

	if( !pOther->IsAlive() )
		return;

	CDODPlayer *pPlayer = ToDODPlayer(pOther);

	ASSERT( pPlayer );

	if ( pPlayer->GetTeamNumber() != m_nOwningTeam )
	{
		bool bAbleToCap = ( pPlayer->GetTeamNumber() == TEAM_ALLIES && m_bAlliesCanCap ) ||
							( pPlayer->GetTeamNumber() == TEAM_AXIS && m_bAxisCanCap );

		if ( bAbleToCap )
            pPlayer->HintMessage( HINT_IN_AREA_CAP );
	}

	pPlayer->m_signals.Signal( SIGNAL_CAPTUREAREA );

	//add them to this area
	pPlayer->SetCapAreaIndex( m_iAreaIndex );

	if ( m_pPoint )
	{
		pPlayer->SetCPIndex( m_pPoint->GetPointIndex() );
	}	
}

/* three ways to be capturing a cap area
 * 1) have the required number of people in the area
 * 2) have less than the required number on your team, new required num is everyone
 * 3) have less than the required number alive, new required is numAlive, but time is lengthened
 */

ConVar dod_simulatemultiplecappers( "dod_simulatemultiplecappers", "1", FCVAR_CHEAT );

void CAreaCapture::Think( void )
{
	SetNextThink( gpGlobals->curtime + AREA_THINK_TIME );

	if( DODGameRules()->State_Get() != STATE_RND_RUNNING )
	{
		// If we were being capped, cancel it
		if( m_nNumAllies > 0 || m_nNumAxis > 0 )
		{
			m_nNumAllies = 0;
			m_nNumAxis = 0;
			SendNumPlayers();

			if( m_pPoint )
			{
				g_pObjectiveResource->SetCappingTeam( m_pPoint->GetPointIndex(), TEAM_UNASSIGNED );
			}
		}
		return;
	}

	// go through our list of players

	int iNumAllies = 0;
	int iNumAxis = 0;

	CDODPlayer *pFirstAlliedTouching = NULL;
	CDODPlayer *pFirstAxisTouching = NULL;

	for ( int i = 1; i <= gpGlobals->maxClients; i++ )
	{
		CBaseEntity *ent = UTIL_PlayerByIndex( i );
		if ( ent )
		{
			CDODPlayer *pPlayer = ToDODPlayer(ent);

			//First check if the player is in fact in this area
			if ( ( pPlayer->m_signals.GetState() & SIGNAL_CAPTUREAREA ) &&
				pPlayer->GetCapAreaIndex() == m_iAreaIndex &&
				pPlayer->IsAlive() )		// alive check is kinda unnecessary, but there is some
											// case where non-present people are messing up this count
			{	
				if ( pPlayer->GetTeamNumber() == TEAM_ALLIES )
				{
					if ( iNumAllies == 0 )
						pFirstAlliedTouching = pPlayer;

					iNumAllies++;					
				}
				else if ( pPlayer->GetTeamNumber() == TEAM_AXIS )
				{
					if ( iNumAxis == 0 )
						pFirstAxisTouching = pPlayer;

					iNumAxis++;
				}
			}
		}
	}

	iNumAllies *= dod_simulatemultiplecappers.GetInt();
	iNumAxis *= dod_simulatemultiplecappers.GetInt();

	if( iNumAllies != m_nNumAllies || iNumAxis != m_nNumAxis )
	{
		m_nNumAllies = iNumAllies;
		m_nNumAxis = iNumAxis;
		SendNumPlayers();
	}

	// when a player blocks, tell them the cap index and attempt number
	// only give successive blocks to them if the attempt number is different

	if( m_bCapturing )
	{
		//its a regular cap
		//Subtract some time from the cap
		m_fTimeRemaining -= AREA_THINK_TIME;

		//if both teams are in the area
		if( iNumAllies > 0 && iNumAxis > 0 )
		{
			// See if anyone gets credit for the block
			float flPercentToGo = m_fTimeRemaining / m_flCapTime;
			if ( flPercentToGo <= 0.5 && m_pPoint )
			{
				// find the first player that is not on the capturing team
				// they have just broken a cap and should be rewarded		
				// tell the player the capture attempt number, for checking later
				CDODPlayer *pBlockingPlayer = ( m_nCapturingTeam == TEAM_ALLIES ) ? pFirstAxisTouching : pFirstAlliedTouching;

				if ( pBlockingPlayer )
				{
					if ( pBlockingPlayer->GetCapAreaIndex() == m_iAreaIndex &&
						 pBlockingPlayer->GetLastBlockCapAttempt() == m_iCapAttemptNumber )
					{
						// this is a repeat block on the same cap, ignore it
						NULL;
					}
					else
					{
                        m_pPoint->CaptureBlocked( pBlockingPlayer );
						pBlockingPlayer->StoreCaptureBlock( m_iAreaIndex, m_iCapAttemptNumber );
					}
				}
			}

			BreakCapture( false );
			return;
		}

		//if no-one is in the area
		if( iNumAllies == 0 && iNumAxis == 0 )
		{
			BreakCapture( true );
			return;
		}
		
		if( m_nCapturingTeam == TEAM_ALLIES )
		{
			if( iNumAllies < m_nAlliesNumCap )
			{
				BreakCapture( true );
			}
		}
		else if( m_nCapturingTeam == TEAM_AXIS )
		{
			if( iNumAxis < m_nAxisNumCap )
			{
				BreakCapture( true );
			}
		}

		//if the cap is done
		if( m_fTimeRemaining <= 0 )
		{
			EndCapture( m_nCapturingTeam );
			return;		//we're done
		}			
	}
	else	//not capturing yet
	{
		bool bStarted = false;
		
		if( iNumAllies > 0 && iNumAxis <= 0 && m_bAlliesCanCap && m_nOwningTeam != TEAM_ALLIES )
		{
			if( iNumAllies >= m_nAlliesNumCap )
			{
				m_iCappingRequired = m_nAlliesNumCap;	
				m_iCappingPlayers = iNumAllies;	
				StartCapture( TEAM_ALLIES, CAPTURE_NORMAL );
				bStarted = true;
			}
		}
		else if( iNumAxis > 0 && iNumAllies <= 0 && m_bAxisCanCap && m_nOwningTeam != TEAM_AXIS )
		{
			if( iNumAxis >= m_nAxisNumCap )
			{
				m_iCappingRequired = m_nAxisNumCap;	
				m_iCappingPlayers = iNumAxis;	
				StartCapture( TEAM_AXIS, CAPTURE_NORMAL );
				bStarted = true;
			}
		}
	}
}

void CAreaCapture::SetOwner( int team )
{
	//break any current capturing
	BreakCapture( false );

	//set the owner to the passed value
	m_nOwningTeam = team;
	g_pObjectiveResource->SetOwningTeam( m_pPoint->GetPointIndex(), m_nOwningTeam );
}

void CAreaCapture::SendNumPlayers( CBasePlayer *pPlayer )
{
	if( !m_pPoint )
		return;

	int index = m_pPoint->GetPointIndex();

	g_pObjectiveResource->SetNumPlayers( index, TEAM_ALLIES, m_nNumAllies );
	g_pObjectiveResource->SetNumPlayers( index, TEAM_AXIS, m_nNumAxis );
}

void CAreaCapture::StartCapture( int team, int capmode )
{
	int iNumCappers = 0;

	//trigger start
	if( team == TEAM_ALLIES )
	{
		m_AlliesStartOutput.FireOutput(this,this);
		iNumCappers = m_nAlliesNumCap;
	}
	else if( team == TEAM_AXIS )
	{
		m_AxisStartOutput.FireOutput(this,this);
		iNumCappers = m_nAxisNumCap;
	}

	m_StartOutput.FireOutput(this,this);
	
	m_nCapturingTeam = team;
	m_fTimeRemaining = m_flCapTime;
	m_bCapturing = true;
	m_iCapMode = capmode;

	if( m_pPoint )
	{
		//send a message that we're starting to cap this area
		g_pObjectiveResource->SetCappingTeam( m_pPoint->GetPointIndex(), m_nCapturingTeam );
	}	
}

#define MAX_AREA_CAPPERS 9

void CAreaCapture::EndCapture( int team )
{
	m_iCapAttemptNumber++;

	//do the triggering
	if( team == TEAM_ALLIES )
	{
		m_AlliesCapOutput.FireOutput(this,this);
	}
	else if( team == TEAM_AXIS )
	{
		m_AxisCapOutput.FireOutput(this,this);
	}

	m_CapOutput.FireOutput(this,this);

	m_iCappingRequired = 0;
	m_iCappingPlayers = 0;

	int numcappers = 0;
	int cappingplayers[MAX_AREA_CAPPERS];

	CDODPlayer *pCappingPlayer = NULL;

	CDODTeam *pTeam = GetGlobalDODTeam(team);
	if ( pTeam )
	{
		int iCount = pTeam->GetNumPlayers();

		for ( int i=0;i<iCount;i++ )
		{
			CDODPlayer *pPlayer = ToDODPlayer( pTeam->GetPlayer(i) );
			if ( pPlayer )
			{
				if( ( pPlayer->m_signals.GetState() & SIGNAL_CAPTUREAREA ) &&
					pPlayer->GetCapAreaIndex() == m_iAreaIndex && 
					pPlayer->IsAlive() )
				{
					if( pCappingPlayer == NULL )
						pCappingPlayer = pPlayer;

					if ( numcappers < MAX_AREA_CAPPERS-1 )
					{
						cappingplayers[numcappers] = pPlayer->entindex();
						numcappers++;
					}
				}
			}
		}
	}	

	if ( numcappers < MAX_AREA_CAPPERS )
	{
		cappingplayers[numcappers] = 0;	//null terminate :)
	}
			
	m_nOwningTeam = team;
	m_bCapturing = false;
	m_fTimeRemaining = 0.0f;

	//there may have been more than one capper, but only report this one.
	//he hasnt gotten points yet, and his name will go in the cap string if its needed
	//first capper gets name sent and points given by flag.
	//other cappers get points manually above, no name in message

	//send the player in the cap string
	if( m_pPoint )
	{
		DODGameRules()->CapEvent( CAP_EVENT_FLAG, m_nOwningTeam );

		g_pObjectiveResource->SetOwningTeam( m_pPoint->GetPointIndex(), m_nOwningTeam );
		m_pPoint->SetOwner( m_nOwningTeam, true, numcappers, cappingplayers );
	}
}

void CAreaCapture::BreakCapture( bool bNotEnoughPlayers )
{
	if( m_bCapturing )
	{
		m_iCappingRequired = 0;
		m_iCappingPlayers = 0;
				
		if( m_nCapturingTeam == TEAM_ALLIES )
			m_AlliesBreakOutput.FireOutput(this,this);

		else if( m_nCapturingTeam == TEAM_AXIS )
			m_AxisBreakOutput.FireOutput(this,this);

		m_BreakOutput.FireOutput(this,this);

		m_bCapturing = false;

		if( m_pPoint )
		{
			g_pObjectiveResource->SetCappingTeam( m_pPoint->GetPointIndex(), TEAM_UNASSIGNED );
		}

		if ( bNotEnoughPlayers )
		{
			m_iCapAttemptNumber++;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAreaCapture::InputDisable( inputdata_t &inputdata )
{
	m_bDisabled = true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CAreaCapture::InputEnable( inputdata_t &inputdata )
{
	m_bDisabled = false;
}

void CAreaCapture::InputRoundInit( inputdata_t &inputdata )
{
	// find the flag we're linked to
	if( !m_pPoint )
	{
		m_pPoint = dynamic_cast<CControlPoint*>( gEntList.FindEntityByName(NULL, STRING(m_iszCapPointName) ) );

		if ( m_pPoint )
		{
			m_pPoint->SetNumCappersRequired( m_nAlliesNumCap, m_nAxisNumCap );
			g_pObjectiveResource->SetCPRequiredCappers( m_pPoint->GetPointIndex(), m_nAlliesNumCap, m_nAxisNumCap );
			g_pObjectiveResource->SetCPCapTime( m_pPoint->GetPointIndex(), m_flCapTime, m_flCapTime );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Check if this player's death causes a block
// return FALSE if the player is not in this area
// return TRUE otherwise ( eg player is in area, but his death does not cause break )
//-----------------------------------------------------------------------------
bool CAreaCapture::CheckIfDeathCausesBlock( CDODPlayer *pVictim, CDODPlayer *pKiller )
{
	// This shouldn't happen
	if ( !pVictim || !pKiller )
	{
		Assert( !"Why are null players getting here?" );
		return false;
	}

	// make sure this player is in this area
	if ( pVictim->GetCapAreaIndex() != m_iAreaIndex )
		return false;

	// Teamkills shouldn't give a block reward
	if ( pVictim->GetTeamNumber() == pKiller->GetTeamNumber() )
		return true;

	// return if the area is not being capped
	if ( !m_bCapturing )
		return true;

	int iTeam = pVictim->GetTeamNumber();

	// return if this player's team is not capping the area
	if ( iTeam != m_nCapturingTeam )
		return true;

	bool bBlocked = false;

	if ( m_nCapturingTeam == TEAM_ALLIES )
	{
		if ( m_nNumAllies-1 < m_nAlliesNumCap )
			bBlocked = true;
	}
	else if ( m_nCapturingTeam == TEAM_AXIS )
	{
		if ( m_nNumAxis-1 < m_nAxisNumCap )
			bBlocked = true;
	}

	// break early incase we kill multiple people in the same frame
	if ( bBlocked )
	{
		m_pPoint->CaptureBlocked( pKiller );
		BreakCapture( false );
	}

	return true;
}