Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

756 lines
20 KiB

5 years ago
//========= 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<CCaptureFlag*>( 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<CCaptureFlag*>( 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().Count(); ++i )
{
CFlagDetectionZone *pZone = static_cast<CFlagDetectionZone *>( 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().Count(); ++i )
{
CFlagDetectionZone *pZone = static_cast<CFlagDetectionZone *>( 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().Count(); ++i )
{
CFlagDetectionZone *pZone = static_cast<CFlagDetectionZone *>( IFlagDetectionZoneAutoList::AutoList()[i] );
if ( !pZone->IsDisabled() )
{
pZone->FlagCaptured( pPlayer );
}
}
}