DETOUR_DECL_MEMBER(void, CTFTankBoss_TankBossThink)
	{
		static CountdownTimer ctNodes;
		
		if (ctNodes.IsElapsed()) {
			ctNodes.Start(0.5f);
			
			ForEachEntityByRTTI<CPathTrack>([](CPathTrack *node){
				NDebugOverlay::Box(node->GetAbsOrigin(), Vector(-10.0f, -10.0f, -10.0f), Vector(10.0f, 10.0f, 10.0f),
					0xff, 0xff, 0xff, 0x80, 0.5f);
				NDebugOverlay::EntityTextAtPosition(node->GetAbsOrigin(), 0, CFmtStrN<16>("#%d", i), 0.5f, 0xff, 0xff, 0xff, 0xff);
				
				CPathTrack *next = node->GetNext();
				if (next != nullptr) {
					NDebugOverlay::HorzArrow(node->GetAbsOrigin(), next->GetAbsOrigin(), 3.0f, 0xff, 0xff, 0xff, 0xff, true, 0.5f);
				}
			});
		}
		
		auto tank = reinterpret_cast<CTFTankBoss *>(this);
		
		NDebugOverlay::EntityText(ENTINDEX(tank), 0, CFmtStrN<256>("%.1f%%", GetTankProgress(tank) * 100.0f),
			gpGlobals->interval_per_tick, 0xff, 0xff, 0xff, 0xff);
		
	//	NDebugOverlay::EntityText(ENTINDEX(tank), 0, CFmtStrN<256>("m_hCurrentNode:    #%d", ENTINDEX(*m_hCurrentNode)),
	//		gpGlobals->interval_per_tick, 0xff, 0xff, 0xff, 0xff);
	//	NDebugOverlay::EntityText(ENTINDEX(tank), 1, CFmtStrN<256>("m_iCurrentNode:    %d", *m_iCurrentNode),
	//		gpGlobals->interval_per_tick, 0xff, 0xff, 0xff, 0xff);
	//	NDebugOverlay::EntityText(ENTINDEX(tank), 2, CFmtStrN<256>("m_flTotalDistance: %.0f", *m_flTotalDistance),
	//		gpGlobals->interval_per_tick, 0xff, 0xff, 0xff, 0xff);
	//	NDebugOverlay::EntityText(ENTINDEX(tank), 3, CFmtStrN<256>("m_NodeDists[i]:    %.0f", (*m_NodeDists)[*m_iCurrentNode]),
	//		gpGlobals->interval_per_tick, 0xff, 0xff, 0xff, 0xff);
		
		DETOUR_MEMBER_CALL(CTFTankBoss_TankBossThink)();
	}
// Project the given position onto the track and return the point and how far along that projected position is
void CTeamTrainWatcher::ProjectPointOntoPath( const Vector &pos, Vector *posOnPathResult, float *distanceAlongPathResult ) const
{
	CPathTrack *nextNode = NULL;
	CPathTrack *node = m_hStartNode;

	Vector toPos;
	Vector alongPath;
	float distanceAlong = 0.0f;

	Vector closestPointOnPath = vec3_origin;
	float closestPerpendicularDistanceSq = FLT_MAX;
	float closestDistanceAlongPath = FLT_MAX;

	CPathTrack::BeginIteration();
	while( node )
	{
		node->Visit();
		nextNode = node->GetNext();

		if ( !nextNode || nextNode->HasBeenVisited() )
			break;

		alongPath = nextNode->GetAbsOrigin() - node->GetAbsOrigin();
		float segmentLength = alongPath.NormalizeInPlace();

		toPos = pos - node->GetAbsOrigin();
		float segmentOverlap = DotProduct( toPos, alongPath );

		if ( segmentOverlap >= 0.0f && segmentOverlap < segmentLength )
		{
			// projection is within segment bounds
			Vector onPath = node->GetAbsOrigin() + alongPath * segmentOverlap;

			float perpendicularDistanceSq = ( onPath - pos ).LengthSqr();
			if ( perpendicularDistanceSq < closestPerpendicularDistanceSq )
			{
				closestPointOnPath = onPath;
				closestPerpendicularDistanceSq = perpendicularDistanceSq;
				closestDistanceAlongPath = distanceAlong + segmentOverlap;
			}
		}

		distanceAlong += segmentLength;
		node = nextNode;
	}
	CPathTrack::EndIteration();

	if ( posOnPathResult )
	{
		*posOnPathResult = closestPointOnPath;
	}

	if ( distanceAlongPathResult )
	{
		*distanceAlongPathResult = closestDistanceAlongPath;
	}
}
Beispiel #3
0
/* <1bd087> ../cstrike/dlls/vehicle.cpp:764 */
void CFuncVehicle::DeadEnd()
{
    CPathTrack *pTrack = m_ppath;
    ALERT(at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname));

    if (pTrack != NULL)
    {
        CPathTrack *pNext;

        if (m_oldSpeed < 0)
        {
            do
            {
                pNext = pTrack->ValidPath(pTrack->GetPrevious(), TRUE);

                if (pNext != NULL)
                {
                    pTrack = pNext;
                }
            }
            while (pNext != NULL);
        }
        else
        {
            do
            {
                pNext = pTrack->ValidPath(pTrack->GetNext(), TRUE);

                if (pNext != NULL)
                {
                    pTrack = pNext;
                }
            }
            while (pNext != NULL);
        }
    }

    pev->velocity = g_vecZero;
    pev->avelocity = g_vecZero;

    if (pTrack != NULL)
    {
        ALERT(at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname));

        if (!FStringNull(pTrack->pev->netname))
        {
            FireTargets(STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0);
        }
    }
    else
        ALERT(at_aiconsole, "\n");
}
Beispiel #4
0
void CFuncVehicle::NearestPath()
{
	CPathTrack *pTrack = nullptr;
	CPathTrack *pNearest = nullptr;
	real_t dist;
	float closest = 1024.0f;

	while ((pTrack = UTIL_FindEntityInSphere(pTrack, pev->origin, 1024.0f)))
	{
		// filter out non-tracks
		if (!(pTrack->pev->flags & (FL_CLIENT | FL_MONSTER)) && FClassnameIs(pTrack->pev, "path_track"))
		{
			dist = (pev->origin - pTrack->pev->origin).Length();

			if (dist < closest)
			{
				closest = dist;
				pNearest = pTrack;
			}
		}
	}

	if (!pNearest)
	{
		ALERT(at_console, "Can't find a nearby track !!!\n");
		SetThink(nullptr);
		return;
	}

	ALERT(at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname));

	// If I'm closer to the next path_track on this path, then it's my real path
	pTrack = pNearest->GetNext();

	if (pTrack)
	{
		if ((pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length())
		{
			pNearest = pTrack;
		}
	}

	m_ppath = pNearest;

	if (pev->speed != 0)
	{
		NextThink(pev->ltime + 0.1f, FALSE);
		SetThink(&CFuncVehicle::Next);
	}
}
//-----------------------------------------------------------------------------
// Purpose: Check to see if we should teleport to the current path corner
//-----------------------------------------------------------------------------
void CNPC_VehicleDriver::CheckForTeleport( void )
{
	if ( !GetGoalEnt() )
		return;

	CPathTrack *pTrack = dynamic_cast<CPathTrack *>( GetGoalEnt() );
	if ( !pTrack )
		return;

	// Does it have the teleport flag set?
	if ( pTrack->HasSpawnFlags( SF_PATH_TELEPORT ) )
	{
		AddEffects( EF_NOINTERP );

		// Teleport the vehicle to the pathcorner
		Vector vecMins, vecMaxs;
		vecMins = m_hVehicleEntity->CollisionProp()->OBBMins();
		vecMaxs = m_hVehicleEntity->CollisionProp()->OBBMaxs();
		Vector vecTarget = pTrack->GetAbsOrigin() - (vecMins + vecMaxs) * 0.5;
		vecTarget.z += ((vecMaxs.z - vecMins.z) * 0.5) + 8;	// Safety buffer

		// Orient it to face the next point
		QAngle vecAngles = pTrack->GetAbsAngles();
		Vector vecToTarget = vec3_origin;
		if ( pTrack->GetNext() )
		{
			vecToTarget = (pTrack->GetNext()->GetAbsOrigin() - pTrack->GetAbsOrigin());
			VectorNormalize( vecToTarget );

			// Vehicles are rotated 90 degrees
			VectorAngles( vecToTarget, vecAngles );
			vecAngles[YAW] -= 90;
		}
		m_hVehicleEntity->Teleport( &vecTarget, &vecAngles, &vec3_origin );

		// Teleport the driver
		SetAbsOrigin( m_hVehicleEntity->WorldSpaceCenter() );
		SetAbsAngles( m_hVehicleEntity->GetAbsAngles() );

		m_vecPrevPoint = pTrack->GetAbsOrigin();

		// Move to the next waypoint, we've reached this one
		if ( GetNavigator()->GetPath() )
		{
			WaypointReached();
		}

		// Clear our waypoints, because the next waypoint is certainly invalid now.
		ClearWaypoints();
	}
}
Beispiel #6
0
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
QAngle CPathTrack::GetOrientation( bool bForwardDir )
{
	TrackOrientationType_t eOrient = GetOrientationType();
	if ( eOrient == TrackOrientation_FacePathAngles )
	{
		return GetLocalAngles();
	}

	CPathTrack *pPrev = this;
	CPathTrack *pNext = GetNextInDir( bForwardDir );

	if ( !pNext )
	{	pPrev = GetNextInDir( !bForwardDir );
		pNext = this;
	}

	Vector vecDir = pNext->GetLocalOrigin() - pPrev->GetLocalOrigin();

	QAngle angDir;
	VectorAngles( vecDir, angDir );
	return angDir;
}
Beispiel #7
0
//-----------------------------------------------------------------------------
// Purpose: Assumes this is ALWAYS enabled
// Input  : origin - position along path to look ahead from
//			dist - distance to look ahead, negative values look backward
//			move - 
// Output : Returns the track that we will be PAST in 'dist' units.
//-----------------------------------------------------------------------------
CPathTrack *CPathTrack::LookAhead( Vector &origin, float dist, int move, CPathTrack **pNextNext )
{
	CPathTrack *pcurrent = this;
	float originalDist = dist;
	Vector currentPos = origin;

	bool bForward = true;
	if ( dist < 0 )
	{
		// Travelling backwards along the path.
		dist = -dist;
		bForward = false;
	}

	// Move along the path, until we've gone 'dist' units or run out of path.
	while ( dist > 0 )
	{
		// If there is no next path track, or it's disabled, we're done.
		if ( !ValidPath( pcurrent->GetNextInDir( bForward ), move ) )
		{
			if ( !move )
			{
				Project( pcurrent->GetNextInDir( !bForward ), pcurrent, origin, dist );
			}

			return NULL;
		}

		// The next path track is valid. How far are we from it?
		Vector dir = pcurrent->GetNextInDir( bForward )->GetLocalOrigin() - currentPos;
		float length = dir.Length();

		// If we are at the next node and there isn't one beyond it, return the next node.
		if ( !length  && !ValidPath( pcurrent->GetNextInDir( bForward )->GetNextInDir( bForward ), move ) )
		{
			if ( pNextNext )
			{
				*pNextNext = NULL;
			}

			if ( dist == originalDist )
			{
				// Didn't move at all, must be in a dead end.
				return NULL;
			}

			return pcurrent->GetNextInDir( bForward );
		}

		// If we don't hit the next path track within the distance remaining, we're done.
		if ( length > dist )
		{
			origin = currentPos + ( dir * ( dist / length ) );
			if ( pNextNext )
			{
				*pNextNext = pcurrent->GetNextInDir( bForward );
			}

			return pcurrent;
		}

		// We hit the next path track, advance to it.
		dist -= length;
		currentPos = pcurrent->GetNextInDir( bForward )->GetLocalOrigin();
		pcurrent = pcurrent->GetNextInDir( bForward );
		origin = currentPos;
	}

	// We consumed all of the distance, and exactly landed on a path track.
	if ( pNextNext )
	{
		*pNextNext = pcurrent->GetNextInDir( bForward );
	}

	return pcurrent;
}
Beispiel #8
0
// Assumes this is ALWAYS enabled
CPathTrack *CPathTrack :: LookAhead( Vector *origin, float dist, int move )
{
	CPathTrack *pcurrent;
	float originalDist = dist;
	
	pcurrent = this;
	Vector currentPos = *origin;

	if ( dist < 0 )		// Travelling backwards through path
	{
		dist = -dist;
		while ( dist > 0 )
		{
			Vector dir = pcurrent->pev->origin - currentPos;
			float length = dir.Length();
			if ( !length )
			{
				if ( !ValidPath(pcurrent->GetPrevious(), move) ) 	// If there is no previous node, or it's disabled, return now.
				{
					if ( !move )
						Project( pcurrent->GetNext(), pcurrent, origin, dist );
					return NULL;
				}
				pcurrent = pcurrent->GetPrevious();
			}
			else if ( length > dist )	// enough left in this path to move
			{
				*origin = currentPos + (dir * (dist / length));
				return pcurrent;
			}
			else
			{
				dist -= length;
				currentPos = pcurrent->pev->origin;
				*origin = currentPos;
				if ( !ValidPath(pcurrent->GetPrevious(), move) )	// If there is no previous node, or it's disabled, return now.
					return NULL;

				pcurrent = pcurrent->GetPrevious();
			}
		}
		*origin = currentPos;
		return pcurrent;
	}
	else 
	{
		while ( dist > 0 )
		{
			if ( !ValidPath(pcurrent->GetNext(), move) )	// If there is no next node, or it's disabled, return now.
			{
				if ( !move )
					Project( pcurrent->GetPrevious(), pcurrent, origin, dist );
				return NULL;
			}
			Vector dir = pcurrent->GetNext()->pev->origin - currentPos;
			float length = dir.Length();
			if ( !length  && !ValidPath( pcurrent->GetNext()->GetNext(), move ) )
			{
				if ( dist == originalDist ) // HACK -- up against a dead end
					return NULL;
				return pcurrent;
			}
			if ( length > dist )	// enough left in this path to move
			{
				*origin = currentPos + (dir * (dist / length));
				return pcurrent;
			}
			else
			{
				dist -= length;
				currentPos = pcurrent->GetNext()->pev->origin;
				pcurrent = pcurrent->GetNext();
				*origin = currentPos;
			}
		}
		*origin = currentPos;
	}

	return pcurrent;
}
Beispiel #9
0
//-----------------------------------------------------------------------------
// Purpose: Dumb linear serach of the path
// Input  : *pStart - starting path node
//			&startPosition - starting position
//			&destination - position to move close to
// Output : int move direction 1 = forward, -1 = reverse, 0 = stop
//-----------------------------------------------------------------------------
int PathFindDirection( CPathTrack *pStart, const Vector &startPosition, const Vector &destination )
{
	if ( !pStart )
		return 0;		// no path, don't move

	CPathTrack *pPath = pStart->m_pnext;
	CPathTrack *pNearest = pStart;

	float nearestDist = (pNearest->GetLocalOrigin() - destination).LengthSqr();
	float length = 0;
	float nearestForward = 0, nearestReverse = 0;

	do
	{
		float dist = (pPath->GetLocalOrigin() - destination).LengthSqr();
		
		// This is closer than our current estimate
		if ( dist < nearestDist )
		{
			nearestDist = dist;
			pNearest = pPath;
			nearestForward = length;	// current path length forward
			nearestReverse = 0;			// count until we hit the start again
		}
		CPathTrack *pNext = pPath->m_pnext;
		if ( pNext )
		{
			// UNDONE: Cache delta in path?
			float delta = (pNext->GetLocalOrigin() - pPath->GetLocalOrigin()).LengthSqr();
			length += delta;
			// add to current reverse estimate
			nearestReverse += delta;
			pPath = pNext;
		}
		else
		{
			// not a looping path
			// traverse back to other end of the path
			int fail = 0;
			while ( pPath->m_pprevious )
			{
				fail++;
				// HACKHACK: Don't infinite loop
				if ( fail > 256 )
					break;
				pPath = pPath->m_pprevious;
			}
			// don't take the reverse path to old node
			nearestReverse = nearestForward + 1;
			// dont' take forward path to new node (if we find one)
			length = (float)COORD_EXTENT * (float)COORD_EXTENT; // HACKHACK: Max quad length
		}

	} while ( pPath != pStart );

	// UNDONE: Fix this fudge factor
	// if you are already at the path, or <100 units away, don't move
	if ( pNearest == pStart || (pNearest->GetLocalOrigin() - startPosition).LengthSqr() < 100 )
		return 0;

	if ( nearestForward <= nearestReverse )
		return 1;

	return -1;
}
// ==========================================================
// 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 );
}
void CTeamTrainWatcher::FireGameEvent( IGameEvent *event )
{
	if ( IsDisabled() || !m_bHandleTrainMovement )
		return;

	const char *pszEventName = event->GetName();
	if ( FStrEq( pszEventName, "path_track_passed" ) )
	{
		int iIndex = event->GetInt( "index" );
		CPathTrack *pNode = dynamic_cast< CPathTrack* >( UTIL_EntityByIndex( iIndex ) );

		if ( pNode )
		{
			bool bHandleEvent = false;
			CPathTrack *pTempNode = m_hStartNode.Get();

			// is this a node in the track we're watching?
			while ( pTempNode )
			{
				if ( pTempNode == pNode )
				{
					bHandleEvent = true;
					break;
				}

				pTempNode = pTempNode->GetNext();
			}

			if ( bHandleEvent )
			{
				// If we're receding and we've hit a node but the next node (going backwards) is disabled 
				// the train is going to stop (like at the base of a downhill section) when we start forward 
				// again we won't pass this node again so don't change our hill state based on this node.
				if ( m_bReceding )
				{
					if ( pNode->GetPrevious() && pNode->GetPrevious()->IsDisabled() )
					{
						return;
					}
				}

				int iHillType = pNode->GetHillType();
				bool bUpdate = ( m_iCurrentHillType != iHillType );

				if ( !bUpdate )
				{
					// the hill settings are the same, but are we leaving an uphill or downhill segment?
					if ( m_iCurrentHillType != HILL_TYPE_NONE )
					{
						// let's peek at the next node
						CPathTrack *pNextNode = pNode->GetNext();
						if ( m_flCurrentSpeed < 0 )
						{
							// we're going backwards
							pNextNode = pNode->GetPrevious();
						}

						if ( pNextNode )
						{
							int iNextHillType = pNextNode->GetHillType();
							if ( m_iCurrentHillType != iNextHillType )
							{
								// we're leaving an uphill or downhill segment...so reset our state until we pass the next node
								bUpdate = true;
								iHillType = HILL_TYPE_NONE;
							}
						}
					}
				}

				if ( bUpdate )
				{
					m_iCurrentHillType = iHillType;
					HandleTrainMovement();
				}
			}
		}
	}
}
void CTeamTrainWatcher::WatcherThink( void )
{
	if ( m_bWaitingToRecede )
	{
		if ( m_flRecedeTime < gpGlobals->curtime )
		{
			m_bWaitingToRecede = false;

			// don't actually recede in overtime
			if ( TeamplayRoundBasedRules() && !TeamplayRoundBasedRules()->InOvertime() )
			{
				// fire recede output
				m_OnTrainStartRecede.FireOutput( this, this );
				HandleTrainMovement( true );
			}
		}
	}

	bool bDisableAlarm = (TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->State_Get() != GR_STATE_RND_RUNNING);
	if ( bDisableAlarm )
	{
		StopCaptureAlarm();
	}

	// given its next node, we can walk the nodes and find the linear
	// distance to the next cp node, or to the goal node

	CFuncTrackTrain *pTrain = m_hTrain;
	if ( pTrain )
	{
		int iOldTrainSpeedLevel = m_iTrainSpeedLevel;

		// how fast is the train moving?
		float flSpeed = pTrain->GetDesiredSpeed();

		// divide speed into regions
		// anything negative is -1

		if ( flSpeed < 0 )
		{
			m_iTrainSpeedLevel = -1;

			// even though our desired speed might be negative,
			// our actual speed might be zero if we're at a dead end...
			// this will turn off the < image when the train is done moving backwards
			if ( pTrain->GetCurrentSpeed() == 0 )
			{
				m_iTrainSpeedLevel = 0;
			}
		}
		else if ( flSpeed > m_flSpeedLevels[2] )
		{
			m_iTrainSpeedLevel = 3;
		}
		else if ( flSpeed > m_flSpeedLevels[1] )
		{
			m_iTrainSpeedLevel = 2;
		}
		else if ( flSpeed > m_flSpeedLevels[0] )
		{
			m_iTrainSpeedLevel = 1;
		}
		else
		{
			m_iTrainSpeedLevel = 0;
		}

		if ( m_iTrainSpeedLevel != iOldTrainSpeedLevel )
		{
			// make sure the sparks are off if we're not moving backwards anymore
			if ( m_bHandleTrainMovement )
			{
				if ( m_iTrainSpeedLevel == 0 && iOldTrainSpeedLevel != 0 )
				{
					HandleSparks( false );
				}
			}

			// play any concepts that we might need to play		
			if ( TeamplayRoundBasedRules() )
			{
				if ( m_iTrainSpeedLevel == 0 && iOldTrainSpeedLevel != 0 )
				{
					TeamplayRoundBasedRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_CART_STOP );
					m_flNextSpeakForwardConceptTime = 0;
				}
				else if ( m_iTrainSpeedLevel < 0 && iOldTrainSpeedLevel == 0 )
				{
					TeamplayRoundBasedRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_CART_MOVING_BACKWARD );
					m_flNextSpeakForwardConceptTime = 0;
				}
			}
		}

		if ( m_iTrainSpeedLevel > 0 && m_flNextSpeakForwardConceptTime < gpGlobals->curtime )
		{
			if ( m_hAreaCap.Get() )
			{
				for ( int i = 1; i <= gpGlobals->maxClients; i++ )
				{
					CBaseMultiplayerPlayer *pPlayer = ToBaseMultiplayerPlayer( UTIL_PlayerByIndex( i ) );
					if ( pPlayer )
					{
						if ( m_hAreaCap->IsTouching( pPlayer ) )
						{
							pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_CART_MOVING_FORWARD );
						}
					}
				}
			}

			m_flNextSpeakForwardConceptTime = gpGlobals->curtime + 3.0;
		}

		// what percent progress are we at?
		CPathTrack *pNode = ( pTrain->m_ppath ) ? pTrain->m_ppath->GetNext() : NULL;

		// if we're moving backwards, GetNext is going to be wrong
		if ( flSpeed < 0 )
		{
			pNode = pTrain->m_ppath;
		}

		if ( pNode )
		{
			float flDistanceToGoal = 0;

			// distance to next node
			Vector vecDir = pNode->GetLocalOrigin() - pTrain->GetLocalOrigin();
			flDistanceToGoal = vecDir.Length();

			// distance of next node to goal node
			if ( pNode && pNode != m_hGoalNode )
			{
				// walk this until we get to goal node, or a dead end
				CPathTrack *pPrev = pNode;
				pNode = pNode->GetNext();
				while ( pNode )
				{
					vecDir = pNode->GetLocalOrigin() - pPrev->GetLocalOrigin();
					flDistanceToGoal += vecDir.Length();

					if ( pNode == m_hGoalNode )
						break;

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

			if ( m_flTotalPathDistance <= 0 )
			{
				Assert( !"No path distance in team_train_watcher\n" );
				m_flTotalPathDistance = 1;
			}

			m_flTotalProgress = clamp( 1.0 - ( flDistanceToGoal / m_flTotalPathDistance ), 0.0, 1.0 );

			m_flTrainDistanceFromStart = m_flTotalPathDistance - flDistanceToGoal;

			// play alert sounds if necessary
			for ( int iCount = 0 ; iCount < m_iNumCPLinks ; iCount++ )
			{
				if ( m_flTrainDistanceFromStart < m_CPLinks[iCount].flDistanceFromStart - TEAM_TRAIN_ALERT_DISTANCE )
				{
					// back up twice the alert distance before resetting our flag to play the warning again
					if ( ( m_flTrainDistanceFromStart < m_CPLinks[iCount].flDistanceFromStart - ( TEAM_TRAIN_ALERT_DISTANCE * 2 ) ) || // has receded back twice the alert distance or...
						 ( !m_bTrainCanRecede ) ) // used to catch the case where the train doesn't normally recede but has rolled back down a hill away from the CP
					{
						// reset our alert flag
						m_CPLinks[iCount].bAlertPlayed = false;
					}
				}
				else
				{
					if ( m_flTrainDistanceFromStart < m_CPLinks[iCount].flDistanceFromStart && !m_CPLinks[iCount].bAlertPlayed )
					{
						m_CPLinks[iCount].bAlertPlayed = true;
						bool bFinalPointInMap = false;

						CTeamControlPoint *pCurrentPoint = m_CPLinks[iCount].hCP.Get();
						CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL;
						if ( pMaster )
						{
							// if we're not playing mini-rounds 
							if ( !pMaster->PlayingMiniRounds() )  
							{
								for ( int i = FIRST_GAME_TEAM ; i < MAX_CONTROL_POINT_TEAMS ; i++ )
								{
									if ( ObjectiveResource() && ObjectiveResource()->TeamCanCapPoint( pCurrentPoint->GetPointIndex(), i ) )
									{
										if ( pMaster->WouldNewCPOwnerWinGame( pCurrentPoint, i ) )
										{
											bFinalPointInMap = true;
										}
									}
								}
							}
							else 
							{
								// or this is the last round
								if ( pMaster->NumPlayableControlPointRounds() == 1 )
								{
									CTeamControlPointRound *pRound = pMaster->GetCurrentRound();
									if ( pRound )
									{
										for ( int i = FIRST_GAME_TEAM ; i < MAX_CONTROL_POINT_TEAMS ; i++ )
										{
											if ( ObjectiveResource() && ObjectiveResource()->TeamCanCapPoint( pCurrentPoint->GetPointIndex(), i ) )
											{
												if ( pRound->WouldNewCPOwnerWinGame( pCurrentPoint, i ) )
												{
													bFinalPointInMap = true;
												}
											}
										}
									}
								}
							}
						}

						PlayCaptureAlert( pCurrentPoint, bFinalPointInMap );
					}
				}
			}

			// check to see if we need to start or stop the alarm
			if ( flDistanceToGoal <= TEAM_TRAIN_ALARM_DISTANCE )
			{
				if ( ObjectiveResource() )
				{
					ObjectiveResource()->SetTrackAlarm( GetTeamNumber(), true );
				}

				if ( !bDisableAlarm )
				{
					if ( !m_pAlarm )
					{
						if ( m_iNumCPLinks > 0 && !m_bAlarmPlayed )
						{
							// start the alarm at the final point
							StartCaptureAlarm( m_CPLinks[m_iNumCPLinks-1].hCP.Get() );
							m_bAlarmPlayed = true; // used to prevent the alarm from starting again on maps where the train doesn't recede (alarm loops for short time then only plays singles)
						}
					}
					else
					{
						if ( !m_bTrainCanRecede ) // if the train won't recede, we only want to play the alarm for a short time
						{
							if ( m_flAlarmEndTime > 0 && m_flAlarmEndTime < gpGlobals->curtime )
							{
								StopCaptureAlarm();
								SetContextThink( &CTeamTrainWatcher::WatcherAlarmThink, gpGlobals->curtime + TW_ALARM_THINK_INTERVAL, TW_ALARM_THINK );
							}
						}
					}
				}
			}
			else
			{
				if ( ObjectiveResource() )
				{
					ObjectiveResource()->SetTrackAlarm( GetTeamNumber(), false );
				}

				StopCaptureAlarm();
				m_bAlarmPlayed = false;
			}
		}

		if ( tf_show_train_path.GetBool() )
		{
			CPathTrack *nextNode = NULL;
			CPathTrack *node = m_hStartNode;

			CPathTrack::BeginIteration();
			while( node )
			{
				node->Visit();
				nextNode = node->GetNext();

				if ( !nextNode || nextNode->HasBeenVisited() )
					break;

				NDebugOverlay::Line( node->GetAbsOrigin(), nextNode->GetAbsOrigin(), 255, 255, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );

				node = nextNode;
			}
			CPathTrack::EndIteration();

			// show segment of path train is actually on
			node = pTrain->m_ppath;
			if ( node && node->GetNext() )
			{
				NDebugOverlay::HorzArrow( node->GetAbsOrigin(), node->GetNext()->GetAbsOrigin(), 5.0f, 255, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
			}
		}
	}

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