//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: CTF Flag Capture Zone. // //=============================================================================// #include "cbase.h" #include "func_capture_zone.h" #include "tf_player.h" #include "tf_item.h" #include "tf_team.h" #include "tf_gamerules.h" #include "entity_capture_flag.h" #include "tf_logic_player_destruction.h" //============================================================================= // // CTF Flag Capture Zone tables. // BEGIN_DATADESC( CCaptureZone ) // Keyfields. DEFINE_KEYFIELD( m_nCapturePoint, FIELD_INTEGER, "CapturePoint" ), DEFINE_KEYFIELD( m_flCaptureDelay, FIELD_FLOAT, "capture_delay" ), DEFINE_KEYFIELD( m_flCaptureDelayOffset, FIELD_FLOAT, "capture_delay_offset" ), DEFINE_KEYFIELD( m_bShouldBlock, FIELD_BOOLEAN, "shouldBlock" ), // Functions. DEFINE_FUNCTION( CCaptureZoneShim::Touch ), // Inputs. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), // Outputs. DEFINE_OUTPUT( m_outputOnCapture, "OnCapture" ), DEFINE_OUTPUT( m_OnCapTeam1, "OnCapTeam1" ), DEFINE_OUTPUT( m_OnCapTeam2, "OnCapTeam2" ), DEFINE_OUTPUT( m_OnCapTeam1_PD, "OnCapTeam1_PD" ), DEFINE_OUTPUT( m_OnCapTeam2_PD, "OnCapTeam2_PD" ), END_DATADESC() LINK_ENTITY_TO_CLASS( func_capturezone, CCaptureZone ); IMPLEMENT_SERVERCLASS_ST( CCaptureZone, DT_CaptureZone ) SendPropBool( SENDINFO( m_bDisabled ) ), END_SEND_TABLE() IMPLEMENT_AUTO_LIST( ICaptureZoneAutoList ); //============================================================================= // // CTF Flag Capture Zone functions. // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CCaptureZone::CCaptureZone() { m_bShouldBlock = true; m_flCaptureDelay = 1.1f; m_flCaptureDelayOffset = 0.025f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCaptureZone::Spawn() { InitTrigger(); SetTouch( &CCaptureZoneShim::Touch ); if ( m_bDisabled ) { SetDisabled( true ); } m_flNextTouchingEnemyZoneWarning = -1; AddSpawnFlags( SF_TRIGGER_ALLOW_ALL ); // so we can keep track of who is touching us } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCaptureZone::Activate( void ) { BaseClass::Activate(); if ( TFGameRules() && ( TFGameRules()->GetGameType() == TF_GAMETYPE_PD ) ) { SetThink( &CCaptureZone::PlayerDestructionThink ); SetNextThink( gpGlobals->curtime + 0.1 ); } else { SetThink( NULL ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCaptureZone::PlayerDestructionThink( void ) { SetNextThink( gpGlobals->curtime + 0.1 ); if ( !IsDisabled() ) { bool bRedInZone = false; bool bBlueInZone = false; // nothing to do while no-one is touching us if ( m_hTouchingEntities.Count() == 0 ) return; // loop through the touching players to figure out the teams involved for ( int i = 0; i < m_hTouchingEntities.Count(); i++ ) { CBaseEntity *pEnt = m_hTouchingEntities[i]; if ( pEnt && pEnt->IsPlayer() ) { CTFPlayer *pTFPlayer = ToTFPlayer( pEnt ); if ( pTFPlayer && pTFPlayer->IsAlive() ) { if ( pTFPlayer->GetTeamNumber() == TF_TEAM_RED ) { bRedInZone = true; } else if ( pTFPlayer->GetTeamNumber() == TF_TEAM_BLUE ) { bBlueInZone = true; } } } } // safety check, but this should have already been caught by the ( m_hTouchingEntities.Count() == 0 ) check above if ( !bRedInZone && !bBlueInZone ) return; if ( m_bShouldBlock ) { // both teams are touching the zone and they block each other if ( bRedInZone && bBlueInZone ) return; } CUtlVector< CTFPlayer* > playerVector; CollectPlayers( &playerVector, TF_TEAM_RED ); CollectPlayers( &playerVector, TF_TEAM_BLUE, false, APPEND_PLAYERS ); float flCaptureDelay = m_flCaptureDelay - ( m_flCaptureDelayOffset * playerVector.Count() ); // let's see if anyone has any player destruction points to capture for ( int i = 0; i < m_hTouchingEntities.Count(); i++ ) { CBaseEntity *pEnt = m_hTouchingEntities[i]; if ( pEnt && pEnt->IsPlayer() ) { CTFPlayer *pTFPlayer = ToTFPlayer( pEnt ); if ( pTFPlayer && pTFPlayer->IsAlive() ) { // does this capture point have a team number assigned? if ( ( GetTeamNumber() != TEAM_UNASSIGNED ) && ( pTFPlayer->GetTeamNumber() != GetTeamNumber() ) ) continue; if ( pTFPlayer->HasTheFlag() && pTFPlayer->CanScorePointForPD() ) { CCaptureFlag *pFlag = dynamic_cast< CCaptureFlag* >( pTFPlayer->GetItem() ); int nPoints = pFlag->GetPointValue(); if ( nPoints > 0 ) { // decrease the number of points pFlag->AddPointValue( -1 ); // fire the output switch ( pTFPlayer->GetTeamNumber() ) { case TF_TEAM_RED: m_OnCapTeam1_PD.FireOutput( this, this ); break; case TF_TEAM_BLUE: m_OnCapTeam2_PD.FireOutput( this, this ); break; default: break; } IGameEvent *event = gameeventmanager->CreateEvent( "special_score" ); if ( event ) { event->SetInt( "player", pTFPlayer->entindex() ); gameeventmanager->FireEvent( event ); } } // remove this flag if this was the last point if ( pFlag->GetPointValue() == 0 ) { UTIL_Remove( pFlag ); } if ( CTFPlayerDestructionLogic::GetPlayerDestructionLogic() ) { CTFPlayerDestructionLogic::GetPlayerDestructionLogic()->CalcTeamLeader( pTFPlayer->GetTeamNumber() ); } pTFPlayer->SetNextScorePointForPD( gpGlobals->curtime + flCaptureDelay ); } } } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCaptureZone::ShimTouch( CBaseEntity *pOther ) { // Is the zone enabled? if ( IsDisabled() ) return; // Get the TF player. CTFPlayer *pPlayer = ToTFPlayer( pOther ); if ( pPlayer ) { // Check to see if the player has the capture flag. if ( pPlayer->HasItem() && ( pPlayer->GetItem()->GetItemID() == TF_ITEM_CAPTURE_FLAG ) ) { CCaptureFlag *pFlag = dynamic_cast( pPlayer->GetItem() ); if ( pFlag ) { // we have a special think that will handle the player destruction flags if ( pFlag->GetType() == TF_FLAGTYPE_PLAYER_DESTRUCTION ) return; if ( !pFlag->IsCaptured() ) { // does this capture point have a team number assigned? if ( GetTeamNumber() != TEAM_UNASSIGNED ) { // Check to see if the capture zone team matches the player's team. if ( pPlayer->GetTeamNumber() != TEAM_UNASSIGNED && pPlayer->GetTeamNumber() != GetTeamNumber() ) { if ( pFlag->GetType() == TF_FLAGTYPE_CTF ) { // Do this at most once every 5 seconds if ( m_flNextTouchingEnemyZoneWarning < gpGlobals->curtime ) { CSingleUserRecipientFilter filter( pPlayer ); TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_TOUCHING_ENEMY_CTF_CAP ); m_flNextTouchingEnemyZoneWarning = gpGlobals->curtime + 5; } } // else if ( pFlag->GetGameType() == TF_FLAGTYPE_INVADE ) // { // } return; } } } // in MvM, the "flag" is the bomb and is captured when the carrying bot deploys it if ( TFGameRules()->FlagsMayBeCapped() && !TFGameRules()->IsMannVsMachineMode() ) { Capture( pOther ); } } } } } void CCaptureZone::Capture( CBaseEntity *pOther ) { CTFPlayer *pPlayer = ToTFPlayer( pOther ); if ( pPlayer ) { // Check to see if the player has the capture flag and flag is allowed to be captured. if ( pPlayer->HasItem() && ( pPlayer->GetItem()->GetItemID() == TF_ITEM_CAPTURE_FLAG ) && TFGameRules()->CanFlagBeCaptured( pOther ) ) { CCaptureFlag *pFlag = dynamic_cast< CCaptureFlag* >( pPlayer->GetItem() ); if ( !pFlag ) return; // we have a special think that will handle the player destruction flags if ( pFlag->GetType() == TF_FLAGTYPE_PLAYER_DESTRUCTION ) return; if ( !pFlag->IsCaptured() ) { pFlag->Capture( pPlayer, m_nCapturePoint ); // 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; } IGameEvent *event = gameeventmanager->CreateEvent( "ctf_flag_captured" ); if ( event ) { int iCappingTeam = pPlayer->GetTeamNumber(); int iCappingTeamScore = 0; CTFTeam* pCappingTeam = pPlayer->GetTFTeam(); if ( pCappingTeam ) { iCappingTeamScore = pCappingTeam->GetFlagCaptures(); } event->SetInt( "capping_team", iCappingTeam ); event->SetInt( "capping_team_score", iCappingTeamScore ); event->SetInt( "capper", pPlayer->GetUserID() ); event->SetInt( "priority", 9 ); // HLTV priority gameeventmanager->FireEvent( event ); } if ( TFGameRules() ) { if ( TFGameRules()->IsHolidayActive( kHoliday_EOTL ) ) { TFGameRules()->DropBonusDuck( pPlayer->GetAbsOrigin(), pPlayer, NULL, NULL, false, true ); } else if ( TFGameRules()->IsHolidayActive( kHoliday_Halloween ) ) { TFGameRules()->DropHalloweenSoulPackToTeam( 5, GetAbsOrigin(), pPlayer->GetTeamNumber(), TEAM_SPECTATOR ); } } } } else if ( !TFGameRules()->CanFlagBeCaptured( pOther ) && TFGameRules()->IsPowerupMode() ) { ClientPrint( pPlayer, HUD_PRINTCENTER, "Cannot capture - your flag is not at base!" ); } } } //----------------------------------------------------------------------------- // Purpose: The timer is always transmitted to clients //----------------------------------------------------------------------------- int CCaptureZone::UpdateTransmitState() { // ALWAYS transmit to all clients. return SetTransmitState( FL_EDICT_ALWAYS ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CCaptureZone::IsDisabled( void ) { return m_bDisabled; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCaptureZone::InputEnable( inputdata_t &inputdata ) { SetDisabled( false ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCaptureZone::InputDisable( inputdata_t &inputdata ) { SetDisabled( true ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CCaptureZone::SetDisabled( bool bDisabled ) { m_bDisabled.Set( bDisabled ); if ( bDisabled ) { BaseClass::Disable(); SetTouch( NULL ); } else { BaseClass::Enable(); SetTouch( &CCaptureZoneShim::Touch ); } } //============================================================================= // // Flag Detection Zone tables. // BEGIN_DATADESC( CFlagDetectionZone ) DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), DEFINE_KEYFIELD( m_bShouldAlarm, FIELD_BOOLEAN, "alarm" ), // Inputs. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), DEFINE_INPUTFUNC( FIELD_VOID, "Test", InputTest ), // Outputs. DEFINE_OUTPUT( m_outputOnStartTouchFlag, "OnStartTouchFlag" ), DEFINE_OUTPUT( m_outputOnEndTouchFlag, "OnEndTouchFlag" ), DEFINE_OUTPUT( m_outputOnDroppedFlag, "OnDroppedFlag" ), DEFINE_OUTPUT( m_outputOnPickedUpFlag, "OnPickedUpFlag" ), END_DATADESC() LINK_ENTITY_TO_CLASS( func_flagdetectionzone, CFlagDetectionZone ); IMPLEMENT_AUTO_LIST( IFlagDetectionZoneAutoList ); //============================================================================= // // Flag Detection Zone functions. // CFlagDetectionZone::CFlagDetectionZone() { m_bShouldAlarm = false; } void CFlagDetectionZone::Spawn() { InitTrigger(); if ( m_bDisabled ) { SetDisabled( true ); } } void CFlagDetectionZone::StartTouch( CBaseEntity *pOther ) { // Is the zone enabled? if ( IsDisabled() ) return; if ( pOther->IsPlayer() ) { EHANDLE hOther; hOther = pOther; if ( m_hTouchingPlayers.Find( hOther ) == m_hTouchingPlayers.InvalidIndex() ) { m_hTouchingPlayers.AddToTail( hOther ); } } if ( EntityIsFlagCarrier( pOther ) ) { EHANDLE hOther; hOther = pOther; bool bAdded = false; if ( m_hTouchingEntities.Find( hOther ) == m_hTouchingEntities.InvalidIndex() ) { m_hTouchingEntities.AddToTail( hOther ); bAdded = true; } m_OnStartTouch.FireOutput( pOther, this ); if ( bAdded && ( m_hTouchingEntities.Count() == 1 ) ) { // First entity to touch us that passes our filters m_outputOnStartTouchFlag.FireOutput( pOther, this ); m_OnStartTouchAll.FireOutput( pOther, this ); } IGameEvent *event = gameeventmanager->CreateEvent( "flag_carried_in_detection_zone" ); if ( event ) { gameeventmanager->FireEvent( event ); } } } void CFlagDetectionZone::EndTouch( CBaseEntity *pOther ) { // Is the zone enabled? if ( IsDisabled() ) return; if ( pOther->IsPlayer() ) { EHANDLE hOther; hOther = pOther; m_hTouchingPlayers.FindAndRemove( hOther ); } if ( IsTouching( pOther ) ) { EHANDLE hOther; hOther = pOther; m_hTouchingEntities.FindAndRemove( hOther ); m_OnEndTouch.FireOutput(pOther, this); // If there are no more entities touching this trigger, fire the lost all touches // Loop through the touching entities backwards. Clean out old ones, and look for existing bool bFoundOtherTouchee = false; int iSize = m_hTouchingEntities.Count(); for ( int i = iSize-1; i >= 0; i-- ) { EHANDLE hOther; hOther = m_hTouchingEntities[i]; if ( !hOther ) { m_hTouchingEntities.Remove( i ); } else if ( hOther->IsPlayer() && !hOther->IsAlive() ) { #ifdef STAGING_ONLY AssertMsg( false, "Dead player [%s] is still touching this trigger at [%f %f %f]", hOther->GetEntityName().ToCStr(), XYZ( hOther->GetAbsOrigin() ) ); Warning( "Dead player [%s] is still touching this trigger at [%f %f %f]", hOther->GetEntityName().ToCStr(), XYZ( hOther->GetAbsOrigin() ) ); #endif m_hTouchingEntities.Remove( i ); } else { bFoundOtherTouchee = true; } } //FIXME: Without this, triggers fire their EndTouch outputs when they are disabled! // Didn't find one? if ( !bFoundOtherTouchee /*&& !m_bDisabled*/ ) { m_outputOnEndTouchFlag.FireOutput( this, this ); m_OnEndTouchAll.FireOutput( pOther, this); } } } void CFlagDetectionZone::InputEnable( inputdata_t &inputdata ) { SetDisabled( false ); } void CFlagDetectionZone::InputDisable( inputdata_t &inputdata ) { SetDisabled( true ); } void CFlagDetectionZone::InputTest( inputdata_t &inputdata ) { // Loop through the touching entities backwards. Clean out old ones, and look for existing int iSize = m_hTouchingEntities.Count(); for ( int i = iSize-1; i >= 0; i-- ) { EHANDLE hOther; hOther = m_hTouchingEntities[i]; if ( !hOther ) { m_hTouchingEntities.Remove( i ); } else if ( hOther->IsPlayer() && !hOther->IsAlive() ) { #ifdef STAGING_ONLY AssertMsg( false, "Dead player [%s] is still touching this trigger at [%f %f %f]", hOther->GetEntityName().ToCStr(), XYZ( hOther->GetAbsOrigin() ) ); Warning( "Dead player [%s] is still touching this trigger at [%f %f %f]", hOther->GetEntityName().ToCStr(), XYZ( hOther->GetAbsOrigin() ) ); #endif m_hTouchingEntities.Remove( i ); } } if ( m_hTouchingEntities.Count() ) { m_OnStartTouch.FireOutput( m_hTouchingEntities[ 0 ], this ); m_outputOnStartTouchFlag.FireOutput( this, this ); } else { m_outputOnEndTouchFlag.FireOutput( this, this ); m_OnEndTouchAll.FireOutput( this, this ); } } void CFlagDetectionZone::SetDisabled( bool bDisabled ) { m_bDisabled = bDisabled; if ( bDisabled ) { BaseClass::Disable(); SetTouch( NULL ); } else { BaseClass::Enable(); } } void CFlagDetectionZone::FlagDropped( CBasePlayer *pPlayer ) { EHANDLE hOther; hOther = pPlayer; if ( m_hTouchingEntities.Find( hOther ) != m_hTouchingEntities.InvalidIndex() ) { m_outputOnDroppedFlag.FireOutput( pPlayer, this ); EndTouch( pPlayer ); // Still touching as a non-carrierz m_hTouchingPlayers.AddToTail( hOther ); } } void CFlagDetectionZone::FlagPickedUp( CBasePlayer *pPlayer ) { EHANDLE hOther; hOther = pPlayer; if ( m_hTouchingPlayers.Find( hOther ) != m_hTouchingPlayers.InvalidIndex() ) { m_outputOnPickedUpFlag.FireOutput( pPlayer, this ); StartTouch( pPlayer ); } } bool CFlagDetectionZone::EntityIsFlagCarrier( CBaseEntity *pEntity ) { // Get the TF player. CTFPlayer *pPlayer = ToTFPlayer( pEntity ); if ( pPlayer ) { // Check to see if the player has the capture flag. if ( pPlayer->HasItem() && ( pPlayer->GetItem()->GetItemID() == TF_ITEM_CAPTURE_FLAG ) ) { CCaptureFlag *pFlag = dynamic_cast( pPlayer->GetItem() ); if ( pFlag && !pFlag->IsCaptured() ) { return true; } } } return false; } void CFlagDetectionZone::FlagCaptured( CBasePlayer *pPlayer ) { if ( !pPlayer ) return; if ( FStrEq( "sd_doomsday", STRING( gpGlobals->mapname ) ) ) { EHANDLE hOther; hOther = pPlayer; if ( m_hTouchingPlayers.Find( hOther ) != m_hTouchingPlayers.InvalidIndex() ) { int nWinningTeam = pPlayer->GetTeamNumber(); CUtlVector< EHANDLE > winningPlayers; for ( int i = 0 ; i < m_hTouchingPlayers.Count() ; i++ ) { EHANDLE hTemp = m_hTouchingPlayers[i]; if ( hTemp && ( hTemp->GetTeamNumber() == nWinningTeam ) ) { winningPlayers.AddToHead( hTemp ); } } // ACHIEVEMENT_TF_MAPS_DOOMSDAY_RIDE_THE_ELEVATOR if ( winningPlayers.Count() >= 5 ) { // loop through and award the achievement for ( int i = 0 ; i < winningPlayers.Count() ; i++ ) { EHANDLE hTemp = winningPlayers[i]; if ( hTemp ) { CTFPlayer *pTFPlayer = ToTFPlayer( hTemp ); if ( pTFPlayer ) { pTFPlayer->AwardAchievement( ACHIEVEMENT_TF_MAPS_DOOMSDAY_RIDE_THE_ELEVATOR ); } } } } } } } //----------------------------------------------------------------------------- // Purpose: Handles if the specified entity is dropped in a detection zone //----------------------------------------------------------------------------- void HandleFlagDroppedInDetectionZone( CBasePlayer *pPlayer ) { for ( int i=0; i( IFlagDetectionZoneAutoList::AutoList()[i] ); if ( !pZone->IsDisabled() ) { pZone->FlagDropped( pPlayer ); } } } //----------------------------------------------------------------------------- // Purpose: Handles if the specified entity is picked up in a detection zone //----------------------------------------------------------------------------- void HandleFlagPickedUpInDetectionZone( CBasePlayer *pPlayer ) { for ( int i=0; i( IFlagDetectionZoneAutoList::AutoList()[i] ); if ( !pZone->IsDisabled() ) { pZone->FlagPickedUp( pPlayer ); } } } //----------------------------------------------------------------------------- // Purpose: Handles if the specified entity is captured in a detection zone //----------------------------------------------------------------------------- void HandleFlagCapturedInDetectionZone( CBasePlayer *pPlayer ) { for ( int i=0; i( IFlagDetectionZoneAutoList::AutoList()[i] ); if ( !pZone->IsDisabled() ) { pZone->FlagCaptured( pPlayer ); } } }