mirror of
https://github.com/mapbase-source/source-sdk-2013.git
synced 2025-01-23 12:17:57 +03:00
beaae8ac45
* 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
1556 lines
42 KiB
C++
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
|
|
|
|
|