From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/team_train_watcher.cpp | 3092 ++++++++++++++--------------- 1 file changed, 1546 insertions(+), 1546 deletions(-) (limited to 'mp/src/game/server/team_train_watcher.cpp') diff --git a/mp/src/game/server/team_train_watcher.cpp b/mp/src/game/server/team_train_watcher.cpp index cf6631fb..dba231af 100644 --- a/mp/src/game/server/team_train_watcher.cpp +++ b/mp/src/game/server/team_train_watcher.cpp @@ -1,1546 +1,1546 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//===========================================================================// - -#include "cbase.h" -#include "team_train_watcher.h" -#include "team_control_point.h" -#include "trains.h" -#include "team_objectiveresource.h" -#include "teamplayroundbased_gamerules.h" -#include "team_control_point.h" -#include "team_control_point_master.h" -#include "engine/IEngineSound.h" -#include "soundenvelope.h" -#include "mp_shareddefs.h" -#include "props.h" -#include "physconstraint.h" - -#ifdef TF_DLL -#include "tf_shareddefs.h" -#endif - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" -/* -#define TWM_FIRSTSTAGEOUTCOME01 "Announcer.PLR_FirstStageOutcome01" -#define TWM_FIRSTSTAGEOUTCOME02 "Announcer.PLR_FirstStageOutcome02" -#define TWM_RACEGENERAL01 "Announcer.PLR_RaceGeneral01" -#define TWM_RACEGENERAL02 "Announcer.PLR_RaceGeneral02" -#define TWM_RACEGENERAL03 "Announcer.PLR_RaceGeneral03" -#define TWM_RACEGENERAL04 "Announcer.PLR_RaceGeneral04" -#define TWM_RACEGENERAL05 "Announcer.PLR_RaceGeneral05" -#define TWM_RACEGENERAL08 "Announcer.PLR_RaceGeneral08" -#define TWM_RACEGENERAL06 "Announcer.PLR_RaceGeneral06" -#define TWM_RACEGENERAL07 "Announcer.PLR_RaceGeneral07" -#define TWM_RACEGENERAL09 "Announcer.PLR_RaceGeneral09" -#define TWM_RACEGENERAL12 "Announcer.PLR_RaceGeneral12" -#define TWM_RACEGENERAL13 "Announcer.PLR_RaceGeneral13" -#define TWM_RACEGENERAL14 "Announcer.PLR_RaceGeneral14" -#define TWM_RACEGENERAL15 "Announcer.PLR_RaceGeneral15" -#define TWM_RACEGENERAL10 "Announcer.PLR_RaceGeneral10" -#define TWM_RACEGENERAL11 "Announcer.PLR_RaceGeneral11" -#define TWM_SECONDSTAGEOUTCOME01 "Announcer.PLR_SecondStageOutcome01" -#define TWM_SECONDSTAGEOUTCOME04 "Announcer.PLR_SecondStageOutcome04" -#define TWM_SECONDSTAGEOUTCOME02 "Announcer.PLR_SecondStageOutcome02" -#define TWM_SECONDSTAGEOUTCOME03 "Announcer.PLR_SecondStageOutcome03" -#define TWM_FINALSTAGEOUTCOME01 "Announcer.PLR_FinalStageOutcome01" -#define TWM_FINALSTAGEOUTCOME02 "Announcer.PLR_FinalStageOutcome02" -#define TWM_FINALSTAGESTART01 "Announcer.PLR_FinalStageStart01" -#define TWM_FINALSTAGESTART04 "Announcer.PLR_FinalStageStart04" -#define TWM_FINALSTAGESTART08 "Announcer.PLR_FinalStageStart08" -#define TWM_FINALSTAGESTART09 "Announcer.PLR_FinalStageStart09" -#define TWM_FINALSTAGESTART07 "Announcer.PLR_FinalStageStart07" -#define TWM_FINALSTAGESTART02 "Announcer.PLR_FinalStageStart02" -#define TWM_FINALSTAGESTART03 "Announcer.PLR_FinalStageStart03" -#define TWM_FINALSTAGESTART05 "Announcer.PLR_FinalStageStart05" -#define TWM_FINALSTAGESTART06 "Announcer.PLR_FinalStageStart06" - -EHANDLE g_hTeamTrainWatcherMaster = NULL; -*/ -#define MAX_ALARM_TIME_NO_RECEDE 18 // max amount of time to play the alarm if the train isn't going to recede - -BEGIN_DATADESC( CTeamTrainWatcher ) - - // Inputs. - DEFINE_INPUTFUNC( FIELD_VOID, "RoundActivate", InputRoundActivate ), - DEFINE_INPUTFUNC( FIELD_INTEGER, "SetNumTrainCappers", InputSetNumTrainCappers ), - DEFINE_INPUTFUNC( FIELD_VOID, "OnStartOvertime", InputOnStartOvertime ), - DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), - DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), - DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedForwardModifier", InputSetSpeedForwardModifier ), - DEFINE_INPUTFUNC( FIELD_INTEGER, "SetTrainRecedeTime", InputSetTrainRecedeTime ), - - // Outputs - DEFINE_OUTPUT( m_OnTrainStartRecede, "OnTrainStartRecede" ), - - // key - DEFINE_KEYFIELD( m_iszTrain, FIELD_STRING, "train" ), - DEFINE_KEYFIELD( m_iszStartNode, FIELD_STRING, "start_node" ), - DEFINE_KEYFIELD( m_iszGoalNode, FIELD_STRING, "goal_node" ), - - DEFINE_KEYFIELD( m_iszLinkedPathTracks[0], FIELD_STRING, "linked_pathtrack_1" ), - DEFINE_KEYFIELD( m_iszLinkedCPs[0], FIELD_STRING, "linked_cp_1" ), - - DEFINE_KEYFIELD( m_iszLinkedPathTracks[1], FIELD_STRING, "linked_pathtrack_2" ), - DEFINE_KEYFIELD( m_iszLinkedCPs[1], FIELD_STRING, "linked_cp_2" ), - - DEFINE_KEYFIELD( m_iszLinkedPathTracks[2], FIELD_STRING, "linked_pathtrack_3" ), - DEFINE_KEYFIELD( m_iszLinkedCPs[2], FIELD_STRING, "linked_cp_3" ), - - DEFINE_KEYFIELD( m_iszLinkedPathTracks[3], FIELD_STRING, "linked_pathtrack_4" ), - DEFINE_KEYFIELD( m_iszLinkedCPs[3], FIELD_STRING, "linked_cp_4" ), - - DEFINE_KEYFIELD( m_iszLinkedPathTracks[4], FIELD_STRING, "linked_pathtrack_5" ), - DEFINE_KEYFIELD( m_iszLinkedCPs[4], FIELD_STRING, "linked_cp_5" ), - - DEFINE_KEYFIELD( m_iszLinkedPathTracks[5], FIELD_STRING, "linked_pathtrack_6" ), - DEFINE_KEYFIELD( m_iszLinkedCPs[5], FIELD_STRING, "linked_cp_6" ), - - DEFINE_KEYFIELD( m_iszLinkedPathTracks[6], FIELD_STRING, "linked_pathtrack_7" ), - DEFINE_KEYFIELD( m_iszLinkedCPs[6], FIELD_STRING, "linked_cp_7" ), - - DEFINE_KEYFIELD( m_iszLinkedPathTracks[7], FIELD_STRING, "linked_pathtrack_8" ), - DEFINE_KEYFIELD( m_iszLinkedCPs[7], FIELD_STRING, "linked_cp_8" ), - - DEFINE_KEYFIELD( m_bTrainCanRecede, FIELD_BOOLEAN, "train_can_recede" ), - - DEFINE_KEYFIELD( m_bHandleTrainMovement, FIELD_BOOLEAN, "handle_train_movement" ), - - // can be up to 8 links - - // min speed for train hud speed levels - DEFINE_KEYFIELD( m_flSpeedLevels[0], FIELD_FLOAT, "hud_min_speed_level_1" ), - DEFINE_KEYFIELD( m_flSpeedLevels[1], FIELD_FLOAT, "hud_min_speed_level_2" ), - DEFINE_KEYFIELD( m_flSpeedLevels[2], FIELD_FLOAT, "hud_min_speed_level_3" ), - - DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), - - DEFINE_KEYFIELD( m_iszSparkName, FIELD_STRING, "env_spark_name" ), - - DEFINE_KEYFIELD( m_flSpeedForwardModifier, FIELD_FLOAT, "speed_forward_modifier" ), - - DEFINE_KEYFIELD( m_nTrainRecedeTime, FIELD_INTEGER, "train_recede_time" ), - -END_DATADESC() - - -IMPLEMENT_SERVERCLASS_ST(CTeamTrainWatcher, DT_TeamTrainWatcher) - - SendPropFloat( SENDINFO( m_flTotalProgress ), 11, 0, 0.0f, 1.0f ), - SendPropInt( SENDINFO( m_iTrainSpeedLevel ), 4 ), - SendPropTime( SENDINFO( m_flRecedeTime ) ), - SendPropInt( SENDINFO( m_nNumCappers ) ), -#ifdef GLOWS_ENABLE - SendPropEHandle( SENDINFO( m_hGlowEnt ) ), -#endif // GLOWS_ENABLE - -END_SEND_TABLE() - - -LINK_ENTITY_TO_CLASS( team_train_watcher, CTeamTrainWatcher ); -/* -LINK_ENTITY_TO_CLASS( team_train_watcher_master, CTeamTrainWatcherMaster ); -PRECACHE_REGISTER( team_train_watcher_master ); - -CTeamTrainWatcherMaster::CTeamTrainWatcherMaster() -{ - m_pBlueWatcher = NULL; - m_pRedWatcher = NULL; - - m_flBlueProgress = 0.0f; - m_flRedProgress = 0.0f; - - ListenForGameEvent( "teamplay_round_start" ); - ListenForGameEvent( "teamplay_round_win" ); -} - -CTeamTrainWatcherMaster::~CTeamTrainWatcherMaster() -{ - if ( g_hTeamTrainWatcherMaster.Get() == this ) - { - g_hTeamTrainWatcherMaster = NULL; - } -} - -void CTeamTrainWatcherMaster::Precache( void ) -{ - PrecacheScriptSound( TWM_FIRSTSTAGEOUTCOME01 ); - PrecacheScriptSound( TWM_FIRSTSTAGEOUTCOME02 ); - PrecacheScriptSound( TWM_RACEGENERAL01 ); - PrecacheScriptSound( TWM_RACEGENERAL02 ); - PrecacheScriptSound( TWM_RACEGENERAL03 ); - PrecacheScriptSound( TWM_RACEGENERAL04 ); - PrecacheScriptSound( TWM_RACEGENERAL05 ); - PrecacheScriptSound( TWM_RACEGENERAL08 ); - PrecacheScriptSound( TWM_RACEGENERAL06 ); - PrecacheScriptSound( TWM_RACEGENERAL07 ); - PrecacheScriptSound( TWM_RACEGENERAL09 ); - PrecacheScriptSound( TWM_RACEGENERAL12 ); - PrecacheScriptSound( TWM_RACEGENERAL13 ); - PrecacheScriptSound( TWM_RACEGENERAL14 ); - PrecacheScriptSound( TWM_RACEGENERAL15 ); - PrecacheScriptSound( TWM_RACEGENERAL10 ); - PrecacheScriptSound( TWM_RACEGENERAL11 ); - PrecacheScriptSound( TWM_SECONDSTAGEOUTCOME01 ); - PrecacheScriptSound( TWM_SECONDSTAGEOUTCOME04 ); - PrecacheScriptSound( TWM_SECONDSTAGEOUTCOME02 ); - PrecacheScriptSound( TWM_SECONDSTAGEOUTCOME03 ); - PrecacheScriptSound( TWM_FINALSTAGEOUTCOME01 ); - PrecacheScriptSound( TWM_FINALSTAGEOUTCOME02 ); - PrecacheScriptSound( TWM_FINALSTAGESTART01 ); - PrecacheScriptSound( TWM_FINALSTAGESTART04 ); - PrecacheScriptSound( TWM_FINALSTAGESTART08 ); - PrecacheScriptSound( TWM_FINALSTAGESTART09 ); - PrecacheScriptSound( TWM_FINALSTAGESTART07 ); - PrecacheScriptSound( TWM_FINALSTAGESTART02 ); - PrecacheScriptSound( TWM_FINALSTAGESTART03 ); - PrecacheScriptSound( TWM_FINALSTAGESTART05 ); - PrecacheScriptSound( TWM_FINALSTAGESTART06 ); - - BaseClass::Precache(); -} - -bool CTeamTrainWatcherMaster::FindTrainWatchers( void ) -{ - m_pBlueWatcher = NULL; - m_pRedWatcher = NULL; - - // find the train_watchers for this round - CTeamTrainWatcher *pTrainWatcher = (CTeamTrainWatcher *)gEntList.FindEntityByClassname( NULL, "team_train_watcher" ); - while ( pTrainWatcher ) - { - if ( pTrainWatcher->IsDisabled() == false ) - { - if ( pTrainWatcher->GetTeamNumber() == TF_TEAM_BLUE ) - { - m_pBlueWatcher = pTrainWatcher; - } - else if ( pTrainWatcher->GetTeamNumber() == TF_TEAM_RED ) - { - m_pRedWatcher = pTrainWatcher; - } - } - - pTrainWatcher = (CTeamTrainWatcher *)gEntList.FindEntityByClassname( pTrainWatcher, "team_train_watcher" ); - } - - return ( m_pBlueWatcher && m_pRedWatcher ); -} - -void CTeamTrainWatcherMaster::TWMThink( void ) -{ - if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->State_Get() != GR_STATE_RND_RUNNING ) - { - // the next time we 'think' - SetContextThink( &CTeamTrainWatcherMaster::TWMThink, gpGlobals->curtime + 0.2, TWMASTER_THINK ); - return; - } - - - - // the next time we 'think' - SetContextThink( &CTeamTrainWatcherMaster::TWMThink, gpGlobals->curtime + 0.2, TWMASTER_THINK ); -} - -void CTeamTrainWatcherMaster::FireGameEvent( IGameEvent *event ) -{ - const char *eventname = event->GetName();` - - if ( FStrEq( "teamplay_round_start", eventname ) ) - { - if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->HasMultipleTrains() ) - { - if ( FindTrainWatchers() ) - { - // we found train watchers so start thinking - SetContextThink( &CTeamTrainWatcherMaster::TWMThink, gpGlobals->curtime + 0.2, TWMASTER_THINK ); - } - } - } - else if ( FStrEq( "teamplay_round_win", eventname ) ) - { - if ( TeamplayRoundBasedRules() ) - { - int iWinningTeam = event->GetInt( "team" ); - int iLosingTeam = ( iWinningTeam == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED; - bool bFullRound = event->GetBool( "full_round" ); - - CTeamRecipientFilter filterWinner( iWinningTeam, true ); - CTeamRecipientFilter filterLoser( iLosingTeam, true ); - - if ( bFullRound ) - { - EmitSound( filterWinner, entindex(), TWM_FINALSTAGEOUTCOME01 ); - EmitSound( filterLoser, entindex(), TWM_FINALSTAGEOUTCOME02 ); - } - else - { - EmitSound( filterWinner, entindex(), TWM_FIRSTSTAGEOUTCOME01 ); - EmitSound( filterLoser, entindex(), TWM_FIRSTSTAGEOUTCOME02 ); - } - } - } -} -*/ -CTeamTrainWatcher::CTeamTrainWatcher() -{ - m_bDisabled = false; - m_flRecedeTime = 0; - m_bWaitingToRecede = false; - m_bCapBlocked = false; - - m_flNextSpeakForwardConceptTime = 0; - m_hAreaCap = NULL; - - m_bTrainCanRecede = true; - m_bAlarmPlayed = false; - m_pAlarm = NULL; - m_flAlarmEndTime = -1; - - m_bHandleTrainMovement = false; - m_flSpeedForwardModifier = 1.0f; - m_iCurrentHillType = HILL_TYPE_NONE; - m_flCurrentSpeed = 0.0f; - m_bReceding = false; - - m_flTrainDistanceFromStart = 0.0f; - - m_nTrainRecedeTime = 0; - -#ifdef GLOWS_ENABLE - m_hGlowEnt.Set( NULL ); -#endif // GLOWS_ENABLE - -#ifdef TF_DLL - ChangeTeam( TF_TEAM_BLUE ); -#else - ChangeTeam( TEAM_UNASSIGNED ); -#endif -/* - // create a CTeamTrainWatcherMaster entity - if ( g_hTeamTrainWatcherMaster.Get() == NULL ) - { - g_hTeamTrainWatcherMaster = CreateEntityByName( "team_train_watcher_master" ); - } -*/ - ListenForGameEvent( "path_track_passed" ); -} - -CTeamTrainWatcher::~CTeamTrainWatcher() -{ - m_Sparks.Purge(); -} - -void CTeamTrainWatcher::UpdateOnRemove( void ) -{ - StopCaptureAlarm(); - - BaseClass::UpdateOnRemove(); -} - -int CTeamTrainWatcher::UpdateTransmitState() -{ - if ( m_bDisabled ) - { - return SetTransmitState( FL_EDICT_DONTSEND ); - } - - return SetTransmitState( FL_EDICT_ALWAYS ); -} - -void CTeamTrainWatcher::InputRoundActivate( inputdata_t &inputdata ) -{ - StopCaptureAlarm(); - - if ( !m_bDisabled ) - { - WatcherActivate(); - } -} - -void CTeamTrainWatcher::InputEnable( inputdata_t &inputdata ) -{ - StopCaptureAlarm(); - - m_bDisabled = false; - - WatcherActivate(); - - UpdateTransmitState(); -} - -void CTeamTrainWatcher::InputDisable( inputdata_t &inputdata ) -{ - StopCaptureAlarm(); - - m_bDisabled = true; - SetContextThink( NULL, 0, TW_THINK ); - - m_bWaitingToRecede = false; - - m_Sparks.Purge(); - -#ifdef GLOWS_ENABLE - m_hGlowEnt.Set( NULL ); -#endif // GLOWS_ENABLE - - // if we're moving the train, let's shut it down - if ( m_bHandleTrainMovement ) - { - m_flCurrentSpeed = 0.0f; - - if ( m_hTrain ) - { - m_hTrain->SetSpeedDirAccel( m_flCurrentSpeed ); - } - - // handle the sparks under the train - HandleSparks( false ); - } - - UpdateTransmitState(); -} - -ConVar tf_escort_recede_time( "tf_escort_recede_time", "30", 0, "", true, 0, false, 0 ); -ConVar tf_escort_recede_time_overtime( "tf_escort_recede_time_overtime", "5", 0, "", true, 0, false, 0 ); - -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::HandleSparks( bool bSparks ) -{ - if ( IsDisabled() || !m_bHandleTrainMovement ) - return; - - for ( int i = 0 ; i < m_Sparks.Count() ; i++ ) - { - CEnvSpark* pSpark = m_Sparks[i].Get(); - if ( pSpark && ( pSpark->IsSparking() != bSparks ) ) - { - if ( bSparks ) - { - pSpark->StartSpark(); - } - else - { - pSpark->StopSpark(); - } - } - } -} - -void CTeamTrainWatcher::HandleTrainMovement( bool bStartReceding /* = false */ ) -{ - if ( IsDisabled() || !m_bHandleTrainMovement ) - return; - - if ( m_hTrain ) - { - float flSpeed = 0.0f; - - if ( bStartReceding ) - { - flSpeed = -0.1f; - m_bReceding = true; - } - else - { - // do we have cappers on the train? - if ( m_nNumCappers > 0 ) - { - m_bReceding = false; - - if ( m_iCurrentHillType == HILL_TYPE_DOWNHILL ) - { - flSpeed = 1.0f; - } - else - { - switch( m_nNumCappers ) - { - case 1: - flSpeed = 0.55f; - break; - case 2: - flSpeed = 0.77f; - break; - case 3: - default: - flSpeed = 1.0f; - break; - } - } - } - else if ( m_nNumCappers == -1 ) - { - // we'll get a -1 for a blocked cart (speed should be 0 for that unless we're on a hill) - if ( m_iCurrentHillType == HILL_TYPE_DOWNHILL ) - { - flSpeed = 1.0f; - } - } - else - { - // there's nobody on the train, what should it be doing? - if ( m_flCurrentSpeed > 0 ) - { - if ( m_iCurrentHillType == HILL_TYPE_DOWNHILL ) - { - flSpeed = 1.0f; - } - } - else - { - // we're rolling backwards - if ( m_iCurrentHillType == HILL_TYPE_UPHILL ) - { - flSpeed = -1.0f; - } - else - { - if ( m_bReceding ) - { - // resume our previous backup speed - flSpeed = -0.1f; - } - } - } - } - } - - // only need to update the train if our speed has changed - if ( m_flCurrentSpeed != flSpeed ) - { - if ( flSpeed >= 0.0f ) - { - m_bReceding = false; - } - - m_flCurrentSpeed = flSpeed; - m_hTrain->SetSpeedDirAccel( m_flCurrentSpeed ); - - // handle the sparks under the train - bool bSparks = false; - if ( m_flCurrentSpeed < 0 ) - { - bSparks = true; - } - - HandleSparks( bSparks ); - } - } -} - -void CTeamTrainWatcher::InputSetSpeedForwardModifier( inputdata_t &inputdata ) -{ - InternalSetSpeedForwardModifier( inputdata.value.Float() ); -} - -void CTeamTrainWatcher::InternalSetSpeedForwardModifier( float flModifier ) -{ - if ( IsDisabled() || !m_bHandleTrainMovement ) - return; - - // store the passed value - float flSpeedForwardModifier = flModifier; - flSpeedForwardModifier = fabs( flSpeedForwardModifier ); - - m_flSpeedForwardModifier = clamp( flSpeedForwardModifier, 0.f, 1.f ); - - if ( m_hTrain ) - { - m_hTrain->SetSpeedForwardModifier( m_flSpeedForwardModifier ); - } -} - -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( 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(); -} - -// only used for train watchers that control the train movement -void CTeamTrainWatcher::SetNumTrainCappers( int iNumCappers, CBaseEntity *pTrigger ) -{ - if ( IsDisabled() || !m_bHandleTrainMovement ) - return; - - InternalSetNumTrainCappers( iNumCappers, pTrigger ); -} - -void CTeamTrainWatcher::InputSetNumTrainCappers( inputdata_t &inputdata ) -{ - InternalSetNumTrainCappers( inputdata.value.Int(), inputdata.pCaller ); -} - -void CTeamTrainWatcher::InputSetTrainRecedeTime( inputdata_t &inputdata ) -{ - int nSeconds = inputdata.value.Int(); - if ( nSeconds >= 0 ) - { - m_nTrainRecedeTime = nSeconds; - } - else - { - m_nTrainRecedeTime = 0; - } -} - -void CTeamTrainWatcher::InputOnStartOvertime( inputdata_t &inputdata ) -{ - // recalculate the recede time - if ( m_bWaitingToRecede ) - { - float flRecedeTimeRemaining = m_flRecedeTime - gpGlobals->curtime; - float flOvertimeRecedeLen = tf_escort_recede_time_overtime.GetFloat(); - - // drop to overtime recede time if it's more than that - if ( flRecedeTimeRemaining > flOvertimeRecedeLen ) - { - m_flRecedeTotalTime = flOvertimeRecedeLen; - m_flRecedeStartTime = gpGlobals->curtime; - m_flRecedeTime = m_flRecedeStartTime + m_flRecedeTotalTime; - } - } -} - -#ifdef GLOWS_ENABLE -void CTeamTrainWatcher::FindGlowEntity( void ) -{ - if ( m_hTrain && ( m_hTrain->GetEntityName() != NULL_STRING ) ) - { - string_t iszTrainName = m_hTrain->GetEntityName(); - CBaseEntity *pGlowEnt = NULL; - - // first try to find a phys_constraint relationship with the train - CPhysFixed *pPhysConstraint = dynamic_cast( gEntList.FindEntityByClassname( NULL, "phys_constraint" ) ); - while ( pPhysConstraint ) - { - string_t iszName1 = pPhysConstraint->GetNameAttach1(); - string_t iszName2 = pPhysConstraint->GetNameAttach2(); - - if ( iszTrainName == iszName1 ) - { - pGlowEnt = gEntList.FindEntityByName( NULL, STRING( iszName2 ) ); - break; - } - else if ( iszTrainName == iszName2 ) - { - pGlowEnt = gEntList.FindEntityByName( NULL, STRING( iszName1 ) ); - break; - } - - pPhysConstraint = dynamic_cast( gEntList.FindEntityByClassname( pPhysConstraint, "phys_constraint" ) ); - } - - if ( !pGlowEnt ) - { - // if we're here, we haven't found the glow entity yet...try all of the prop_dynamic entities - CDynamicProp *pPropDynamic = dynamic_cast( gEntList.FindEntityByClassname( NULL, "prop_dynamic" ) ); - while ( pPropDynamic ) - { - if ( pPropDynamic->GetParent() == m_hTrain ) - { - pGlowEnt = pPropDynamic; - break; - } - - pPropDynamic = dynamic_cast( gEntList.FindEntityByClassname( pPropDynamic, "prop_dynamic" ) ); - } - } - - // if we still haven't found a glow entity, just have the CFuncTrackTrain glow - if ( !pGlowEnt ) - { - pGlowEnt = m_hTrain.Get(); - } - - if ( pGlowEnt ) - { - pGlowEnt->SetTransmitState( FL_EDICT_ALWAYS ); - m_hGlowEnt.Set( pGlowEnt ); - } - } -} -#endif // GLOWS_ENABLE - -// ========================================================== -// 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( 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 ) - { - for ( int i=0; i( ITriggerAreaCaptureAutoList::AutoList()[i] ); - 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; - } - } - } - - // init the sprites (if any) - CEnvSpark *pSpark = dynamic_cast( gEntList.FindEntityByName( NULL, m_iszSparkName ) ); - while ( pSpark ) - { - m_Sparks.AddToTail( pSpark ); - pSpark = dynamic_cast( 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( gEntList.FindEntityByName( NULL, m_iszLinkedPathTracks[i] ) ); - CTeamControlPoint *pCP = dynamic_cast( 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( 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( 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;iGetPointIndex(); -// 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::StopCaptureAlarm( void ) -{ - if ( m_pAlarm ) - { - CSoundEnvelopeController::GetController().SoundDestroy( m_pAlarm ); - m_pAlarm = NULL; - m_flAlarmEndTime = -1.0f; - } - - SetContextThink( NULL, 0, TW_ALARM_THINK ); -} - -void CTeamTrainWatcher::StartCaptureAlarm( CTeamControlPoint *pPoint ) -{ - StopCaptureAlarm(); - - if ( pPoint ) - { - CReliableBroadcastRecipientFilter filter; - CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); - m_pAlarm = controller.SoundCreate( filter, pPoint->entindex(), CHAN_STATIC, TEAM_TRAIN_ALARM, ATTN_NORM ); - controller.Play( m_pAlarm, 1.0, PITCH_NORM ); - - m_flAlarmEndTime = gpGlobals->curtime + MAX_ALARM_TIME_NO_RECEDE; - } -} - -void CTeamTrainWatcher::PlayCaptureAlert( CTeamControlPoint *pPoint, bool bFinalPointInMap ) -{ - if ( !pPoint ) - return; - - if ( TeamplayRoundBasedRules() ) - { - TeamplayRoundBasedRules()->PlayTrainCaptureAlert( pPoint, bFinalPointInMap ); - } -} - - -ConVar tf_show_train_path( "tf_show_train_path", "0", FCVAR_CHEAT ); - -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 ); -} - -void CTeamTrainWatcher::WatcherAlarmThink( void ) -{ - CTeamControlPoint *pPoint = m_CPLinks[m_iNumCPLinks-1].hCP.Get(); - if ( pPoint ) - { - pPoint->EmitSound( TEAM_TRAIN_ALARM_SINGLE ); - } - - SetContextThink( &CTeamTrainWatcher::WatcherAlarmThink, gpGlobals->curtime + TW_ALARM_THINK_INTERVAL, TW_ALARM_THINK ); -} - -CBaseEntity *CTeamTrainWatcher::GetTrainEntity( void ) -{ - return m_hTrain.Get(); -} - -bool CTeamTrainWatcher::TimerMayExpire( void ) -{ - if ( IsDisabled() ) - { - return true; - } - - // Still in overtime if we're waiting to recede - if ( m_bWaitingToRecede ) - return false; - - // capture blocked so we're not receding, but game shouldn't end - if ( m_bCapBlocked ) - return false; - - // not waiting, so we're capping, in which case the area capture - // will not let us expire - return true; -} - - -// 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; - } -} - - -// Return true if the given position is farther down the track than the train is -bool CTeamTrainWatcher::IsAheadOfTrain( const Vector &pos ) const -{ - float distanceAlongPath; - ProjectPointOntoPath( pos, NULL, &distanceAlongPath ); - - return ( distanceAlongPath > m_flTrainDistanceFromStart ); -} - - -// return true if the train is almost at the next checkpoint -bool CTeamTrainWatcher::IsTrainNearCheckpoint( void ) const -{ - for( int i = 0; i < m_iNumCPLinks ; ++i ) - { - if ( m_flTrainDistanceFromStart > m_CPLinks[i].flDistanceFromStart - TEAM_TRAIN_ALERT_DISTANCE && - m_flTrainDistanceFromStart < m_CPLinks[i].flDistanceFromStart ) - { - return true; - } - } - - return false; -} - - -// return true if the train hasn't left its starting position yet -bool CTeamTrainWatcher::IsTrainAtStart( void ) const -{ - return ( m_flTrainDistanceFromStart < TEAM_TRAIN_ALARM_DISTANCE ); -} - - -// return world space location of next checkpoint along the path -Vector CTeamTrainWatcher::GetNextCheckpointPosition( void ) const -{ - for( int i = 0; i < m_iNumCPLinks ; ++i ) - { - if ( m_flTrainDistanceFromStart < m_CPLinks[i].flDistanceFromStart ) - { - return m_CPLinks[i].hPathTrack->GetAbsOrigin(); - } - } - - Assert( !"No checkpoint found in team train watcher\n" ); - return vec3_origin; -} - -#if defined( STAGING_ONLY ) && defined( TF_DLL ) -CON_COMMAND_F( tf_dumptrainstats, "Dump the stats for the current train watcher to the console", FCVAR_GAMEDLL ) -{ - // Listenserver host or rcon access only! - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - return; - - CTeamTrainWatcher *pWatcher = NULL; - while( ( pWatcher = dynamic_cast< CTeamTrainWatcher * >( gEntList.FindEntityByClassname( pWatcher, "team_train_watcher" ) ) ) != NULL ) - { - pWatcher->DumpStats(); - } -} - -void CTeamTrainWatcher::DumpStats( void ) -{ - float flLastPosition = 0.0f; - float flTotalDistance = 0.0f; - char szOutput[2048]; - char szTemp[256]; - - V_strcpy_safe( szOutput, "\n\nTrain Watcher stats for team " ); - V_strcat_safe( szOutput, ( GetTeamNumber() == TF_TEAM_RED ) ? "Red\n" : "Blue\n" ); - - for( int i = 0; i < m_iNumCPLinks ; ++i ) - { - float flDistance = m_CPLinks[i].flDistanceFromStart - flLastPosition; - if ( i == 0 ) - { - V_sprintf_safe( szTemp, "\tControl Point: %d\tDistance from start: %0.2f\n", i + 1, flDistance ); - } - else - { - V_sprintf_safe( szTemp, "\tControl Point: %d\tDistance from previous point: %0.2f\n", i + 1, flDistance ); - } - V_strcat_safe( szOutput, szTemp ); - flTotalDistance += flDistance; - flLastPosition = m_CPLinks[i].flDistanceFromStart; - } - - V_sprintf_safe( szTemp, "\tTotal Distance: %0.2f\n\n", flTotalDistance ); - V_strcat_safe( szOutput, szTemp ); - Msg( "%s", szOutput ); -} -#endif // STAGING_ONLY && TF_DLL - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#include "cbase.h" +#include "team_train_watcher.h" +#include "team_control_point.h" +#include "trains.h" +#include "team_objectiveresource.h" +#include "teamplayroundbased_gamerules.h" +#include "team_control_point.h" +#include "team_control_point_master.h" +#include "engine/IEngineSound.h" +#include "soundenvelope.h" +#include "mp_shareddefs.h" +#include "props.h" +#include "physconstraint.h" + +#ifdef TF_DLL +#include "tf_shareddefs.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" +/* +#define TWM_FIRSTSTAGEOUTCOME01 "Announcer.PLR_FirstStageOutcome01" +#define TWM_FIRSTSTAGEOUTCOME02 "Announcer.PLR_FirstStageOutcome02" +#define TWM_RACEGENERAL01 "Announcer.PLR_RaceGeneral01" +#define TWM_RACEGENERAL02 "Announcer.PLR_RaceGeneral02" +#define TWM_RACEGENERAL03 "Announcer.PLR_RaceGeneral03" +#define TWM_RACEGENERAL04 "Announcer.PLR_RaceGeneral04" +#define TWM_RACEGENERAL05 "Announcer.PLR_RaceGeneral05" +#define TWM_RACEGENERAL08 "Announcer.PLR_RaceGeneral08" +#define TWM_RACEGENERAL06 "Announcer.PLR_RaceGeneral06" +#define TWM_RACEGENERAL07 "Announcer.PLR_RaceGeneral07" +#define TWM_RACEGENERAL09 "Announcer.PLR_RaceGeneral09" +#define TWM_RACEGENERAL12 "Announcer.PLR_RaceGeneral12" +#define TWM_RACEGENERAL13 "Announcer.PLR_RaceGeneral13" +#define TWM_RACEGENERAL14 "Announcer.PLR_RaceGeneral14" +#define TWM_RACEGENERAL15 "Announcer.PLR_RaceGeneral15" +#define TWM_RACEGENERAL10 "Announcer.PLR_RaceGeneral10" +#define TWM_RACEGENERAL11 "Announcer.PLR_RaceGeneral11" +#define TWM_SECONDSTAGEOUTCOME01 "Announcer.PLR_SecondStageOutcome01" +#define TWM_SECONDSTAGEOUTCOME04 "Announcer.PLR_SecondStageOutcome04" +#define TWM_SECONDSTAGEOUTCOME02 "Announcer.PLR_SecondStageOutcome02" +#define TWM_SECONDSTAGEOUTCOME03 "Announcer.PLR_SecondStageOutcome03" +#define TWM_FINALSTAGEOUTCOME01 "Announcer.PLR_FinalStageOutcome01" +#define TWM_FINALSTAGEOUTCOME02 "Announcer.PLR_FinalStageOutcome02" +#define TWM_FINALSTAGESTART01 "Announcer.PLR_FinalStageStart01" +#define TWM_FINALSTAGESTART04 "Announcer.PLR_FinalStageStart04" +#define TWM_FINALSTAGESTART08 "Announcer.PLR_FinalStageStart08" +#define TWM_FINALSTAGESTART09 "Announcer.PLR_FinalStageStart09" +#define TWM_FINALSTAGESTART07 "Announcer.PLR_FinalStageStart07" +#define TWM_FINALSTAGESTART02 "Announcer.PLR_FinalStageStart02" +#define TWM_FINALSTAGESTART03 "Announcer.PLR_FinalStageStart03" +#define TWM_FINALSTAGESTART05 "Announcer.PLR_FinalStageStart05" +#define TWM_FINALSTAGESTART06 "Announcer.PLR_FinalStageStart06" + +EHANDLE g_hTeamTrainWatcherMaster = NULL; +*/ +#define MAX_ALARM_TIME_NO_RECEDE 18 // max amount of time to play the alarm if the train isn't going to recede + +BEGIN_DATADESC( CTeamTrainWatcher ) + + // Inputs. + DEFINE_INPUTFUNC( FIELD_VOID, "RoundActivate", InputRoundActivate ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetNumTrainCappers", InputSetNumTrainCappers ), + DEFINE_INPUTFUNC( FIELD_VOID, "OnStartOvertime", InputOnStartOvertime ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedForwardModifier", InputSetSpeedForwardModifier ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetTrainRecedeTime", InputSetTrainRecedeTime ), + + // Outputs + DEFINE_OUTPUT( m_OnTrainStartRecede, "OnTrainStartRecede" ), + + // key + DEFINE_KEYFIELD( m_iszTrain, FIELD_STRING, "train" ), + DEFINE_KEYFIELD( m_iszStartNode, FIELD_STRING, "start_node" ), + DEFINE_KEYFIELD( m_iszGoalNode, FIELD_STRING, "goal_node" ), + + DEFINE_KEYFIELD( m_iszLinkedPathTracks[0], FIELD_STRING, "linked_pathtrack_1" ), + DEFINE_KEYFIELD( m_iszLinkedCPs[0], FIELD_STRING, "linked_cp_1" ), + + DEFINE_KEYFIELD( m_iszLinkedPathTracks[1], FIELD_STRING, "linked_pathtrack_2" ), + DEFINE_KEYFIELD( m_iszLinkedCPs[1], FIELD_STRING, "linked_cp_2" ), + + DEFINE_KEYFIELD( m_iszLinkedPathTracks[2], FIELD_STRING, "linked_pathtrack_3" ), + DEFINE_KEYFIELD( m_iszLinkedCPs[2], FIELD_STRING, "linked_cp_3" ), + + DEFINE_KEYFIELD( m_iszLinkedPathTracks[3], FIELD_STRING, "linked_pathtrack_4" ), + DEFINE_KEYFIELD( m_iszLinkedCPs[3], FIELD_STRING, "linked_cp_4" ), + + DEFINE_KEYFIELD( m_iszLinkedPathTracks[4], FIELD_STRING, "linked_pathtrack_5" ), + DEFINE_KEYFIELD( m_iszLinkedCPs[4], FIELD_STRING, "linked_cp_5" ), + + DEFINE_KEYFIELD( m_iszLinkedPathTracks[5], FIELD_STRING, "linked_pathtrack_6" ), + DEFINE_KEYFIELD( m_iszLinkedCPs[5], FIELD_STRING, "linked_cp_6" ), + + DEFINE_KEYFIELD( m_iszLinkedPathTracks[6], FIELD_STRING, "linked_pathtrack_7" ), + DEFINE_KEYFIELD( m_iszLinkedCPs[6], FIELD_STRING, "linked_cp_7" ), + + DEFINE_KEYFIELD( m_iszLinkedPathTracks[7], FIELD_STRING, "linked_pathtrack_8" ), + DEFINE_KEYFIELD( m_iszLinkedCPs[7], FIELD_STRING, "linked_cp_8" ), + + DEFINE_KEYFIELD( m_bTrainCanRecede, FIELD_BOOLEAN, "train_can_recede" ), + + DEFINE_KEYFIELD( m_bHandleTrainMovement, FIELD_BOOLEAN, "handle_train_movement" ), + + // can be up to 8 links + + // min speed for train hud speed levels + DEFINE_KEYFIELD( m_flSpeedLevels[0], FIELD_FLOAT, "hud_min_speed_level_1" ), + DEFINE_KEYFIELD( m_flSpeedLevels[1], FIELD_FLOAT, "hud_min_speed_level_2" ), + DEFINE_KEYFIELD( m_flSpeedLevels[2], FIELD_FLOAT, "hud_min_speed_level_3" ), + + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + DEFINE_KEYFIELD( m_iszSparkName, FIELD_STRING, "env_spark_name" ), + + DEFINE_KEYFIELD( m_flSpeedForwardModifier, FIELD_FLOAT, "speed_forward_modifier" ), + + DEFINE_KEYFIELD( m_nTrainRecedeTime, FIELD_INTEGER, "train_recede_time" ), + +END_DATADESC() + + +IMPLEMENT_SERVERCLASS_ST(CTeamTrainWatcher, DT_TeamTrainWatcher) + + SendPropFloat( SENDINFO( m_flTotalProgress ), 11, 0, 0.0f, 1.0f ), + SendPropInt( SENDINFO( m_iTrainSpeedLevel ), 4 ), + SendPropTime( SENDINFO( m_flRecedeTime ) ), + SendPropInt( SENDINFO( m_nNumCappers ) ), +#ifdef GLOWS_ENABLE + SendPropEHandle( SENDINFO( m_hGlowEnt ) ), +#endif // GLOWS_ENABLE + +END_SEND_TABLE() + + +LINK_ENTITY_TO_CLASS( team_train_watcher, CTeamTrainWatcher ); +/* +LINK_ENTITY_TO_CLASS( team_train_watcher_master, CTeamTrainWatcherMaster ); +PRECACHE_REGISTER( team_train_watcher_master ); + +CTeamTrainWatcherMaster::CTeamTrainWatcherMaster() +{ + m_pBlueWatcher = NULL; + m_pRedWatcher = NULL; + + m_flBlueProgress = 0.0f; + m_flRedProgress = 0.0f; + + ListenForGameEvent( "teamplay_round_start" ); + ListenForGameEvent( "teamplay_round_win" ); +} + +CTeamTrainWatcherMaster::~CTeamTrainWatcherMaster() +{ + if ( g_hTeamTrainWatcherMaster.Get() == this ) + { + g_hTeamTrainWatcherMaster = NULL; + } +} + +void CTeamTrainWatcherMaster::Precache( void ) +{ + PrecacheScriptSound( TWM_FIRSTSTAGEOUTCOME01 ); + PrecacheScriptSound( TWM_FIRSTSTAGEOUTCOME02 ); + PrecacheScriptSound( TWM_RACEGENERAL01 ); + PrecacheScriptSound( TWM_RACEGENERAL02 ); + PrecacheScriptSound( TWM_RACEGENERAL03 ); + PrecacheScriptSound( TWM_RACEGENERAL04 ); + PrecacheScriptSound( TWM_RACEGENERAL05 ); + PrecacheScriptSound( TWM_RACEGENERAL08 ); + PrecacheScriptSound( TWM_RACEGENERAL06 ); + PrecacheScriptSound( TWM_RACEGENERAL07 ); + PrecacheScriptSound( TWM_RACEGENERAL09 ); + PrecacheScriptSound( TWM_RACEGENERAL12 ); + PrecacheScriptSound( TWM_RACEGENERAL13 ); + PrecacheScriptSound( TWM_RACEGENERAL14 ); + PrecacheScriptSound( TWM_RACEGENERAL15 ); + PrecacheScriptSound( TWM_RACEGENERAL10 ); + PrecacheScriptSound( TWM_RACEGENERAL11 ); + PrecacheScriptSound( TWM_SECONDSTAGEOUTCOME01 ); + PrecacheScriptSound( TWM_SECONDSTAGEOUTCOME04 ); + PrecacheScriptSound( TWM_SECONDSTAGEOUTCOME02 ); + PrecacheScriptSound( TWM_SECONDSTAGEOUTCOME03 ); + PrecacheScriptSound( TWM_FINALSTAGEOUTCOME01 ); + PrecacheScriptSound( TWM_FINALSTAGEOUTCOME02 ); + PrecacheScriptSound( TWM_FINALSTAGESTART01 ); + PrecacheScriptSound( TWM_FINALSTAGESTART04 ); + PrecacheScriptSound( TWM_FINALSTAGESTART08 ); + PrecacheScriptSound( TWM_FINALSTAGESTART09 ); + PrecacheScriptSound( TWM_FINALSTAGESTART07 ); + PrecacheScriptSound( TWM_FINALSTAGESTART02 ); + PrecacheScriptSound( TWM_FINALSTAGESTART03 ); + PrecacheScriptSound( TWM_FINALSTAGESTART05 ); + PrecacheScriptSound( TWM_FINALSTAGESTART06 ); + + BaseClass::Precache(); +} + +bool CTeamTrainWatcherMaster::FindTrainWatchers( void ) +{ + m_pBlueWatcher = NULL; + m_pRedWatcher = NULL; + + // find the train_watchers for this round + CTeamTrainWatcher *pTrainWatcher = (CTeamTrainWatcher *)gEntList.FindEntityByClassname( NULL, "team_train_watcher" ); + while ( pTrainWatcher ) + { + if ( pTrainWatcher->IsDisabled() == false ) + { + if ( pTrainWatcher->GetTeamNumber() == TF_TEAM_BLUE ) + { + m_pBlueWatcher = pTrainWatcher; + } + else if ( pTrainWatcher->GetTeamNumber() == TF_TEAM_RED ) + { + m_pRedWatcher = pTrainWatcher; + } + } + + pTrainWatcher = (CTeamTrainWatcher *)gEntList.FindEntityByClassname( pTrainWatcher, "team_train_watcher" ); + } + + return ( m_pBlueWatcher && m_pRedWatcher ); +} + +void CTeamTrainWatcherMaster::TWMThink( void ) +{ + if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->State_Get() != GR_STATE_RND_RUNNING ) + { + // the next time we 'think' + SetContextThink( &CTeamTrainWatcherMaster::TWMThink, gpGlobals->curtime + 0.2, TWMASTER_THINK ); + return; + } + + + + // the next time we 'think' + SetContextThink( &CTeamTrainWatcherMaster::TWMThink, gpGlobals->curtime + 0.2, TWMASTER_THINK ); +} + +void CTeamTrainWatcherMaster::FireGameEvent( IGameEvent *event ) +{ + const char *eventname = event->GetName();` + + if ( FStrEq( "teamplay_round_start", eventname ) ) + { + if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->HasMultipleTrains() ) + { + if ( FindTrainWatchers() ) + { + // we found train watchers so start thinking + SetContextThink( &CTeamTrainWatcherMaster::TWMThink, gpGlobals->curtime + 0.2, TWMASTER_THINK ); + } + } + } + else if ( FStrEq( "teamplay_round_win", eventname ) ) + { + if ( TeamplayRoundBasedRules() ) + { + int iWinningTeam = event->GetInt( "team" ); + int iLosingTeam = ( iWinningTeam == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED; + bool bFullRound = event->GetBool( "full_round" ); + + CTeamRecipientFilter filterWinner( iWinningTeam, true ); + CTeamRecipientFilter filterLoser( iLosingTeam, true ); + + if ( bFullRound ) + { + EmitSound( filterWinner, entindex(), TWM_FINALSTAGEOUTCOME01 ); + EmitSound( filterLoser, entindex(), TWM_FINALSTAGEOUTCOME02 ); + } + else + { + EmitSound( filterWinner, entindex(), TWM_FIRSTSTAGEOUTCOME01 ); + EmitSound( filterLoser, entindex(), TWM_FIRSTSTAGEOUTCOME02 ); + } + } + } +} +*/ +CTeamTrainWatcher::CTeamTrainWatcher() +{ + m_bDisabled = false; + m_flRecedeTime = 0; + m_bWaitingToRecede = false; + m_bCapBlocked = false; + + m_flNextSpeakForwardConceptTime = 0; + m_hAreaCap = NULL; + + m_bTrainCanRecede = true; + m_bAlarmPlayed = false; + m_pAlarm = NULL; + m_flAlarmEndTime = -1; + + m_bHandleTrainMovement = false; + m_flSpeedForwardModifier = 1.0f; + m_iCurrentHillType = HILL_TYPE_NONE; + m_flCurrentSpeed = 0.0f; + m_bReceding = false; + + m_flTrainDistanceFromStart = 0.0f; + + m_nTrainRecedeTime = 0; + +#ifdef GLOWS_ENABLE + m_hGlowEnt.Set( NULL ); +#endif // GLOWS_ENABLE + +#ifdef TF_DLL + ChangeTeam( TF_TEAM_BLUE ); +#else + ChangeTeam( TEAM_UNASSIGNED ); +#endif +/* + // create a CTeamTrainWatcherMaster entity + if ( g_hTeamTrainWatcherMaster.Get() == NULL ) + { + g_hTeamTrainWatcherMaster = CreateEntityByName( "team_train_watcher_master" ); + } +*/ + ListenForGameEvent( "path_track_passed" ); +} + +CTeamTrainWatcher::~CTeamTrainWatcher() +{ + m_Sparks.Purge(); +} + +void CTeamTrainWatcher::UpdateOnRemove( void ) +{ + StopCaptureAlarm(); + + BaseClass::UpdateOnRemove(); +} + +int CTeamTrainWatcher::UpdateTransmitState() +{ + if ( m_bDisabled ) + { + return SetTransmitState( FL_EDICT_DONTSEND ); + } + + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +void CTeamTrainWatcher::InputRoundActivate( inputdata_t &inputdata ) +{ + StopCaptureAlarm(); + + if ( !m_bDisabled ) + { + WatcherActivate(); + } +} + +void CTeamTrainWatcher::InputEnable( inputdata_t &inputdata ) +{ + StopCaptureAlarm(); + + m_bDisabled = false; + + WatcherActivate(); + + UpdateTransmitState(); +} + +void CTeamTrainWatcher::InputDisable( inputdata_t &inputdata ) +{ + StopCaptureAlarm(); + + m_bDisabled = true; + SetContextThink( NULL, 0, TW_THINK ); + + m_bWaitingToRecede = false; + + m_Sparks.Purge(); + +#ifdef GLOWS_ENABLE + m_hGlowEnt.Set( NULL ); +#endif // GLOWS_ENABLE + + // if we're moving the train, let's shut it down + if ( m_bHandleTrainMovement ) + { + m_flCurrentSpeed = 0.0f; + + if ( m_hTrain ) + { + m_hTrain->SetSpeedDirAccel( m_flCurrentSpeed ); + } + + // handle the sparks under the train + HandleSparks( false ); + } + + UpdateTransmitState(); +} + +ConVar tf_escort_recede_time( "tf_escort_recede_time", "30", 0, "", true, 0, false, 0 ); +ConVar tf_escort_recede_time_overtime( "tf_escort_recede_time_overtime", "5", 0, "", true, 0, false, 0 ); + +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::HandleSparks( bool bSparks ) +{ + if ( IsDisabled() || !m_bHandleTrainMovement ) + return; + + for ( int i = 0 ; i < m_Sparks.Count() ; i++ ) + { + CEnvSpark* pSpark = m_Sparks[i].Get(); + if ( pSpark && ( pSpark->IsSparking() != bSparks ) ) + { + if ( bSparks ) + { + pSpark->StartSpark(); + } + else + { + pSpark->StopSpark(); + } + } + } +} + +void CTeamTrainWatcher::HandleTrainMovement( bool bStartReceding /* = false */ ) +{ + if ( IsDisabled() || !m_bHandleTrainMovement ) + return; + + if ( m_hTrain ) + { + float flSpeed = 0.0f; + + if ( bStartReceding ) + { + flSpeed = -0.1f; + m_bReceding = true; + } + else + { + // do we have cappers on the train? + if ( m_nNumCappers > 0 ) + { + m_bReceding = false; + + if ( m_iCurrentHillType == HILL_TYPE_DOWNHILL ) + { + flSpeed = 1.0f; + } + else + { + switch( m_nNumCappers ) + { + case 1: + flSpeed = 0.55f; + break; + case 2: + flSpeed = 0.77f; + break; + case 3: + default: + flSpeed = 1.0f; + break; + } + } + } + else if ( m_nNumCappers == -1 ) + { + // we'll get a -1 for a blocked cart (speed should be 0 for that unless we're on a hill) + if ( m_iCurrentHillType == HILL_TYPE_DOWNHILL ) + { + flSpeed = 1.0f; + } + } + else + { + // there's nobody on the train, what should it be doing? + if ( m_flCurrentSpeed > 0 ) + { + if ( m_iCurrentHillType == HILL_TYPE_DOWNHILL ) + { + flSpeed = 1.0f; + } + } + else + { + // we're rolling backwards + if ( m_iCurrentHillType == HILL_TYPE_UPHILL ) + { + flSpeed = -1.0f; + } + else + { + if ( m_bReceding ) + { + // resume our previous backup speed + flSpeed = -0.1f; + } + } + } + } + } + + // only need to update the train if our speed has changed + if ( m_flCurrentSpeed != flSpeed ) + { + if ( flSpeed >= 0.0f ) + { + m_bReceding = false; + } + + m_flCurrentSpeed = flSpeed; + m_hTrain->SetSpeedDirAccel( m_flCurrentSpeed ); + + // handle the sparks under the train + bool bSparks = false; + if ( m_flCurrentSpeed < 0 ) + { + bSparks = true; + } + + HandleSparks( bSparks ); + } + } +} + +void CTeamTrainWatcher::InputSetSpeedForwardModifier( inputdata_t &inputdata ) +{ + InternalSetSpeedForwardModifier( inputdata.value.Float() ); +} + +void CTeamTrainWatcher::InternalSetSpeedForwardModifier( float flModifier ) +{ + if ( IsDisabled() || !m_bHandleTrainMovement ) + return; + + // store the passed value + float flSpeedForwardModifier = flModifier; + flSpeedForwardModifier = fabs( flSpeedForwardModifier ); + + m_flSpeedForwardModifier = clamp( flSpeedForwardModifier, 0.f, 1.f ); + + if ( m_hTrain ) + { + m_hTrain->SetSpeedForwardModifier( m_flSpeedForwardModifier ); + } +} + +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( 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(); +} + +// only used for train watchers that control the train movement +void CTeamTrainWatcher::SetNumTrainCappers( int iNumCappers, CBaseEntity *pTrigger ) +{ + if ( IsDisabled() || !m_bHandleTrainMovement ) + return; + + InternalSetNumTrainCappers( iNumCappers, pTrigger ); +} + +void CTeamTrainWatcher::InputSetNumTrainCappers( inputdata_t &inputdata ) +{ + InternalSetNumTrainCappers( inputdata.value.Int(), inputdata.pCaller ); +} + +void CTeamTrainWatcher::InputSetTrainRecedeTime( inputdata_t &inputdata ) +{ + int nSeconds = inputdata.value.Int(); + if ( nSeconds >= 0 ) + { + m_nTrainRecedeTime = nSeconds; + } + else + { + m_nTrainRecedeTime = 0; + } +} + +void CTeamTrainWatcher::InputOnStartOvertime( inputdata_t &inputdata ) +{ + // recalculate the recede time + if ( m_bWaitingToRecede ) + { + float flRecedeTimeRemaining = m_flRecedeTime - gpGlobals->curtime; + float flOvertimeRecedeLen = tf_escort_recede_time_overtime.GetFloat(); + + // drop to overtime recede time if it's more than that + if ( flRecedeTimeRemaining > flOvertimeRecedeLen ) + { + m_flRecedeTotalTime = flOvertimeRecedeLen; + m_flRecedeStartTime = gpGlobals->curtime; + m_flRecedeTime = m_flRecedeStartTime + m_flRecedeTotalTime; + } + } +} + +#ifdef GLOWS_ENABLE +void CTeamTrainWatcher::FindGlowEntity( void ) +{ + if ( m_hTrain && ( m_hTrain->GetEntityName() != NULL_STRING ) ) + { + string_t iszTrainName = m_hTrain->GetEntityName(); + CBaseEntity *pGlowEnt = NULL; + + // first try to find a phys_constraint relationship with the train + CPhysFixed *pPhysConstraint = dynamic_cast( gEntList.FindEntityByClassname( NULL, "phys_constraint" ) ); + while ( pPhysConstraint ) + { + string_t iszName1 = pPhysConstraint->GetNameAttach1(); + string_t iszName2 = pPhysConstraint->GetNameAttach2(); + + if ( iszTrainName == iszName1 ) + { + pGlowEnt = gEntList.FindEntityByName( NULL, STRING( iszName2 ) ); + break; + } + else if ( iszTrainName == iszName2 ) + { + pGlowEnt = gEntList.FindEntityByName( NULL, STRING( iszName1 ) ); + break; + } + + pPhysConstraint = dynamic_cast( gEntList.FindEntityByClassname( pPhysConstraint, "phys_constraint" ) ); + } + + if ( !pGlowEnt ) + { + // if we're here, we haven't found the glow entity yet...try all of the prop_dynamic entities + CDynamicProp *pPropDynamic = dynamic_cast( gEntList.FindEntityByClassname( NULL, "prop_dynamic" ) ); + while ( pPropDynamic ) + { + if ( pPropDynamic->GetParent() == m_hTrain ) + { + pGlowEnt = pPropDynamic; + break; + } + + pPropDynamic = dynamic_cast( gEntList.FindEntityByClassname( pPropDynamic, "prop_dynamic" ) ); + } + } + + // if we still haven't found a glow entity, just have the CFuncTrackTrain glow + if ( !pGlowEnt ) + { + pGlowEnt = m_hTrain.Get(); + } + + if ( pGlowEnt ) + { + pGlowEnt->SetTransmitState( FL_EDICT_ALWAYS ); + m_hGlowEnt.Set( pGlowEnt ); + } + } +} +#endif // GLOWS_ENABLE + +// ========================================================== +// 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( 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 ) + { + for ( int i=0; i( ITriggerAreaCaptureAutoList::AutoList()[i] ); + 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; + } + } + } + + // init the sprites (if any) + CEnvSpark *pSpark = dynamic_cast( gEntList.FindEntityByName( NULL, m_iszSparkName ) ); + while ( pSpark ) + { + m_Sparks.AddToTail( pSpark ); + pSpark = dynamic_cast( 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( gEntList.FindEntityByName( NULL, m_iszLinkedPathTracks[i] ) ); + CTeamControlPoint *pCP = dynamic_cast( 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( 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( 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;iGetPointIndex(); +// 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::StopCaptureAlarm( void ) +{ + if ( m_pAlarm ) + { + CSoundEnvelopeController::GetController().SoundDestroy( m_pAlarm ); + m_pAlarm = NULL; + m_flAlarmEndTime = -1.0f; + } + + SetContextThink( NULL, 0, TW_ALARM_THINK ); +} + +void CTeamTrainWatcher::StartCaptureAlarm( CTeamControlPoint *pPoint ) +{ + StopCaptureAlarm(); + + if ( pPoint ) + { + CReliableBroadcastRecipientFilter filter; + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + m_pAlarm = controller.SoundCreate( filter, pPoint->entindex(), CHAN_STATIC, TEAM_TRAIN_ALARM, ATTN_NORM ); + controller.Play( m_pAlarm, 1.0, PITCH_NORM ); + + m_flAlarmEndTime = gpGlobals->curtime + MAX_ALARM_TIME_NO_RECEDE; + } +} + +void CTeamTrainWatcher::PlayCaptureAlert( CTeamControlPoint *pPoint, bool bFinalPointInMap ) +{ + if ( !pPoint ) + return; + + if ( TeamplayRoundBasedRules() ) + { + TeamplayRoundBasedRules()->PlayTrainCaptureAlert( pPoint, bFinalPointInMap ); + } +} + + +ConVar tf_show_train_path( "tf_show_train_path", "0", FCVAR_CHEAT ); + +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 ); +} + +void CTeamTrainWatcher::WatcherAlarmThink( void ) +{ + CTeamControlPoint *pPoint = m_CPLinks[m_iNumCPLinks-1].hCP.Get(); + if ( pPoint ) + { + pPoint->EmitSound( TEAM_TRAIN_ALARM_SINGLE ); + } + + SetContextThink( &CTeamTrainWatcher::WatcherAlarmThink, gpGlobals->curtime + TW_ALARM_THINK_INTERVAL, TW_ALARM_THINK ); +} + +CBaseEntity *CTeamTrainWatcher::GetTrainEntity( void ) +{ + return m_hTrain.Get(); +} + +bool CTeamTrainWatcher::TimerMayExpire( void ) +{ + if ( IsDisabled() ) + { + return true; + } + + // Still in overtime if we're waiting to recede + if ( m_bWaitingToRecede ) + return false; + + // capture blocked so we're not receding, but game shouldn't end + if ( m_bCapBlocked ) + return false; + + // not waiting, so we're capping, in which case the area capture + // will not let us expire + return true; +} + + +// 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; + } +} + + +// Return true if the given position is farther down the track than the train is +bool CTeamTrainWatcher::IsAheadOfTrain( const Vector &pos ) const +{ + float distanceAlongPath; + ProjectPointOntoPath( pos, NULL, &distanceAlongPath ); + + return ( distanceAlongPath > m_flTrainDistanceFromStart ); +} + + +// return true if the train is almost at the next checkpoint +bool CTeamTrainWatcher::IsTrainNearCheckpoint( void ) const +{ + for( int i = 0; i < m_iNumCPLinks ; ++i ) + { + if ( m_flTrainDistanceFromStart > m_CPLinks[i].flDistanceFromStart - TEAM_TRAIN_ALERT_DISTANCE && + m_flTrainDistanceFromStart < m_CPLinks[i].flDistanceFromStart ) + { + return true; + } + } + + return false; +} + + +// return true if the train hasn't left its starting position yet +bool CTeamTrainWatcher::IsTrainAtStart( void ) const +{ + return ( m_flTrainDistanceFromStart < TEAM_TRAIN_ALARM_DISTANCE ); +} + + +// return world space location of next checkpoint along the path +Vector CTeamTrainWatcher::GetNextCheckpointPosition( void ) const +{ + for( int i = 0; i < m_iNumCPLinks ; ++i ) + { + if ( m_flTrainDistanceFromStart < m_CPLinks[i].flDistanceFromStart ) + { + return m_CPLinks[i].hPathTrack->GetAbsOrigin(); + } + } + + Assert( !"No checkpoint found in team train watcher\n" ); + return vec3_origin; +} + +#if defined( STAGING_ONLY ) && defined( TF_DLL ) +CON_COMMAND_F( tf_dumptrainstats, "Dump the stats for the current train watcher to the console", FCVAR_GAMEDLL ) +{ + // Listenserver host or rcon access only! + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + CTeamTrainWatcher *pWatcher = NULL; + while( ( pWatcher = dynamic_cast< CTeamTrainWatcher * >( gEntList.FindEntityByClassname( pWatcher, "team_train_watcher" ) ) ) != NULL ) + { + pWatcher->DumpStats(); + } +} + +void CTeamTrainWatcher::DumpStats( void ) +{ + float flLastPosition = 0.0f; + float flTotalDistance = 0.0f; + char szOutput[2048]; + char szTemp[256]; + + V_strcpy_safe( szOutput, "\n\nTrain Watcher stats for team " ); + V_strcat_safe( szOutput, ( GetTeamNumber() == TF_TEAM_RED ) ? "Red\n" : "Blue\n" ); + + for( int i = 0; i < m_iNumCPLinks ; ++i ) + { + float flDistance = m_CPLinks[i].flDistanceFromStart - flLastPosition; + if ( i == 0 ) + { + V_sprintf_safe( szTemp, "\tControl Point: %d\tDistance from start: %0.2f\n", i + 1, flDistance ); + } + else + { + V_sprintf_safe( szTemp, "\tControl Point: %d\tDistance from previous point: %0.2f\n", i + 1, flDistance ); + } + V_strcat_safe( szOutput, szTemp ); + flTotalDistance += flDistance; + flLastPosition = m_CPLinks[i].flDistanceFromStart; + } + + V_sprintf_safe( szTemp, "\tTotal Distance: %0.2f\n\n", flTotalDistance ); + V_strcat_safe( szOutput, szTemp ); + Msg( "%s", szOutput ); +} +#endif // STAGING_ONLY && TF_DLL + + -- cgit v1.2.3