source-sdk-2013-mapbase/sp/src/game/server/team_train_watcher.cpp
Joe Ludwig beaae8ac45 Updated the SDK with the latest code from the TF and HL2 branches
* Adds support for Visual Studio 2012 and 2013
* VR Mode:
. Switches from headtrack.dll to sourcevr.dll
. Improved readability of the UI in VR
. Removed the IPD calibration tool. TF2 will now obey the Oculus
configuration file. Use the Oculus calibration tool in your SDK or
install and run "OpenVR" under Tools in Steam to calibrate your IPD.
. Added dropdown to enable VR mode in the Video options. Removed the -vr
command line option.
. Added the ability to switch in and out of VR mode without quitting the
game
. By default VR mode will run full screen. To switch back to a
borderless window set the vr_force_windowed convar.
. Added support for VR mode on Linux
* Many assorted bug fixes and other changes from Team Fortress in
various shared files
2013-12-03 08:54:16 -08:00

1556 lines
42 KiB
C++

//========= 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 ),
DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetTrainCanRecede", InputSetTrainCanRecede ),
// 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 );
IMPLEMENT_AUTO_LIST( ITFTeamTrainWatcher );
/*
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::InputSetTrainCanRecede( inputdata_t &inputdata )
{
m_bTrainCanRecede = inputdata.value.Bool();
}
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