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; } }
/* <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"); }
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(); } }
//----------------------------------------------------------------------------- // 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; }
//----------------------------------------------------------------------------- // 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; }
// 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; }
//----------------------------------------------------------------------------- // 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 ); }