text: ﻿//========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose: 
  //
  // $NoKeywords: $
  //
  //=============================================================================//
  #include "cbase.h"
  #include "EntityOutput.h"
  #include "EntityList.h"
  #include "tf_player.h"
  #include "tf_func_resource.h"
  #include "tf_team.h"
  #include "tf_basecombatweapon.h"
  #include "gamerules.h"
  #include "ammodef.h"
  #include "tf_obj.h"
  #include "resource_chunk.h"
  #include "vstdlib/random.h"
  #include "engine/IEngineSound.h"
  #include "team_messages.h"
  #include "tf_stats.h"
  
  LINK_ENTITY_TO_CLASS( trigger_resourcezone, CResourceZone);
  
  BEGIN_DATADESC( CResourceZone )
  
  	// keys 
  	DEFINE_KEYFIELD_NOT_SAVED( m_nResourcesLeft, FIELD_INTEGER, "ResourceAmount" ),
  	DEFINE_KEYFIELD_NOT_SAVED( m_iMaxChunks, FIELD_INTEGER, "ResourceChunks" ),
  	DEFINE_KEYFIELD_NOT_SAVED( m_flResourceRate, FIELD_FLOAT, "ResourceRate" ),
  	DEFINE_KEYFIELD_NOT_SAVED( m_flChunkValueMin, FIELD_FLOAT, "ChunkValueMin" ),
  	DEFINE_KEYFIELD_NOT_SAVED( m_flChunkValueMax, FIELD_FLOAT, "ChunkValueMax" ),
  
  	// inputs
  	DEFINE_INPUTFUNC( FIELD_INTEGER, "SetAmount", InputSetAmount ),
  	DEFINE_INPUTFUNC( FIELD_VOID, "ResetAmount", InputResetAmount ),
  	DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ),
  	DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ),
  	DEFINE_INPUTFUNC( FIELD_VOID, "ToggleActive", InputToggleActive ),
  
  	// outputs
  	DEFINE_OUTPUT( m_OnEmpty, "OnEmpty" ),
  
  END_DATADESC()
  
  
  IMPLEMENT_SERVERCLASS_ST(CResourceZone, DT_ResourceZone)
  	SendPropFloat( SENDINFO( m_flClientResources ),	8,	SPROP_UNSIGNED,	0.0f,	1.0f ),
  	SendPropInt( SENDINFO( m_nResourcesLeft ),	20,	SPROP_UNSIGNED ),
  END_SEND_TABLE();
  
  PRECACHE_REGISTER( trigger_resourcezone );
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  CResourceZone::CResourceZone()
  {
  #ifdef _DEBUG
  	m_vecGatherPoint.Init();
  	m_angGatherPoint.Init();
  #endif
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Initializes the resource zone
  //-----------------------------------------------------------------------------
  void CResourceZone::Spawn( void )
  {
  	SetSolid( SOLID_BSP );
  	AddSolidFlags( FSOLID_TRIGGER );
  	SetMoveType( MOVETYPE_NONE );
  	AddEffects( EF_NODRAW );
  	SetModel( STRING( GetModelName() ) );
  
  	if ( !m_nResourcesLeft )
  	{
  		m_nResourcesLeft = 10000;
  	}
  
  	m_nMaxResources = m_nResourcesLeft;
  	m_flClientResources = 1;
  
  	SetActive( false );
  	m_vecGatherPoint = WorldSpaceCenter();
  	m_angGatherPoint = vec3_angle;
  	m_iTeamGathering = -1;
  	m_aSpawners.Purge();
  	m_hResourcePump = NULL;
  
  	if ( !m_iMaxChunks )
  		m_iMaxChunks = 5;
  	if ( !m_flChunkValueMin )
  		m_flChunkValueMin = 20;
  	if ( !m_flChunkValueMax )
  		m_flChunkValueMax = 60;
  
  	m_flBaseResourceRate = m_flResourceRate;
  	
  	m_flRespawnTimeModifier = 1.0f;
  
  	m_flTestTime = 0;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::Precache( void )
  {
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: See if we've got a gather point specified 
  //-----------------------------------------------------------------------------
  void CResourceZone::Activate( void )
  {
  	BaseClass::Activate();
  
  	if (m_target != NULL_STRING)
  	{
  		CBaseEntity	*pEnt = gEntList.FindEntityByName( NULL, m_target );
  		if ( pEnt )
  		{
  			m_vecGatherPoint = pEnt->GetLocalOrigin();
  			m_angGatherPoint = pEnt->GetLocalAngles();
  		}
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::InputSetAmount( inputdata_t &inputdata )
  {
  	m_nMaxResources = m_nResourcesLeft = inputdata.value.Int();
  	RecomputeClientResources();
  
  	// We may have just been reactivated
  	if ( m_nResourcesLeft )
  	{
  		SetActive( true );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::InputResetAmount( inputdata_t &inputdata )
  {
  	m_nResourcesLeft = m_nMaxResources;
  	m_flClientResources = 1;
  
  	// We may have just been reactivated
  	if ( m_nResourcesLeft )
  	{
  		SetActive( true );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::InputSetActive( inputdata_t &inputdata )
  {
  	SetActive( true );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::InputSetInactive( inputdata_t &inputdata )
  {
  	SetActive( false );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::InputToggleActive( inputdata_t &inputdata )
  {
  	if ( GetActive() )
  	{
  		SetActive( false );
  	}
  	else
  	{
  		SetActive( true );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::SetActive( bool bActive )
  {
  	// Going active?
  	if ( !m_bActive && bActive )
  	{
  		// Start our ambient sound
  		//EmitAmbientSound( this, Center(), "ResourceZone.AmbientActiveSound" );
  	}
  	else if ( m_bActive && !bActive )
  	{
  		// Going inactive
  
  		// Stop our sound loop
  		//StopSound( "ResourceZone.AmbientActiveSound" );
  	}
  
  	m_bActive = bActive;
  
  	// Tell all my spawners
  	for ( int i = 0; i < m_aSpawners.Size(); i++ )
  	{
  		m_aSpawners[i]->SetActive( bActive );
  	}
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  bool CResourceZone::GetActive() const
  {
  	return m_bActive;
  }
  
  //-----------------------------------------------------------------------------
  // Zone increasing....
  //-----------------------------------------------------------------------------
  void CResourceZone::AddZoneIncreaser( float rate )
  {
  	Assert( rate != 0.0f );
  
  	m_flRespawnTimeModifier *= rate;
  	m_flResourceRate = m_flBaseResourceRate / m_flRespawnTimeModifier;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::RemoveZoneIncreaser( float rate )
  {
  	Assert( rate != 0.0f );
  
  	m_flRespawnTimeModifier /= rate;
  	m_flResourceRate = m_flBaseResourceRate / m_flRespawnTimeModifier;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Removes resources from the zone, and returns true if it's now empty
  //-----------------------------------------------------------------------------
  bool CResourceZone::RemoveResources( int nResourcesRemoved )
  {
  	if ( IsEmpty() )
  		return true;
  
  	m_nResourcesLeft = MAX(0, m_nResourcesLeft - nResourcesRemoved);
  	RecomputeClientResources();
  
  	// If I'm out of resources, destroy my resource spawners
  	if ( IsEmpty() )
  	{
  		// Tell all existing chunks to stay forever
  		int i;
  		for ( i = 0; i < m_aChunks.Size(); i++ )
  		{
  			// Clear them removal think
  			m_aChunks[i]->SetThink( NULL );
  		}
  
  		SetActive( false );
  		
  		// Tell teams about it
  		for ( i = 0; i < GetNumberOfTeams(); i++ )
  		{
  			CTFTeam *pTeam = GetGlobalTFTeam( i );
  			pTeam->PostMessage( TEAMMSG_RESOURCE_ZONE_EMPTIED );
  		}
  
  		// Fire my output
  		m_OnEmpty.FireOutput( NULL,this );
  		return true;
  	}
  
  	return false;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Return true if this zone is empty
  //-----------------------------------------------------------------------------
  bool CResourceZone::IsEmpty( void )
  { 
  	// Inactive zones pretend to be empty, so nothing tries to do anything with them
  	if ( !GetActive() )
  		return true;
  
  	return (m_nResourcesLeft <= 0);
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Return true if the specified point is within this zone
  //-----------------------------------------------------------------------------
  bool CResourceZone::PointIsWithin( const Vector &vecPoint )
  {
  	return CollisionProp()->IsPointInBounds( vecPoint );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Resource zones have 1 build point
  //-----------------------------------------------------------------------------
  int CResourceZone::GetNumBuildPoints( void ) const
  {
  	return 1;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Return true if the specified object type can be built on this point
  //-----------------------------------------------------------------------------
  bool CResourceZone::CanBuildObjectOnBuildPoint( int iPoint, int iObjectType )
  {
  	ASSERT( iPoint <= GetNumBuildPoints() );
  
  	// Don't allow more than one pump
  	if ( m_hResourcePump.Get() )
  		return false;
  
  	// Only pumps can be built on zones
  	return ( iObjectType == OBJ_RESOURCEPUMP );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  bool CResourceZone::GetBuildPoint( int iPoint, Vector &vecOrigin, QAngle &vecAngles )
  {
  	ASSERT( iPoint <= GetNumBuildPoints() );
  
  	// If we have a gather point, return it
  	if ( m_vecGatherPoint != WorldSpaceCenter() )
  	{
  		vecOrigin = m_vecGatherPoint;
  	}
  	else if ( m_aSpawners.Size() )
  	{
  		// Return the first resource spawner
  		vecOrigin = m_aSpawners[0]->GetAbsOrigin();
  	}
  	else
  	{
  		vecOrigin = GetAbsOrigin();
  	}
  
  	vecAngles = QAngle(0,0,0);
  	return true;
  }
  
  int CResourceZone::GetBuildPointAttachmentIndex( int iPoint ) const
  {
  	return 0;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::SetObjectOnBuildPoint( int iPoint, CBaseObject *pObject )
  {
  	m_hResourcePump = pObject;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  int CResourceZone::GetNumObjectsOnMe( void )
  {
  	if ( m_hResourcePump.Get() )
  		return 1;
  
  	return 0;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  CBaseEntity	*CResourceZone::GetFirstObjectOnMe( void )
  {
  	return m_hResourcePump;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  CBaseObject *CResourceZone::GetObjectOfTypeOnMe( int iObjectType )
  {
  	if ( GetNumObjectsOnMe() == 1 )
  	{
  		CBaseObject *pObject = dynamic_cast<CBaseObject*>( m_hResourcePump.Get() );
  		if ( pObject )
  		{
  			if ( pObject->GetType() == iObjectType )
  				return pObject;
  		}
  	}
  
  	return NULL;
  }
  
  int	CResourceZone::FindObjectOnBuildPoint( CBaseObject *pObject )
  {
  	if (m_hResourcePump == pObject)
  		return 0;
  	return -1;
  }
  
  void CResourceZone::GetExitPoint( CBaseEntity *pPlayer, int iPoint, Vector *pAbsOrigin, QAngle *pAbsAngles )
  {
  	Assert(0);
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::RemoveAllObjects( void )
  {
  	UTIL_Remove( m_hResourcePump );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Get the team that's gathering from this point
  //-----------------------------------------------------------------------------
  CTFTeam *CResourceZone::GetOwningTeam( void )
  {
  	if ( m_iTeamGathering == -1 )
  		return NULL;
  
  	return (CTFTeam*)GetGlobalTeam(m_iTeamGathering);
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceZone::SetOwningTeam( int iTeamNumber )
  {
  	m_iTeamGathering = iTeamNumber;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Transmit this to all players who are in commander mode
  //-----------------------------------------------------------------------------
  int CResourceZone::ShouldTransmit( const CCheckTransmitInfo *pInfo )
  {
  	// Team rules may tell us that we should
  	CBaseEntity* pRecipientEntity = CBaseEntity::Instance( pInfo->m_pClientEnt );
  	Assert( pRecipientEntity->IsPlayer() );
  	
  	CBasePlayer *pPlayer = (CBasePlayer*)pRecipientEntity;
  	if ( pPlayer->GetTeam() )
  	{
  		if (pPlayer->GetTeam()->ShouldTransmitToPlayer( pPlayer, this ))
  			return FL_EDICT_ALWAYS;
  	}
  
  	return FL_EDICT_DONTSEND;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Check to see if we should create any more resource chunks
  //-----------------------------------------------------------------------------
  bool CResourceZone::ShouldSpawnChunk( void )
  {
  	// Don't spawn chunks if we're outta resources
  	if ( IsEmpty() )
  		return false;
  
  	// Create a chunk if we're below our max
  	if ( m_aChunks.Size() >= m_iMaxChunks )
  		return false;
  
  	return true;
  }
  
  void CResourceZone::SpawnChunk( const Vector &vecOrigin )
  {
  	// ROBIN: Disabled for now
  	return;
  
  	TFStats()->IncrementStat( TF_STAT_RESOURCE_CHUNKS_SPAWNED, 1 );
  
  	// Create a resource chunk and add it to our list
  	Vector vecVelocity = Vector( random->RandomFloat( -100,100 ), random->RandomFloat( -100,100 ), random->RandomFloat( 300,600 ));
  	CResourceChunk *pChunk = CResourceChunk::Create( false, vecOrigin, vecVelocity );
  	pChunk->m_hZone = this;
  
  	// Add it to our list
  	m_aChunks.AddToTail( pChunk );
  
  	// Remove it's value from the zone
  	RemoveResources( pChunk->GetResourceValue() );
  }
  
  
  void CResourceZone::RecomputeClientResources( )
  {
  	m_flClientResources = clamp( (float)m_nResourcesLeft / (float)m_nMaxResources, 0.0f, 1.0f );
  }
  
  
  void CResourceZone::RemoveChunk( CResourceChunk *pChunk, bool bReturn )
  {
  	if (bReturn)
  	{
  		TFStats()->IncrementStat( TF_STAT_RESOURCE_CHUNKS_RETIRED, 1 );
  	}
  
  	m_aChunks.FindAndRemove( pChunk );
  
  	// If I'm being returned, re-add my value to the resource level of the zone
  	if ( bReturn )
  	{
  		m_nResourcesLeft += pChunk->GetResourceValue();
  		RecomputeClientResources();
  	}
  }
  
  void CResourceZone::AddSpawner( CResourceSpawner *pSpawner )
  {
  	m_aSpawners.AddToTail( pSpawner );
  	pSpawner->SetActive( GetActive() );
  }
  
  //========================================================================================================================
  // RESOURCE CHUNK SPAWNER
  //========================================================================================================================
  LINK_ENTITY_TO_CLASS( env_resourcespawner, CResourceSpawner );
  PRECACHE_REGISTER( env_resourcespawner );
  
  BEGIN_DATADESC( CResourceSpawner )
  
  	// functions
  	DEFINE_FUNCTION( SpawnChunkThink ),
  
  END_DATADESC()
  
  
  IMPLEMENT_SERVERCLASS_ST(CResourceSpawner, DT_ResourceSpawner)
  	SendPropInt( SENDINFO( m_bActive ), 1, SPROP_UNSIGNED ),
  END_SEND_TABLE();
  
  // Resource Spawner Models
  char *sResourceSpawnerModel = "models/resources/resource_spawner_B.mdl";
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceSpawner::Spawn( void )
  {
  	m_hZone = NULL;
  	m_bActive = false;
  	SetModel( sResourceSpawnerModel );
  
  	// Create the object in the physics system
  	/*
  	VPhysicsInitStatic();
  	*/
  	SetMoveType( MOVETYPE_NONE );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceSpawner::Precache( void )
  {
  	PrecacheModel( sResourceSpawnerModel );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Find my resource point
  //-----------------------------------------------------------------------------
  void CResourceSpawner::Activate( void )
  {
  	if ( m_target != NULL_STRING )
  	{
  		// Find my resource zone
  		CResourceZone *pZone = (CResourceZone*)gEntList.FindEntityByName( NULL, m_target );
  		if ( pZone )
  		{
  			m_hZone = pZone;
  			SetModel( sResourceSpawnerModel );
  			m_hZone->AddSpawner( this );
  			return;
  		}
  	}
  
  	Warning( "ERROR: Resource Spawner without a target resource zone specified.\n" );
  	UTIL_Remove( this );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: 
  //-----------------------------------------------------------------------------
  void CResourceSpawner::SetActive( bool bActive )
  {
  	// Going active?
  	if ( !m_bActive && bActive )
  	{
  		// Randomize the thinks a little to reduce network usage ong chunk spawning 
  		SetNextThink( gpGlobals->curtime + m_hZone->GetResourceRate() + random->RandomFloat( 0.0, 1.0 ) );
  		SetThink( SpawnChunkThink );
  		RemoveEffects( EF_NODRAW );
  	}
  	else if ( m_bActive && !bActive )
  	{
  		// Going inactive
  		SetThink( NULL );
  		AddEffects( EF_NODRAW );
  	}
  
  	m_bActive = bActive;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Spawn a chunk from this spawner
  //-----------------------------------------------------------------------------
  void CResourceSpawner::SpawnChunkThink( void )
  {
  	// Lost our zone?
  	if ( !m_hZone )
  	{
  		SetActive( false );
  		return;
  	}
  
  	if ( m_hZone->ShouldSpawnChunk() )
  	{
  		// Start spawning events
  		EntityMessageBegin( this );
  		MessageEnd();
  
  		m_hZone->SpawnChunk( GetAbsOrigin() + Vector(0,0,64) );
  	}
  
  	// Randomize the thinks a little to reduce network usage on chunk spawning 
  	SetNextThink( gpGlobals->curtime + m_hZone->GetResourceRate() + random->RandomFloat( 0.0, 1.0 ) );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Convert an amount of resources into a number of processed & unprocessed resource chunks
  //-----------------------------------------------------------------------------
  void ConvertResourceValueToChunks( int iResources, int *iNumProcessed, int *iNumNormal )
  {
  	*iNumProcessed = *iNumNormal = 0;
  
  	while ( iResources >= resource_chunk_processed_value.GetFloat() )
  	{
  		iResources -= resource_chunk_processed_value.GetFloat();
  		*iNumProcessed += 1;
  	}
  
  	while ( iResources >= resource_chunk_value.GetFloat() )
  	{
  		iResources -= resource_chunk_value.GetFloat();
  		*iNumNormal += 1;
  	}
  
  	// Round up
  	if ( iResources )
  	{
  		*iNumNormal++;
  	}
  }