void CTeamTrainWatcher::InternalSetNumTrainCappers( int iNumCappers, CBaseEntity *pTrigger )
{
	if ( IsDisabled() )
		return;

	m_nNumCappers = iNumCappers;

	// inputdata.pCaller is hopefully an area capture
	// lets see if its blocked, and not start receding if it is
	CTriggerAreaCapture *pAreaCap = dynamic_cast<CTriggerAreaCapture *>( pTrigger );
	if ( pAreaCap )
	{
		m_bCapBlocked = pAreaCap->IsBlocked();
		m_hAreaCap = pAreaCap;
	}

	if ( iNumCappers <= 0 && !m_bCapBlocked && m_bTrainCanRecede )
	{
		if ( !m_bWaitingToRecede )
		{
			// start receding in [tf_escort_cart_recede_time] seconds
			m_bWaitingToRecede = true;

			if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->InOvertime() )
			{
				m_flRecedeTotalTime = tf_escort_recede_time_overtime.GetFloat();
			}
			else
			{
				m_flRecedeTotalTime = tf_escort_recede_time.GetFloat();
				if ( m_nTrainRecedeTime > 0 )
				{
					m_flRecedeTotalTime = m_nTrainRecedeTime;
				}
			}

			m_flRecedeStartTime = gpGlobals->curtime;
			m_flRecedeTime = m_flRecedeStartTime + m_flRecedeTotalTime;
		}		
	}
	else
	{
		// cancel receding
		m_bWaitingToRecede = false;
		m_flRecedeTime = 0;
	}

	HandleTrainMovement();
}
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamControlPointRound::MakePlayable( void )
{
	CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
	if ( pMaster )
	{
		if ( !IsPlayable() )
		{
			// we need to try switching the owners of the teams to make this round playable
			for ( int i = FIRST_GAME_TEAM ; i < GetNumberOfTeams() ; i++ )
			{
				for ( int j = 0 ; j < m_ControlPoints.Count() ; j++ )
				{
					if ( ( !pMaster->IsBaseControlPoint( m_ControlPoints[j]->GetPointIndex() ) ) && // this is NOT the base point for one of the teams (we don't want to assign the base to the wrong team)
						 ( !WouldNewCPOwnerWinGame( m_ControlPoints[j], i ) ) ) // making this change would make this round playable
					{
						// need to find the trigger area associated with this point
						CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, pMaster->GetTriggerAreaCaptureName() );
						while( pEnt )
						{
							CTriggerAreaCapture *pArea = assert_cast<CTriggerAreaCapture*>( pEnt );
							if ( pArea )
							{	
								if ( pArea->TeamCanCap( i ) )
								{
									CHandle<CTeamControlPoint> hPoint = pArea->GetControlPoint();
									if ( hPoint == m_ControlPoints[j] )
									{
										// found! 
										pArea->ForceOwner( i ); // this updates the trigger_area *and* the control_point
										return true;
									}
								}
							}

							pEnt = gEntList.FindEntityByClassname( pEnt, pMaster->GetTriggerAreaCaptureName() );
						}
					}
				}
			}
		}
	}

	return false;
}
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTeamControlPointRound::MakePlayable( void )
{
	CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
	if ( pMaster )
	{
		if ( !IsPlayable() )
		{
			// we need to try switching the owners of the teams to make this round playable
			for ( int iTeam = FIRST_GAME_TEAM ; iTeam < GetNumberOfTeams() ; iTeam++ )
			{
				for ( int iControlPoint = 0 ; iControlPoint < m_ControlPoints.Count() ; iControlPoint++ )
				{
					if ( ( !pMaster->IsBaseControlPoint( m_ControlPoints[iControlPoint]->GetPointIndex() ) ) && // this is NOT the base point for one of the teams (we don't want to assign the base to the wrong team)
						 ( !WouldNewCPOwnerWinGame( m_ControlPoints[iControlPoint], iTeam ) ) ) // making this change would make this round playable
					{
						// need to find the trigger area associated with this point
						for ( int iObj=0; iObj<ITriggerAreaCaptureAutoList::AutoList().Count(); ++iObj )
						{
							CTriggerAreaCapture *pArea = static_cast< CTriggerAreaCapture * >( ITriggerAreaCaptureAutoList::AutoList()[iObj] );	
							if ( pArea->TeamCanCap( iTeam ) )
							{
								CHandle<CTeamControlPoint> hPoint = pArea->GetControlPoint();
								if ( hPoint == m_ControlPoints[iControlPoint] )
								{
									// found! 
									pArea->ForceOwner( iTeam ); // this updates the trigger_area *and* the control_point
									return true;
								}
							}
						}
					}
				}
			}
		}
	}

	return false;
}
// ==========================================================
// given a start node and a list of goal nodes
// calculate the distance between each
// ==========================================================
void CTeamTrainWatcher::WatcherActivate( void )
{		
	m_flRecedeTime = 0;
	m_bWaitingToRecede = false;
	m_bCapBlocked = false;
	m_flNextSpeakForwardConceptTime = 0;
	m_hAreaCap = NULL;
	m_flTrainDistanceFromStart = 0.0f;

	m_bAlarmPlayed = false;

	m_Sparks.Purge();

	StopCaptureAlarm();

	// init our train
	m_hTrain = dynamic_cast<CFuncTrackTrain*>( gEntList.FindEntityByName( NULL, m_iszTrain ) );
	if ( !m_hTrain )
	{
		Warning("%s failed to find train named '%s'\n", GetClassname(), STRING( m_iszTrain ) );
	}

	// find the trigger area that will give us movement updates and find the sparks (if we're going to handle the train movement)
	if ( m_bHandleTrainMovement )
	{
		if ( m_hTrain )
		{
			CTriggerAreaCapture *pArea = dynamic_cast<CTriggerAreaCapture *>( gEntList.FindEntityByClassname( NULL, "trigger_capture_area" ) );
			while( pArea )
			{
				if ( pArea->GetParent() == m_hTrain.Get() )
				{
					// this is the capture area we care about, so let it know that we want updates on the capture numbers
					pArea->SetTrainWatcher( this );
					break;
				}

				pArea = dynamic_cast<CTriggerAreaCapture *>( gEntList.FindEntityByClassname( pArea, "trigger_capture_area" ) );
			}
		}

		// init the sprites (if any)
		CEnvSpark *pSpark = dynamic_cast<CEnvSpark*>( gEntList.FindEntityByName( NULL, m_iszSparkName ) );
		while ( pSpark )
		{
			m_Sparks.AddToTail( pSpark );
			pSpark = dynamic_cast<CEnvSpark*>( gEntList.FindEntityByName( pSpark, m_iszSparkName ) );
		}
	}

	// init our array of path_tracks linked to control points
	m_iNumCPLinks = 0;

	int i;
	for ( i = 0 ; i < MAX_CONTROL_POINTS ; i++ )
	{
		CPathTrack *pPathTrack = dynamic_cast<CPathTrack*>( gEntList.FindEntityByName( NULL, m_iszLinkedPathTracks[i] ) );
		CTeamControlPoint *pCP = dynamic_cast<CTeamControlPoint*>( gEntList.FindEntityByName( NULL, m_iszLinkedCPs[i] ) );
		if ( pPathTrack && pCP )
		{
			m_CPLinks[m_iNumCPLinks].hPathTrack = pPathTrack;
			m_CPLinks[m_iNumCPLinks].hCP = pCP;
			m_CPLinks[m_iNumCPLinks].flDistanceFromStart = 0;	// filled in when we parse the nodes
			m_CPLinks[m_iNumCPLinks].bAlertPlayed = false;
			m_iNumCPLinks++;
		}
	}

	// init our start and goal nodes
	m_hStartNode = dynamic_cast<CPathTrack*>( gEntList.FindEntityByName( NULL, m_iszStartNode ) );
	if ( !m_hStartNode )
	{
		Warning("%s failed to find path_track named '%s'\n", GetClassname(), STRING(m_iszStartNode) );
	}

	m_hGoalNode = dynamic_cast<CPathTrack*>( gEntList.FindEntityByName( NULL, m_iszGoalNode ) );
	if ( !m_hGoalNode )
	{
		Warning("%s failed to find path_track named '%s'\n", GetClassname(), STRING(m_iszGoalNode) );
	}

	m_flTotalPathDistance = 0.0f;

	CUtlVector< float > hillData;
	bool bOnHill = false;

	bool bDownHillData[TEAM_TRAIN_MAX_HILLS];
	Q_memset( bDownHillData, 0, sizeof( bDownHillData ) );
	int iHillCount = 0;

	if( m_hStartNode.Get() && m_hGoalNode.Get() )
	{
		CPathTrack *pNode = m_hStartNode;
		CPathTrack *pPrev = pNode;
		CPathTrack *pHillStart = NULL;
		pNode = pNode->GetNext();
		int iHillType = HILL_TYPE_NONE;

		// don't check the start node for links. If it's linked, it will have 0 distance anyway
		while ( pNode )
		{
			Vector dir = pNode->GetLocalOrigin() - pPrev->GetLocalOrigin();
			float length = dir.Length();

			m_flTotalPathDistance += length;

			// gather our hill data for the HUD
			if ( pNode->GetHillType() != iHillType )
			{
				if ( !bOnHill ) // we're at the start of a hill
				{
					hillData.AddToTail( m_flTotalPathDistance );
					bOnHill = true;
					pHillStart = pNode;

					if ( iHillCount < TEAM_TRAIN_MAX_HILLS )
					{
						bDownHillData[iHillCount] = pNode->IsDownHill() ? true : false;
						iHillCount++;
					}
				}
				else // we're at the end of a hill
				{
					float flDistance = m_flTotalPathDistance - length; // subtract length because the prev node was the end of the hill (not this one)

					if ( pHillStart && ( pHillStart == pPrev ) )
					{
						flDistance = m_flTotalPathDistance; // we had a single node marked as a hill, so we'll use the current distance as the next marker
					}

					hillData.AddToTail( flDistance ); 

					// is our current node the start of another hill?
					if ( pNode->GetHillType() != HILL_TYPE_NONE )
					{
						hillData.AddToTail( m_flTotalPathDistance );
						bOnHill = true;
						pHillStart = pNode;

						if ( iHillCount < TEAM_TRAIN_MAX_HILLS )
						{
							bDownHillData[iHillCount] = pNode->IsDownHill() ? true : false;
							iHillCount++;
						}
					}
					else
					{
						bOnHill = false;
						pHillStart = NULL;
					}
				}

				iHillType = pNode->GetHillType();
			}

			// if pNode is one of our cp nodes, store its distance from m_hStartNode
			for ( i = 0 ; i < m_iNumCPLinks ; i++ )
			{
				if ( m_CPLinks[i].hPathTrack == pNode )
				{
					m_CPLinks[i].flDistanceFromStart = m_flTotalPathDistance;
					break;
				}
			}

			if ( pNode == m_hGoalNode )
				break;

			pPrev = pNode;
			pNode = pNode->GetNext();
		}
	}

	// if we don't have an even number of entries in our hill data (beginning/end) add the final distance
	if ( ( hillData.Count() % 2 ) != 0 )
	{
		hillData.AddToTail( m_flTotalPathDistance );
	}

	if ( ObjectiveResource() )
	{
		ObjectiveResource()->ResetHillData( GetTeamNumber() );

		// convert our hill data into 0-1 percentages for networking
		if ( m_flTotalPathDistance > 0 && hillData.Count() > 0 )
		{
			i = 0;
	 		while ( i < hillData.Count() )
			{
				if ( i < TEAM_TRAIN_HILLS_ARRAY_SIZE - 1 ) // - 1 because we want to use 2 entries
				{
					// add/subtract to the hill start/end to fix rounding errors in the HUD when the train
					// stops at the bottom/top of a hill but the HUD thinks the train is still on the hill
					ObjectiveResource()->SetHillData( GetTeamNumber(), (hillData[i] / m_flTotalPathDistance) + 0.005f, (hillData[i+1] / m_flTotalPathDistance) - 0.005f, bDownHillData[i/2] );
				}
				i = i + 2;
			}
		}
	}
 
	// We have total distance and increments in our links array
	for ( i=0;i<m_iNumCPLinks;i++ )
	{
		int iCPIndex = m_CPLinks[i].hCP.Get()->GetPointIndex();
// This can be pulled once DoD includes team_objectiveresource.* and c_team_objectiveresource.*
#ifndef DOD_DLL 
		ObjectiveResource()->SetTrainPathDistance( iCPIndex, m_CPLinks[i].flDistanceFromStart / m_flTotalPathDistance );
#endif
	}

#ifdef GLOWS_ENABLE
	FindGlowEntity();
#endif // GLOWS_ENABLE

	InternalSetSpeedForwardModifier( m_flSpeedForwardModifier );

	SetContextThink( &CTeamTrainWatcher::WatcherThink, gpGlobals->curtime + 0.1, TW_THINK );
}