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.
2134 lines
59 KiB
2134 lines
59 KiB
//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "weapon_c4.h"
|
|
#include "in_buttons.h"
|
|
#include "cs_gamerules.h"
|
|
#include "decals.h"
|
|
#include "SoundEmitterSystem/isoundemittersystembase.h"
|
|
#include "keyvalues.h"
|
|
#include "fx_cs_shared.h"
|
|
#include "obstacle_pushaway.h"
|
|
#include "particle_parse.h"
|
|
#include "mathlib/vector.h"
|
|
|
|
#if defined( CLIENT_DLL )
|
|
#include "c_cs_player.h"
|
|
#include "HUD/sfweaponselection.h"
|
|
#else
|
|
#include "cs_player.h"
|
|
#include "explode.h"
|
|
#include "mapinfo.h"
|
|
#include "team.h"
|
|
#include "func_bomb_target.h"
|
|
#include "vguiscreen.h"
|
|
#include "bot.h"
|
|
#include "cs_player.h"
|
|
|
|
#include "cs_gamestats.h"
|
|
#include "cs_achievement_constants.h"
|
|
|
|
#include "cvisibilitymonitor.h"
|
|
#include "cs_entity_spotting.h"
|
|
|
|
#endif
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
|
|
#define BLINK_INTERVAL 2.0
|
|
#define PLANTED_C4_MODEL "models/weapons/w_ied_dropped.mdl"
|
|
#define HEIST_MODE_C4_TIME 25
|
|
|
|
#define WEAPON_C4_ARM_TIME 3.0
|
|
|
|
#ifndef CLIENT_DLL
|
|
|
|
#define WEAPON_C4_UPDATE_LAST_VALID_PLAYER_HELD_POSITION_INTERVAL 0.2
|
|
|
|
// amount of time a player can stop defusing and continue
|
|
const float C4_DEFUSE_GRACE_PERIOD = 0.5f;
|
|
|
|
// amount of time a player is forced to continue defusing after not USEing. this effects other player's ability to interrupt
|
|
const float C4_DEFUSE_LOCKIN_PERIOD = 0.05f;
|
|
|
|
extern ConVar mp_anyone_can_pickup_c4;
|
|
extern ConVar mp_c4_cannot_be_defused;
|
|
|
|
CON_COMMAND_F( clear_bombs, "", FCVAR_CHEAT )
|
|
{
|
|
FOR_EACH_VEC( g_PlantedC4s, iBomb )
|
|
{
|
|
CPlantedC4 * pC4 = g_PlantedC4s[iBomb];
|
|
if ( pC4 )
|
|
{
|
|
UTIL_Remove( pC4 );
|
|
}
|
|
}
|
|
g_PlantedC4s.RemoveAll();
|
|
}
|
|
|
|
LINK_ENTITY_TO_CLASS( planted_c4, CPlantedC4 );
|
|
PRECACHE_REGISTER( planted_c4 );
|
|
|
|
BEGIN_DATADESC( CPlantedC4 )
|
|
DEFINE_FUNCTION( C4Think ),
|
|
//Outputs
|
|
DEFINE_OUTPUT( m_OnBombBeginDefuse, "OnBombBeginDefuse" ),
|
|
DEFINE_OUTPUT( m_OnBombDefused, "OnBombDefused" ),
|
|
DEFINE_OUTPUT( m_OnBombDefuseAborted, "OnBombDefuseAborted" ),
|
|
END_DATADESC()
|
|
|
|
|
|
IMPLEMENT_SERVERCLASS_ST( CPlantedC4, DT_PlantedC4 )
|
|
SendPropBool( SENDINFO(m_bBombTicking) ),
|
|
SendPropFloat( SENDINFO(m_flC4Blow), 0, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO(m_flTimerLength), 0, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO(m_flDefuseLength), 0, SPROP_NOSCALE ),
|
|
SendPropFloat( SENDINFO(m_flDefuseCountDown), 0, SPROP_NOSCALE ),
|
|
SendPropBool( SENDINFO(m_bBombDefused) ),
|
|
SendPropEHandle( SENDINFO(m_hBombDefuser) ),
|
|
END_SEND_TABLE()
|
|
|
|
|
|
BEGIN_PREDICTION_DATA( CPlantedC4 )
|
|
END_PREDICTION_DATA()
|
|
|
|
|
|
|
|
CUtlVector< CPlantedC4* > g_PlantedC4s;
|
|
|
|
|
|
CPlantedC4::CPlantedC4()
|
|
{
|
|
g_PlantedC4s.AddToTail( this );
|
|
|
|
// [tj] No planter initially
|
|
m_pPlanter = NULL;
|
|
m_pBombDefuser = NULL; //No Defuser Initially
|
|
|
|
// [tj] Assume this is the original owner
|
|
m_bPlantedAfterPickup = false;
|
|
|
|
m_bTrainingPlacedByPlayer = false;
|
|
m_bVoiceAlertFired = false;
|
|
|
|
SetSpotRules( CCSEntitySpotting::SPOT_RULE_CT | CCSEntitySpotting::SPOT_RULE_ALWAYS_SEEN_BY_T );
|
|
}
|
|
|
|
CPlantedC4::~CPlantedC4()
|
|
{
|
|
g_PlantedC4s.FindAndRemove( this );
|
|
|
|
RemoveControlPanels();
|
|
}
|
|
|
|
void CPlantedC4::Spawn()
|
|
{
|
|
BaseClass::Spawn();
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
SetSolid( SOLID_NONE );
|
|
AddFlag( FL_OBJECT );
|
|
|
|
SetModel( PLANTED_C4_MODEL );
|
|
SetSequence(1); // this sequence keeps the toggle switch in the 'up' position
|
|
|
|
SetCollisionBounds( Vector( 0, 0, 0 ), Vector( 8, 8, 8 ) );
|
|
|
|
// Detonate in "time" seconds
|
|
SetThink( &CPlantedC4::C4Think );
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
if ( CSGameRules() && CSGameRules()->IsPlayingCoopMission() && mp_anyone_can_pickup_c4.GetBool() )
|
|
m_flTimerLength = 9999;
|
|
else
|
|
m_flTimerLength = mp_c4timer.GetInt();
|
|
|
|
m_flC4Blow = gpGlobals->curtime + m_flTimerLength;
|
|
m_fLastDefuseTime = 0.0f;
|
|
m_bBeingDefused = false;
|
|
m_bHasExploded = false;
|
|
m_bBombDefused = false;
|
|
|
|
SetFriction( 0.9 );
|
|
|
|
m_flDefuseLength = 0.0f;
|
|
|
|
SpawnControlPanels();
|
|
|
|
VisibilityMonitor_AddEntity( this, 600.0f, NULL, NULL );
|
|
}
|
|
|
|
int CPlantedC4::UpdateTransmitState()
|
|
{
|
|
return SetTransmitState( FL_EDICT_FULLCHECK );
|
|
}
|
|
|
|
int CPlantedC4::ShouldTransmit( const CCheckTransmitInfo *pInfo )
|
|
{
|
|
// Terrorists always need this object for the radar
|
|
// Everybody needs it for hiding the round timer and showing the planted C4 scenario icon
|
|
return FL_EDICT_ALWAYS;
|
|
}
|
|
|
|
void CPlantedC4::Precache()
|
|
{
|
|
PrecacheModel( "models/weapons/w_c4_planted.mdl" ); // old, unused
|
|
PrecacheModel( PLANTED_C4_MODEL );
|
|
PrecacheModel( "models/props/de_overpass/balloon.mdl" );
|
|
PrecacheParticleSystem( "weapon_confetti_balloons" );
|
|
PrecacheModel( "models/weapons/w_eq_multimeter.mdl" );
|
|
PrecacheVGuiScreen( "c4_panel" );
|
|
}
|
|
|
|
void CPlantedC4::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
|
|
{
|
|
pPanelName = "c4_panel";
|
|
}
|
|
|
|
void CPlantedC4::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName )
|
|
{
|
|
pPanelName = "vgui_screen";
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This is called by the base object when it's time to spawn the control panels
|
|
//-----------------------------------------------------------------------------
|
|
void CPlantedC4::SpawnControlPanels()
|
|
{
|
|
char buf[64];
|
|
|
|
// FIXME: Deal with dynamically resizing control panels?
|
|
|
|
// If we're attached to an entity, spawn control panels on it instead of use
|
|
CBaseAnimating *pEntityToSpawnOn = this;
|
|
char *pOrgLL = "controlpanel%d_ll";
|
|
char *pOrgUR = "controlpanel%d_ur";
|
|
char *pAttachmentNameLL = pOrgLL;
|
|
char *pAttachmentNameUR = pOrgUR;
|
|
|
|
Assert( pEntityToSpawnOn );
|
|
|
|
// Lookup the attachment point...
|
|
int nPanel;
|
|
for ( nPanel = 0; true; ++nPanel )
|
|
{
|
|
Q_snprintf( buf, sizeof( buf ), pAttachmentNameLL, nPanel );
|
|
int nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
|
|
if (nLLAttachmentIndex <= 0)
|
|
{
|
|
// Try and use my panels then
|
|
pEntityToSpawnOn = this;
|
|
Q_snprintf( buf, sizeof( buf ), pOrgLL, nPanel );
|
|
nLLAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
|
|
if (nLLAttachmentIndex <= 0)
|
|
return;
|
|
}
|
|
|
|
Q_snprintf( buf, sizeof( buf ), pAttachmentNameUR, nPanel );
|
|
int nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
|
|
if (nURAttachmentIndex <= 0)
|
|
{
|
|
// Try and use my panels then
|
|
Q_snprintf( buf, sizeof( buf ), pOrgUR, nPanel );
|
|
nURAttachmentIndex = pEntityToSpawnOn->LookupAttachment(buf);
|
|
if (nURAttachmentIndex <= 0)
|
|
return;
|
|
}
|
|
|
|
const char *pScreenName;
|
|
GetControlPanelInfo( nPanel, pScreenName );
|
|
if (!pScreenName)
|
|
continue;
|
|
|
|
const char *pScreenClassname;
|
|
GetControlPanelClassName( nPanel, pScreenClassname );
|
|
if ( !pScreenClassname )
|
|
continue;
|
|
|
|
// Compute the screen size from the attachment points...
|
|
matrix3x4_t panelToWorld;
|
|
pEntityToSpawnOn->GetAttachment( nLLAttachmentIndex, panelToWorld );
|
|
|
|
matrix3x4_t worldToPanel;
|
|
MatrixInvert( panelToWorld, worldToPanel );
|
|
|
|
// Now get the lower right position + transform into panel space
|
|
Vector lr, lrlocal;
|
|
pEntityToSpawnOn->GetAttachment( nURAttachmentIndex, panelToWorld );
|
|
MatrixGetColumn( panelToWorld, 3, lr );
|
|
VectorTransform( lr, worldToPanel, lrlocal );
|
|
|
|
float flWidth = fabs( lrlocal.x );
|
|
float flHeight = fabs( lrlocal.y );
|
|
|
|
CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, pEntityToSpawnOn, this, nLLAttachmentIndex );
|
|
pScreen->ChangeTeam( GetTeamNumber() );
|
|
pScreen->SetActualSize( flWidth, flHeight );
|
|
pScreen->SetActive( true );
|
|
pScreen->MakeVisibleOnlyToTeammates( false );
|
|
int nScreen = m_hScreens.AddToTail( );
|
|
m_hScreens[nScreen].Set( pScreen );
|
|
}
|
|
}
|
|
|
|
void CPlantedC4::RemoveControlPanels()
|
|
{
|
|
// Clear off any screens that are still live.
|
|
for ( int ii = m_hScreens.Count(); --ii >= 0; )
|
|
{
|
|
DestroyVGuiScreen( m_hScreens[ii].Get() );
|
|
}
|
|
m_hScreens.RemoveAll();
|
|
}
|
|
|
|
void CPlantedC4::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
|
|
{
|
|
// Are we already marked for transmission?
|
|
if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
|
|
return;
|
|
|
|
BaseClass::SetTransmit( pInfo, bAlways );
|
|
|
|
// Force our screens to be sent too.
|
|
for ( int i=0; i < m_hScreens.Count(); i++ )
|
|
{
|
|
CVGuiScreen *pScreen = m_hScreens[i].Get();
|
|
pScreen->SetTransmit( pInfo, bAlways );
|
|
}
|
|
}
|
|
|
|
CPlantedC4* CPlantedC4::ShootSatchelCharge( CCSPlayer *pevOwner, Vector vecStart, QAngle vecAngles )
|
|
{
|
|
CPlantedC4 *pGrenade;
|
|
bool bTrainingPlacedByPlayer = false;
|
|
if ( CSGameRules()->IsPlayingTraining() )
|
|
{
|
|
pGrenade = dynamic_cast< CPlantedC4Training* >( CreateEntityByName( "planted_c4_training" ) );
|
|
bTrainingPlacedByPlayer = true;
|
|
}
|
|
else
|
|
{
|
|
pGrenade = dynamic_cast< CPlantedC4* >( CreateEntityByName( "planted_c4" ) );
|
|
}
|
|
|
|
if ( pGrenade )
|
|
{
|
|
vecAngles[0] = 0;
|
|
vecAngles[2] = 0;
|
|
pGrenade->Init( pevOwner, vecStart, vecAngles, bTrainingPlacedByPlayer );
|
|
return pGrenade;
|
|
}
|
|
else
|
|
{
|
|
Warning( "Can't create planted_c4 entity!\n" );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void CPlantedC4::Init( CCSPlayer *pevOwner, Vector vecStart, QAngle vecAngles, bool bTrainingPlacedByPlayer )
|
|
{
|
|
SetAbsOrigin( vecStart );
|
|
SetAbsAngles( vecAngles );
|
|
SetOwnerEntity( pevOwner );
|
|
SetPlanter( pevOwner );
|
|
|
|
m_bTrainingPlacedByPlayer = bTrainingPlacedByPlayer;
|
|
if ( !m_bTrainingPlacedByPlayer )
|
|
m_bBombTicking = true;
|
|
|
|
Spawn();
|
|
}
|
|
|
|
void CPlantedC4::C4Think()
|
|
{
|
|
if (!IsInWorld())
|
|
{
|
|
UTIL_Remove( this );
|
|
return;
|
|
}
|
|
|
|
// network the defuser handle
|
|
if ( m_bBeingDefused )
|
|
{
|
|
m_hBombDefuser = m_pBombDefuser;
|
|
SetBodygroupPreset( "show_clips" );
|
|
}
|
|
else
|
|
{
|
|
m_hBombDefuser = INVALID_EHANDLE;
|
|
SetBodygroupPreset( "hide_clips" );
|
|
}
|
|
|
|
if ( m_bHasExploded )
|
|
{
|
|
if ( CSGameRules()->IsPlayingTraining() )
|
|
{
|
|
SetThink( &CBaseEntity::SUB_Remove );
|
|
SetNextThink( gpGlobals->curtime + 1.0f );
|
|
|
|
if ( m_pBombDefuser )
|
|
{
|
|
m_pBombDefuser->m_bIsDefusing = false;
|
|
m_pBombDefuser->SetProgressBarTime( 0 );
|
|
m_pBombDefuser->OnCanceledDefuse();
|
|
m_pBombDefuser = NULL;
|
|
}
|
|
|
|
m_bBeingDefused = false;
|
|
m_flDefuseCountDown = 0;
|
|
m_flDefuseLength = 0;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//Bomb is not ticking, don't think anymore
|
|
if( !IsBombActive() )
|
|
{
|
|
SetThink( NULL );
|
|
return;
|
|
}
|
|
// [hpe:jason] Decrease the latency between c4 think updates
|
|
SetNextThink( gpGlobals->curtime + 0.05f );
|
|
|
|
// let the bots hear the bomb beeping
|
|
// BOTPORT: Emit beep events at same time as client effects
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_beep" );
|
|
if( event )
|
|
{
|
|
event->SetInt( "entindex", entindex() );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
// 4 seconds before the bomb blows up, have anyone close by on each team say something about it going to blow
|
|
if (m_flC4Blow - 3.0 <= gpGlobals->curtime && !m_bVoiceAlertFired)
|
|
{
|
|
CCSPlayer *pCT = NULL;
|
|
CCSPlayer *pT = NULL;
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
|
|
if ( pPlayer && pPlayer->IsAlive() )
|
|
{
|
|
if ( !pCT && pPlayer->GetTeamNumber() == TEAM_CT && pCT != m_pBombDefuser )
|
|
{
|
|
if ((GetAbsOrigin() - pPlayer->GetAbsOrigin()).AsVector2D().IsLengthLessThan( 1200.0 ))
|
|
pCT = pPlayer;
|
|
}
|
|
else if ( !pT && pPlayer->GetTeamNumber() == TEAM_TERRORIST )
|
|
{
|
|
if ((GetAbsOrigin() - pPlayer->GetAbsOrigin()).AsVector2D().IsLengthLessThan( 1200.0 ))
|
|
pT = pPlayer;
|
|
}
|
|
}
|
|
|
|
// we have a player from both teams, don't need to find anymore
|
|
if ( pCT && pT )
|
|
break;
|
|
}
|
|
|
|
if ( pCT && (!m_bBeingDefused || (m_bBeingDefused && (m_flDefuseCountDown > m_flC4Blow) ) ) )
|
|
{
|
|
pCT->Radio( "Radio.GetOutOfThere", "", true );
|
|
m_bVoiceAlertFired = true;
|
|
}
|
|
|
|
if ( pT )
|
|
{
|
|
pT->Radio( "Radio.GetOutOfThere", "", true );
|
|
m_bVoiceAlertFired = true;
|
|
}
|
|
}
|
|
|
|
// IF the timer has expired ! blow this bomb up!
|
|
if (m_flC4Blow <= gpGlobals->curtime)
|
|
{
|
|
// kick off the person trying to defuse the bomb
|
|
if ( m_pBombDefuser )
|
|
{
|
|
m_pBombDefuser->m_bIsDefusing = false;
|
|
m_pBombDefuser->SetProgressBarTime( 0 );
|
|
m_pBombDefuser->OnCanceledDefuse();
|
|
m_pBombDefuser = NULL;
|
|
m_bBeingDefused = false;
|
|
}
|
|
|
|
// for the music, we added a tension filled second after the bomb has ceased to be defusable and explode 1 second after
|
|
if (m_flC4Blow + 1.0f <= gpGlobals->curtime)
|
|
{
|
|
// give the bomber credit for planting the bomb
|
|
CCSPlayer* pBombOwner = ToCSPlayer(GetOwnerEntity());
|
|
|
|
// NOTE[pmf]: removed by design decision
|
|
// if ( pBombOwner )
|
|
// {
|
|
// if (CSGameRules()->m_iRoundWinStatus == WINNER_NONE)
|
|
// pBombOwner->IncrementFragCount( 3 );
|
|
// }
|
|
|
|
CSGameRules()->m_bBombDropped = false;
|
|
|
|
trace_t tr;
|
|
Vector vecSpot = GetAbsOrigin();
|
|
vecSpot[2] += 8;
|
|
|
|
UTIL_TraceLine( vecSpot, vecSpot + Vector ( 0, 0, -40 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
|
|
|
|
Explode( &tr, DMG_BLAST );
|
|
|
|
CSGameRules()->m_bBombPlanted = false;
|
|
|
|
CCS_GameStats.Event_BombExploded(pBombOwner);
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_exploded" );
|
|
if( event )
|
|
{
|
|
event->SetInt( "userid", pBombOwner?pBombOwner->GetUserID():-1 );
|
|
event->SetInt( "site", m_iBombSiteIndex );
|
|
event->SetInt( "priority", 20 ); // bomb_exploded
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
RemoveControlPanels();
|
|
|
|
// skip additional processing once the bomb has exploded
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// make sure our defuser exists
|
|
if ( m_bBeingDefused && (m_pBombDefuser == NULL) )
|
|
{
|
|
m_bBeingDefused = false;
|
|
}
|
|
|
|
|
|
//if the defusing process has started
|
|
if ( m_bBeingDefused && (m_pBombDefuser != NULL) && mp_c4_cannot_be_defused.GetBool() == false )
|
|
{
|
|
//if the defusing process has not ended yet
|
|
if ( gpGlobals->curtime < m_flDefuseCountDown )
|
|
{
|
|
int iOnGround = FBitSet( m_pBombDefuser->GetFlags(), FL_ONGROUND );
|
|
|
|
const CUserCmd *pCmd = m_pBombDefuser->GetLastUserCommand();
|
|
bool bPlayerStoppedHoldingUse = !(pCmd->buttons & IN_USE) && (gpGlobals->curtime > m_fLastDefuseTime + C4_DEFUSE_LOCKIN_PERIOD);
|
|
|
|
CConfigurationForHighPriorityUseEntity_t cfgUseEntity;
|
|
bool bPlayerUseIsValidNow = m_pBombDefuser->GetUseConfigurationForHighPriorityUseEntity( this, cfgUseEntity ) &&
|
|
( cfgUseEntity.m_pEntity == this ) && cfgUseEntity.UseByPlayerNow( m_pBombDefuser, cfgUseEntity.k_EPlayerUseType_Progress );
|
|
|
|
//if the bomb defuser has stopped defusing the bomb
|
|
if ( bPlayerStoppedHoldingUse || !bPlayerUseIsValidNow || !iOnGround )
|
|
{
|
|
if ( !iOnGround && m_pBombDefuser->IsAlive() )
|
|
ClientPrint( m_pBombDefuser, HUD_PRINTCENTER, "#SFUI_Notice_C4_Defuse_Must_Be_On_Ground");
|
|
|
|
// tell the bots someone has aborted defusing
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_abortdefuse" );
|
|
if( event )
|
|
{
|
|
event->SetInt("userid", m_pBombDefuser->GetUserID() );
|
|
event->SetInt( "priority", 5 ); // bomb_abortdefuse
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
//cancel the progress bar
|
|
m_pBombDefuser->SetProgressBarTime( 0 );
|
|
m_pBombDefuser->OnCanceledDefuse();
|
|
|
|
// release the player from being frozen
|
|
m_pBombDefuser->m_bIsDefusing = false;
|
|
m_bBeingDefused = false;
|
|
}
|
|
|
|
return;
|
|
}
|
|
// training has to pick elements
|
|
else if ( m_pBombDefuser->IsAlive() && CSGameRules()->IsPlayingTraining() )
|
|
{
|
|
Vector soundPosition = m_pBombDefuser->GetAbsOrigin() + Vector( 0, 0, 5 );
|
|
CPASAttenuationFilter filter( soundPosition );
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_defused" );
|
|
if( event )
|
|
{
|
|
event->SetInt("userid", m_pBombDefuser->GetUserID() );
|
|
event->SetInt("site", m_iBombSiteIndex );
|
|
event->SetInt( "priority", 5 ); // bomb_defused
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
EmitSound( filter, 0, "c4.disarmfinish", &GetAbsOrigin() );
|
|
|
|
// The bomb has just been disarmed.. Check to see if the round should end now
|
|
m_bBombTicking = false;
|
|
|
|
// release the player from being frozen
|
|
m_pBombDefuser->m_bIsDefusing = false;
|
|
|
|
// Clear their progress bar.
|
|
m_pBombDefuser->SetProgressBarTime( 0 );
|
|
|
|
m_pBombDefuser = NULL;
|
|
m_bBeingDefused = false;
|
|
|
|
m_flDefuseLength = 10;
|
|
|
|
m_OnBombDefused.FireOutput(this, m_pBombDefuser);
|
|
return;
|
|
}
|
|
//if the defuse process has ended, kill the c4 (for safety we also check whether the Terrorists have already won the round in which case we cannot score the defuse!)
|
|
else if ( m_pBombDefuser->IsAlive() && ( CSGameRules()->m_iRoundWinStatus != WINNER_TER ) )
|
|
{
|
|
bool roundWasAlreadyWon = ( CSGameRules()->m_iRoundWinStatus != WINNER_NONE ); // This condition checks whether CTs already won the round
|
|
// Check if there are Terrorists alive
|
|
bool bTerroristsAlive = false;
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer* pCheckPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
|
|
if ( !pCheckPlayer )
|
|
continue;
|
|
if ( pCheckPlayer->GetTeamNumber() != TEAM_TERRORIST )
|
|
continue;
|
|
if ( pCheckPlayer->IsAlive() )
|
|
{
|
|
bTerroristsAlive = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// set down-to-the-wire defuse fun fact
|
|
m_pBombDefuser->SetDefusedBombWithThisTimeRemaining( m_flC4Blow - gpGlobals->curtime );
|
|
|
|
CCS_GameStats.Event_BombDefused(m_pBombDefuser);
|
|
CSGameRules()->ScoreBombDefuse( m_pBombDefuser, ( bTerroristsAlive && !roundWasAlreadyWon ) );
|
|
m_pBombDefuser->AddAccountAward( PlayerCashAward::BOMB_DEFUSED );
|
|
|
|
|
|
// record in matchstats
|
|
if ( CSGameRules()->ShouldRecordMatchStats() )
|
|
{
|
|
int iCurrentRound = CSGameRules()->GetTotalRoundsPlayed();
|
|
++ m_pBombDefuser->m_iMatchStats_Objective.GetForModify( iCurrentRound );
|
|
|
|
// Keep track of Match stats in QMM data
|
|
if ( CCSGameRules::CQMMPlayerData_t *pQMM = CSGameRules()->QueuedMatchmakingPlayersDataFind( m_pBombDefuser->GetHumanPlayerAccountID() ) )
|
|
{
|
|
pQMM->m_iMatchStats_Objective[ iCurrentRound ] = m_pBombDefuser->m_iMatchStats_Objective.Get( iCurrentRound );
|
|
}
|
|
}
|
|
|
|
|
|
if ( !roundWasAlreadyWon )
|
|
{
|
|
// All alive CTs also get assistance credit for bomb defuse.
|
|
// This way if all Terrorists are eliminated then all alive CTs get 1pt and defuser gets 2pt;
|
|
// if a Terrorist remains alive then the defuser gets 5 pts and all other alive
|
|
// teammates get 1 pt for assist / suppressing fire.
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer* pCheckPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
|
|
if ( !pCheckPlayer )
|
|
continue;
|
|
if ( pCheckPlayer->GetTeamNumber() != TEAM_CT )
|
|
continue;
|
|
if ( pCheckPlayer->IsAlive() )
|
|
CSGameRules()->ScoreBombDefuse( pCheckPlayer, false );
|
|
}
|
|
}
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_defused" );
|
|
if( event )
|
|
{
|
|
event->SetInt("userid", m_pBombDefuser->GetUserID() );
|
|
event->SetInt("site", m_iBombSiteIndex );
|
|
event->SetInt( "priority", 5 ); // bomb_defused
|
|
gameeventmanager->FireEvent( event );
|
|
|
|
m_pBombDefuser->AwardAchievement( CSWinBombDefuse );
|
|
|
|
float timeToDetonation = ( m_flC4Blow - gpGlobals->curtime );
|
|
|
|
if ( (timeToDetonation > 0.0f) && (timeToDetonation <= AchievementConsts::BombDefuseCloseCall_MaxTimeRemaining) )
|
|
{
|
|
// Give achievement for defusing with < 1 second before detonation
|
|
m_pBombDefuser->AwardAchievement( CSBombDefuseCloseCall );
|
|
}
|
|
|
|
// [dwenger] Added for fun-fact support
|
|
if ( m_pBombDefuser->PickedUpDefuser() )
|
|
{
|
|
// Defuser kit was picked up, so set the fun fact
|
|
m_pBombDefuser->SetDefusedWithPickedUpKit( true );
|
|
}
|
|
}
|
|
|
|
|
|
Vector soundPosition = m_pBombDefuser->GetAbsOrigin() + Vector( 0, 0, 5 );
|
|
CPASAttenuationFilter filter( soundPosition );
|
|
|
|
EmitSound( filter, 0, "c4.disarmfinish", &GetAbsOrigin() );
|
|
|
|
// The bomb has just been disarmed.. Check to see if the round should end now
|
|
m_bBombTicking = false;
|
|
|
|
// release the player from being frozen
|
|
m_pBombDefuser->m_bIsDefusing = false;
|
|
|
|
CSGameRules()->m_bBombDefused = true;
|
|
|
|
if ( CSGameRules() && CSGameRules()->IsCSGOBirthday() )
|
|
{
|
|
DispatchParticleEffect( "weapon_confetti_balloons", GetAbsOrigin(), QAngle( 0, 0, 0 ) );
|
|
CPASAttenuationFilter filter( this );
|
|
filter.UsePredictionRules();
|
|
EmitSound( filter, entindex(), "Weapon_PartyHorn.Single" );
|
|
//EmitSound( filter, entindex(), "Birthday_PartyHorn.VO" );
|
|
//C_BaseEntity::EmitSound(filter, SOUND_FROM_LOCAL_PLAYER, "Birthday_PartyHorn.VO");
|
|
}
|
|
|
|
// Setup MVP granting class in case round wasn't already won
|
|
class CPlantedC4DefusedMVP : public CCSGameRules::ICalculateEndOfRoundMVPHook_t
|
|
{
|
|
public:
|
|
virtual CCSPlayer* CalculateEndOfRoundMVP() OVERRIDE
|
|
{
|
|
if( m_pBombDefuser->HasControlledBotThisRound() )
|
|
{
|
|
// [dkorus] if we controlled a bot this round, use standard MVP conditions
|
|
return CSGameRules()->CalculateEndOfRoundMVP();
|
|
}
|
|
|
|
bool bTerroristsAlive = false;
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer* pCheckPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
|
|
if ( !pCheckPlayer )
|
|
continue;
|
|
if ( pCheckPlayer->GetTeamNumber() != TEAM_TERRORIST )
|
|
continue;
|
|
if ( pCheckPlayer->IsAlive() )
|
|
{
|
|
bTerroristsAlive = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( bTerroristsAlive || ( m_pBombDefuser->GetNumRoundKills() && !m_pBombDefuser->m_iNumRoundTKs ) )
|
|
{
|
|
m_pBombDefuser->IncrementNumMVPs( CSMVP_BOMBDEFUSE );
|
|
return m_pBombDefuser;
|
|
}
|
|
|
|
if ( CCSPlayer *pDefaultMvp = CSGameRules()->CalculateEndOfRoundMVP() )
|
|
return pDefaultMvp;
|
|
|
|
m_pBombDefuser->IncrementNumMVPs( CSMVP_BOMBDEFUSE );
|
|
return m_pBombDefuser;
|
|
}
|
|
CHandle<CCSPlayer> m_pBombDefuser;
|
|
} mvpHook;
|
|
mvpHook.m_pBombDefuser = m_pBombDefuser;
|
|
if ( !roundWasAlreadyWon )
|
|
CSGameRules()->m_pfnCalculateEndOfRoundMVPHook = &mvpHook;
|
|
|
|
// [menglish] Give the bomb defuser an mvp if they ended the round
|
|
CSGameRules()->CheckWinConditions();
|
|
|
|
// Reset the MVP hook
|
|
if ( !roundWasAlreadyWon )
|
|
CSGameRules()->m_pfnCalculateEndOfRoundMVPHook = NULL;
|
|
|
|
// NOTE[pmf]: removed by design decision
|
|
// // give the defuser credit for defusing the bomb
|
|
// m_pBombDefuser->IncrementFragCount( 3 );
|
|
|
|
CSGameRules()->m_bBombDropped = false;
|
|
CSGameRules()->m_bBombPlanted = false;
|
|
|
|
// Clear their progress bar.
|
|
m_pBombDefuser->SetProgressBarTime( 0 );
|
|
|
|
m_pBombDefuser = NULL;
|
|
m_bBeingDefused = false;
|
|
m_bBombDefused = true;
|
|
|
|
m_flDefuseLength = 10;
|
|
|
|
m_OnBombDefused.FireOutput(this, m_pBombDefuser);
|
|
|
|
return;
|
|
}
|
|
|
|
//if it gets here then the previouse defuser has taken off or been killed
|
|
m_OnBombDefuseAborted.FireOutput(this, m_pBombDefuser);
|
|
|
|
// tell the bots someone has aborted defusing
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_abortdefuse" );
|
|
if ( event )
|
|
{
|
|
event->SetInt("userid", m_pBombDefuser->GetUserID() );
|
|
event->SetInt( "priority", 5 ); // bomb_abortdefuse
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
// release the player from being frozen
|
|
m_pBombDefuser->m_bIsDefusing = false;
|
|
m_bBeingDefused = false;
|
|
m_pBombDefuser = NULL;
|
|
}
|
|
}
|
|
|
|
// Regular explosions
|
|
void CPlantedC4::Explode( trace_t *pTrace, int bitsDamageType )
|
|
{
|
|
// Check to see if the round is over after the bomb went off...
|
|
CSGameRules()->m_bTargetBombed = true;
|
|
m_bBombTicking = false;
|
|
m_bHasExploded = true;
|
|
m_bBombDefused = false;
|
|
|
|
bool roundWasAlreadyWon = ( CSGameRules()->m_iRoundWinStatus != WINNER_NONE );
|
|
|
|
// MVP hook to award the MVP to person who planted the bomb
|
|
class CPlantedC4ExplodedMVP : public CCSGameRules::ICalculateEndOfRoundMVPHook_t
|
|
{
|
|
public:
|
|
virtual CCSPlayer* CalculateEndOfRoundMVP() OVERRIDE
|
|
{
|
|
// All alive Terrorists also get credit for bomb exploding,
|
|
// this intentionally may include the original planter.
|
|
// This way if bomb planter survives until explosion he will
|
|
// get 2 pts and all other alive teammates will get 1 pt
|
|
// If bomb planter doesn't survive then he gets 1 pt and all
|
|
// teammates who remained alive defending the bomb get 1 pt
|
|
for ( int i = 1; i <= MAX_PLAYERS; i++ )
|
|
{
|
|
CCSPlayer* pCheckPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
|
|
if ( !pCheckPlayer )
|
|
continue;
|
|
if ( pCheckPlayer->GetTeamNumber() != TEAM_TERRORIST )
|
|
continue;
|
|
if ( pCheckPlayer->IsAlive() )
|
|
CSGameRules()->ScoreBombExploded( pCheckPlayer );
|
|
}
|
|
|
|
if ( !pBombOwner )
|
|
return CSGameRules()->CalculateEndOfRoundMVP();
|
|
|
|
// Person who planted the bomb gets credit for the explosion
|
|
CSGameRules()->ScoreBombExploded( pBombOwner );
|
|
|
|
if( pBombOwner->HasControlledBotThisRound() )
|
|
{
|
|
// [dkorus] if we controlled a bot this round, use standard MVP conditions
|
|
return CSGameRules()->CalculateEndOfRoundMVP();
|
|
}
|
|
else
|
|
{
|
|
pBombOwner->IncrementNumMVPs( CSMVP_BOMBPLANT );
|
|
return pBombOwner;
|
|
}
|
|
}
|
|
CCSPlayer *pBombOwner;
|
|
} mvpHook;
|
|
mvpHook.pBombOwner = ToCSPlayer( GetOwnerEntity() );
|
|
|
|
if ( !roundWasAlreadyWon )
|
|
CSGameRules()->m_pfnCalculateEndOfRoundMVPHook = &mvpHook;
|
|
|
|
bool bWin = CSGameRules()->CheckWinConditions();
|
|
if ( bWin && mvpHook.pBombOwner )
|
|
{
|
|
mvpHook.pBombOwner->AwardAchievement( CSWinBombPlant );
|
|
|
|
//[tj]more specific achievement for planting the bomb after recovering it.
|
|
if ( m_bPlantedAfterPickup )
|
|
{
|
|
mvpHook.pBombOwner->AwardAchievement( CSWinBombPlantAfterRecovery );
|
|
}
|
|
}
|
|
|
|
if ( !roundWasAlreadyWon )
|
|
CSGameRules()->m_pfnCalculateEndOfRoundMVPHook = NULL;
|
|
|
|
|
|
// Do the Damage
|
|
float flBombRadius;
|
|
|
|
if ( CSGameRules()->IsPlayingGunGameTRBomb() )
|
|
{
|
|
flBombRadius = 300;
|
|
}
|
|
else
|
|
{
|
|
flBombRadius = 500;
|
|
}
|
|
|
|
if ( g_pMapInfo )
|
|
flBombRadius = g_pMapInfo->m_flBombRadius;
|
|
|
|
// Output to the bomb target ent
|
|
CBaseEntity *pTarget = NULL;
|
|
variant_t emptyVariant;
|
|
while ((pTarget = gEntList.FindEntityByClassname( pTarget, "func_bomb_target" )) != NULL)
|
|
{
|
|
//Adrian - But only to the one we want!
|
|
if ( pTarget->entindex() != m_iBombSiteIndex )
|
|
continue;
|
|
|
|
pTarget->AcceptInput( "BombExplode", this, this, emptyVariant, 0 );
|
|
break;
|
|
}
|
|
|
|
// Pull out of the wall a bit
|
|
if ( pTrace->fraction != 1.0 )
|
|
{
|
|
SetAbsOrigin( pTrace->endpos + (pTrace->plane.normal * 0.6) );
|
|
}
|
|
|
|
{
|
|
Vector pos = GetAbsOrigin() + Vector( 0,0,8 );
|
|
|
|
// Make sure that the bomb exploding is a reliable effect that all clients on this server and GOTV receive
|
|
CReliableBroadcastRecipientFilter filterBombExplodeReliable;
|
|
|
|
// Try using the new particle system instead of temp ents
|
|
QAngle vecAngles;
|
|
DispatchParticleEffect( "explosion_c4_500", pos, vecAngles, ( CBaseEntity * ) NULL, int( -1 ), &filterBombExplodeReliable );
|
|
}
|
|
|
|
// Sound! for everyone
|
|
CBroadcastRecipientFilter filter;
|
|
EmitSound( filter, 0, "c4.explode", &GetAbsOrigin() );
|
|
|
|
// Decal!
|
|
UTIL_DecalTrace( pTrace, "Scorch" );
|
|
|
|
|
|
// Shake!
|
|
UTIL_ScreenShake( pTrace->endpos, 25.0, 150.0, 1.0, 3000, SHAKE_START );
|
|
|
|
|
|
SetOwnerEntity( NULL ); // can't traceline attack owner if this is set
|
|
|
|
CSGameRules()->RadiusDamage(
|
|
CTakeDamageInfo( this, GetOwnerEntity(), flBombRadius, bitsDamageType ),
|
|
GetAbsOrigin(),
|
|
flBombRadius * 3.5, //Matt - don't ask me, this is how CS does it.
|
|
CLASS_NONE,
|
|
true ); // IGNORE THE WORLD!!
|
|
|
|
// send director message, that something important happed here
|
|
/*
|
|
MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR );
|
|
WRITE_BYTE ( 9 ); // command length in bytes
|
|
WRITE_BYTE ( DRC_CMD_EVENT ); // bomb explode
|
|
WRITE_SHORT( ENTINDEX(this->edict()) ); // index number of primary entity
|
|
WRITE_SHORT( 0 ); // index number of secondary entity
|
|
WRITE_LONG( 15 | DRC_FLAG_FINAL ); // eventflags (priority and flags)
|
|
MESSAGE_END();
|
|
*/
|
|
}
|
|
|
|
|
|
// For CTs to defuse the c4
|
|
void CPlantedC4::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
//Can't defuse if its already defused or if it has blown up (or training)
|
|
if( !IsBombActive() || m_flC4Blow < gpGlobals->curtime || mp_c4_cannot_be_defused.GetBool() == true )
|
|
{
|
|
SetUse( NULL );
|
|
return;
|
|
}
|
|
|
|
CCSPlayer *player = dynamic_cast< CCSPlayer* >( pActivator );
|
|
|
|
// Can't defuse a bomb if we're not CT or we're in a no defuse area.
|
|
if ( !player || player->GetTeamNumber() != TEAM_CT || player->m_bInNoDefuseArea)
|
|
return;
|
|
|
|
// make sure human players can see the bomb (or the bomb's nearby line-of-sight points)
|
|
// to ensure we aren't defusing from the room below or something
|
|
if ( !player->IsBot() )
|
|
{
|
|
trace_t result;
|
|
bool bFoundBomb = false;
|
|
|
|
Vector vecLineOfSightPoints[] = {
|
|
Vector( 0, 0, 1 ),
|
|
Vector( 5, 5, 5 ),
|
|
Vector( -5, 5, 5 ),
|
|
Vector( 5, -5, 5 ),
|
|
Vector( -5, -5, 5 ),
|
|
Vector( 0, 0, 5 )
|
|
};
|
|
int nNumLOSpts = ARRAYSIZE( vecLineOfSightPoints );
|
|
|
|
for ( int i=0; i<nNumLOSpts; i++ )
|
|
{
|
|
Vector vecLineOfSightTarget = VectorTransform( vecLineOfSightPoints[i], EntityToWorldTransform() );
|
|
//debugoverlay->AddBoxOverlay( vecLineOfSightTarget, Vector(-0.5f,-0.5f,-0.5f), Vector(0.5f,0.5f,0.5f), QAngle(0,0,0), 0,0,255,255, 5 );
|
|
|
|
UTIL_TraceLine( player->EyePosition(), vecLineOfSightTarget, CONTENTS_SOLID, this, COLLISION_GROUP_NONE, &result );
|
|
|
|
if ( result.fraction < 1.0f )
|
|
{
|
|
//debugoverlay->AddLineOverlay( result.startpos, result.endpos, 255,0,0, 255, 0.15f, 10 );
|
|
continue;
|
|
}
|
|
//debugoverlay->AddLineOverlay( result.startpos, result.endpos, 0,255,0, 255, 0.15f, 10 );
|
|
|
|
// this trace succeeded, so we're ok to defuse.
|
|
bFoundBomb = true;
|
|
break;
|
|
}
|
|
|
|
if ( !bFoundBomb )
|
|
return;
|
|
}
|
|
|
|
if ( m_bBeingDefused )
|
|
{
|
|
if ( player != m_pBombDefuser )
|
|
{
|
|
if ( player->m_iNextTimeCheck < gpGlobals->curtime )
|
|
{
|
|
ClientPrint( player, HUD_PRINTCENTER, "#SFUI_Notice_Bomb_Already_Being_Defused" );
|
|
player->m_iNextTimeCheck = gpGlobals->curtime + 1.f;
|
|
}
|
|
return;
|
|
}
|
|
|
|
m_fLastDefuseTime = gpGlobals->curtime;
|
|
}
|
|
else
|
|
{
|
|
// freeze the player in place while defusing
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent("bomb_begindefuse" );
|
|
if( event )
|
|
{
|
|
event->SetInt( "userid", player->GetUserID() );
|
|
if ( player->HasDefuser() )
|
|
{
|
|
event->SetInt( "haskit", 1 );
|
|
}
|
|
else
|
|
{
|
|
event->SetInt( "haskit", 0 );
|
|
}
|
|
event->SetInt( "priority", 5 ); // bomb_begindefuse
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
Vector soundPosition = player->GetAbsOrigin() + Vector( 0, 0, 5 );
|
|
CPASAttenuationFilter filter( soundPosition );
|
|
EmitSound( filter, 0, "c4.disarmstart", &GetAbsOrigin() );
|
|
|
|
/*
|
|
if ( m_pBombDefuser == player && gpGlobals->curtime < ( m_fLastDefuseTime + C4_DEFUSE_GRACE_PERIOD ) )
|
|
{
|
|
// if we're allowing the player to continue defusing, push the completion time ahead by the appropriate amount
|
|
float fTimeGap = m_fLastDefuseTime - gpGlobals->curtime;
|
|
m_flDefuseCountDown += fTimeGap;
|
|
|
|
// we don't have a method for setting up the progress bar "in progress", so do it manually
|
|
player->m_iProgressBarDuration = m_flDefuseLength;
|
|
player->m_flProgressBarStartTime = m_flDefuseCountDown - m_flDefuseLength;
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
m_flDefuseLength = player->HasDefuser() ? 5 : 10;
|
|
m_flDefuseCountDown = gpGlobals->curtime + m_flDefuseLength;
|
|
player->SetProgressBarTime( m_flDefuseLength );
|
|
}
|
|
|
|
m_pBombDefuser = player;
|
|
m_bBeingDefused = TRUE;
|
|
player->m_bIsDefusing = true;
|
|
|
|
m_fLastDefuseTime = gpGlobals->curtime;
|
|
|
|
//start the progress bar
|
|
|
|
player->OnStartedDefuse();
|
|
|
|
m_OnBombBeginDefuse.FireOutput(this, player);
|
|
}
|
|
}
|
|
|
|
void CPlantedC4::ActivateSetTimerLength( float flTimerLength )
|
|
{
|
|
if ( flTimerLength < 0 )
|
|
{
|
|
Error( "ActivateSetTimerLength value is less than 0!" );
|
|
return;
|
|
}
|
|
|
|
// Detonate in "time" seconds
|
|
m_flTimerLength = flTimerLength;
|
|
m_flC4Blow = gpGlobals->curtime + m_flTimerLength;
|
|
|
|
SetThink( &CPlantedC4::C4Think );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
m_bBombTicking = true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////
|
|
// Training version of the C4 - doesn't explode
|
|
///////////////////////////////////////////////////////////
|
|
|
|
LINK_ENTITY_TO_CLASS( planted_c4_training, CPlantedC4Training );
|
|
PRECACHE_REGISTER( planted_c4_training );
|
|
|
|
BEGIN_PREDICTION_DATA( CPlantedC4Training )
|
|
END_PREDICTION_DATA()
|
|
|
|
BEGIN_DATADESC( CPlantedC4Training )
|
|
|
|
// Inputs
|
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "ActivateSetTimerLength", InputActivateSetTimerLength ),
|
|
//Outputs
|
|
DEFINE_OUTPUT( m_OnBombExploded, "OnBombExploded" ),
|
|
|
|
END_DATADESC()
|
|
|
|
void CPlantedC4Training::InputActivateSetTimerLength( inputdata_t &inputdata )
|
|
{
|
|
ActivateSetTimerLength( inputdata.value.Float() );
|
|
}
|
|
|
|
void CPlantedC4Training::Explode( trace_t *pTrace, int bitsDamageType )
|
|
{
|
|
// Check to see if the round is over after the bomb went off...
|
|
CSGameRules()->m_bTargetBombed = true;
|
|
m_bBombTicking = false;
|
|
m_bHasExploded = true;
|
|
|
|
// Pull out of the wall a bit
|
|
if ( pTrace->fraction != 1.0 )
|
|
{
|
|
SetAbsOrigin( pTrace->endpos + (pTrace->plane.normal * 0.6) );
|
|
}
|
|
|
|
QAngle vecAngles;
|
|
DispatchParticleEffect( "c4_train_ground_effect", GetAbsOrigin(), vecAngles );
|
|
|
|
// Sound! for everyone
|
|
CBroadcastRecipientFilter filter;
|
|
EmitSound( filter, 0, "tr.C4Explode", &GetAbsOrigin() );
|
|
|
|
// Decal!
|
|
//UTIL_DecalTrace( pTrace, "Scorch" );
|
|
|
|
// Shake!
|
|
//UTIL_ScreenShake( pTrace->endpos, 25.0, 150.0, 1.0, 3000, SHAKE_START );
|
|
|
|
SetOwnerEntity( NULL ); // can't traceline attack owner if this is set
|
|
|
|
m_OnBombExploded.FireOutput(this, this);
|
|
}
|
|
#endif // #ifndef CLIENT_DLL
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------- //
|
|
// Tables.
|
|
// -------------------------------------------------------------------------------- //
|
|
|
|
IMPLEMENT_NETWORKCLASS_ALIASED( C4, DT_WeaponC4 )
|
|
|
|
BEGIN_NETWORK_TABLE( CC4, DT_WeaponC4 )
|
|
#ifdef CLIENT_DLL
|
|
RecvPropBool( RECVINFO( m_bStartedArming ) ),
|
|
RecvPropBool( RECVINFO( m_bBombPlacedAnimation ) ),
|
|
RecvPropFloat( RECVINFO( m_fArmedTime ) ),
|
|
RecvPropBool( RECVINFO( m_bShowC4LED ) ),
|
|
RecvPropBool( RECVINFO( m_bIsPlantingViaUse ) )
|
|
#else
|
|
SendPropBool( SENDINFO( m_bStartedArming ) ),
|
|
SendPropBool( SENDINFO( m_bBombPlacedAnimation ) ),
|
|
SendPropFloat( SENDINFO( m_fArmedTime ), 0, SPROP_NOSCALE ),
|
|
SendPropBool( SENDINFO( m_bShowC4LED ) ),
|
|
SendPropBool( SENDINFO( m_bIsPlantingViaUse ) )
|
|
#endif
|
|
END_NETWORK_TABLE()
|
|
|
|
#if defined CLIENT_DLL
|
|
BEGIN_PREDICTION_DATA( CC4 )
|
|
DEFINE_PRED_FIELD( m_bStartedArming, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_bBombPlacedAnimation, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_fArmedTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_bShowC4LED, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
|
|
DEFINE_PRED_FIELD( m_bIsPlantingViaUse, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
|
|
END_PREDICTION_DATA()
|
|
#endif
|
|
|
|
LINK_ENTITY_TO_CLASS_ALIASED( weapon_c4, C4 );
|
|
PRECACHE_REGISTER( weapon_c4 );
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------------------- //
|
|
// Globals.
|
|
// -------------------------------------------------------------------------------- //
|
|
|
|
const float DROPPED_LIGHT_INTERVAL = 1.0f;
|
|
|
|
// -------------------------------------------------------------------------------- //
|
|
// CC4 implementation.
|
|
// -------------------------------------------------------------------------------- //
|
|
|
|
CC4::CC4()
|
|
{
|
|
m_bDroppedFromDeath = false;
|
|
|
|
#if defined( CLIENT_DLL )
|
|
m_szScreenText[0] = '\0';
|
|
m_bShowC4LED = false;
|
|
#else
|
|
SetSpotRules( CCSEntitySpotting::SPOT_RULE_CT | CCSEntitySpotting::SPOT_RULE_ALWAYS_SEEN_BY_T );
|
|
m_vecLastValidPlayerHeldPosition = Vector(0,0,0);
|
|
#endif
|
|
|
|
m_bIsPlantingViaUse = false;
|
|
}
|
|
|
|
|
|
CC4::~CC4()
|
|
{
|
|
}
|
|
|
|
void CC4::Spawn()
|
|
{
|
|
BaseClass::Spawn();
|
|
|
|
//Don't allow players to shoot the C4 around
|
|
SetCollisionGroup( COLLISION_GROUP_DEBRIS );
|
|
|
|
//Don't be damaged / moved by explosions
|
|
m_takedamage = DAMAGE_NO;
|
|
|
|
m_bBombPlanted = false;
|
|
|
|
#if defined( CLIENT_DLL )
|
|
|
|
SetNextClientThink( gpGlobals->curtime + DROPPED_LIGHT_INTERVAL );
|
|
|
|
#else
|
|
SetNextThink( gpGlobals->curtime );
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
void CC4::ItemPostFrame()
|
|
{
|
|
CCSPlayer *pPlayer = GetPlayerOwner();
|
|
if ( !pPlayer )
|
|
return;
|
|
|
|
// Disable all the firing code.. the C4 grenade is all custom.
|
|
if ( pPlayer->m_nButtons & IN_ATTACK || (pPlayer->m_nButtons & IN_USE && m_bIsPlantingViaUse) )
|
|
{
|
|
if ( gpGlobals->curtime >= m_flNextPrimaryAttack )
|
|
PrimaryAttack();
|
|
}
|
|
else
|
|
{
|
|
WeaponIdle();
|
|
}
|
|
|
|
if ( !(pPlayer->m_nButtons & IN_USE) )
|
|
m_bIsPlantingViaUse = false;
|
|
|
|
}
|
|
|
|
void CC4::WeaponReset( void )
|
|
{
|
|
m_bShowC4LED = false;
|
|
|
|
BaseClass::WeaponReset();
|
|
}
|
|
|
|
#if defined( CLIENT_DLL )
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CC4::OnDataChanged( DataUpdateType_t type )
|
|
{
|
|
BaseClass::OnDataChanged( type );
|
|
}
|
|
|
|
void CC4::UpdateOnRemove( void )
|
|
{
|
|
BaseClass::UpdateOnRemove();
|
|
|
|
// when a c4 is removed, force the local player to update thier inventory screen
|
|
if ( !C_BasePlayer::GetLocalPlayer() || !engine->IsLocalPlayerResolvable() )
|
|
return; // early out if local player does not exsist
|
|
|
|
C_CSPlayer *pPlayer = C_CSPlayer::GetLocalCSPlayer();
|
|
if ( pPlayer )
|
|
{
|
|
SFWeaponSelection *pHudWS = GET_HUDELEMENT( SFWeaponSelection );
|
|
if ( pHudWS )
|
|
{
|
|
pHudWS->ShowAndUpdateSelection( WEPSELECT_SWITCH, NULL );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CC4::ClientThink( void )
|
|
{
|
|
BaseClass::ClientThink();
|
|
|
|
SetNextClientThink( gpGlobals->curtime + DROPPED_LIGHT_INTERVAL );
|
|
|
|
// This think function is just for updating the blinking light of the dropped bomb.
|
|
// So if we have an owner, we don't want to blink.
|
|
if ( IsDormant() || NULL != GetPlayerOwner() || !CSGameRules()->m_bBombDropped )
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
int ledAttachmentIndex = LookupAttachment("led");
|
|
DispatchParticleEffect( "c4_timer_light_dropped", PATTACH_POINT_FOLLOW, this, ledAttachmentIndex, false, -1 );
|
|
|
|
}
|
|
|
|
bool CC4::OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options )
|
|
{
|
|
if( event == 7001 )
|
|
{
|
|
// "Custom code" c4 disabled
|
|
//
|
|
/*//if we're clearing the screen text, or have no custom description, set the text normally using the passed param.
|
|
if ( !V_strcmp( options, "" )
|
|
|| !V_strcmp( options, "*******" )
|
|
|| GetEconItemView() == NULL
|
|
|| CALL_ATTRIB_HOOK_BOOL( custom_bombcode ) == false
|
|
|| GetEconItemView()->GetCustomDesc() == NULL
|
|
)
|
|
{
|
|
Q_strncpy( m_szScreenText, options, 16 );
|
|
}
|
|
else
|
|
{
|
|
//otherwise we have a custom code to display
|
|
CEconItemView *pItem = GetEconItemView();
|
|
if ( pItem && pItem->GetCustomDesc() )
|
|
{
|
|
if ( strlen(m_szScreenText) >= 7 )
|
|
{
|
|
//if the 7-char code is full, replace it with asterisks (this will happen at the end of the plant animation anyway)
|
|
Q_strncpy( m_szScreenText, "*******\0", 8 );
|
|
}
|
|
else
|
|
{
|
|
//we're still under 7 chars, so add an additional char of the custom code
|
|
Q_strncpy( m_szScreenText, pItem->GetCustomDesc(), strlen(m_szScreenText)+2 );
|
|
}
|
|
}
|
|
}*/
|
|
|
|
//set the screen text to the string in 'options'
|
|
Q_strncpy( m_szScreenText, options, 16 );
|
|
|
|
return true;
|
|
}
|
|
return BaseClass::OnFireEvent( pViewModel, origin, angles, event, options );
|
|
}
|
|
|
|
char *CC4::GetScreenText( void )
|
|
{
|
|
return m_szScreenText;
|
|
}
|
|
|
|
void CC4::CreateLEDEffect( void )
|
|
{
|
|
if ( GetPlayerOwner() )
|
|
GetPlayerOwner()->CreateC4Effect( GetWeaponForEffect(), true );
|
|
}
|
|
|
|
void CC4::RemoveLEDEffect( void )
|
|
{
|
|
if ( GetPlayerOwner() )
|
|
GetPlayerOwner()->RemoveC4Effect( true );
|
|
}
|
|
|
|
#else
|
|
|
|
void CC4::PhysicsTouchTriggers(const Vector *pPrevAbsOrigin)
|
|
{
|
|
// Normally items like ammo or weapons aren't expected to touch other triggers, but C4 is a special case
|
|
edict_t *pEntity = edict();
|
|
|
|
if (pEntity && !IsWorld())
|
|
{
|
|
Assert(CollisionProp());
|
|
|
|
//Dropped bombs are both solid and have no owner. In this state, unlike other weapons, they can
|
|
//now touch triggers so long they haven't had their position reset by a bomb reset trigger.
|
|
if ( IsSolid() && (GetPlayerOwner() == NULL) )
|
|
{
|
|
SetCheckUntouch(true);
|
|
engine->SolidMoved(pEntity, CollisionProp(), pPrevAbsOrigin, sm_bAccurateTriggerBboxChecks);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif //CLIENT_DLL
|
|
|
|
#ifdef GAME_DLL
|
|
|
|
void CC4::Think()
|
|
{
|
|
//If the bomb is held by an alive player standing on the ground, then we can use this
|
|
//position as the last known valid position to respawn the bomb if it gets reset.
|
|
CCSPlayer *pPlayer = GetPlayerOwner();
|
|
if ( pPlayer && pPlayer->IsAlive() && FBitSet( pPlayer->GetFlags(), FL_ONGROUND ) )
|
|
{
|
|
m_vecLastValidPlayerHeldPosition = pPlayer->GetAbsOrigin();
|
|
}
|
|
|
|
SetNextThink( gpGlobals->curtime + WEAPON_C4_UPDATE_LAST_VALID_PLAYER_HELD_POSITION_INTERVAL );
|
|
}
|
|
|
|
void CC4::ResetToLastValidPlayerHeldPosition()
|
|
{
|
|
//When reset, the bomb returns to its last known valid position.
|
|
CCSPlayer *pPlayer = GetPlayerOwner();
|
|
if ( !pPlayer && GetAbsOrigin() != m_vecLastValidPlayerHeldPosition )
|
|
{
|
|
// Teleport the bomb facing up so the flashing light is clearly visible.
|
|
Vector vecResetPos = m_vecLastValidPlayerHeldPosition + Vector(0,0,8);
|
|
QAngle angResetAng = QAngle( 0, RandomInt(0,360), 0 );
|
|
|
|
// trace to ground
|
|
trace_t c4TeleportTrace;
|
|
UTIL_TraceHull( vecResetPos, vecResetPos + Vector(0,0,-8), Vector(-3,-3,-1), Vector(3,3,1), MASK_PLAYERSOLID, NULL, COLLISION_GROUP_PLAYER_MOVEMENT, &c4TeleportTrace );
|
|
if ( !c4TeleportTrace.startsolid && c4TeleportTrace.DidHit() )
|
|
{
|
|
vecResetPos += ( c4TeleportTrace.fraction * Vector(0,0,-8) );
|
|
}
|
|
|
|
Teleport( &vecResetPos, &angResetAng, NULL );
|
|
|
|
// Set the physics object asleep so it doesn't tumble off precarious ledges and keep resetting.
|
|
IPhysicsObject *pObj = VPhysicsGetObject();
|
|
if ( pObj )
|
|
pObj->Sleep();
|
|
|
|
}
|
|
}
|
|
|
|
unsigned int CC4::PhysicsSolidMaskForEntity( void ) const
|
|
{
|
|
return BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_PLAYERCLIP;
|
|
}
|
|
|
|
void CC4::Precache()
|
|
{
|
|
PrecacheVGuiScreen( "c4_view_panel" );
|
|
|
|
PrecacheScriptSound( "c4.disarmfinish" );
|
|
PrecacheScriptSound( "c4.explode" );
|
|
PrecacheScriptSound( "c4.disarmstart" );
|
|
PrecacheScriptSound( "c4.plant" );
|
|
PrecacheScriptSound( "C4.PlantSound" );
|
|
PrecacheScriptSound( "c4.click" );
|
|
|
|
// training
|
|
PrecacheParticleSystem( "c4_train_ground_effect" );
|
|
PrecacheScriptSound( "tr.C4Explode" );
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
int CC4::UpdateTransmitState()
|
|
{
|
|
return SetTransmitState( FL_EDICT_FULLCHECK );
|
|
}
|
|
|
|
int CC4::ShouldTransmit( const CCheckTransmitInfo *pInfo )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( CBaseEntity::Instance( pInfo->m_pClientEnt ) );
|
|
if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_CT )
|
|
{
|
|
return FL_EDICT_PVSCHECK;
|
|
}
|
|
else
|
|
{
|
|
// This is needed for the instructor message
|
|
return FL_EDICT_ALWAYS;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets info about the control panels
|
|
//-----------------------------------------------------------------------------
|
|
void CC4::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
|
|
{
|
|
pPanelName = "c4_view_panel";
|
|
}
|
|
|
|
bool CC4::ShouldRemoveOnRoundRestart()
|
|
{
|
|
// Doesn't matter if we have an owner or not.. always remove the C4 when the round restarts.
|
|
// The gamerules will give another C4 to some lucky player.
|
|
CCSPlayer *pPlayer = GetPlayerOwner();
|
|
if ( pPlayer && pPlayer->GetActiveWeapon() == this )
|
|
engine->ClientCommand( pPlayer->edict(), "lastinv reset\n" );
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
bool CC4::Deploy( )
|
|
{
|
|
//m_bShowC4LED = true;
|
|
#ifdef CLIENT_DLL
|
|
//CreateLEDEffect();
|
|
#endif
|
|
|
|
return BaseClass::Deploy();
|
|
}
|
|
|
|
bool CC4::Holster( CBaseCombatWeapon *pSwitchingTo )
|
|
{
|
|
|
|
#ifdef GAME_DLL
|
|
CCSPlayer *pPlayer = GetPlayerOwner();
|
|
if ( pPlayer )
|
|
pPlayer->SetProgressBarTime( 0 );
|
|
|
|
if ( m_bStartedArming )
|
|
{
|
|
AbortBombPlant();
|
|
}
|
|
#else
|
|
//RemoveLEDEffect();
|
|
#endif
|
|
|
|
//m_bShowC4LED = false;
|
|
|
|
return BaseClass::Holster( pSwitchingTo );
|
|
}
|
|
|
|
void CC4::PrimaryAttack()
|
|
{
|
|
bool bArmingTimeSatisfied = false;
|
|
CCSPlayer *pPlayer = GetPlayerOwner();
|
|
if ( !pPlayer )
|
|
return;
|
|
|
|
int onGround = FBitSet( pPlayer->GetFlags(), FL_ONGROUND );
|
|
CBaseEntity *groundEntity = (onGround) ? pPlayer->GetGroundEntity() : NULL;
|
|
trace_t trPlant;
|
|
if ( groundEntity )
|
|
{
|
|
// Don't let us stand on players, breakables, or pushaway physics objects to plant
|
|
if ( groundEntity->IsPlayer() ||
|
|
IsPushableEntity( groundEntity ) ||
|
|
#ifndef CLIENT_DLL
|
|
IsBreakableEntity( groundEntity ) ||
|
|
#endif // !CLIENT_DLL
|
|
IsPushAwayEntity( groundEntity ) )
|
|
{
|
|
onGround = false;
|
|
}
|
|
|
|
if ( onGround )
|
|
{
|
|
Vector vecStart = GetAbsOrigin() + Vector(0,0,8);
|
|
Vector vecEnd = GetAbsOrigin() + Vector(0,0,-38);
|
|
UTIL_TraceHull( vecStart, vecEnd, Vector( -3, -3, 0 ), Vector( 3, 3, 16 ), MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_PLAYER_MOVEMENT, &trPlant );
|
|
if ( trPlant.fraction == 1.0 )
|
|
{
|
|
onGround = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( m_bStartedArming == false && m_bBombPlanted == false )
|
|
{
|
|
if( pPlayer->m_bInBombZone && onGround )
|
|
{
|
|
m_bStartedArming = true;
|
|
m_fArmedTime = gpGlobals->curtime + WEAPON_C4_ARM_TIME;
|
|
m_bBombPlacedAnimation = false;
|
|
|
|
pPlayer->m_bDuckOverride = true;
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
pPlayer->SetAttemptedBombPlace();
|
|
|
|
// init the beep flags
|
|
int i;
|
|
for( i=0;i<NUM_BEEPS;i++ )
|
|
m_bPlayedArmingBeeps[i] = false;
|
|
|
|
// freeze the player in place while planting
|
|
|
|
// player "arming bomb" animation
|
|
pPlayer->SetAnimation( PLAYER_ATTACK1 );
|
|
|
|
pPlayer->SetNextAttack( gpGlobals->curtime );
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_beginplant" );
|
|
if( event )
|
|
{
|
|
event->SetInt("userid", pPlayer->GetUserID() );
|
|
event->SetInt("site", pPlayer->m_iBombSiteIndex );
|
|
event->SetInt( "priority", 5 ); // bomb_beginplant
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
PlayPlantInitSound();
|
|
|
|
if ( pPlayer && !pPlayer->IsBot() && pPlayer->m_flC4PlantTalkTimer < gpGlobals->curtime )
|
|
{
|
|
// for console, we don't want to show the chat text because it almost always overlaps
|
|
// with the bomb planted alert text in the center of the screen
|
|
if ( IsGameConsole() || engine->IsDedicatedServerForXbox() || engine->IsDedicatedServerForPS3() )
|
|
pPlayer->Radio( "PlantingBomb", "", true );
|
|
else
|
|
pPlayer->Radio( "PlantingBomb", "#Cstrike_TitlesTXT_Planting_Bomb", true );
|
|
pPlayer->m_flC4PlantTalkTimer = gpGlobals->curtime + 10.0f;
|
|
}
|
|
#endif
|
|
|
|
SendWeaponAnim( ACT_VM_PRIMARYATTACK );
|
|
|
|
#ifndef CLIENT_DLL
|
|
if ( pPlayer && !pPlayer->IsDormant() )
|
|
{
|
|
pPlayer->DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN_PRIMARY );
|
|
}
|
|
#endif
|
|
//FX_PlantBomb( pPlayer->entindex(), pPlayer->Weapon_ShootPosition(), PLANTBOMB_PLANT );
|
|
}
|
|
else
|
|
{
|
|
if ( !pPlayer->GetUseEntity() )
|
|
{
|
|
if ( !pPlayer->m_bInBombZone )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCENTER, "#SFUI_Notice_C4_Plant_At_Bomb_Spot");
|
|
|
|
#if defined( CLIENT_DLL )
|
|
STEAMWORKS_TESTSECRET_AMORTIZE(5);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCENTER, "#SFUI_Notice_C4_Plant_Must_Be_On_Ground");
|
|
|
|
#if defined( CLIENT_DLL )
|
|
STEAMWORKS_TESTSECRET_AMORTIZE(7);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
m_flNextPrimaryAttack = gpGlobals->curtime + 1.0;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( !onGround || !pPlayer->m_bInBombZone )
|
|
{
|
|
if( !pPlayer->m_bInBombZone )
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCENTER, "#SFUI_Notice_C4_Arming_Cancelled" );
|
|
}
|
|
else
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCENTER, "#SFUI_Notice_C4_Plant_Must_Be_On_Ground" );
|
|
|
|
#if defined( CLIENT_DLL )
|
|
STEAMWORKS_TESTSECRET_AMORTIZE(9);
|
|
#endif
|
|
}
|
|
|
|
AbortBombPlant();
|
|
|
|
if(m_bBombPlacedAnimation == true) //this means the placement animation is canceled
|
|
{
|
|
SendWeaponAnim( ACT_VM_DRAW );
|
|
}
|
|
else
|
|
{
|
|
SendWeaponAnim( ACT_VM_IDLE );
|
|
}
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// we no longer play arming beeps to all player, we only play the initialization sound
|
|
// #ifndef CLIENT_DLL
|
|
// PlayArmingBeeps();
|
|
// #endif
|
|
|
|
if( gpGlobals->curtime >= m_fArmedTime ) //the c4 is ready to be armed
|
|
{
|
|
//check to make sure the player is still in the bomb target area
|
|
bArmingTimeSatisfied = true;
|
|
}
|
|
else if( ( gpGlobals->curtime >= (m_fArmedTime - 0.75) ) && ( !m_bBombPlacedAnimation ) )
|
|
{
|
|
//call the c4 Placement animation
|
|
m_bBombPlacedAnimation = true;
|
|
|
|
SendWeaponAnim( ACT_VM_SECONDARYATTACK );
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
// player "place" animation
|
|
//pPlayer->SetAnimation( PLAYER_HOLDBOMB );
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bArmingTimeSatisfied && m_bStartedArming )
|
|
{
|
|
m_bStartedArming = false;
|
|
m_fArmedTime = 0;
|
|
|
|
if( pPlayer->m_bInBombZone )
|
|
{
|
|
#if !defined( CLIENT_DLL )
|
|
CPlantedC4 *pC4 = CPlantedC4::ShootSatchelCharge( pPlayer, pPlayer->GetAbsOrigin(), pPlayer->GetAbsAngles() );
|
|
|
|
if ( pC4 && trPlant.fraction < 1.0 )
|
|
{
|
|
pC4->SetBombSiteIndex( pPlayer->m_iBombSiteIndex );
|
|
pC4->SetAbsOrigin( trPlant.endpos );
|
|
|
|
//bomb aligns to planted surface normal within a threshold
|
|
if ( fabs(trPlant.plane.normal.Dot(Vector(0,0,1))) > 0.65f )
|
|
{
|
|
//get the player forward vector
|
|
Vector vecFlatForward;
|
|
VectorCopy( pPlayer->Forward(), vecFlatForward );
|
|
vecFlatForward.z = 0;
|
|
|
|
//derive c4 forward and right
|
|
Vector vecC4Right = CrossProduct( vecFlatForward.Normalized(), trPlant.plane.normal );
|
|
Vector vecC4Forward = CrossProduct( vecC4Right, trPlant.plane.normal );
|
|
|
|
QAngle C4Angle;
|
|
VectorAngles( -vecC4Forward, trPlant.plane.normal, C4Angle );
|
|
pC4->SetAbsAngles( C4Angle );
|
|
}
|
|
|
|
CBombTarget *pBombTarget = (CBombTarget*)UTIL_EntityByIndex( pPlayer->m_iBombSiteIndex );
|
|
|
|
if ( pBombTarget )
|
|
{
|
|
CBaseEntity *pAttachPoint = gEntList.FindEntityByName( NULL, pBombTarget->GetBombMountTarget() );
|
|
|
|
if ( pAttachPoint )
|
|
{
|
|
pC4->SetAbsOrigin( pAttachPoint->GetAbsOrigin() );
|
|
pC4->SetAbsAngles( pAttachPoint->GetAbsAngles() );
|
|
pC4->SetParent( pAttachPoint );
|
|
}
|
|
|
|
variant_t emptyVariant;
|
|
pBombTarget->AcceptInput( "BombPlanted", pC4, pC4, emptyVariant, 0 );
|
|
}
|
|
|
|
// [tj] If the bomb is planted by someone that picked it up after the
|
|
// original owner was killed, pass that along to the planted bomb
|
|
pC4->SetPlantedAfterPickup( m_bDroppedFromDeath );
|
|
}
|
|
|
|
// Determine how elapsed time from start of round until the bomb was planted
|
|
float plantingTime = gpGlobals->curtime - CSGameRules()->GetRoundStartTime();
|
|
|
|
// Award achievement to bomb planter if time <= 25 seconds
|
|
if ( (plantingTime > 0.0f) &&
|
|
(plantingTime <= AchievementConsts::FastBombPlant_Time) &&
|
|
!CSGameRules()->IsPlayingGunGameTRBomb() )
|
|
{
|
|
pPlayer->AwardAchievement( CSPlantBombWithin25Seconds );
|
|
}
|
|
|
|
pPlayer->SetLastWeaponBeforeAutoSwitchToC4( NULL ); // completed a bomb plant, this clears out our saved value for switching back to a saved weapon
|
|
pPlayer->SetBombPlacedTime( gpGlobals->curtime );
|
|
CCS_GameStats.Event_BombPlanted( pPlayer );
|
|
CSGameRules()->ScoreBombPlant( pPlayer );
|
|
|
|
// record in matchstats
|
|
if ( CSGameRules()->ShouldRecordMatchStats() )
|
|
{
|
|
int iCurrentRound = CSGameRules()->GetTotalRoundsPlayed();
|
|
++ pPlayer->m_iMatchStats_Objective.GetForModify( iCurrentRound );
|
|
|
|
// Keep track of Match stats in QMM data
|
|
if ( CCSGameRules::CQMMPlayerData_t *pQMM = CSGameRules()->QueuedMatchmakingPlayersDataFind( pPlayer->GetHumanPlayerAccountID() ) )
|
|
{
|
|
pQMM->m_iMatchStats_Objective[ iCurrentRound ] = pPlayer->m_iMatchStats_Objective.Get( iCurrentRound );
|
|
}
|
|
}
|
|
|
|
pPlayer->AddAccountAward( PlayerCashAward::BOMB_PLANTED );
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_planted" );
|
|
if( event )
|
|
{
|
|
event->SetInt("userid", pPlayer->GetUserID() );
|
|
event->SetInt("site", pPlayer->m_iBombSiteIndex );
|
|
event->SetInt("posx", pPlayer->GetAbsOrigin().x );
|
|
event->SetInt("posy", pPlayer->GetAbsOrigin().y );
|
|
event->SetInt( "priority", 5 ); // bomb_planted
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
// Fire a beep event also so the bots have a chance to hear the bomb
|
|
event = gameeventmanager->CreateEvent( "bomb_beep" );
|
|
|
|
if ( event )
|
|
{
|
|
event->SetInt( "entindex", entindex() );
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
pPlayer->SetProgressBarTime( 0 );
|
|
|
|
CSGameRules()->m_bBombDropped = false;
|
|
CSGameRules()->m_bBombPlanted = true;
|
|
|
|
|
|
// Play the plant sound.
|
|
// don't play a bomb plant sound for everyone anymore ?
|
|
Vector plantPosition = pPlayer->GetAbsOrigin() + Vector( 0, 0, 5 );
|
|
CPASAttenuationFilter filter( plantPosition );
|
|
EmitSound( filter, 0, "c4.plantquiet", &GetAbsOrigin() );
|
|
|
|
// No more c4!
|
|
pPlayer->Weapon_Drop( this, NULL, NULL );
|
|
UTIL_Remove( this );
|
|
|
|
pPlayer->m_bDuckOverride = false;
|
|
#endif
|
|
//don't allow the planting to start over again next frame.
|
|
m_bBombPlanted = true;
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
if ( CSGameRules()->IsPlayingCooperativeGametype() )
|
|
CSGameRules()->CheckWinConditions();// %plant bomb%
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ClientPrint( pPlayer, HUD_PRINTCENTER, "#SFUI_Notice_C4_Activated_At_Bomb_Spot" );
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
//pPlayer->SetAnimation( PLAYER_HOLDBOMB );
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_abortplant" );
|
|
if( event )
|
|
{
|
|
event->SetInt("userid", pPlayer->GetUserID() );
|
|
event->SetInt("site", pPlayer->m_iBombSiteIndex );
|
|
event->SetInt( "priority", 5 ); // bomb_abortplant
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
#endif
|
|
|
|
m_flNextPrimaryAttack = gpGlobals->curtime + 1.0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_flNextPrimaryAttack = gpGlobals->curtime + 0.3;
|
|
SetWeaponIdleTime( gpGlobals->curtime + SharedRandomFloat("C4IdleTime", 10, 15 ) );
|
|
}
|
|
|
|
void CC4::WeaponIdle()
|
|
{
|
|
// if the player releases the attack button cancel the arming sequence
|
|
if ( m_bStartedArming )
|
|
{
|
|
AbortBombPlant();
|
|
|
|
CCSPlayer *pPlayer = GetPlayerOwner();
|
|
|
|
pPlayer->m_bDuckOverride = false;
|
|
|
|
// TODO: make this use SendWeaponAnim and activities when the C4 has the activities hooked up.
|
|
if ( pPlayer )
|
|
{
|
|
SendWeaponAnim( ACT_VM_IDLE );
|
|
pPlayer->SetNextAttack( gpGlobals->curtime );
|
|
}
|
|
|
|
if(m_bBombPlacedAnimation == true) //this means the placement animation is canceled
|
|
SendWeaponAnim( ACT_VM_DRAW );
|
|
else
|
|
SendWeaponAnim( ACT_VM_IDLE );
|
|
}
|
|
}
|
|
|
|
void CC4::UpdateShieldState( void )
|
|
{
|
|
//ADRIANTODO
|
|
CCSPlayer *pPlayer = GetPlayerOwner();
|
|
if ( !pPlayer )
|
|
return;
|
|
|
|
if ( pPlayer->HasShield() )
|
|
{
|
|
pPlayer->SetShieldDrawnState( false );
|
|
|
|
CBaseViewModel *pVM = pPlayer->GetViewModel( 1 );
|
|
|
|
if ( pVM )
|
|
{
|
|
pVM->AddEffects( EF_NODRAW );
|
|
}
|
|
//pPlayer->SetHitBoxSet( 3 );
|
|
}
|
|
else
|
|
BaseClass::UpdateShieldState();
|
|
}
|
|
|
|
|
|
int m_iBeepFrames[NUM_BEEPS] = { 20, 29, 37, 44, 50, 59, 65 };
|
|
int iNumArmingAnimFrames = 83;
|
|
|
|
void CC4::PlayArmingBeeps( void )
|
|
{
|
|
float flStartTime = m_fArmedTime - WEAPON_C4_ARM_TIME;
|
|
|
|
float flProgress = ( gpGlobals->curtime - flStartTime ) / ( WEAPON_C4_ARM_TIME - 0.75 );
|
|
|
|
int currentFrame = (int)( (float)iNumArmingAnimFrames * flProgress );
|
|
|
|
int i;
|
|
for( i=0;i<NUM_BEEPS;i++ )
|
|
{
|
|
if( currentFrame <= m_iBeepFrames[i] )
|
|
{
|
|
break;
|
|
}
|
|
else if( !m_bPlayedArmingBeeps[i] )
|
|
{
|
|
m_bPlayedArmingBeeps[i] = true;
|
|
|
|
CCSPlayer *owner = GetPlayerOwner();
|
|
if ( !owner && !owner->IsAlive() )
|
|
break;
|
|
|
|
Vector soundPosition = owner->GetAbsOrigin() + Vector( 0, 0, 5 );
|
|
CPASAttenuationFilter filter( soundPosition );
|
|
|
|
filter.RemoveRecipient( owner );
|
|
|
|
// remove anyone that is first person spec'ing the planter
|
|
int i;
|
|
CBasePlayer *pPlayer;
|
|
for( i=1;i<=gpGlobals->maxClients;i++ )
|
|
{
|
|
pPlayer = UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
if( pPlayer->GetObserverMode() == OBS_MODE_IN_EYE && pPlayer->GetObserverTarget() == GetOwner() )
|
|
{
|
|
filter.RemoveRecipient( pPlayer );
|
|
}
|
|
}
|
|
|
|
EmitSound(filter, 0, "c4.click", &GetAbsOrigin() );
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CC4::PlayPlantInitSound( void )
|
|
{
|
|
CCSPlayer *owner = GetPlayerOwner();
|
|
if ( !owner && !owner->IsAlive() )
|
|
return;
|
|
|
|
Vector soundPosition = owner->GetAbsOrigin() + Vector( 0, 0, 5 );
|
|
CPASAttenuationFilter filter( soundPosition );
|
|
|
|
filter.RemoveRecipient( owner );
|
|
|
|
// remove anyone that is first person spec'ing the planter
|
|
int i;
|
|
CBasePlayer *pPlayer;
|
|
for( i=1;i<=gpGlobals->maxClients;i++ )
|
|
{
|
|
pPlayer = UTIL_PlayerByIndex( i );
|
|
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
if( pPlayer->GetObserverMode() == OBS_MODE_IN_EYE && pPlayer->GetObserverTarget() == GetOwner() )
|
|
{
|
|
filter.RemoveRecipient( pPlayer );
|
|
}
|
|
}
|
|
|
|
EmitSound(filter, 0, "c4.initiate", &GetAbsOrigin() );
|
|
}
|
|
|
|
float CC4::GetMaxSpeed() const
|
|
{
|
|
if ( m_bStartedArming )
|
|
return CS_PLAYER_SPEED_STOPPED;
|
|
else
|
|
return BaseClass::GetMaxSpeed();
|
|
}
|
|
|
|
|
|
void CC4::OnPickedUp( CBaseCombatCharacter *pNewOwner )
|
|
{
|
|
BaseClass::OnPickedUp( pNewOwner );
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
CCSPlayer *pPlayer = dynamic_cast<CCSPlayer *>( pNewOwner );
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_pickup" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "userid", pPlayer->GetUserID() );
|
|
event->SetInt( "priority", 5 ); // bomb_pickup
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
CSGameRules()->m_bBombDropped = false;
|
|
|
|
if ( !CSGameRules()->IsPlayingTraining() )
|
|
ClientPrint( pPlayer, HUD_PRINTCENTER, "#SFUI_Notice_Got_Bomb" );
|
|
|
|
pPlayer->SetBombPickupTime( gpGlobals->curtime );
|
|
#endif
|
|
}
|
|
|
|
// HACK - Ask Mike Booth...
|
|
#ifndef CLIENT_DLL
|
|
#include "cs_bot.h"
|
|
#endif
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
void CC4::Drop( const Vector &vecVelocity )
|
|
{
|
|
#if !defined( CLIENT_DLL )
|
|
if ( !CSGameRules()->m_bBombPlanted ) // its not dropped if its planted
|
|
{
|
|
// tell the bots about the dropped bomb
|
|
TheCSBots()->SetLooseBomb( this );
|
|
|
|
CSGameRules()->m_bBombDropped = true;
|
|
|
|
CBasePlayer *pPlayer = dynamic_cast<CBasePlayer *>(GetOwnerEntity());
|
|
Assert( pPlayer );
|
|
if ( pPlayer )
|
|
{
|
|
CCSPlayer *pCCSPlayer = dynamic_cast<CCSPlayer *>( pPlayer );
|
|
|
|
pCCSPlayer->SetBombDroppedTime( gpGlobals->curtime );
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_dropped" );
|
|
if ( event )
|
|
{
|
|
event->SetInt( "userid", pPlayer->GetUserID() );
|
|
event->SetInt( "entindex", entindex() );
|
|
event->SetInt( "priority", 5 ); // bomb_dropped
|
|
gameeventmanager->FireEvent( event );
|
|
|
|
//event->SetInt( "entindex", iTest );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined( CLIENT_DLL )
|
|
STEAMWORKS_TESTSECRET_AMORTIZE( 13 );
|
|
#endif
|
|
|
|
if ( m_bStartedArming )
|
|
AbortBombPlant(); // stop arming sequence
|
|
|
|
BaseClass::Drop( vecVelocity );
|
|
}
|
|
|
|
void CC4::AbortBombPlant()
|
|
{
|
|
m_bStartedArming = false;
|
|
|
|
CCSPlayer *pPlayer = GetPlayerOwner();
|
|
if ( !pPlayer )
|
|
return;
|
|
|
|
#if !defined( CLIENT_DLL )
|
|
|
|
m_flNextPrimaryAttack = gpGlobals->curtime + 1.0;
|
|
|
|
pPlayer->SetProgressBarTime( 0 );
|
|
|
|
IGameEvent * event = gameeventmanager->CreateEvent( "bomb_abortplant" );
|
|
if( event )
|
|
{
|
|
event->SetInt("userid", pPlayer->GetUserID() );
|
|
event->SetInt("site", pPlayer->m_iBombSiteIndex );
|
|
event->SetInt( "priority", 5 ); // bomb_abortplant
|
|
gameeventmanager->FireEvent( event );
|
|
}
|
|
|
|
if( pPlayer->GetLastWeaponBeforeAutoSwitchToC4() != NULL )
|
|
{
|
|
CBaseViewModel *vm = pPlayer->GetViewModel();
|
|
if ( vm )
|
|
{
|
|
vm->AddEffects( EF_NODRAW );
|
|
}
|
|
|
|
pPlayer->Weapon_Switch( pPlayer->GetLastWeaponBeforeAutoSwitchToC4() );
|
|
pPlayer->SetLastWeaponBeforeAutoSwitchToC4( NULL );
|
|
}
|
|
|
|
#else
|
|
|
|
// Clear the numbers from the screen if we just aborted.
|
|
m_szScreenText[0] = '\0';
|
|
|
|
#endif
|
|
|
|
#ifndef CLIENT_DLL
|
|
if ( pPlayer && !pPlayer->IsDormant() )
|
|
{
|
|
pPlayer->DoAnimationEvent( PLAYERANIMEVENT_CLEAR_FIRING );
|
|
}
|
|
#endif
|
|
//FX_PlantBomb( pPlayer->entindex(), pPlayer->Weapon_ShootPosition(), PLANTBOMB_ABORT );
|
|
}
|