diff options
Diffstat (limited to 'game/shared/tf/entity_capture_flag.cpp')
| -rw-r--r-- | game/shared/tf/entity_capture_flag.cpp | 3100 |
1 files changed, 3100 insertions, 0 deletions
diff --git a/game/shared/tf/entity_capture_flag.cpp b/game/shared/tf/entity_capture_flag.cpp new file mode 100644 index 0000000..e858f2b --- /dev/null +++ b/game/shared/tf/entity_capture_flag.cpp @@ -0,0 +1,3100 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: CTF Flag. +// +//=============================================================================// +#include "cbase.h" +#include "entity_capture_flag.h" +#include "tf_gamerules.h" +#include "tf_shareddefs.h" +#include "filesystem.h" +#include "tf_logic_player_destruction.h" + +#ifdef CLIENT_DLL +#include <vgui_controls/Panel.h> +#include <vgui_controls/ImagePanel.h> +#include <vgui_controls/EditablePanel.h> +#include <vgui/IScheme.h> +#include "hudelement.h" +#include "iclientmode.h" +#include "hud_numericdisplay.h" +#include "tf_imagepanel.h" +#include "c_tf_player.h" +#include "c_tf_team.h" +#include "tf_hud_objectivestatus.h" +#include "view.h" + +ConVar cl_flag_return_size( "cl_flag_return_size", "20", FCVAR_CHEAT ); + +extern ConVar tf_rd_flag_ui_mode; + +#else +#include "tf_player.h" +#include "tf_team.h" +#include "tf_objective_resource.h" +#include "tf_gamestats.h" +#include "func_respawnroom.h" +#include "datacache/imdlcache.h" +#include "func_respawnflag.h" +#include "func_capture_zone.h" +#include "nav_mesh/tf_nav_mesh.h" +#include "player_vs_environment/tf_population_manager.h" +#include "tf_logic_robot_destruction.h" +#include "tf_logic_halloween_2014.h" +extern ConVar tf_flag_caps_per_round; +extern ConVar tf_mvm_endless_bomb_reset; +extern ConVar tf_rd_min_points_to_steal; + +ConVar cl_flag_return_height( "cl_flag_return_height", "82", FCVAR_CHEAT ); +ConVar tf_rd_return_min_time( "tf_rd_return_min_time", "30" ); +ConVar tf_rd_return_max_time( "tf_rd_return_max_time", "90" ); + +#endif + +ConVar tf_flag_return_on_touch( "tf_flag_return_on_touch", "0", FCVAR_REPLICATED, "If this is set, your flag must be at base in order to capture the enemy flag. Remote friendly flags return to your base instantly when you touch them" ); + +#ifdef STAGING_ONLY +ConVar tf_flag_return_time_override( "tf_flag_return_time_override", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "How long before a dropped flag will return (in seconds). 0 = Use map/default settings. For internal-use only.", true, 0.f, false, 0.f ); +#endif // STAGING_ONLY + +ConVar tf_flag_return_time_credit_factor( "tf_flag_return_time_credit_factor", "1.0", FCVAR_REPLICATED, "Number of seconds the flag's return time will be credited for each second the flag is being carried.", true, 0.f, false, 0.f ); + +enum +{ + INVADE_NEUTRAL_TYPE_NONE = 0, // no neutral time + INVADE_NEUTRAL_TYPE_DEFAULT, // current behavior....30 secs (TF_INVADE_NEUTRAL_TIME) + INVADE_NEUTRAL_TYPE_HALF, // half the return time +}; + +enum +{ + INVADE_SCORING_TEAM_SCORE = 0, + INVADE_SCORING_TEAM_CAPTURE_COUNT, +}; + +#define FLAG_EFFECTS_NONE 0 +#define FLAG_EFFECTS_ALL 1 +#define FLAG_EFFECTS_PAPERONLY 2 +#define FLAG_EFFECTS_COLORONLY 3 + +#ifdef CLIENT_DLL + +static void RecvProxy_IsDisabled( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + CCaptureFlag *pFlag = (CCaptureFlag *) pStruct; + bool bIsDisabled = ( pData->m_Value.m_Int > 0 ); + + if ( pFlag ) + { + pFlag->SetDisabled( bIsDisabled ); + } +} + +static void RecvProxy_IsVisibleWhenDisabled( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + CCaptureFlag *pFlag = (CCaptureFlag *) pStruct; + bool bVisible = ( pData->m_Value.m_Int > 0 ); + + if ( pFlag ) + { + pFlag->SetVisibleWhenDisabled( bVisible ); + } +} + +static void RecvProxy_FlagStatus( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + CCaptureFlag *pFlag = (CCaptureFlag *) pStruct; + + if ( pFlag ) + { + pFlag->SetFlagStatus( pData->m_Value.m_Int ); + + IGameEvent *pEvent = gameeventmanager->CreateEvent( "flagstatus_update" ); + if ( pEvent ) + { + pEvent->SetInt( "entindex", pFlag->entindex() ); + gameeventmanager->FireEventClientSide( pEvent ); + } + } +} + +#endif + +//============================================================================= +// +// CTF Flag tables. +// + +IMPLEMENT_NETWORKCLASS_ALIASED( CaptureFlag, DT_CaptureFlag ) + +BEGIN_NETWORK_TABLE( CCaptureFlag, DT_CaptureFlag ) + +#ifdef GAME_DLL + SendPropBool( SENDINFO( m_bDisabled ) ), + SendPropBool( SENDINFO( m_bVisibleWhenDisabled ) ), + SendPropInt( SENDINFO( m_nType ), 5, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_nFlagStatus ), 3, SPROP_UNSIGNED ), + SendPropTime( SENDINFO( m_flResetTime ) ), + SendPropTime( SENDINFO( m_flNeutralTime ) ), + SendPropTime( SENDINFO( m_flMaxResetTime ) ), + SendPropEHandle( SENDINFO( m_hPrevOwner ) ), + SendPropString( SENDINFO( m_szModel ) ), + SendPropString( SENDINFO( m_szHudIcon ) ), + SendPropString( SENDINFO( m_szPaperEffect ) ), + SendPropString( SENDINFO( m_szTrailEffect ) ), + SendPropInt( SENDINFO( m_nUseTrailEffect ), 3, SPROP_UNSIGNED ), + SendPropInt( SENDINFO( m_nPointValue ) ), + SendPropFloat( SENDINFO( m_flAutoCapTime ) ), + SendPropBool( SENDINFO( m_bGlowEnabled ) ), + SendPropFloat( SENDINFO( m_flTimeToSetPoisonous ) ), + +#else + RecvPropInt( RECVINFO( m_bDisabled ), 0, RecvProxy_IsDisabled ), + RecvPropInt( RECVINFO( m_bVisibleWhenDisabled ), 0, RecvProxy_IsVisibleWhenDisabled ), + RecvPropInt( RECVINFO( m_nType ) ), + RecvPropInt( RECVINFO( m_nFlagStatus ), 0, RecvProxy_FlagStatus ), + RecvPropTime( RECVINFO( m_flResetTime ) ), + RecvPropTime( RECVINFO( m_flNeutralTime ) ), + RecvPropTime( RECVINFO( m_flMaxResetTime ) ), + RecvPropEHandle( RECVINFO( m_hPrevOwner ) ), + RecvPropString( RECVINFO( m_szModel ) ), + RecvPropString( RECVINFO( m_szHudIcon ) ), + RecvPropString( RECVINFO( m_szPaperEffect ) ), + RecvPropString( RECVINFO( m_szTrailEffect ) ), + RecvPropInt( RECVINFO( m_nUseTrailEffect ) ), + RecvPropInt( RECVINFO( m_nPointValue ) ), + RecvPropFloat( RECVINFO( m_flAutoCapTime ) ), + RecvPropBool( RECVINFO( m_bGlowEnabled ) ), + RecvPropFloat( RECVINFO( m_flTimeToSetPoisonous ) ), + +#endif +END_NETWORK_TABLE() + +BEGIN_DATADESC( CCaptureFlag ) + + // Keyfields. + DEFINE_KEYFIELD( m_nType, FIELD_INTEGER, "GameType" ), + DEFINE_KEYFIELD( m_nReturnTime, FIELD_INTEGER, "ReturnTime" ), + DEFINE_KEYFIELD( m_nUseTrailEffect, FIELD_INTEGER, "trail_effect" ), + DEFINE_KEYFIELD( m_nNeutralType, FIELD_INTEGER, "NeutralType" ), + DEFINE_KEYFIELD( m_nScoringType, FIELD_INTEGER, "ScoringType" ), + DEFINE_KEYFIELD( m_bReturnBetweenWaves, FIELD_BOOLEAN, "ReturnBetweenWaves" ), + DEFINE_KEYFIELD( m_bVisibleWhenDisabled, FIELD_BOOLEAN, "VisibleWhenDisabled" ), + DEFINE_KEYFIELD( m_bUseShotClockMode, FIELD_BOOLEAN, "ShotClockMode" ), + DEFINE_KEYFIELD( m_nPointValue, FIELD_INTEGER, "PointValue" ), + +#ifdef GAME_DLL + DEFINE_KEYFIELD( m_iszModel, FIELD_STRING, "flag_model" ), + DEFINE_KEYFIELD( m_iszHudIcon, FIELD_STRING, "flag_icon" ), + DEFINE_KEYFIELD( m_iszPaperEffect, FIELD_STRING, "flag_paper" ), + DEFINE_KEYFIELD( m_iszTrailEffect, FIELD_STRING, "flag_trail" ), + DEFINE_KEYFIELD( m_iszTags, FIELD_STRING, "tags" ), + + // Inputs. + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "RoundActivate", InputRoundActivate ), + DEFINE_INPUTFUNC( FIELD_VOID, "ForceDrop", InputForceDrop ), + DEFINE_INPUTFUNC( FIELD_VOID, "ForceReset", InputForceReset ), + DEFINE_INPUTFUNC( FIELD_VOID, "ForceResetSilent", InputForceResetSilent ), + DEFINE_INPUTFUNC( FIELD_VOID, "ForceResetAndDisableSilent", InputForceResetAndDisableSilent ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetReturnTime", InputSetReturnTime ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "ShowTimer", InputShowTimer ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "ForceGlowDisabled", InputForceGlowDisabled ), + + // Outputs. + DEFINE_OUTPUT( m_outputOnReturn, "OnReturn" ), + DEFINE_OUTPUT( m_outputOnPickUp, "OnPickUp" ), + DEFINE_OUTPUT( m_outputOnPickUpTeam1, "OnPickupTeam1" ), + DEFINE_OUTPUT( m_outputOnPickUpTeam2, "OnPickupTeam2" ), + DEFINE_OUTPUT( m_outputOnDrop, "OnDrop" ), + DEFINE_OUTPUT( m_outputOnCapture, "OnCapture" ), + DEFINE_OUTPUT( m_OnCapTeam1, "OnCapTeam1" ), + DEFINE_OUTPUT( m_OnCapTeam2, "OnCapTeam2" ), + DEFINE_OUTPUT( m_OnTouchSameTeam, "OnTouchSameTeam" ), +#endif + +END_DATADESC(); + +LINK_ENTITY_TO_CLASS( item_teamflag, CCaptureFlag ); + +IMPLEMENT_AUTO_LIST( ICaptureFlagAutoList ); + +//============================================================================= +// +// CTF Flag functions. +// + +CCaptureFlag::CCaptureFlag() +{ +#ifdef CLIENT_DLL + m_pGlowTrailEffect = NULL; + m_pPaperTrailEffect = NULL; + m_pGlowEffect = NULL; + m_hOldOwner = NULL; + m_bOldGlowEnabled = true; +#else + m_hReturnIcon = NULL; + m_nReturnTime = 60; + m_hInitialPlayer = NULL; + m_hInitialParent = NULL; + m_vecOffset.Init( 0, 0, 0 ); + + m_iszModel = NULL_STRING; + m_iszHudIcon = NULL_STRING; + m_iszPaperEffect = NULL_STRING; + m_iszTrailEffect = NULL_STRING; + m_nPointValue = 0; + m_flTimeToSetPoisonous = 0.f; + + // Team specific sound throttling for Special Delivery + for ( int i = 0; i < ARRAYSIZE( m_flNextTeamSoundTime ); i++ ) + { + m_flNextTeamSoundTime[i] = 0.f; + } +#endif + + m_nNeutralType = INVADE_NEUTRAL_TYPE_DEFAULT; + m_nScoringType = INVADE_SCORING_TEAM_SCORE; + m_bReturnBetweenWaves = true; + m_bVisibleWhenDisabled = false; + m_bUseShotClockMode = false; + m_bGlowEnabled = true; + + UseClientSideAnimation(); + + m_szModel.GetForModify()[ 0 ] = '\0'; + m_szHudIcon.GetForModify()[ 0 ] = '\0'; + m_szPaperEffect.GetForModify()[ 0 ] = '\0'; + m_szTrailEffect.GetForModify()[ 0 ] = '\0'; + m_nUseTrailEffect.Set( FLAG_EFFECTS_ALL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCaptureFlag::~CCaptureFlag() +{ +#ifndef GAME_DLL + if ( m_pGlowEffect ) + { + delete m_pGlowEffect; + m_pGlowEffect = NULL; + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +unsigned int CCaptureFlag::GetItemID( void ) const +{ + return TF_ITEM_CAPTURE_FLAG; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CCaptureFlag::GetFlagModel( void ) +{ + if ( m_szModel[ 0 ] != '\0' ) + { + if ( g_pFullFileSystem->FileExists( m_szModel.Get(), "GAME" ) ) + { + return ( m_szModel.Get() ); + } + } + + return TF_FLAG_MODEL; +} + +void CCaptureFlag::GetHudIcon( int nTeam, char *pchName, int nBuffSize ) +{ + V_snprintf( pchName, nBuffSize, "%s_%s", + ( ( m_szHudIcon[ 0 ] != '\0' ) ? ( m_szHudIcon.Get() ) : ( TF_FLAG_ICON ) ), + ( ( nTeam == TF_TEAM_BLUE ) ? ( "blue" ) : ( "red" ) ) ); +} + +const char *CCaptureFlag::GetPaperEffect( void ) +{ + if ( m_szPaperEffect[ 0 ] != '\0' ) + { + return ( m_szPaperEffect.Get() ); + } + + return TF_FLAG_EFFECT; +} + +void CCaptureFlag::GetTrailEffect( int nTeam, char *pchName, int nBuffSize ) +{ + V_snprintf( pchName, nBuffSize, "effects/%s_%s.vmt", + ( ( m_szTrailEffect[ 0 ] != '\0' ) ? ( m_szTrailEffect.Get() ) : ( TF_FLAG_TRAIL ) ), + ( ( nTeam == TF_TEAM_RED ) ? ( "red" ) : ( "blu" ) ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Precache the model and sounds. +//----------------------------------------------------------------------------- +void CCaptureFlag::Precache( void ) +{ + PrecacheModel( GetFlagModel() ); + + PrecacheScriptSound( TF_CTF_FLAGSPAWN ); + PrecacheScriptSound( TF_CTF_ENEMY_STOLEN ); + PrecacheScriptSound( TF_CTF_ENEMY_DROPPED ); + PrecacheScriptSound( TF_CTF_ENEMY_CAPTURED ); + PrecacheScriptSound( TF_CTF_ENEMY_RETURNED ); + PrecacheScriptSound( TF_CTF_TEAM_STOLEN ); + PrecacheScriptSound( TF_CTF_TEAM_DROPPED ); + PrecacheScriptSound( TF_CTF_TEAM_CAPTURED ); + PrecacheScriptSound( TF_CTF_TEAM_RETURNED ); + + PrecacheScriptSound( TF_AD_CAPTURED_SOUND ); + PrecacheScriptSound( TF_AD_ENEMY_STOLEN ); + PrecacheScriptSound( TF_AD_ENEMY_DROPPED ); + PrecacheScriptSound( TF_AD_ENEMY_CAPTURED ); + PrecacheScriptSound( TF_AD_ENEMY_RETURNED ); + PrecacheScriptSound( TF_AD_TEAM_STOLEN ); + PrecacheScriptSound( TF_AD_TEAM_DROPPED ); + PrecacheScriptSound( TF_AD_TEAM_CAPTURED ); + PrecacheScriptSound( TF_AD_TEAM_RETURNED ); + + PrecacheScriptSound( TF_MVM_AD_ENEMY_STOLEN ); + PrecacheScriptSound( TF_MVM_AD_ENEMY_DROPPED ); + PrecacheScriptSound( TF_MVM_AD_ENEMY_CAPTURED ); + PrecacheScriptSound( TF_MVM_AD_ENEMY_RETURNED ); + + PrecacheScriptSound( TF_INVADE_ENEMY_STOLEN ); + PrecacheScriptSound( TF_INVADE_ENEMY_DROPPED ); + PrecacheScriptSound( TF_INVADE_ENEMY_CAPTURED ); + PrecacheScriptSound( TF_INVADE_TEAM_STOLEN ); + PrecacheScriptSound( TF_INVADE_TEAM_DROPPED ); + PrecacheScriptSound( TF_INVADE_TEAM_CAPTURED ); + PrecacheScriptSound( TF_INVADE_FLAG_RETURNED ); + + PrecacheScriptSound( TF_RESOURCE_FLAGSPAWN ); + PrecacheScriptSound( TF_RESOURCE_ENEMY_STOLEN ); + PrecacheScriptSound( TF_RESOURCE_ENEMY_DROPPED ); + PrecacheScriptSound( TF_RESOURCE_ENEMY_CAPTURED ); + PrecacheScriptSound( TF_RESOURCE_TEAM_STOLEN ); + PrecacheScriptSound( TF_RESOURCE_TEAM_DROPPED ); + PrecacheScriptSound( TF_RESOURCE_TEAM_CAPTURED ); + PrecacheScriptSound( TF_RESOURCE_RETURNED ); + PrecacheScriptSound( TF_RESOURCE_EVENT_ENEMY_STOLEN ); + PrecacheScriptSound( TF_RESOURCE_EVENT_ENEMY_DROPPED ); + PrecacheScriptSound( TF_RESOURCE_EVENT_TEAM_STOLEN ); + PrecacheScriptSound( TF_RESOURCE_EVENT_TEAM_DROPPED ); + PrecacheScriptSound( TF_RESOURCE_EVENT_RETURNED ); + PrecacheScriptSound( TF_RESOURCE_EVENT_NAGS ); + PrecacheScriptSound( TF_RESOURCE_EVENT_RED_CAPPED ); + PrecacheScriptSound( TF_RESOURCE_EVENT_BLUE_CAPPED ); + + PrecacheScriptSound( TF_RD_ENEMY_STOLEN ); + PrecacheScriptSound( TF_RD_ENEMY_DROPPED ); + PrecacheScriptSound( TF_RD_ENEMY_CAPTURED ); + PrecacheScriptSound( TF_RD_ENEMY_RETURNED ); + PrecacheScriptSound( TF_RD_TEAM_STOLEN ); + PrecacheScriptSound( TF_RD_TEAM_DROPPED ); + PrecacheScriptSound( TF_RD_TEAM_CAPTURED ); + PrecacheScriptSound( TF_RD_TEAM_RETURNED ); + + PrecacheScriptSound( TF_RUNE_INTEL_CAPTURED ); + + PrecacheParticleSystem( GetPaperEffect() ); + + char szFileName[ MAX_PATH ]; + GetTrailEffect( TF_TEAM_BLUE, szFileName, sizeof( szFileName ) ); + PrecacheModel( szFileName ); + + GetTrailEffect( TF_TEAM_RED, szFileName, sizeof( szFileName ) ); + PrecacheModel( szFileName ); +} + +#ifdef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCaptureFlag::ShouldDraw() +{ + // don't draw flag on player in PD + if ( m_nType == TF_FLAGTYPE_PLAYER_DESTRUCTION ) + { + if ( GetMoveParent() && GetMoveParent()->IsPlayer() ) + { + return false; + } + } + + return BaseClass::ShouldDraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCaptureFlag::IsVisibleToTargetID() const +{ + return !IsDisabled() && GetPointValue() > 0 && const_cast<CCaptureFlag*>( this )->ShouldDraw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::OnPreDataChanged( DataUpdateType_t updateType ) +{ + m_nOldTeamNumber = GetTeamNumber(); + m_bOldGlowEnabled = m_bGlowEnabled; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::OnDataChanged( DataUpdateType_t updateType ) +{ + bool bUpdateGlow = false; + + if ( m_nOldTeamNumber != GetTeamNumber() ) + { + bUpdateGlow = true; + } + else if ( m_hOldOwner.Get() != GetOwnerEntity() ) + { + bUpdateGlow = true; + m_hOldOwner = GetOwnerEntity(); + } + else if ( m_bOldGlowEnabled != m_bGlowEnabled ) + { + bUpdateGlow = true; + } + + if ( bUpdateGlow ) + { + UpdateGlowEffect(); + } + + CreateSiren(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::CreateSiren( void ) +{ + if ( m_hSirenEffect ) + return; + + int iAttachment = GetBaseAnimating()->LookupAttachment( "siren" ); + if ( iAttachment != INVALID_PARTICLE_ATTACHMENT ) + { + const char* flashlightName = "cart_flashinglight"; + if ( GetTeamNumber() == TF_TEAM_RED ) + { + flashlightName = "cart_flashinglight_red"; + } + m_hSirenEffect = ParticleProp()->Create( flashlightName, PATTACH_POINT_FOLLOW, iAttachment ); + } +} + +void CCaptureFlag::DestroySiren( void ) +{ + if ( m_hSirenEffect ) + { + ParticleProp()->StopEmission( m_hSirenEffect ); + m_hSirenEffect = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::UpdateGlowEffect( void ) +{ + if ( !m_pGlowEffect ) + { + m_pGlowEffect = new CGlowObject( this, Vector( 0.76f, 0.76f, 0.76f ), 1.0, true ); + } + + if ( m_pGlowEffect ) + { + if ( ShouldHideGlowEffect() ) + { + m_pGlowEffect->SetEntity( NULL ); + } + else + { + m_pGlowEffect->SetEntity( this ); + + float r, g, b; + TeamplayRoundBasedRules()->GetTeamGlowColor( GetTeamNumber(), r, g, b ); + m_pGlowEffect->SetColor( Vector( r, g, b ) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCaptureFlag::ShouldHideGlowEffect( void ) +{ + if ( !IsGlowEnabled() ) + { + return true; + } + + if ( TFGameRules() && TFGameRules()->IsPowerupMode() ) + { + return false; + } + + if ( m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION && tf_rd_flag_ui_mode.GetInt() ) + return true; + + if ( m_nType == TF_FLAGTYPE_PLAYER_DESTRUCTION && CTFPlayerDestructionLogic::GetPlayerDestructionLogic() ) + { + C_TFPlayer *pOwner = ToTFPlayer( m_hPrevOwner ); + if ( !pOwner ) + return true; + + return CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->GetTeamLeader( pOwner->GetTeamNumber() ) != pOwner; + } + + // If the opposite team stole our intel we need to hide the glow + bool bIsHiddenTeam = false; + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer ) + { + if ( m_nType == TF_FLAGTYPE_CTF ) + { + // In CTF the flag is the team of where it was originally sitting + bIsHiddenTeam = ( pLocalPlayer->GetTeamNumber() == GetTeamNumber() ); + } + else + { + // In non-CTF control the flag changes to the team that's carrying it + bIsHiddenTeam = ( pLocalPlayer->GetTeamNumber() != TEAM_SPECTATOR && pLocalPlayer->GetTeamNumber() != GetTeamNumber() ); + } + + if ( pLocalPlayer->m_Shared.IsFullyInvisible() ) + { + C_TFPlayer *pOwner = ToTFPlayer( m_hPrevOwner ); + if ( pOwner && pOwner != pLocalPlayer ) + return true; + } + } + + bool bHide = IsStolen() && bIsHiddenTeam; + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + bHide = IsHome(); + } + + return ( IsDisabled() || bHide || IsEffectActive( EF_NODRAW ) ); +} +#endif //#ifdef CLIENT_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::Spawn( void ) +{ +#ifdef GAME_DLL + V_strncpy( m_szModel.GetForModify(), STRING( m_iszModel ), MAX_PATH ); + V_strncpy( m_szHudIcon.GetForModify(), STRING( m_iszHudIcon ), MAX_PATH ); + V_strncpy( m_szPaperEffect.GetForModify(), STRING( m_iszPaperEffect ), MAX_PATH ); + V_strncpy( m_szTrailEffect.GetForModify(), STRING( m_iszTrailEffect ), MAX_PATH ); +#endif + + // Precache the model and sounds. Set the flag model. + Precache(); + SetModel( GetFlagModel() ); + + // Set the flag solid and the size for touching. + SetSolid( SOLID_BBOX ); +#ifdef STAGING_ONLY + SetSolidFlags( FSOLID_TRIGGER ); + SetSize( vec3_origin, vec3_origin ); + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); +#else + SetSolidFlags( FSOLID_NOT_SOLID | FSOLID_TRIGGER ); + SetSize( vec3_origin, vec3_origin ); +#endif + + // Bloat the box for player pickup + CollisionProp()->UseTriggerBounds( true, 24 ); + + // use the initial dynamic prop "m_bStartDisabled" setting to set our own m_bDisabled flag +#ifdef GAME_DLL + m_bDisabled = m_bStartDisabled; + m_bStartDisabled = false; + m_bInstantTrailRemove = false; + m_flTimeToSetPoisonous = 0.f; + + // Don't allow the intelligence to fade. + m_flFadeScale = 0.0f; +#else + m_bDisabled = false; +#endif + + // Base class spawn. + BaseClass::Spawn(); + + // Force specific collision bounds! + // This is to prevent a case where the flag can fall through the world + // If the model's bounds reach outside the player's from player center + SetCollisionBounds( Vector( -19.5f, -22.5f, -6.5f ), Vector( 19.5f, 22.5f, 6.5f ) ); + +#ifdef GAME_DLL + // Save the starting position, so we can reset the flag later if need be. + m_vecResetPos = GetAbsOrigin(); + m_vecResetAng = GetAbsAngles(); + + CBaseEntity *pParent = GetParent(); + if ( pParent ) + { + m_hInitialParent = pParent; + m_vecOffset = GetAbsOrigin() - pParent->GetAbsOrigin(); + } + + SetFlagStatus( TF_FLAGINFO_HOME ); + ResetFlagReturnTime(); + ResetFlagNeutralTime(); + + m_hInitialPlayer = NULL; + + m_bAllowOwnerPickup = true; + m_hPrevOwner = NULL; + + m_bCaptured = false; + + // update the objective resource so clients have the information + if ( TFObjectiveResource() ) + { + TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 0 ); + TFObjectiveResource()->SetBaseMvMBombUpgradeTime( -1 ); + TFObjectiveResource()->SetNextMvMBombUpgradeTime( -1 ); + } + + const char* tags = STRING( m_iszTags ); + CSplitString splitTags( tags, " " ); + for ( int i=0; i<splitTags.Count(); ++i ) + { + m_tags.CopyAndAddToTail( splitTags[i] ); + } +#endif + + SetDisabled( m_bDisabled ); +} + +void CCaptureFlag::UpdateOnRemove( void ) +{ +#ifndef GAME_DLL + DestroySiren(); +#endif + + // This makes the player stop glowing + CTFPlayer *pOwnerPlayer = dynamic_cast< CTFPlayer * >( GetOwnerEntity() ); + if ( pOwnerPlayer ) + { + pOwnerPlayer->SetItem( NULL ); + } + + BaseClass::UpdateOnRemove(); +} + +#ifdef GAME_DLL + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::PlaySound( IRecipientFilter& filter, const char *pszString, int iTeam /*= TEAM_ANY */ ) +{ + // Note: iTeam parameter is only used for rate-limiting flag sounds based on team, and does not affect + // who the sound is targetted at; the filter parameter is the only thing that will affect who hears this. + + if ( TFGameRules()->IsMannVsMachineMode() ) + { + // Don't play bomb announcements unless we're at least 5 seconds into a wave in MVM + if ( !( TFGameRules()->State_Get() == GR_STATE_RND_RUNNING && gpGlobals->curtime - TFGameRules()->GetLastRoundStateChangeTime() >= 5.0f ) ) + { + return; + } + + // Only play the reset sound in MVM + // Other flag announcements are too noisy + if ( V_strcmp( pszString, TF_MVM_AD_ENEMY_RETURNED ) == 0 ) + { + EmitSound( filter, entindex(), pszString ); + } + } + else if ( TFGameRules()->IsPlayingSpecialDeliveryMode() && ( ( V_strcmp( pszString, TF_RESOURCE_TEAM_DROPPED ) == 0 ) || ( V_strcmp( pszString, TF_RESOURCE_EVENT_TEAM_DROPPED ) == 0 ) ) ) + { + // Rate limit certain flag sounds in Special Delivery + if ( iTeam == TEAM_ANY || gpGlobals->curtime >= m_flNextTeamSoundTime[iTeam] ) + { + EmitSound( filter, entindex(), pszString ); + + if ( iTeam != TEAM_ANY ) + { + m_flNextTeamSoundTime[iTeam] = gpGlobals->curtime + 20.0f; + } + } + } + else + { + EmitSound( filter, entindex(), pszString ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Gets the return time for first down mode, if enabled and supported, otherwise +// simply returns the passed in nReturnTime. +//----------------------------------------------------------------------------- +int CCaptureFlag::GetReturnTimeShotClockMode(int nStartReturnTime) +{ + int nReturnTime = nStartReturnTime; + + // Only enable this for specific modes. + if (IsFlagShotClockModePossible()) + { + if (m_bUseShotClockMode) + { + // When the game is in a standoff (both flags are stolen and poisonous), return when next dropped + // This makes it easier to resolve the standoff and continue the game + if ( TFGameRules() && TFGameRules()->IsPowerupMode() && TFGameRules()->PowerupModeFlagStandoffActive() ) + { + return 0; + } + + float flCreditTime = (gpGlobals->curtime - m_flLastPickupTime) * tf_flag_return_time_credit_factor.GetFloat() + + m_flLastResetDuration; + int nPossibleCreditTime = RoundFloatToInt(flCreditTime); + int nActualCreditTime = MAX(0, nPossibleCreditTime); + nReturnTime = MIN(nStartReturnTime, nActualCreditTime); + } + } + + return nReturnTime; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCaptureFlag &CCaptureFlag::operator=( const CCaptureFlag& rhs ) +{ + m_bDisabled = rhs.m_bDisabled; + m_nType = rhs.m_nType; + m_nReturnTime = rhs.m_nReturnTime; + m_nUseTrailEffect = rhs.m_nUseTrailEffect; + m_nNeutralType = rhs.m_nNeutralType; + m_nScoringType = rhs.m_nScoringType; + m_bReturnBetweenWaves = rhs.m_bReturnBetweenWaves; + m_bVisibleWhenDisabled = rhs.m_bVisibleWhenDisabled; + m_iszModel = rhs.m_iszModel; + m_iszHudIcon = rhs.m_iszHudIcon; + m_iszPaperEffect = rhs.m_iszPaperEffect; + m_iszTrailEffect = rhs.m_iszTrailEffect; + m_iszTags = rhs.m_iszTags; + m_nSkin = rhs.m_nSkin; + m_bUseShotClockMode = rhs.m_bUseShotClockMode; + + ChangeTeam( rhs.GetTeamNumber() ); + + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::Activate( void ) +{ + BaseClass::Activate(); + + m_iOriginalTeam = GetTeamNumber(); + m_nSkin = ( GetTeamNumber() == TEAM_UNASSIGNED ) ? 2 : (GetTeamNumber() - 2); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCaptureFlag* CCaptureFlag::Create( const Vector& vecOrigin, const char *pszModelName, ETFFlagType type ) +{ + CCaptureFlag *pFlag = static_cast< CCaptureFlag* >( CBaseEntity::CreateNoSpawn( "item_teamflag", vecOrigin, vec3_angle, NULL ) ); + pFlag->m_iszModel = MAKE_STRING( pszModelName ); + pFlag->m_nType = type; + + // don't show trail effect for PD flag + if ( type == TF_FLAGTYPE_PLAYER_DESTRUCTION ) + { + pFlag->m_nUseTrailEffect = FLAG_EFFECTS_NONE; + } + + DispatchSpawn( pFlag ); + + return pFlag; +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: Reset the flag position state. +//----------------------------------------------------------------------------- +void CCaptureFlag::Reset( void ) +{ +#ifdef GAME_DLL + m_bInstantTrailRemove = true; + RemoveFlagTrail(); + + if ( m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION && !IsDisabled() ) + { + if ( m_nPointValue > 0 ) + { + // Score points! + if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() ) + { + CTFRobotDestructionLogic::GetRobotDestructionLogic()->ScorePoints( GetTeamNumber() + , m_nPointValue.Get() + , SCORE_REACTOR_RETURNED + , NULL ); + + CTFRobotDestructionLogic::GetRobotDestructionLogic()->FlagDestroyed( GetTeamNumber() ); + m_nPointValue = 0; + } + } + + // Disable ourselves if our team currently has no points + SetDisabled( CTFRobotDestructionLogic::GetRobotDestructionLogic()->GetTargetScore( GetTeamNumber() ) < tf_rd_min_points_to_steal.GetInt() ); + } + else if ( m_nType == TF_FLAGTYPE_PLAYER_DESTRUCTION ) + { + UTIL_Remove( this ); + return; + } + + // Set the flag position. + if ( !m_hInitialParent.Get() ) + { + SetAbsOrigin( m_vecResetPos ); + SetParent( NULL ); + } + else + { + SetAbsOrigin( m_hInitialParent->GetAbsOrigin() + m_vecOffset ); + SetParent( m_hInitialParent.Get() ); + } + + SetAbsAngles( m_vecResetAng ); + + // No longer dropped, if it was. + SetFlagStatus( TF_FLAGINFO_HOME ); + ResetFlagReturnTime(); + ResetFlagNeutralTime(); + + m_hInitialPlayer = NULL; + + m_bAllowOwnerPickup = true; + m_hPrevOwner = NULL; + m_flTimeToSetPoisonous = 0.f; + + if ( m_nType == TF_FLAGTYPE_INVADE || m_nType == TF_FLAGTYPE_RESOURCE_CONTROL ) + { + ChangeTeam( m_iOriginalTeam ); + m_nSkin = ( GetTeamNumber() == TEAM_UNASSIGNED ) ? 2 : (GetTeamNumber() - 2); + } + + SetMoveType( MOVETYPE_NONE ); + + // update the objective resource so clients have the information + if ( TFObjectiveResource() ) + { + TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 0 ); + TFObjectiveResource()->SetBaseMvMBombUpgradeTime( -1 ); + TFObjectiveResource()->SetNextMvMBombUpgradeTime( -1 ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::ResetMessage( void ) +{ +#ifdef GAME_DLL + if ( m_nType == TF_FLAGTYPE_CTF ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam == GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_CTF_ENEMY_RETURNED ); + + TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_YOUR_FLAG_RETURNED ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_CTF_TEAM_RETURNED ); + + TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_ENEMY_FLAG_RETURNED ); + } + } + + IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_flag_event" ); + if ( event ) + { + event->SetInt( "eventtype", TF_FLAGEVENT_RETURNED ); + event->SetInt( "priority", 8 ); + event->SetInt( "team", GetTeamNumber() ); + gameeventmanager->FireEvent( event ); + } + + // Returned sound + CPASAttenuationFilter filter( this, TF_CTF_FLAGSPAWN ); + PlaySound( filter, TF_CTF_FLAGSPAWN ); + } + else if ( m_nType == TF_FLAGTYPE_ATTACK_DEFEND ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam == GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_AD_TEAM_RETURNED ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + if ( TFGameRules()->IsMannVsMachineMode() ) + { + PlaySound( filter, TF_MVM_AD_ENEMY_RETURNED ); + } + else + { + PlaySound( filter, TF_AD_ENEMY_RETURNED ); + } + } + } + } + else if ( m_nType == TF_FLAGTYPE_INVADE ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_INVADE_FLAG_RETURNED ); + } + } + else if ( m_nType == TF_FLAGTYPE_RESOURCE_CONTROL ) + { + const char *pszSound = TF_RESOURCE_RETURNED; + if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) ) + { + TFGameRules()->StartDoomsdayTicketsTimer(); + pszSound = TF_RESOURCE_EVENT_RETURNED; + } + + TFGameRules()->BroadcastSound( 255, pszSound ); + + IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_flag_event" ); + if ( event ) + { + event->SetInt( "eventtype", TF_FLAGEVENT_RETURNED ); + event->SetInt( "priority", 8 ); + event->SetInt( "team", GetTeamNumber() ); + gameeventmanager->FireEvent( event ); + } + + // Returned sound + CPASAttenuationFilter filter( this, TF_RESOURCE_FLAGSPAWN ); + PlaySound( filter, TF_RESOURCE_FLAGSPAWN ); + } + else if ( m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam == GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_RD_ENEMY_RETURNED ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_RD_TEAM_RETURNED ); + } + } + } + + // Output. + m_outputOnReturn.FireOutput( this, this ); + + DestroyReturnIcon(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::FlagTouch( CBaseEntity *pOther ) +{ + // Is the flag disabled or stolen already? + if ( IsDisabled() || IsStolen() ) + { + return; + } + + // The touch from a live player. + if ( !pOther->IsPlayer() || !pOther->IsAlive() ) + { + return; + } + +#ifdef GAME_DLL + // Don't let the person who threw this flag pick it up until it hits the ground. + // This way we can throw the flag to people, but not touch it as soon as we throw it ourselves + if( m_hPrevOwner.Get() && m_hPrevOwner.Get() == pOther && m_bAllowOwnerPickup == false ) + { + return; + } +#endif + + if ( pOther->GetTeamNumber() == GetTeamNumber() ) + { +#ifdef GAME_DLL + m_OnTouchSameTeam.FireOutput( this, this ); +#endif + + // Does my team own this flag? If so, no touch. + if ( m_nType == TF_FLAGTYPE_CTF || m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION ) + { + if ( !tf_flag_return_on_touch.GetBool() ) + return; + + if ( IsHome() || IsStolen() ) + return; + } + } + + if ( ( m_nType == TF_FLAGTYPE_ATTACK_DEFEND || m_nType == TF_FLAGTYPE_TERRITORY_CONTROL ) && + pOther->GetTeamNumber() != GetTeamNumber() ) + { + return; + } + + if ( ( m_nType == TF_FLAGTYPE_INVADE || m_nType == TF_FLAGTYPE_RESOURCE_CONTROL ) && ( GetTeamNumber() != TEAM_UNASSIGNED ) ) + { + if ( pOther->GetTeamNumber() != GetTeamNumber() ) + { + return; + } + } + + // Can't pickup flags during WaitingForPlayers + if ( TFGameRules()->IsInWaitingForPlayers() ) + return; + + // Get the touching player. + CTFPlayer *pPlayer = ToTFPlayer( pOther ); + if ( !pPlayer ) + { + return; + } + + if ( !pPlayer->IsAllowedToPickUpFlag() ) + { + return; + } + +#ifdef GAME_DLL + if ( TFGameRules()->IsMannVsMachineMode() ) + { + // skip all the restrictions and let bots pick up the flag + PickUp( pPlayer, true ); + return; + } +#endif + + // Is the touching player about to teleport? + if ( pPlayer->m_Shared.InCond( TF_COND_SELECTED_TO_TELEPORT ) ) + return; + + // Don't let invulnerable players pickup flags, except in PD + if ( pPlayer->m_Shared.IsInvulnerable() && m_nType != TF_FLAGTYPE_PLAYER_DESTRUCTION ) + return; + + // Don't let stealthed spies pickup the flag + if ( pPlayer->m_Shared.IsStealthed() || pPlayer->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) || pPlayer->m_Shared.GetPercentInvisible() > 0.25f ) + return; + + // Don't let phased scouts pickup flags + if ( pPlayer->m_Shared.InCond( TF_COND_PHASE ) ) + return; + + // Don't let players carry multiple flags for user-made maps with >1 + if ( pPlayer->HasTheFlag() && !( m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION || m_nType == TF_FLAGTYPE_PLAYER_DESTRUCTION ) && !tf_flag_return_on_touch.GetBool() ) + return; + +#ifdef GAME_DLL + if ( PointInRespawnRoom(pPlayer,pPlayer->WorldSpaceCenter()) ) + return; +#endif + + if ( IsDropped() && ( pOther->GetTeamNumber() == GetTeamNumber() ) && ( m_nType == TF_FLAGTYPE_CTF ) && tf_flag_return_on_touch.GetBool() ) + { + Reset(); + ResetMessage(); +#ifdef GAME_DLL + CTF_GameStats.Event_PlayerReturnedFlag( pPlayer ); +#endif + } + else + { + // Pick up the flag. + PickUp( pPlayer, true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::PickUp( CTFPlayer *pPlayer, bool bInvisible ) +{ + // Is the flag enabled? + if ( IsDisabled() ) + return; + + if ( !TFGameRules()->FlagsMayBeCapped() ) + return; + + // For maps/scenarios with multiple flags, only allow one flag per player + if ( TFGameRules()->IsMannVsMachineMode() ) + { + if ( pPlayer->HasTheFlag() ) + return; + } + +#ifdef GAME_DLL + if ( !m_bAllowOwnerPickup ) + { + if ( m_hPrevOwner.Get() && m_hPrevOwner.Get() == pPlayer ) + { + return; + } + } + + if ( TFGameRules()->IsMannVsMachineMode() && pPlayer->IsBot() ) + { + CTFBot *pBot = assert_cast< CTFBot* >( pPlayer ); + + if ( pBot->HasAttribute( CTFBot::IGNORE_FLAG ) ) + return; + + pBot->SetFlagTarget( this ); + } + + if ( m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION ) + { + if ( pPlayer->HasItem() && ( pPlayer->GetItem()->GetItemID() == TF_ITEM_CAPTURE_FLAG ) && pPlayer->GetItem() != this ) + { + CTFRobotDestructionLogic::GetRobotDestructionLogic()->FlagDestroyed( GetTeamNumber() ); + + // If the player who touched us is on the other team and is already carrying a flag, add our score + // onto the flag that they're carrying + CCaptureFlag* pOtherFlag = static_cast< CCaptureFlag * >( pPlayer->GetItem() ); + pOtherFlag->AddPointValue( m_nPointValue ); + UTIL_Remove( this ); + return; + } + } + else if ( m_nType == TF_FLAGTYPE_PLAYER_DESTRUCTION ) + { + if ( pPlayer->HasItem() && ( pPlayer->GetItem()->GetItemID() == TF_ITEM_CAPTURE_FLAG ) && pPlayer->GetItem() != this ) + { + // If the player who touched us is already carrying a flag, add our score + // onto the flag that they're carrying + CCaptureFlag* pOtherFlag = static_cast< CCaptureFlag * >( pPlayer->GetItem() ); + pOtherFlag->AddPointValue( m_nPointValue ); + UTIL_Remove( this ); + + if ( CTFPlayerDestructionLogic::GetPlayerDestructionLogic() ) + { + CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->CalcTeamLeader( pPlayer->GetTeamNumber() ); + CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->PlayPropPickupSound( pPlayer ); + } + + return; + } + } +#endif + + // Call into the base class pickup. + BaseClass::PickUp( pPlayer, false ); + + pPlayer->TeamFortress_SetSpeed(); + +#ifdef GAME_DLL + + // Update the parent to set the correct place on the model to attach the flag. + int iAttachment = pPlayer->LookupAttachment( "flag" ); + if( iAttachment > 0 ) + { + SetParent( pPlayer, iAttachment ); + SetLocalOrigin( vec3_origin ); + SetLocalAngles( vec3_angle ); + } + + // Remove the player's disguise if they're a spy, but not in PD + if ( pPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_SPY && m_nType != TF_FLAGTYPE_PLAYER_DESTRUCTION ) + { + if ( pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) || + pPlayer->m_Shared.InCond( TF_COND_DISGUISING )) + { + pPlayer->m_Shared.RemoveDisguise(); + } + } + + // switch to brighter picked up skin + m_nSkin = m_nSkin + TF_FLAG_NUMBEROFSKINS; + + // Remove the touch function. + SetTouch( NULL ); + + m_hPrevOwner = pPlayer; + m_bAllowOwnerPickup = true; + + if ( m_hInitialPlayer.Get() == NULL ) + { + m_hInitialPlayer = pPlayer; + } + + if ( m_nType == TF_FLAGTYPE_CTF ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_CTF_ENEMY_STOLEN ); + + TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_YOUR_FLAG_TAKEN ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_CTF_TEAM_STOLEN ); + + // exclude the guy who just picked it up + filter.RemoveRecipient( pPlayer ); + + TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_ENEMY_FLAG_TAKEN ); + } + } + } + else if ( m_nType == TF_FLAGTYPE_ATTACK_DEFEND ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + + if ( TFGameRules()->IsMannVsMachineMode() ) + { + PlaySound( filter, TF_MVM_AD_ENEMY_STOLEN ); + } + else + { + PlaySound( filter, TF_AD_ENEMY_STOLEN ); + } + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_AD_TEAM_STOLEN ); + } + } + } + else if ( m_nType == TF_FLAGTYPE_INVADE ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_INVADE_ENEMY_STOLEN ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_INVADE_TEAM_STOLEN ); + } + } + + // set the flag's team to match the player's team + ChangeTeam( pPlayer->GetTeamNumber() ); + m_nSkin = ( GetTeamNumber() == TEAM_UNASSIGNED ) ? 2 : (GetTeamNumber() - 2); + m_nSkin = m_nSkin + TF_FLAG_NUMBEROFSKINS; + } + else if ( m_nType == TF_FLAGTYPE_RESOURCE_CONTROL ) + { + // In Special delivery we only tell them about the very first flag pick up from neutral + if ( GetTeamNumber() == TEAM_UNASSIGNED ) + { + bool bEventMap = TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ); + if ( bEventMap ) + { + TFGameRules()->StopDoomsdayTicketsTimer(); + } + + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + const char *pszSound = TF_RESOURCE_ENEMY_STOLEN; + + if ( iTeam != pPlayer->GetTeamNumber() ) + { + if ( bEventMap ) + { + pszSound = TF_RESOURCE_EVENT_ENEMY_STOLEN; + } + } + else + { + pszSound = TF_RESOURCE_TEAM_STOLEN; + if ( bEventMap ) + { + pszSound = TF_RESOURCE_EVENT_TEAM_STOLEN; + } + } + + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, pszSound, iTeam ); + } + } + + // set the flag's team to match the player's team + ChangeTeam( pPlayer->GetTeamNumber() ); + m_nSkin = ( GetTeamNumber() == TEAM_UNASSIGNED ) ? 2 : (GetTeamNumber() - 2); + m_nSkin = m_nSkin + TF_FLAG_NUMBEROFSKINS; + } + else if ( m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_RD_ENEMY_STOLEN, iTeam ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_RD_TEAM_STOLEN, iTeam ); + } + } + } + else if ( m_nType == TF_FLAGTYPE_PLAYER_DESTRUCTION ) + { + if ( CTFPlayerDestructionLogic::GetPlayerDestructionLogic() ) + { + CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->CalcTeamLeader( pPlayer->GetTeamNumber() ); + CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->PlayPropPickupSound( pPlayer ); + } + } + + if ( TFGameRules() && TFGameRules()->IsPowerupMode() && m_flTimeToSetPoisonous == 0.f ) + { + // replace 90.f with a convar? + m_flTimeToSetPoisonous = gpGlobals->curtime + 90.f; + } + + // If the flag was at home, set the initial reset time to the max allowable time, otherwise it's to whatever it was + // right now so we can persist that until later. + if ( m_nFlagStatus == TF_FLAGINFO_HOME ) + { + m_flLastResetDuration = GetMaxReturnTime(); + } + else + { + m_flLastResetDuration = m_flResetTime - gpGlobals->curtime; + } + + // Remember that this is when the item was picked up. + m_flLastPickupTime = gpGlobals->curtime; + + int nOldFlagStatus = m_nFlagStatus; + SetFlagStatus( TF_FLAGINFO_STOLEN, pPlayer ); + ResetFlagReturnTime(); + ResetFlagNeutralTime(); + + IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_flag_event" ); + if ( event ) + { + event->SetInt( "player", pPlayer->entindex() ); + event->SetInt( "eventtype", TF_FLAGEVENT_PICKUP ); + event->SetInt( "priority", 8 ); + event->SetInt( "home", ( nOldFlagStatus == TF_FLAGINFO_HOME ) ? 1 : 0 ); + event->SetInt( "team", GetTeamNumber() ); + gameeventmanager->FireEvent( event ); + } + + pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_FLAGPICKUP ); + + // Output. + m_outputOnPickUp.FireOutput( this, this ); + + switch ( pPlayer->GetTeamNumber() ) + { + case TF_TEAM_RED: + m_outputOnPickUpTeam1.FireOutput( this, this ); + break; + case TF_TEAM_BLUE: + m_outputOnPickUpTeam2.FireOutput( this, this ); + break; + default: + break; + } + + DestroyReturnIcon(); + + StartFlagTrail(); + + HandleFlagPickedUpInDetectionZone( pPlayer ); + + // update the objective resource so clients have the information + if ( TFObjectiveResource() ) + { + TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 0 ); + TFObjectiveResource()->SetBaseMvMBombUpgradeTime( -1 ); + TFObjectiveResource()->SetNextMvMBombUpgradeTime( -1 ); + } + + if ( TFGameRules()->IsMannVsMachineMode() ) + { + if ( nOldFlagStatus == TF_FLAGINFO_HOME ) + { + if ( pPlayer->IsMiniBoss() ) + { + TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_GIANT_HAS_BOMB, TF_TEAM_PVE_DEFENDERS ); + } + else + { + TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_FIRST_BOMB_PICKUP, TF_TEAM_PVE_DEFENDERS ); + } + } + else + { + if ( pPlayer->IsMiniBoss() ) + { + TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_GIANT_HAS_BOMB, TF_TEAM_PVE_DEFENDERS ); + } + else + { + TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_BOMB_PICKUP, TF_TEAM_PVE_DEFENDERS ); + } + } + } + +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::Capture( CTFPlayer *pPlayer, int nCapturePoint ) +{ + // Is the flag enabled? + if ( IsDisabled() ) + return; + +#ifdef GAME_DLL + + if ( m_nType == TF_FLAGTYPE_CTF ) + { + bool bNotify = true; + + // don't play any sounds if this is going to win the round for one of the teams (victory sound will be played instead) + if ( tf_flag_caps_per_round.GetInt() > 0 ) + { + int nCaps = TFTeamMgr()->GetFlagCaptures( pPlayer->GetTeamNumber() ); + + if ( ( nCaps >= 0 ) && ( tf_flag_caps_per_round.GetInt() - nCaps <= 1 ) ) + { + // this cap is going to win, so don't play a sound + bNotify = false; + } + } + + if ( bNotify ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_CTF_ENEMY_CAPTURED ); + + TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_YOUR_FLAG_CAPTURED ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + + if ( TFGameRules() && TFGameRules()->IsPowerupMode() ) + { + PlaySound( filter, TF_RUNE_INTEL_CAPTURED ); + } + else + { + PlaySound( filter, TF_CTF_TEAM_CAPTURED ); + } + + TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_ENEMY_FLAG_CAPTURED ); + } + } + + if ( TFGameRules() ) + { + TFGameRules()->HandleCTFCaptureBonus( pPlayer->GetTeamNumber() ); + } + } + + // Reward the player + CTF_GameStats.Event_PlayerCapturedPoint( pPlayer ); + + int nAmount = TFGameRules()->CalculateCurrencyAmount_ByType( TF_CURRENCY_CAPTURED_OBJECTIVE ); +#ifdef STAGING_ONLY + if ( TFGameRules()->GameModeUsesExperience() ) + { + pPlayer->AddExperiencePoints( nAmount ); + } +#endif // STAGING_ONLY + TFGameRules()->DistributeCurrencyAmount( nAmount, pPlayer ); + + // if someone else stole the flag, give them credit, too + if ( m_hInitialPlayer.Get() && m_hInitialPlayer.Get() != pPlayer ) + { + CTF_GameStats.Event_PlayerCapturedPoint( ToTFPlayer( m_hInitialPlayer.Get() ) ); + m_hInitialPlayer = NULL; + } + + // Reward the team + if ( tf_flag_caps_per_round.GetInt() > 0 ) + { + TFTeamMgr()->IncrementFlagCaptures( pPlayer->GetTeamNumber() ); + } + else + { + TFTeamMgr()->AddTeamScore( pPlayer->GetTeamNumber(), TF_CTF_CAPTURED_TEAM_SCORE ); + } + } + else if ( m_nType == TF_FLAGTYPE_ATTACK_DEFEND ) + { + char szNumber[64]; + Q_snprintf( szNumber, sizeof(szNumber), "%d", nCapturePoint ); + + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + + if ( TFGameRules()->IsMannVsMachineMode() ) + { + PlaySound( filter, TF_MVM_AD_ENEMY_CAPTURED ); + } + else + { + PlaySound( filter, TF_AD_ENEMY_CAPTURED ); + } + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_AD_TEAM_CAPTURED ); + } + } + + // Capture sound + CBroadcastRecipientFilter filter; + PlaySound( filter, TF_AD_CAPTURED_SOUND ); + + // Reward the player + CTF_GameStats.Event_PlayerCapturedPoint( pPlayer ); + + // TFTODO:: Reward the team + } + else if ( m_nType == TF_FLAGTYPE_INVADE ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_INVADE_ENEMY_CAPTURED ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_INVADE_TEAM_CAPTURED ); + } + } + + // Reward the player + CTF_GameStats.Event_PlayerCapturedPoint( pPlayer ); + + // Reward the team + if ( m_nScoringType == INVADE_SCORING_TEAM_CAPTURE_COUNT ) + { + if ( tf_flag_caps_per_round.GetInt() > 0 ) + { + TFTeamMgr()->IncrementFlagCaptures( pPlayer->GetTeamNumber() ); + } + else + { + TFTeamMgr()->AddTeamScore( pPlayer->GetTeamNumber(), TF_INVADE_CAPTURED_TEAM_SCORE ); + } + } + else + { + TFTeamMgr()->AddTeamScore( pPlayer->GetTeamNumber(), TF_INVADE_CAPTURED_TEAM_SCORE ); + } + } + else if ( m_nType == TF_FLAGTYPE_RESOURCE_CONTROL ) + { + if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) ) + { + TFGameRules()->BroadcastSound( 255, ( pPlayer->GetTeamNumber() == TF_TEAM_RED ) ? TF_RESOURCE_EVENT_RED_CAPPED : TF_RESOURCE_EVENT_BLUE_CAPPED ); + } + else + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + const char *pszSound = TF_RESOURCE_ENEMY_CAPTURED; + if ( iTeam == pPlayer->GetTeamNumber() ) + { + pszSound = TF_RESOURCE_TEAM_CAPTURED; + } + + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, pszSound, iTeam ); + } + } + + // Reward the player + CTF_GameStats.Event_PlayerCapturedPoint( pPlayer ); + + // if someone else stole the flag, give them credit, too + if ( m_hInitialPlayer.Get() && m_hInitialPlayer.Get() != pPlayer ) + { + CTF_GameStats.Event_PlayerCapturedPoint( ToTFPlayer( m_hInitialPlayer.Get() ) ); + m_hInitialPlayer = NULL; + } + + // Reward the team + if ( tf_flag_caps_per_round.GetInt() > 0 ) + { + TFTeamMgr()->IncrementFlagCaptures( pPlayer->GetTeamNumber() ); + } + else + { + TFTeamMgr()->AddTeamScore( pPlayer->GetTeamNumber(), TF_RESOURCE_CAPTURED_TEAM_SCORE ); + } + } + else if ( m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_RD_ENEMY_CAPTURED, iTeam ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_RD_TEAM_CAPTURED, iTeam ); + } + } + + // Score points! + if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() ) + { + CTFRobotDestructionLogic::GetRobotDestructionLogic()->ScorePoints( pPlayer->GetTeamNumber() + , m_nPointValue.Get() + , SCORE_REACTOR_CAPTURED + , pPlayer ); + CTFRobotDestructionLogic::GetRobotDestructionLogic()->FlagDestroyed( GetTeamNumber() ); + m_nPointValue = 0; + } + } + + if ( IsPoisonous() ) + { + pPlayer->m_Shared.RemoveCond( TF_COND_MARKEDFORDEATH ); + } + + IGameEvent *event = gameeventmanager->CreateEvent( "teamplay_flag_event" ); + if ( event ) + { + event->SetInt( "player", pPlayer->entindex() ); + event->SetInt( "eventtype", TF_FLAGEVENT_CAPTURE ); + event->SetInt( "priority", 9 ); + event->SetInt( "team", GetTeamNumber() ); + gameeventmanager->FireEvent( event ); + } + + SetFlagStatus( TF_FLAGINFO_HOME ); + ResetFlagReturnTime(); + ResetFlagNeutralTime(); + + m_bInstantTrailRemove = true; + RemoveFlagTrail(); + m_nSkin = ( GetTeamNumber() == TEAM_UNASSIGNED ) ? 2 : (GetTeamNumber() - 2); + + HandleFlagCapturedInDetectionZone( pPlayer ); + HandleFlagDroppedInDetectionZone( pPlayer ); + + // Reset the flag. + BaseClass::Drop( pPlayer, true ); + + Reset(); + + pPlayer->TeamFortress_SetSpeed(); + + if ( !TFGameRules() || !TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) ) + { + pPlayer->SpeakConceptIfAllowed( MP_CONCEPT_FLAGCAPTURED ); + } + + // Outputs + m_outputOnCapture.FireOutput( this, this ); + + switch ( pPlayer->GetTeamNumber() ) + { + case TF_TEAM_RED: + m_OnCapTeam1.FireOutput( this, this ); + break; + case TF_TEAM_BLUE: + m_OnCapTeam2.FireOutput( this, this ); + break; + default: + break; + } + + m_bCaptured = true; + SetNextThink( gpGlobals->curtime + TF_FLAG_THINK_TIME ); + + if ( TFGameRules()->InStalemate() ) + { + // whoever capped the flag is the winner, give them enough caps to win + CTFTeam *pTeam = pPlayer->GetTFTeam(); + if ( !pTeam ) + return; + + // if we still need more caps to trigger a win, give them to us + if ( pTeam->GetFlagCaptures() < tf_flag_caps_per_round.GetInt() ) + { + pTeam->SetFlagCaptures( tf_flag_caps_per_round.GetInt() ); + } + } + +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: A player drops the flag. +//----------------------------------------------------------------------------- +void CCaptureFlag::Drop( CTFPlayer *pPlayer, bool bVisible, bool bThrown /*= false*/, bool bMessage /*= true*/ ) +{ + // Is the flag enabled? + if ( IsDisabled() ) + return; + + // Call into the base class drop. + BaseClass::Drop( pPlayer, bVisible ); + + pPlayer->TeamFortress_SetSpeed(); + +#ifdef GAME_DLL + + if ( bThrown ) + { + m_bAllowOwnerPickup = false; + m_flOwnerPickupTime = gpGlobals->curtime + TF_FLAG_OWNER_PICKUP_TIME; + } + + // Drop from the player's center so we can guarantee that it is in a valid spot + Vector vecStart = pPlayer->WorldSpaceCenter(); + Vector vecEnd = vecStart; + vecEnd.z -= 8000.0f; + trace_t trace; + UTIL_TraceHull( vecStart, vecEnd, WorldAlignMins(), WorldAlignMaxs(), MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &trace ); + + if ( trace.startsolid ) + { + DevWarning( "Dropped flag trace started solid!\nWiggle around each axis to find a safer fit!\n" ); + + const float fMultipliers[ 3 ] = { 0.0f, 1.0f, -1.0f }; + + // Wiggle it around on each axis to find a safe place + for ( int z = 0; z < ARRAYSIZE( fMultipliers ) && trace.startsolid; z++ ) + { + for ( int y = 0; y < ARRAYSIZE( fMultipliers ) && trace.startsolid; y++ ) + { + for ( int x = 0; x < ARRAYSIZE( fMultipliers ) && trace.startsolid; x++ ) + { + vecStart = pPlayer->WorldSpaceCenter(); + vecStart += Vector( fMultipliers[ x ] * 10.0f, fMultipliers[ y ] * 10.0f, fMultipliers[ z ] * 10.0f ); + + vecEnd = vecStart; + vecEnd.z -= 8000.0f; + UTIL_TraceHull( vecStart, vecEnd, WorldAlignMins(), WorldAlignMaxs(), MASK_SOLID, this, COLLISION_GROUP_DEBRIS, &trace ); + } + } + } + } + + if ( trace.startsolid ) + { + // Couldn't find a good spot... just leave it in the center of where the player died + AssertMsg( 0, "Couldn't find a safe place to drop the flag!\n" ); + DevWarning( "Couldn't find a safe place to drop the flag!\nDropping at the player's center!\n" ); + + SetAbsOrigin( pPlayer->WorldSpaceCenter() ); + } + else + { + // Found a good spot for it + SetAbsOrigin( trace.endpos ); + + // If it lands on an elevator, parent it to the elevator + if ( trace.m_pEnt && trace.m_pEnt->GetMoveType() == MOVETYPE_PUSH ) + { + SetParent( trace.m_pEnt ); + } + } + + // ensure the bomb drops somewhere the bots can reach it in MvM mode + if ( TFGameRules()->IsMannVsMachineMode() ) + { + Vector bombPos = GetAbsOrigin(); + + CTFNavArea *bombArea = (CTFNavArea *)TheNavMesh->GetNavArea( bombPos, 99999.9f ); + + bool isBombInBadPlace = false; + + if ( bombArea ) + { + if ( bombArea->HasAttributeTF( TF_NAV_BOMB_CAN_DROP_HERE ) ) + { + float height = bombArea->GetZ( bombPos ); + + if ( height > HalfHumanHeight ) + { + isBombInBadPlace = true; + } + } + else + { + // Bomb not allowed in this nav area + isBombInBadPlace = true; + } + } + else + { + // Bomb is off the mesh + isBombInBadPlace = true; + } + + if ( isBombInBadPlace ) + { + // the bomb has dropped in an invalid spot - move it to a nearby valid area + const float searchRange = 500.0f; + + Extent nearExtent; + nearExtent.lo = bombPos; + nearExtent.lo.x -= searchRange; + nearExtent.lo.y -= searchRange; + nearExtent.lo.z = MIN_COORD_FLOAT; // make sure we catch all areas under flag, even it is way up in the air for some reason + + nearExtent.hi = bombPos; + nearExtent.hi.x += searchRange; + nearExtent.hi.y += searchRange; + nearExtent.hi.z += searchRange; + + CUtlVector< CTFNavArea * > nearAreaVector; + + TheNavMesh->CollectAreasOverlappingExtent< CTFNavArea >( nearExtent, &nearAreaVector ); + + CTFNavArea *nearValidArea = NULL; + float nearRangeSq = FLT_MAX; + Vector nearSpot; + + for( int i=0; i<nearAreaVector.Count(); ++i ) + { + CTFNavArea *area = nearAreaVector[i]; + + if ( area->HasAttributeTF( TF_NAV_BOMB_CAN_DROP_HERE ) ) + { + area->GetClosestPointOnArea( bombPos, &nearSpot ); + + float rangeSq = ( nearSpot - bombPos ).LengthSqr(); + + if ( rangeSq < nearRangeSq ) + { + nearRangeSq = rangeSq; + nearValidArea = area; + } + } + } + + + if ( nearValidArea ) + { + nearValidArea->GetClosestPointOnArea( bombPos, &bombPos ); + bombPos.z += 5.0f; + + SetAbsOrigin( bombPos ); + } + } + } + + if ( m_nType == TF_FLAGTYPE_CTF ) + { + if ( bMessage ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_CTF_ENEMY_DROPPED ); + + TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_YOUR_FLAG_DROPPED ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_CTF_TEAM_DROPPED ); + + TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_ENEMY_FLAG_DROPPED ); + } + } + } + } + else if ( m_nType == TF_FLAGTYPE_INVADE ) + { + if ( bMessage ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_INVADE_ENEMY_DROPPED ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_INVADE_TEAM_DROPPED ); + } + } + } + + if ( m_nNeutralType == INVADE_NEUTRAL_TYPE_HALF ) + { + SetFlagNeutralIn( (float)GetMaxReturnTime() / 2.0 ); + } + else if ( m_nNeutralType == INVADE_NEUTRAL_TYPE_DEFAULT ) + { + // if our return time is less than the neutral time, we don't need a neutral time + if ( TF_INVADE_NEUTRAL_TIME < GetMaxReturnTime() ) + { + SetFlagNeutralIn( TF_INVADE_NEUTRAL_TIME ); + } + } + } + else if ( m_nType == TF_FLAGTYPE_ATTACK_DEFEND ) + { + if ( bMessage ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + if ( iTeam != pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + + if ( TFGameRules()->IsMannVsMachineMode() ) + { + PlaySound( filter, TF_MVM_AD_ENEMY_DROPPED ); + } + else + { + PlaySound( filter, TF_AD_ENEMY_DROPPED ); + } + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_AD_TEAM_DROPPED ); + } + } + } + } + else if ( m_nType == TF_FLAGTYPE_RESOURCE_CONTROL ) + { + if ( bMessage ) + { + const char *pszSound = TF_RESOURCE_TEAM_DROPPED; + if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) ) + { + pszSound = TF_RESOURCE_EVENT_TEAM_DROPPED; + } + + // We only care about our own team dropping it in Special Delivery + int iTeam = pPlayer->GetTeamNumber(); + + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, pszSound, iTeam ); + } + } + else if ( m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION ) + { + // If a player dropped this flag but the flag has less than the min to steal + // we just return the flag rather than have it exist on the ground + if ( GetPointValue() < tf_rd_min_points_to_steal.GetInt() ) + { + ResetFlag(); + return; + } + else if ( bMessage ) + { + for ( int iTeam = TF_TEAM_RED; iTeam < TF_TEAM_COUNT; ++iTeam ) + { + // We only care about our own team dropping it in Special Delivery + if ( iTeam == pPlayer->GetTeamNumber() ) + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_RD_TEAM_DROPPED, iTeam ); + } + else + { + CTeamRecipientFilter filter( iTeam, true ); + PlaySound( filter, TF_RD_ENEMY_DROPPED, iTeam ); + } + } + } + } + else if ( m_nType == TF_FLAGTYPE_PLAYER_DESTRUCTION ) + { + if ( CTFPlayerDestructionLogic::GetPlayerDestructionLogic() ) + { + CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->CalcTeamLeader( pPlayer->GetTeamNumber() ); + CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->PlayPropDropSound( pPlayer ); + } + } + + if ( IsPoisonous() ) + { + pPlayer->m_Shared.RemoveCond( TF_COND_MARKEDFORDEATH ); + } + + m_nSkin = m_nSkin - TF_FLAG_NUMBEROFSKINS; + + RemoveFlagTrail(); + + int nMaxReturnTime = GetMaxReturnTime(); + SetFlagReturnIn( GetReturnTime( nMaxReturnTime ), nMaxReturnTime ); + + // Reset the flag's angles. + SetAbsAngles( m_vecResetAng ); + + // Reset the touch function. + SetTouch( &CCaptureFlag::FlagTouch ); + + SetFlagStatus( TF_FLAGINFO_DROPPED ); + + // Output. + m_outputOnDrop.FireOutput( this, this ); + + if ( !TFGameRules()->IsMannVsMachineMode() || ( GetMaxReturnTime() < 600 ) ) + { + CreateReturnIcon(); + } + + // did we get dropped in a func_respawnflag zone? + if ( PointInRespawnFlagZone( GetAbsOrigin() ) == true ) + { + Reset(); + ResetMessage(); + } + + HandleFlagDroppedInDetectionZone( pPlayer ); + + // update the objective resource so clients have the information + if ( TFObjectiveResource() ) + { + TFObjectiveResource()->SetFlagCarrierUpgradeLevel( 0 ); + TFObjectiveResource()->SetBaseMvMBombUpgradeTime( -1 ); + TFObjectiveResource()->SetNextMvMBombUpgradeTime( -1 ); + } + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_MVM_BOMB_DROPPED, TF_TEAM_PVE_DEFENDERS ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCaptureFlag::IsDropped( void ) +{ + return ( m_nFlagStatus == TF_FLAGINFO_DROPPED ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCaptureFlag::IsHome( void ) +{ + return ( m_nFlagStatus == TF_FLAGINFO_HOME ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCaptureFlag::IsStolen( void ) +{ + return ( m_nFlagStatus == TF_FLAGINFO_STOLEN ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCaptureFlag::IsDisabled( void ) const +{ + return m_bDisabled; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::SetDisabled( bool bDisabled ) +{ + m_bDisabled = bDisabled; + + if ( bDisabled ) + { + if ( m_bVisibleWhenDisabled ) + { + SetRenderMode( kRenderTransAlpha ); + SetRenderColorA( 180 ); + RemoveEffects( EF_NODRAW ); + } + else + { + AddEffects( EF_NODRAW ); + } + + SetTouch( NULL ); + SetThink( NULL ); + } + else + { + RemoveEffects( EF_NODRAW ); + SetRenderMode( kRenderNormal ); + SetRenderColorA( 255 ); + + // The flag in RD is not actually touched by players when it's home + SetTouch( &CCaptureFlag::FlagTouch ); + + + SetThink( &CCaptureFlag::Think ); + SetNextThink( gpGlobals->curtime ); + +#ifdef GAME_DLL + if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) && ( GetTeamNumber() == TEAM_UNASSIGNED ) ) + { + TFGameRules()->StartDoomsdayTicketsTimer(); + } +#endif + } + +#ifdef CLIENT_DLL + UpdateGlowEffect(); +#endif +} + +void CCaptureFlag::SetVisibleWhenDisabled( bool bVisible ) +{ + m_bVisibleWhenDisabled = bVisible; + SetDisabled( IsDisabled() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the flag status +//----------------------------------------------------------------------------- +void CCaptureFlag::SetFlagStatus( int iStatus, CBasePlayer *pNewOwner /*= NULL*/ ) +{ +#ifdef GAME_DLL + MDLCACHE_CRITICAL_SECTION(); +#endif + + if ( m_nFlagStatus != iStatus ) + { + m_nFlagStatus = iStatus; + + IGameEvent *pEvent = gameeventmanager->CreateEvent( "flagstatus_update" ); + if ( pEvent ) + { +#ifdef GAME_DLL + pEvent->SetInt( "userid", pNewOwner ? pNewOwner->GetUserID() : -1 ); + pEvent->SetInt( "entindex", entindex() ); +#endif + gameeventmanager->FireEvent( pEvent ); + } + } + +#ifdef CLIENT_DLL + UpdateGlowEffect(); +#endif + +#ifdef GAME_DLL + switch ( m_nFlagStatus ) + { + case TF_FLAGINFO_HOME: + case TF_FLAGINFO_DROPPED: + ResetSequence( LookupSequence("spin") ); // set spin animation if it's not being held + break; + case TF_FLAGINFO_STOLEN: + ResetSequence( LookupSequence("idle") ); // set idle animation if it is being held + break; + default: + AssertOnce( false ); // invalid stats + break; + } +#endif +} + +//----------------------------------------------------------------------------------------------- +// GAME DLL Functions +//----------------------------------------------------------------------------------------------- +#ifdef GAME_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::Think( void ) +{ + // Is the flag enabled? + if ( IsDisabled() ) + return; + + if ( !TFGameRules()->FlagsMayBeCapped() ) + { + SetNextThink( gpGlobals->curtime + TF_FLAG_THINK_TIME ); + return; + } + + if ( m_bCaptured ) + { + m_bCaptured = false; + SetTouch( &CCaptureFlag::FlagTouch ); + } + + if ( IsDropped() ) + { + if ( !m_bAllowOwnerPickup ) + { + if ( m_flOwnerPickupTime && gpGlobals->curtime > m_flOwnerPickupTime ) + { + m_bAllowOwnerPickup = true; + } + } + + if ( TFGameRules()->IsMannVsMachineMode() && m_bReturnBetweenWaves ) + { + if ( TFGameRules()->InSetup() || ( TFObjectiveResource() && TFObjectiveResource()->GetMannVsMachineIsBetweenWaves() ) ) + { + Reset(); + } + else if ( g_pPopulationManager && g_pPopulationManager->IsInEndlessWaves() && g_pPopulationManager->EndlessShouldResetFlag() ) + { + Reset(); + g_pPopulationManager->EndlessFlagHasReset(); + ResetMessage(); + } + } + + if ( m_nType == TF_FLAGTYPE_INVADE ) + { + if ( m_flResetTime && gpGlobals->curtime > m_flResetTime ) + { + Reset(); + ResetMessage(); + } + else if ( m_flNeutralTime && gpGlobals->curtime > m_flNeutralTime ) + { + // reset the team to the original team setting (when it spawned) + ChangeTeam( m_iOriginalTeam ); + m_nSkin = ( GetTeamNumber() == TEAM_UNASSIGNED ) ? 2 : (GetTeamNumber() - 2); + + ResetFlagNeutralTime(); + } + } + else + { + if ( m_flResetTime && gpGlobals->curtime > m_flResetTime ) + { + Reset(); + ResetMessage(); + } + } + } + else if ( IsStolen() && m_hPrevOwner ) + { + CBasePlayer *pPlayer = ToBasePlayer( m_hPrevOwner ); + if ( pPlayer ) + { + pPlayer->SetLastObjectiveTime( gpGlobals->curtime ); + } + } + + if ( m_flResetTime && gpGlobals->curtime > m_flResetTime ) + { + DestroyReturnIcon(); + } + + CTFPlayer *pPlayer = ToTFPlayer( GetPrevOwner() ); + if ( pPlayer ) + { + bool bRunning; + float flSpeed = pPlayer->MaxSpeed(); + flSpeed *= flSpeed; + if ( pPlayer->GetAbsVelocity().LengthSqr() >= (flSpeed* 0.1f) ) + { + bRunning = true; + } + else + { + bRunning = false; + } + + if ( !bRunning && m_pFlagTrail ) + { + RemoveFlagTrail(); + } + else if ( bRunning && !m_pFlagTrail ) + { + StartFlagTrail(); + } + } + + if ( m_nType == TF_FLAGTYPE_RESOURCE_CONTROL ) + { + if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_DOOMSDAY ) ) + { + if ( TFGameRules()->DoomsdayTicketTimerElapsed() ) + { + if ( CTFMinigameLogic::GetMinigameLogic() && CTFMinigameLogic::GetMinigameLogic()->GetActiveMinigame() ) + { + // we've started playing a minigame so just cancel the timer + TFGameRules()->StopDoomsdayTicketsTimer(); + } + else + { + TFGameRules()->StartDoomsdayTicketsTimer(); // start the timer again + TFGameRules()->BroadcastSound( 255, TF_RESOURCE_EVENT_NAGS ); + } + } + } + } + + if ( IsStolen() && TFGameRules() && TFGameRules()->IsPowerupMode() && IsPoisonous() && !pPlayer->m_Shared.InCond( TF_COND_MARKEDFORDEATH ) ) + { + pPlayer->m_Shared.AddCond( TF_COND_MARKEDFORDEATH ); + } + + SetNextThink( gpGlobals->curtime + TF_FLAG_THINK_TIME ); +} + +void CCaptureFlag::CreateReturnIcon( void ) +{ + if ( m_hReturnIcon.Get() ) + return; + + CBaseEntity *pReturnIcon = CBaseEntity::Create( "item_teamflag_return_icon", GetAbsOrigin() + Vector(0,0,cl_flag_return_height.GetFloat()), vec3_angle, this ); + if ( pReturnIcon ) + { + m_hReturnIcon = pReturnIcon; + m_hReturnIcon->SetParent( this ); + } +} + +void CCaptureFlag::DestroyReturnIcon( void ) +{ + if ( !m_hReturnIcon.Get() ) + return; + + UTIL_Remove( m_hReturnIcon ); + m_hReturnIcon = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::InputEnable( inputdata_t &inputdata ) +{ + SetDisabled( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::InputDisable( inputdata_t &inputdata ) +{ + SetDisabled( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::InputRoundActivate( inputdata_t &inputdata ) +{ + CTFPlayer *pPlayer = ToTFPlayer( m_hPrevOwner.Get() ); + + // If the player has a capture flag, drop it. + if ( pPlayer && pPlayer->HasItem() && ( pPlayer->GetItem() == this ) ) + { + Drop( pPlayer, true, false, false ); + } + + Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::InputForceDrop( inputdata_t &inputdata ) +{ + CTFPlayer *pPlayer = ToTFPlayer( m_hPrevOwner.Get() ); + + // If the player has a capture flag, drop it. + if ( pPlayer && pPlayer->HasItem() && ( pPlayer->GetItem() == this ) ) + { + pPlayer->DropFlag(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::InternalForceReset( bool bSilent /* = false */ ) +{ + if ( IsHome() ) + return; + + CTFPlayer *pPlayer = ToTFPlayer( m_hPrevOwner.Get() ); + + // If the player has a capture flag, drop it. + if ( pPlayer && pPlayer->HasItem() && ( pPlayer->GetItem() == this ) ) + { + pPlayer->DropFlag( bSilent ); + } + + if ( !bSilent ) + { + ResetFlag(); + } + else + { + Reset(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::InputForceReset( inputdata_t &inputdata ) +{ + InternalForceReset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::InputForceResetSilent( inputdata_t &inputdata ) +{ + InternalForceReset( true ); +} + +void CCaptureFlag::InputForceResetAndDisableSilent( inputdata_t &inputdata ) +{ + InternalForceReset( true ); + SetDisabled( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::InputSetReturnTime( inputdata_t &inputdata ) +{ + int nReturnTime = inputdata.value.Int(); + m_nReturnTime = ( nReturnTime >= 0 ) ? nReturnTime : 0; + + if ( IsDropped() ) + { + // do we currently have a neutral time? + if ( m_flNeutralTime > 0 ) + { + if ( m_nNeutralType == INVADE_NEUTRAL_TYPE_HALF ) + { + SetFlagNeutralIn( (float)m_nReturnTime / 2.0 ); + } + else if ( m_nNeutralType == INVADE_NEUTRAL_TYPE_DEFAULT ) + { + // if our return time is less than the neutral time, we don't need a neutral time + if ( TF_INVADE_NEUTRAL_TIME < m_nReturnTime ) + { + SetFlagNeutralIn( TF_INVADE_NEUTRAL_TIME ); + } + } + } + + SetFlagReturnIn( m_nReturnTime ); + } +} + +void CCaptureFlag::InputShowTimer( inputdata_t &inputdata ) +{ + int nReturnTime = inputdata.value.Int(); + m_nReturnTime = ( nReturnTime >= 0 ) ? nReturnTime : 0; + + SetFlagReturnIn( m_nReturnTime ); + + CreateReturnIcon(); +} + +void CCaptureFlag::InputForceGlowDisabled( inputdata_t &inputdata ) +{ + int nState = inputdata.value.Int(); + SetGlowEnabled( nState == 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Always transmitted to clients +//----------------------------------------------------------------------------- +int CCaptureFlag::UpdateTransmitState() +{ + // ALWAYS transmit to all clients. + return SetTransmitState( FL_EDICT_ALWAYS ); +} + + +#else + +float CCaptureFlag::GetReturnProgress() +{ + float flEventTime = MAX( m_flResetTime.m_Value, m_flNeutralTime.m_Value ); + + return ( 1.0 - ( ( flEventTime - gpGlobals->curtime ) / m_flMaxResetTime ) ); +} + + +void CCaptureFlag::Simulate( void ) +{ + BaseClass::Simulate(); + + ManageTrailEffects(); + + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( m_hPrevOwner && m_hPrevOwner->IsPlayer() && pLocalPlayer && pLocalPlayer->m_Shared.IsFullyInvisible() && !IsEffectActive( EF_NODRAW ) ) + { + C_TFPlayer *pTFOwner = ToTFPlayer( m_hPrevOwner ); + if ( pTFOwner && pTFOwner != pLocalPlayer ) + { + AddEffects( EF_NODRAW ); + } + } + else if ( IsEffectActive( EF_NODRAW ) && ( IsStolen() || IsDropped() ) ) + { + RemoveEffects( EF_NODRAW ); + } +} + +void CCaptureFlag::ManageTrailEffects( void ) +{ + if ( ( m_nUseTrailEffect == FLAG_EFFECTS_NONE ) || ( m_nUseTrailEffect == FLAG_EFFECTS_COLORONLY ) ) + return; + + if ( m_nFlagStatus == TF_FLAGINFO_STOLEN ) + { + if ( GetPrevOwner() ) + { + CTFPlayer *pPlayer = ToTFPlayer( GetPrevOwner() ); + + if ( pPlayer ) + { + if ( pPlayer->GetAbsVelocity().Length() >= pPlayer->MaxSpeed() * 0.2f ) + { + if ( m_pPaperTrailEffect == NULL ) + { + if ( !( TFGameRules() && TFGameRules()->IsPVEModeActive() ) ) + { + m_pPaperTrailEffect = ParticleProp()->Create( GetPaperEffect(), PATTACH_ABSORIGIN_FOLLOW ); + } + } + } + else + { + if ( m_pPaperTrailEffect ) + { + ParticleProp()->StopEmission( m_pPaperTrailEffect ); + m_pPaperTrailEffect = NULL; + } + } + } + } + + } + + else + { + if ( m_pPaperTrailEffect ) + { + ParticleProp()->StopEmission( m_pPaperTrailEffect ); + m_pPaperTrailEffect = NULL; + } + } +} + + +#endif + + +LINK_ENTITY_TO_CLASS( item_teamflag_return_icon, CCaptureFlagReturnIcon ); + +IMPLEMENT_NETWORKCLASS_ALIASED( CaptureFlagReturnIcon, DT_CaptureFlagReturnIcon ) + +BEGIN_NETWORK_TABLE( CCaptureFlagReturnIcon, DT_CaptureFlagReturnIcon ) +END_NETWORK_TABLE() + +CCaptureFlagReturnIcon::CCaptureFlagReturnIcon() +{ +#ifdef CLIENT_DLL + m_pReturnProgressMaterial_Empty = NULL; + m_pReturnProgressMaterial_Full = NULL; +#endif +} + +#ifdef GAME_DLL + +void CCaptureFlagReturnIcon::Spawn( void ) +{ + BaseClass::Spawn(); + + UTIL_SetSize( this, Vector(-8,-8,-8), Vector(8,8,8) ); + + CollisionProp()->SetCollisionBounds( Vector( -50, -50, -50 ), Vector( 50, 50, 50 ) ); +} + +int CCaptureFlagReturnIcon::UpdateTransmitState( void ) +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +//----------------------------------------------------------------------------- +// Purpose: Start the flag trail +//----------------------------------------------------------------------------- +void CCaptureFlag::StartFlagTrail( void ) +{ + if ( ( m_nUseTrailEffect == FLAG_EFFECTS_NONE ) || ( m_nUseTrailEffect == FLAG_EFFECTS_PAPERONLY ) ) + return; + + if ( m_pFlagTrail ) + return; + + CTFPlayer *pPlayer = ToTFPlayer( GetPrevOwner() ); + + if ( pPlayer ) + { + if ( !m_pFlagTrail ) + { + char szTrailTeamName[ MAX_PATH ]; + GetTrailEffect( pPlayer->GetTeamNumber(), szTrailTeamName, sizeof( szTrailTeamName ) ); + + CSpriteTrail *pTempTrail = NULL; + + pTempTrail = CSpriteTrail::SpriteTrailCreate( szTrailTeamName, GetAbsOrigin(), true ); + pTempTrail->SetTransmit( false ); + pTempTrail->FollowEntity( this ); + pTempTrail->SetTransparency( kRenderTransAlpha, 255, 255, 255, TF_FLAG_TRAIL_ALPHA, kRenderFxNone ); + pTempTrail->SetStartWidth( 32 ); + pTempTrail->SetTextureResolution( 1.0f / ( 96.0f * 1.0f ) ); + pTempTrail->SetLifeTime( 0.70 ); + pTempTrail->TurnOn(); + pTempTrail->SetAttachment( this, 0 ); + m_pFlagTrail = pTempTrail; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Fade and kill the trail +//----------------------------------------------------------------------------- +void CCaptureFlag::RemoveFlagTrail( void ) +{ + if ( !m_pFlagTrail ) + return; + + if (m_pFlagTrail) + { + if (m_flFlagTrailLife <= 0 || m_bInstantTrailRemove == true ) + { + UTIL_Remove( m_pFlagTrail); + m_flFlagTrailLife = 1.0f; + } + else + { + float fAlpha = TF_FLAG_TRAIL_ALPHA * m_flFlagTrailLife; + + CSpriteTrail *pTempTrail = dynamic_cast< CSpriteTrail*>( m_pFlagTrail.Get() ); + + if ( pTempTrail ) + { + pTempTrail->SetBrightness( int(fAlpha) ); + } + + m_flFlagTrailLife = m_flFlagTrailLife - 0.1f; + SetContextThink( &CCaptureFlag::RemoveFlagTrail, gpGlobals->curtime + 0.05, "FadeFlagTrail"); + } + } + m_bInstantTrailRemove = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::AddFollower( CTFBot* pBot ) +{ + if ( !m_followers.HasElement( pBot ) ) + { + m_followers.AddToTail( pBot ); + for ( int i=0; i<m_tags.Count(); ++i ) + { + pBot->AddTag( m_tags[i] ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::RemoveFollower( CTFBot* pBot ) +{ + int index = m_followers.Find( pBot ); + if ( index != m_followers.InvalidIndex() ) + { + m_followers.Remove( index ); + for ( int i=0; i<m_tags.Count(); ++i ) + { + pBot->RemoveTag( m_tags[i] ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CCaptureFlag::GetReturnTime( int nMaxReturnTime ) +{ + return GetReturnTimeShotClockMode( nMaxReturnTime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CCaptureFlag::GetMaxReturnTime( void ) +{ + int nReturnTime = m_nReturnTime; + +#ifdef STAGING_ONLY + if (tf_flag_return_time_override.GetInt() > 0) + { + nReturnTime = tf_flag_return_time_override.GetInt(); + } +#endif // STAGING_ONLY + + if ( m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION ) + { + int nMaxPoints = 300; + if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() ) + { + nMaxPoints = CTFRobotDestructionLogic::GetRobotDestructionLogic()->GetMaxPoints(); + } + const int nMaxReturnTimePoints = nMaxPoints / 3; + nReturnTime = RemapValClamped(m_nPointValue, 0.f, nMaxReturnTimePoints, tf_rd_return_min_time.GetFloat(), tf_rd_return_max_time.GetFloat()); + } + else if ( m_nType == TF_FLAGTYPE_PLAYER_DESTRUCTION ) + { + if ( CTFPlayerDestructionLogic::GetPlayerDestructionLogic() ) + { + nReturnTime = CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->GetFlagResetDelay(); + } + } + + return nReturnTime; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlag::AddPointValue( int nPoints ) +{ + m_nPointValue += nPoints; + + if ( m_nType == TF_FLAGTYPE_ROBOT_DESTRUCTION || m_nType == TF_FLAGTYPE_PLAYER_DESTRUCTION ) + { + IGameEvent *pEvent = gameeventmanager->CreateEvent( "flagstatus_update" ); + if ( pEvent ) + { + pEvent->SetInt( "userid", m_hPrevOwner && m_hPrevOwner->IsPlayer() ? ToBasePlayer( m_hPrevOwner )->GetUserID() : -1 ); + pEvent->SetInt( "entindex", entindex() ); + gameeventmanager->FireEvent( pEvent ); + + // The return time is determined by how many points are in the flag, so update that. + m_flLastResetDuration = GetMaxReturnTime(); + } + + if ( nPoints > 0 ) + { + pEvent = gameeventmanager->CreateEvent( "teamplay_flag_event" ); + if ( pEvent ) + { + pEvent->SetInt( "player", m_hPrevOwner && m_hPrevOwner->IsPlayer() ? ToBasePlayer( m_hPrevOwner )->entindex() : -1 ); + pEvent->SetInt( "eventtype", TF_FLAGEVENT_PICKUP ); + gameeventmanager->FireEvent( pEvent ); + } + } + } +} +#endif // GAME_DLL + +#ifdef CLIENT_DLL + +typedef struct +{ + float maxProgress; + + float vert1x; + float vert1y; + float vert2x; + float vert2y; + + int swipe_dir_x; + int swipe_dir_y; +} progress_segment_t; + + +// This defines the properties of the 8 circle segments +// in the circular progress bar. +progress_segment_t Segments[8] = +{ + { 0.125, 0.5, 0.0, 1.0, 0.0, 1, 0 }, + { 0.25, 1.0, 0.0, 1.0, 0.5, 0, 1 }, + { 0.375, 1.0, 0.5, 1.0, 1.0, 0, 1 }, + { 0.50, 1.0, 1.0, 0.5, 1.0, -1, 0 }, + { 0.625, 0.5, 1.0, 0.0, 1.0, -1, 0 }, + { 0.75, 0.0, 1.0, 0.0, 0.5, 0, -1 }, + { 0.875, 0.0, 0.5, 0.0, 0.0, 0, -1 }, + { 1.0, 0.0, 0.0, 0.5, 0.0, 1, 0 }, +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +RenderGroup_t CCaptureFlagReturnIcon::GetRenderGroup( void ) +{ + return RENDER_GROUP_TRANSLUCENT_ENTITY; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCaptureFlagReturnIcon::GetRenderBounds( Vector& theMins, Vector& theMaxs ) +{ + theMins.Init( -20, -20, -20 ); + theMaxs.Init( 20, 20, 20 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CCaptureFlagReturnIcon::DrawModel( int flags ) +{ + int nRetVal = BaseClass::DrawModel( flags ); + + DrawReturnProgressBar(); + + return nRetVal; +} + +//----------------------------------------------------------------------------- +// Purpose: Draw progress bar above the flag indicating when it will return +//----------------------------------------------------------------------------- +void CCaptureFlagReturnIcon::DrawReturnProgressBar( void ) +{ + CCaptureFlag *pFlag = dynamic_cast< CCaptureFlag * > ( GetOwnerEntity() ); + + if ( !pFlag ) + return; + + // Don't draw if this flag is not going to reset + if ( pFlag->GetMaxResetTime() <= 0 ) + return; + + if ( !TFGameRules()->FlagsMayBeCapped() ) + return; + + if ( !m_pReturnProgressMaterial_Full ) + { + m_pReturnProgressMaterial_Full = materials->FindMaterial( "VGUI/flagtime_full", TEXTURE_GROUP_VGUI ); + } + + if ( !m_pReturnProgressMaterial_Empty ) + { + m_pReturnProgressMaterial_Empty = materials->FindMaterial( "VGUI/flagtime_empty", TEXTURE_GROUP_VGUI ); + } + + if ( !m_pReturnProgressMaterial_Full || !m_pReturnProgressMaterial_Empty ) + { + return; + } + + CMatRenderContextPtr pRenderContext( materials ); + + Vector vOrigin = GetAbsOrigin(); + QAngle vAngle = vec3_angle; + + // Align it towards the viewer + Vector vUp = CurrentViewUp(); + Vector vRight = CurrentViewRight(); + if ( fabs( vRight.z ) > 0.95 ) // don't draw it edge-on + return; + + vRight.z = 0; + VectorNormalize( vRight ); + + float flSize = cl_flag_return_size.GetFloat(); + + unsigned char ubColor[4]; + ubColor[3] = 255; + + switch( pFlag->GetTeamNumber() ) + { + case TF_TEAM_RED: + ubColor[0] = 255; + ubColor[1] = 0; + ubColor[2] = 0; + break; + case TF_TEAM_BLUE: + ubColor[0] = 0; + ubColor[1] = 0; + ubColor[2] = 255; + break; + default: + ubColor[0] = 200; + ubColor[1] = 200; + ubColor[2] = 200; + break; + } + + // First we draw a quad of a complete icon, background + CMeshBuilder meshBuilder; + + pRenderContext->Bind( m_pReturnProgressMaterial_Empty ); + IMesh *pMesh = pRenderContext->GetDynamicMesh(); + + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + meshBuilder.Color4ubv( ubColor ); + meshBuilder.TexCoord2f( 0,0,0 ); + meshBuilder.Position3fv( (vOrigin + (vRight * -flSize) + (vUp * flSize)).Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ubv( ubColor ); + meshBuilder.TexCoord2f( 0,1,0 ); + meshBuilder.Position3fv( (vOrigin + (vRight * flSize) + (vUp * flSize)).Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ubv( ubColor ); + meshBuilder.TexCoord2f( 0,1,1 ); + meshBuilder.Position3fv( (vOrigin + (vRight * flSize) + (vUp * -flSize)).Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ubv( ubColor ); + meshBuilder.TexCoord2f( 0,0,1 ); + meshBuilder.Position3fv( (vOrigin + (vRight * -flSize) + (vUp * -flSize)).Base() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + + pMesh->Draw(); + + float flProgress = pFlag->GetReturnProgress(); + + pRenderContext->Bind( m_pReturnProgressMaterial_Full ); + pMesh = pRenderContext->GetDynamicMesh(); + + vRight *= flSize * 2; + vUp *= flSize * -2; + + // Next we're drawing the circular progress bar, in 8 segments + // For each segment, we calculate the vertex position that will draw + // the slice. + int i; + for ( i=0;i<8;i++ ) + { + if ( flProgress < Segments[i].maxProgress ) + { + CMeshBuilder meshBuilder_Full; + + meshBuilder_Full.Begin( pMesh, MATERIAL_TRIANGLES, 3 ); + + // vert 0 is ( 0.5, 0.5 ) + meshBuilder_Full.Color4ubv( ubColor ); + meshBuilder_Full.TexCoord2f( 0, 0.5, 0.5 ); + meshBuilder_Full.Position3fv( vOrigin.Base() ); + meshBuilder_Full.AdvanceVertex(); + + // Internal progress is the progress through this particular slice + float internalProgress = RemapVal( flProgress, Segments[i].maxProgress - 0.125, Segments[i].maxProgress, 0.0, 1.0 ); + internalProgress = clamp( internalProgress, 0.0f, 1.0f ); + + // Calculate the x,y of the moving vertex based on internal progress + float swipe_x = Segments[i].vert2x - ( 1.0 - internalProgress ) * 0.5 * Segments[i].swipe_dir_x; + float swipe_y = Segments[i].vert2y - ( 1.0 - internalProgress ) * 0.5 * Segments[i].swipe_dir_y; + + // vert 1 is calculated from progress + meshBuilder_Full.Color4ubv( ubColor ); + meshBuilder_Full.TexCoord2f( 0, swipe_x, swipe_y ); + meshBuilder_Full.Position3fv( (vOrigin + (vRight * ( swipe_x - 0.5 ) ) + (vUp *( swipe_y - 0.5 ) ) ).Base() ); + meshBuilder_Full.AdvanceVertex(); + + // vert 2 is ( Segments[i].vert1x, Segments[i].vert1y ) + meshBuilder_Full.Color4ubv( ubColor ); + meshBuilder_Full.TexCoord2f( 0, Segments[i].vert2x, Segments[i].vert2y ); + meshBuilder_Full.Position3fv( (vOrigin + (vRight * ( Segments[i].vert2x - 0.5 ) ) + (vUp *( Segments[i].vert2y - 0.5 ) ) ).Base() ); + meshBuilder_Full.AdvanceVertex(); + + meshBuilder_Full.End(); + + pMesh->Draw(); + } + } +} + +#endif |