You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1615 lines
44 KiB
1615 lines
44 KiB
//========= 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 ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetTrainRecedeTimeAndUpdate", InputSetTrainRecedeTimeAndUpdate ), |
|
|
|
// 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_flTrainDistanceAccumulator = 0.f; |
|
|
|
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::InputSetTrainRecedeTimeAndUpdate(inputdata_t &inputdata) |
|
{ |
|
InputSetTrainRecedeTime( inputdata ); |
|
|
|
// update our time if we're already counting down |
|
if ( m_flRecedeTime > 0 ) |
|
{ |
|
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; |
|
} |
|
} |
|
|
|
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 ); |
|
|
|
float flLastDistanceFromStart = m_flTrainDistanceFromStart; |
|
m_flTrainDistanceFromStart = m_flTotalPathDistance - flDistanceToGoal; |
|
|
|
//engine->Con_NPrintf( 0, "%f", m_flTrainDistanceAccumulator ); |
|
|
|
const int nMinProgressToReport = 16; // 16 hammer units per foot |
|
// Check if we've made forward progress. |
|
if ( m_flTrainDistanceFromStart > flLastDistanceFromStart ) |
|
{ |
|
// Accumulate distance traveled |
|
m_flTrainDistanceAccumulator += m_flTrainDistanceFromStart - flLastDistanceFromStart; |
|
|
|
// If we're travelling forward and made enough progress to warrant firing an event |
|
if ( m_iTrainSpeedLevel > 0 && m_flTrainDistanceAccumulator >= nMinProgressToReport ) |
|
{ |
|
|
|
m_flTrainDistanceAccumulator -= nMinProgressToReport; |
|
|
|
// Fire an event for all the touching players |
|
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 ) ) |
|
{ |
|
IGameEvent *pEvent = gameeventmanager->CreateEvent( "payload_pushed" ); |
|
if ( pEvent ) |
|
{ |
|
pEvent->SetInt( "pusher", pPlayer->GetUserID() ); |
|
pEvent->SetInt( "distance", 1 ); |
|
gameeventmanager->FireEvent( pEvent, true ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// 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 |
|
|
|
|
|
|