aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/team_train_watcher.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/team_train_watcher.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/team_train_watcher.cpp')
-rw-r--r--mp/src/game/server/team_train_watcher.cpp3092
1 files changed, 1546 insertions, 1546 deletions
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<CTriggerAreaCapture *>( pTrigger );
- if ( pAreaCap )
- {
- m_bCapBlocked = pAreaCap->IsBlocked();
- m_hAreaCap = pAreaCap;
- }
-
- if ( iNumCappers <= 0 && !m_bCapBlocked && m_bTrainCanRecede )
- {
- if ( !m_bWaitingToRecede )
- {
- // start receding in [tf_escort_cart_recede_time] seconds
- m_bWaitingToRecede = true;
-
- if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->InOvertime() )
- {
- m_flRecedeTotalTime = tf_escort_recede_time_overtime.GetFloat();
- }
- else
- {
- m_flRecedeTotalTime = tf_escort_recede_time.GetFloat();
- if ( m_nTrainRecedeTime > 0 )
- {
- m_flRecedeTotalTime = m_nTrainRecedeTime;
- }
- }
-
- m_flRecedeStartTime = gpGlobals->curtime;
- m_flRecedeTime = m_flRecedeStartTime + m_flRecedeTotalTime;
- }
- }
- else
- {
- // cancel receding
- m_bWaitingToRecede = false;
- m_flRecedeTime = 0;
- }
-
- HandleTrainMovement();
-}
-
-// 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<CPhysFixed*>( 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<CPhysFixed*>( 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<CDynamicProp*>( gEntList.FindEntityByClassname( NULL, "prop_dynamic" ) );
- while ( pPropDynamic )
- {
- if ( pPropDynamic->GetParent() == m_hTrain )
- {
- pGlowEnt = pPropDynamic;
- break;
- }
-
- pPropDynamic = dynamic_cast<CDynamicProp*>( 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<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 )
- {
- for ( int i=0; i<ITriggerAreaCaptureAutoList::AutoList().Count(); ++i )
- {
- CTriggerAreaCapture *pArea = static_cast< CTriggerAreaCapture * >( 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<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::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<CTriggerAreaCapture *>( pTrigger );
+ if ( pAreaCap )
+ {
+ m_bCapBlocked = pAreaCap->IsBlocked();
+ m_hAreaCap = pAreaCap;
+ }
+
+ if ( iNumCappers <= 0 && !m_bCapBlocked && m_bTrainCanRecede )
+ {
+ if ( !m_bWaitingToRecede )
+ {
+ // start receding in [tf_escort_cart_recede_time] seconds
+ m_bWaitingToRecede = true;
+
+ if ( TeamplayRoundBasedRules() && TeamplayRoundBasedRules()->InOvertime() )
+ {
+ m_flRecedeTotalTime = tf_escort_recede_time_overtime.GetFloat();
+ }
+ else
+ {
+ m_flRecedeTotalTime = tf_escort_recede_time.GetFloat();
+ if ( m_nTrainRecedeTime > 0 )
+ {
+ m_flRecedeTotalTime = m_nTrainRecedeTime;
+ }
+ }
+
+ m_flRecedeStartTime = gpGlobals->curtime;
+ m_flRecedeTime = m_flRecedeStartTime + m_flRecedeTotalTime;
+ }
+ }
+ else
+ {
+ // cancel receding
+ m_bWaitingToRecede = false;
+ m_flRecedeTime = 0;
+ }
+
+ HandleTrainMovement();
+}
+
+// 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<CPhysFixed*>( 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<CPhysFixed*>( 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<CDynamicProp*>( gEntList.FindEntityByClassname( NULL, "prop_dynamic" ) );
+ while ( pPropDynamic )
+ {
+ if ( pPropDynamic->GetParent() == m_hTrain )
+ {
+ pGlowEnt = pPropDynamic;
+ break;
+ }
+
+ pPropDynamic = dynamic_cast<CDynamicProp*>( 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<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 )
+ {
+ for ( int i=0; i<ITriggerAreaCaptureAutoList::AutoList().Count(); ++i )
+ {
+ CTriggerAreaCapture *pArea = static_cast< CTriggerAreaCapture * >( 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<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::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
+
+