//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: Complete definition of the ControlZone behavioral entity
//
// $NoKeywords: $
//=============================================================================

#include "tf_shareddefs.h"
#include "cbase.h"
#include "EntityOutput.h"
#include "tf_player.h"
#include "controlzone.h"
#include "team.h"

//-----------------------------------------------------------------------------
// Purpose: Since the control zone is a data only class, force it to always be sent ( shouldn't change often so )
//  bandwidth usage should be small.
// Input  : **ppSendTable - 
//			*recipient - 
//			*pvs - 
//			clientArea - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CControlZone::ShouldTransmit( const edict_t *recipient, const void *pvs, int clientArea )
{
	if ( m_fEffects & EF_NODRAW )
		return false;

	return true;
}

IMPLEMENT_SERVERCLASS_ST(CControlZone, DT_ControlZone)
	SendPropInt( SENDINFO(m_nZoneNumber), 8, SPROP_UNSIGNED ),
END_SEND_TABLE()

LINK_ENTITY_TO_CLASS( trigger_controlzone, CControlZone);

BEGIN_DATADESC( CControlZone )

	// outputs
	DEFINE_OUTPUT( CControlZone, m_ControllingTeam, "ControllingTeam" ),

	// inputs
	DEFINE_INPUTFUNC( CControlZone, FIELD_VOID, "SetTeam", InputSetTeam ),
	DEFINE_INPUTFUNC( CControlZone, FIELD_VOID, "LockTeam", InputLockControllingTeam ),

	// keys 
	DEFINE_KEYFIELD_NOT_SAVED( CControlZone, m_iLockAfterChange, FIELD_INTEGER, "LockAfterChange" ),
	DEFINE_KEYFIELD_NOT_SAVED( CControlZone, m_flTimeTillCaptured, FIELD_FLOAT, "UncontestedTime" ),
	DEFINE_KEYFIELD_NOT_SAVED( CControlZone, m_flTimeTillContested, FIELD_FLOAT, "ContestedTime" ),
	DEFINE_KEYFIELD_NOT_SAVED( CControlZone, m_nZoneNumber, FIELD_INTEGER, "ZoneNumber" ),

END_DATADESC()



// Control Zone Ent Flags
#define CZF_DONT_USE_TOUCHES		1

//-----------------------------------------------------------------------------
// Purpose: Initializes the control zone
//			Records who was the original controlling team (for control locking)
//-----------------------------------------------------------------------------
void CControlZone::Spawn( void )
{
	// set the starting controlling team
	m_ControllingTeam.Set( GetTeamNumber(), this, this );

	// remember who the original controlling team was (for control locking)
	m_iDefendingTeam = GetTeamNumber();

	// Solid
	SetSolid( SOLID_BSP );
	AddSolidFlags( FSOLID_TRIGGER );
	SetMoveType( MOVETYPE_NONE );
	SetModel( STRING( GetModelName() ) );    // set size and link into world

	// TF2 rules
	m_flTimeTillContested = 10.0;	// Go to contested 10 seconds after enemies enter the zone
	m_flTimeTillCaptured = 5.0;		// Go to captured state as soon as only one team holds the zone	

	if ( m_nZoneNumber == 0 )
	{
		Warning( "Warning, trigger_controlzone without Zone Number set\n" );
	}

	m_ZonePlayerList.Purge();
}

//-----------------------------------------------------------------------------
// Purpose: Records that a player has entered the zone, and updates it's state
//			according, maybe starting to change team.
// Input  : *pOther - the entity that left the zone
//-----------------------------------------------------------------------------
void CControlZone::StartTouch( CBaseEntity *pOther )
{
	CBaseTFPlayer *pl = ToBaseTFPlayer( pOther );
	if ( !pl )
		return;

	CHandle< CBaseTFPlayer > hHandle;
	hHandle = pl;

	m_ZonePlayerList.AddToTail( hHandle );

	ReevaluateControllingTeam();

	// Set this player's current zone to this zone
	pl->SetCurrentZone( this );
}

//-----------------------------------------------------------------------------
// Purpose: Records that a player has left the zone, and updates it's state
//			according, maybe starting to change team.
// Input  : *pOther - the entity that left the zone
//-----------------------------------------------------------------------------
void CControlZone::EndTouch( CBaseEntity *pOther )
{
	CBaseTFPlayer *pl = ToBaseTFPlayer( pOther );
	if ( !pl )
		return;

	CHandle< CBaseTFPlayer > hHandle;
	hHandle = pl;
	m_ZonePlayerList.FindAndRemove( hHandle );

	ReevaluateControllingTeam();

	// Unset this player's current zone if it's this one
	if ( pl->GetCurrentZone() == this )
		pl->SetCurrentZone( NULL );
}

//-----------------------------------------------------------------------------
// Purpose: Checks to see if it's time to change controllers
//-----------------------------------------------------------------------------
void CControlZone::ReevaluateControllingTeam( void )
{
	// Count the number of players in each team
	int i;
	memset( m_iPlayersInZone, 0, sizeof( m_iPlayersInZone ) );
	for ( i = 0; i < m_ZonePlayerList.Size(); i++ )
	{
		if ( m_ZonePlayerList[i] != NULL && (m_ZonePlayerList[i]->GetTeamNumber() > 0) )
		{
			m_iPlayersInZone[ m_ZonePlayerList[i]->GetTeamNumber() ] += 1;
		}
	}

	// Abort immediately if we're not using touches to changes teams
	if ( HasSpawnFlags( CZF_DONT_USE_TOUCHES ) )
		return;

	// if we're locked in place, no changes can occur to controlling team except through an explicit map ResetTeam
	if ( m_iLocked )
		return;

	bool foundAnyTeam = false;
	int teamFound = 0;

	// check to see if any teams have no players
	for ( i = 1; i <= GetNumberOfTeams(); i++ )
	{
		if ( m_iPlayersInZone[i] )
		{
			if ( foundAnyTeam )
			{
				// we've already found a team, so it's being contested;
				teamFound = ZONE_CONTESTED;
				break;
			}

			foundAnyTeam = true;
			teamFound = i;			
		}
	}

	// no one in the area!
	if ( teamFound == 0 )
	{
		// just leave it as it is, let it continue to change team
		// exception: if the zone state is contested, and there aren't any players in the zone,
		// just return to the team who used to own the zone.
		if ( GetTeamNumber() == ZONE_CONTESTED )
		{
			ChangeTeam(m_iDefendingTeam);
			SetControllingTeam( this, m_iDefendingTeam );
		}

		return;
	}

	// if it's the same controlling team, don't worry about it
	if ( teamFound == GetTeamNumber() )
	{
		// the right team is in control, don't even think of switching
		m_iTryingToChangeToTeam = 0;
		SetNextThink( TICK_NEVER_THINK );
		return;
	}

	// Find out if the zone isn't owned by anyone at all (hasn't been touched since the map started, and it started un-owned)
	bool bHasBeenOwned = true;
	if ( m_iDefendingTeam == 0 && GetTeamNumber() == 0 ) 
		bHasBeenOwned = false;

	// if it's not contested, always go to contested mode 
	if ( GetTeamNumber() != ZONE_CONTESTED && teamFound != GetTeamNumber() )
	{
		// Unowned zones are captured immediately (no contesting stage)
		if ( bHasBeenOwned )
			teamFound = ZONE_CONTESTED;
	}

	// if it's the team we're trying to change to, don't worry about it
	if ( teamFound == m_iTryingToChangeToTeam )
		return;

	// set up the time to change to the new team soon
	m_iTryingToChangeToTeam = teamFound;

	// changing from contested->uncontested and visa-versa have different delays
	if ( m_iTryingToChangeToTeam != ZONE_CONTESTED )
	{
		if ( !bHasBeenOwned )
		{
			DevMsg( 1, "trigger_controlzone: (%s) changing team to %d NOW\n", GetDebugName(), m_iTryingToChangeToTeam );
			SetNextThink( gpGlobals->curtime + 0.1f );
		}
		else
		{
			DevMsg( 1, "trigger_controlzone: (%s) changing team to %d in %.2f seconds\n", GetDebugName(), m_iTryingToChangeToTeam, m_flTimeTillCaptured );
			SetNextThink( gpGlobals->curtime + m_flTimeTillCaptured );
		}
	}
	else
	{
		DevMsg( 1, "trigger_controlzone: (%s) changing to contested in %f seconds\n", GetDebugName(), m_flTimeTillContested );
		SetNextThink( gpGlobals->curtime + m_flTimeTillContested );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Checks to see if an uncontested territory is ready to change state
//			to the new controlling team.
//-----------------------------------------------------------------------------
void CControlZone::Think( void )
{
	if ( m_iTryingToChangeToTeam != 0 )
	{
		// held zone long enough
		SetControllingTeam( this, m_iTryingToChangeToTeam );

		// lock against further change if set
		if ( m_iLockAfterChange )
		{
			LockControllingTeam();
		}

		// Re-evaluate controlling team if we were changing to Contested (enemy may have withdrawn)
		if ( GetTeamNumber() == ZONE_CONTESTED )
		{
			ReevaluateControllingTeam();
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: set it so the team can no longer change, until a set controlling team action occurs
//-----------------------------------------------------------------------------
void CControlZone::InputLockControllingTeam( inputdata_t &inputdata )
{
	LockControllingTeam();
}


//-----------------------------------------------------------------------------
// Purpose: Input handler that sets the controlling team to the activator's team.
//-----------------------------------------------------------------------------
void CControlZone::InputSetTeam( inputdata_t &inputdata )
{
	// Abort if it's already the defending team
	if ( inputdata.pActivator->GetTeamNumber() == GetTeamNumber() )
		return;

	// set the new team
	ChangeTeam(inputdata.pActivator->GetTeamNumber());
	SetControllingTeam( inputdata.pActivator, GetTeamNumber() );
}


//-----------------------------------------------------------------------------
// Purpose: Changes the team controlling this zone
// Input  : newTeam - the new team to change to
//-----------------------------------------------------------------------------
void CControlZone::SetControllingTeam( CBaseEntity *pActivator, int newTeam )
{
	DevMsg( 1, "trigger_controlzone: (%s) changing team to: %d\n", GetDebugName(), newTeam );

	// remember this team as the defenders of the zone
	m_iDefendingTeam = GetTeamNumber();

	// reset state, firing the output
	ChangeTeam(newTeam);
	m_ControllingTeam.Set( GetTeamNumber(), pActivator, this );
	m_iLocked = FALSE;
	m_iTryingToChangeToTeam = 0;
	SetNextThink( TICK_NEVER_THINK );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CControlZone::LockControllingTeam( void )
{
	// never lock a zone in contested mode
	if ( GetTeamNumber() == ZONE_CONTESTED )
		return;
		
	// zones never lock to the defenders
	if ( GetTeamNumber() == m_iDefendingTeam )
		return;

	m_iLocked = TRUE;
}

