|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "tf_weapon_pipebomblauncher.h"
#include "tf_fx_shared.h"
#include "tf_weapon_grenade_pipebomb.h"
#include "in_buttons.h"
#include "datacache/imdlcache.h"
#include "tf_gamerules.h"
// Client specific.
#ifdef CLIENT_DLL
#include "c_tf_player.h"
#include <vgui_controls/Panel.h>
#include <vgui/ISurface.h>
#include "prediction.h"
#include "c_tf_gamestats.h"
// Server specific.
#else
#include "tf_player.h"
#include "tf_gamestats.h"
#endif
#define TF_PIPEBOMB_HIGHLIGHT 1
#define TF_PIPEBOMB_DETONATE 2
#define TF_WEAPON_PIPEBOMBD_MODEL "models/weapons/w_models/w_stickybomb_d.mdl"
#define TF_WEAPON_PIPEBOMB_LAUNCHER_CHARGE_SOUND "Weapon_StickyBombLauncher.ChargeUp"
//=============================================================================
//
// Weapon Pipebomb Launcher tables.
//
IMPLEMENT_NETWORKCLASS_ALIASED( TFPipebombLauncher, DT_WeaponPipebombLauncher )
BEGIN_NETWORK_TABLE_NOBASE( CTFPipebombLauncher, DT_PipebombLauncherLocalData ) #ifdef CLIENT_DLL
RecvPropInt( RECVINFO( m_iPipebombCount ) ), RecvPropFloat( RECVINFO( m_flChargeBeginTime ) ), #else
SendPropInt( SENDINFO( m_iPipebombCount ), 5, SPROP_UNSIGNED ), SendPropFloat( SENDINFO( m_flChargeBeginTime ) ), #endif
END_NETWORK_TABLE()
BEGIN_NETWORK_TABLE( CTFPipebombLauncher, DT_WeaponPipebombLauncher ) #ifdef CLIENT_DLL
RecvPropDataTable( "PipebombLauncherLocalData", 0, 0, &REFERENCE_RECV_TABLE( DT_PipebombLauncherLocalData ) ), #else
SendPropDataTable( "PipebombLauncherLocalData", 0, &REFERENCE_SEND_TABLE( DT_PipebombLauncherLocalData ), SendProxy_SendLocalWeaponDataTable ), #endif
END_NETWORK_TABLE()
#ifdef CLIENT_DLL
BEGIN_PREDICTION_DATA( CTFPipebombLauncher ) DEFINE_FIELD( m_flChargeBeginTime, FIELD_FLOAT ) END_PREDICTION_DATA() #endif
LINK_ENTITY_TO_CLASS( tf_weapon_pipebomblauncher, CTFPipebombLauncher ); PRECACHE_WEAPON_REGISTER( tf_weapon_pipebomblauncher );
// Server specific.
#ifndef CLIENT_DLL
BEGIN_DATADESC( CTFPipebombLauncher ) END_DATADESC() #endif
//=============================================================================
//
// Weapon Pipebomb Launcher functions.
//
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
CTFPipebombLauncher::CTFPipebombLauncher() { m_bReloadsSingly = true; m_flLastDenySoundTime = 0.0f; m_bNoAutoRelease = false; m_bWantsToShoot = false; #ifdef CLIENT_DLL
m_flNextBombCheckTime = 0; m_bBombThinking = false; #endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
CTFPipebombLauncher::~CTFPipebombLauncher() { }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::Spawn( void ) { m_iAltFireHint = HINT_ALTFIRE_PIPEBOMBLAUNCHER; BaseClass::Spawn(); }
//-----------------------------------------------------------------------------
// Purpose: Reset the charge when we holster
//-----------------------------------------------------------------------------
bool CTFPipebombLauncher::Holster( CBaseCombatWeapon *pSwitchingTo ) { m_flChargeBeginTime = 0;
return BaseClass::Holster( pSwitchingTo ); }
//-----------------------------------------------------------------------------
// Purpose: Reset the charge when we deploy
//-----------------------------------------------------------------------------
bool CTFPipebombLauncher::Deploy( void ) { m_flChargeBeginTime = 0;
return BaseClass::Deploy(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::WeaponReset( void ) { BaseClass::WeaponReset();
#ifndef CLIENT_DLL
DetonateRemotePipebombs( true ); #endif
m_flChargeBeginTime = 0.0f; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::ItemPostFrame( void ) { BaseClass::ItemPostFrame();
if ( m_flChargeBeginTime > 0 ) { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return;
// If we're not holding down the attack button, launch our grenade
if ( m_iClip1 > 0 && !(pPlayer->m_nButtons & IN_ATTACK) && (pPlayer->m_afButtonReleased & IN_ATTACK) ) { LaunchGrenade(); } else if ( !m_bNoAutoRelease ) { float flTotalChargeTime = gpGlobals->curtime - m_flChargeBeginTime; if ( flTotalChargeTime >= GetChargeForceReleaseTime() ) { ForceLaunchGrenade(); } } } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::PrimaryAttack( void ) { // Check for ammunition.
if ( m_iClip1 <= 0 && m_iClip1 != -1 ) return;
// Are we capable of firing again?
if ( m_flNextPrimaryAttack > gpGlobals->curtime ) return;
if ( !CanAttack() ) { m_flChargeBeginTime = 0; return; }
if ( m_flChargeBeginTime <= 0 ) { // Set the weapon mode.
m_iWeaponMode = TF_WEAPON_PRIMARY_MODE;
// save that we had the attack button down
m_flChargeBeginTime = gpGlobals->curtime;
SendWeaponAnim( ACT_VM_PULLBACK );
#ifdef CLIENT_DLL
EmitSound( TF_WEAPON_PIPEBOMB_LAUNCHER_CHARGE_SOUND ); #endif // CLIENT_DLL
} else { float flTotalChargeTime = gpGlobals->curtime - m_flChargeBeginTime;
if ( flTotalChargeTime >= GetChargeMaxTime() ) { LaunchGrenade(); } }
#ifdef CLIENT_DLL
if ( GetDetonateMode() == TF_DETONATE_MODE_DOT && !m_bBombThinking ) { m_bBombThinking = true; SetContextThink( &CTFPipebombLauncher::BombHighlightThink, gpGlobals->curtime + 0.1f, "BOMB_HIGHLIGHT_THINK" ); } #endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
#ifdef CLIENT_DLL
void CTFPipebombLauncher::BombHighlightThink( void ) { ModifyPipebombsInView( TF_PIPEBOMB_HIGHLIGHT ); if ( GetOwner() ) { SetContextThink( &CTFPipebombLauncher::BombHighlightThink, gpGlobals->curtime + 0.1f, "BOMB_HIGHLIGHT_THINK" ); } } #endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::WeaponIdle( void ) { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return;
if ( m_flChargeBeginTime > 0 && m_iClip1 > 0 && (pPlayer->m_afButtonReleased & IN_ATTACK) ) { if ( m_iClip1 > 0 ) { m_bWantsToShoot = true; } }
if ( m_bWantsToShoot ) { LaunchGrenade(); } else { BaseClass::WeaponIdle(); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::LaunchGrenade( void ) { // Get the player owning the weapon.
CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return;
m_bWantsToShoot = false;
CalcIsAttackCritical();
SendWeaponAnim( ACT_VM_PRIMARYATTACK );
pPlayer->SetAnimation( PLAYER_ATTACK1 ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
CTFGrenadePipebombProjectile *pProjectile = static_cast<CTFGrenadePipebombProjectile*>( FireProjectile( pPlayer ) ); if ( pProjectile ) { // Save the charge time to scale the detonation timer.
pProjectile->SetChargeTime( gpGlobals->curtime - m_flChargeBeginTime );
#ifdef GAME_DLL
if ( GetDetonateMode() == TF_DETONATE_MODE_AIR ) { pProjectile->m_bWallShatter = true; } else if ( GetDetonateMode() == TF_DETONATE_MODE_DOT ) { pProjectile->m_bDefensiveBomb = true; pProjectile->SetModel( TF_WEAPON_PIPEBOMBD_MODEL ); }
float flChargeDmg = 1.0f; CALL_ATTRIB_HOOK_FLOAT( flChargeDmg, stickybomb_charge_damage_increase ); if ( flChargeDmg != 1.0f ) { float flDamage = pProjectile->GetDamage(); flDamage += flDamage * ( flChargeDmg - 1.0f ) * GetCurrentCharge(); pProjectile->SetDamage( flDamage ); } #endif // GAME_DLL
}
#ifdef CLIENT_DLL
C_CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() ); StopSound( TF_WEAPON_PIPEBOMB_LAUNCHER_CHARGE_SOUND ); #else
pPlayer->SpeakWeaponFire(); CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() ); #endif
// Set next attack times.
float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay );
m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay; m_flLastDenySoundTime = gpGlobals->curtime;
SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() );
// Check the reload mode and behave appropriately.
if ( m_bReloadsSingly ) { m_iReloadMode.Set( TF_RELOAD_START ); }
m_flChargeBeginTime = 0;
if ( TFGameRules()->GameModeUsesUpgrades() ) { PlayUpgradedShootSound( "Weapon_Upgrade.DamageBonus" ); } }
float CTFPipebombLauncher::GetProjectileSpeed( void ) { float flForwardSpeed = RemapValClamped( ( gpGlobals->curtime - m_flChargeBeginTime ), 0.0f, GetChargeMaxTime(), TF_PIPEBOMB_MIN_CHARGE_VEL, TF_PIPEBOMB_MAX_CHARGE_VEL );
return flForwardSpeed; }
void CTFPipebombLauncher::AddPipeBomb( CTFGrenadePipebombProjectile *pBomb ) { PipebombHandle hHandle; hHandle = pBomb; m_Pipebombs.AddToTail( hHandle ); }
//-----------------------------------------------------------------------------
// Purpose: Add pipebombs to our list as they're fired
//-----------------------------------------------------------------------------
CBaseEntity *CTFPipebombLauncher::FireProjectile( CTFPlayer *pPlayer ) { CBaseEntity *pProjectile = BaseClass::FireProjectile( pPlayer ); if ( pProjectile ) { #ifdef GAME_DLL
// If we've gone over the max pipebomb count, detonate the oldest
int nMaxPipebombs = TF_WEAPON_PIPEBOMB_COUNT; CALL_ATTRIB_HOOK_INT( nMaxPipebombs, add_max_pipebombs ); if ( m_Pipebombs.Count() >= nMaxPipebombs ) { CTFGrenadePipebombProjectile *pTemp = m_Pipebombs[0]; if ( pTemp ) { pTemp->SetTimer( gpGlobals->curtime ); // explode NOW
}
m_Pipebombs.Remove(0); }
CTFGrenadePipebombProjectile *pPipebomb = (CTFGrenadePipebombProjectile*)pProjectile;
PipebombHandle hHandle; hHandle = pPipebomb; m_Pipebombs.AddToTail( hHandle );
m_iPipebombCount = m_Pipebombs.Count(); #endif
}
return pProjectile; }
//-----------------------------------------------------------------------------
// Purpose: Detonate this demoman's pipebombs if secondary fire is down.
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::ItemBusyFrame( void ) { #ifdef GAME_DLL
CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner && pOwner->m_nButtons & IN_ATTACK2 ) { // We need to do this to catch the case of player trying to detonate
// pipebombs while in the middle of reloading.
SecondaryAttack(); } #endif
BaseClass::ItemBusyFrame(); }
//-----------------------------------------------------------------------------
// Purpose: Detonate active pipebombs
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::SecondaryAttack( void ) { if ( !CanAttack() ) return;
if ( m_iPipebombCount ) { // Get a valid player.
CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( !pPlayer ) return;
//If one or more pipebombs failed to detonate then play a sound.
if ( DetonateRemotePipebombs( false ) == true ) { if ( m_flLastDenySoundTime <= gpGlobals->curtime ) { // Deny!
m_flLastDenySoundTime = gpGlobals->curtime + 1; WeaponSound( SPECIAL2 ); return; } } else { // Play a detonate sound.
WeaponSound( SPECIAL3 );
#ifdef GAME_DLL
IGameEvent *pDetEvent = gameeventmanager->CreateEvent( "demoman_det_stickies" );
if ( pDetEvent ) { pDetEvent->SetInt( "player", pPlayer->entindex() );
// Send the event
gameeventmanager->FireEvent( pDetEvent ); } #endif
} } }
//=============================================================================
//
// Server specific functions.
//
#ifdef GAME_DLL
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::UpdateOnRemove(void) { // If we just died, we want to fizzle our pipebombs.
// If the player switched classes, our pipebombs have already been removed.
DetonateRemotePipebombs( true );
BaseClass::UpdateOnRemove(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::ApplyPostHitEffects( const CTakeDamageInfo &inputInfo, CTFPlayer *pPlayer ) { if ( !GetTFPlayerOwner() ) return;
if ( pPlayer->m_Shared.GetWeaponKnockbackID() == -1 ) { pPlayer->m_Shared.SetWeaponKnockbackID( GetTFPlayerOwner()->GetUserID() ); } }
#endif
//-----------------------------------------------------------------------------
// Purpose: If a pipebomb has been removed, remove it from our list
//-----------------------------------------------------------------------------
void CTFPipebombLauncher::DeathNotice( CBaseEntity *pVictim ) { Assert( dynamic_cast<CTFGrenadePipebombProjectile*>(pVictim) );
PipebombHandle hHandle; hHandle = (CTFGrenadePipebombProjectile*)pVictim; m_Pipebombs.FindAndRemove( hHandle );
m_iPipebombCount = m_Pipebombs.Count(); }
//-----------------------------------------------------------------------------
// Purpose: Remove *with* explosions
//-----------------------------------------------------------------------------
bool CTFPipebombLauncher::DetonateRemotePipebombs( bool bFizzle ) { if ( GetDetonateMode() == TF_DETONATE_MODE_DOT && !bFizzle ) { return ModifyPipebombsInView( TF_PIPEBOMB_DETONATE ); }
bool bFailedToDetonate = false;
int count = m_Pipebombs.Count();
for ( int i = 0; i < count; i++ ) { CTFGrenadePipebombProjectile *pTemp = m_Pipebombs[i]; if ( pTemp ) { //This guy will die soon enough.
if ( pTemp->IsEffectActive( EF_NODRAW ) ) continue; #ifdef GAME_DLL
if ( bFizzle ) { pTemp->Fizzle(); } #endif
if ( bFizzle == false ) { if ( ( gpGlobals->curtime - pTemp->m_flCreationTime ) < pTemp->GetLiveTime() ) { if ( pTemp->GetLiveTime() <= 0.5f ) { pTemp->SetDetonateOnPulse( true ); } bFailedToDetonate = true; continue; } } #ifdef GAME_DLL
if ( CanDestroyStickies() ) { pTemp->DetonateStickies(); } pTemp->Detonate(); #endif
} }
return bFailedToDetonate; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPipebombLauncher::ModifyPipebombsInView( int iEffect ) { CTFPlayer* pPlayer = ToTFPlayer( GetOwner() ); if ( !pPlayer ) return true;
// Dot product from the view angle to determine which bombs to detonate.
bool bFailedToDetonate = true; int count = m_Pipebombs.Count(); for ( int i=0; i<count; ++i ) { CTFGrenadePipebombProjectile *pTemp = m_Pipebombs[i]; if ( !pTemp || pTemp->IsEffectActive( EF_NODRAW ) ) continue;
Vector vecToTarget; vecToTarget = pTemp->WorldSpaceCenter() - pPlayer->EyePosition(); vecToTarget.NormalizeInPlace();
Vector vecPlayerForward; AngleVectors( pPlayer->EyeAngles(), &vecPlayerForward, NULL, NULL ); vecPlayerForward.NormalizeInPlace();
bool bArmed = ( ( gpGlobals->curtime - pTemp->m_flCreationTime ) > pTemp->GetLiveTime() ); float flDist = pPlayer->GetAbsOrigin().DistTo( pTemp->GetAbsOrigin() ); float flDot = DotProduct( vecToTarget, vecPlayerForward );
// Detonate sticky bombs directly under the crosshair or under our feet (to allow sticky jumping)
if ( flDot > 0.975f || flDist < pTemp->GetDamageRadius() ) { switch ( iEffect ) { case TF_PIPEBOMB_HIGHLIGHT: #ifdef CLIENT_DLL
pTemp->SetHighlight( true ); #endif
break; case TF_PIPEBOMB_DETONATE: if ( bArmed ) { bFailedToDetonate = false; #ifdef GAME_DLL
if ( CanDestroyStickies() ) { pTemp->DetonateStickies(); } #endif
pTemp->Detonate(); } break; } } else if ( iEffect == TF_PIPEBOMB_HIGHLIGHT ) { #ifdef CLIENT_DLL
pTemp->SetHighlight( false ); #endif
} }
return bFailedToDetonate; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTFPipebombLauncher::Reload( void ) { if ( m_flChargeBeginTime > 0 ) return false;
return BaseClass::Reload(); }
|