summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_weapon_rocketlauncher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/tf_weapon_rocketlauncher.cpp')
-rw-r--r--game/shared/tf/tf_weapon_rocketlauncher.cpp816
1 files changed, 816 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weapon_rocketlauncher.cpp b/game/shared/tf/tf_weapon_rocketlauncher.cpp
new file mode 100644
index 0000000..7bde0e8
--- /dev/null
+++ b/game/shared/tf/tf_weapon_rocketlauncher.cpp
@@ -0,0 +1,816 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// TF Rocket Launcher
+//
+//=============================================================================
+#include "cbase.h"
+#include "tf_weapon_rocketlauncher.h"
+#include "tf_fx_shared.h"
+#include "tf_weaponbase_rocket.h"
+#include "in_buttons.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 "soundenvelope.h"
+#include "particle_property.h"
+// Server specific.
+#else
+#include "tf_player.h"
+#include "tf_obj_sentrygun.h"
+#include "tf_projectile_arrow.h"
+
+#endif
+
+#define BOMBARDMENT_ROCKET_MODEL "models/buildables/sentry3_rockets.mdl"
+
+//=============================================================================
+//
+// Weapon Rocket Launcher tables.
+//
+IMPLEMENT_NETWORKCLASS_ALIASED( TFRocketLauncher, DT_WeaponRocketLauncher )
+
+BEGIN_NETWORK_TABLE( CTFRocketLauncher, DT_WeaponRocketLauncher )
+#ifndef CLIENT_DLL
+// SendPropInt( SENDINFO( m_iSecondaryShotsFired ) ),
+#else
+// RecvPropInt( RECVINFO( m_iSecondaryShotsFired ) ),
+#endif
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CTFRocketLauncher )
+END_PREDICTION_DATA()
+
+LINK_ENTITY_TO_CLASS( tf_weapon_rocketlauncher, CTFRocketLauncher );
+PRECACHE_WEAPON_REGISTER( tf_weapon_rocketlauncher );
+
+// Server specific.
+#ifndef CLIENT_DLL
+BEGIN_DATADESC( CTFRocketLauncher )
+END_DATADESC()
+#endif
+
+//=============================================================================
+//
+// Direct Hit tables.
+//
+IMPLEMENT_NETWORKCLASS_ALIASED( TFRocketLauncher_DirectHit, DT_WeaponRocketLauncher_DirectHit )
+
+BEGIN_NETWORK_TABLE( CTFRocketLauncher_DirectHit, DT_WeaponRocketLauncher_DirectHit )
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CTFRocketLauncher_DirectHit )
+END_PREDICTION_DATA()
+
+LINK_ENTITY_TO_CLASS( tf_weapon_rocketlauncher_directhit, CTFRocketLauncher_DirectHit );
+PRECACHE_WEAPON_REGISTER( tf_weapon_rocketlauncher_directhit );
+
+// Server specific.
+#ifndef CLIENT_DLL
+BEGIN_DATADESC( CTFRocketLauncher_DirectHit )
+END_DATADESC()
+#endif
+
+//=============================================================================
+//
+// AIRSTRIKE BEGIN
+IMPLEMENT_NETWORKCLASS_ALIASED( TFRocketLauncher_AirStrike, DT_WeaponRocketLauncher_AirStrike )
+
+BEGIN_NETWORK_TABLE( CTFRocketLauncher_AirStrike, DT_WeaponRocketLauncher_AirStrike )
+#ifndef CLIENT_DLL
+// SendPropInt( SENDINFO( m_iRocketKills ) ),
+#else
+// RecvPropInt( RECVINFO( m_iRocketKills ) ),
+#endif
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CTFRocketLauncher_AirStrike )
+END_PREDICTION_DATA()
+
+LINK_ENTITY_TO_CLASS( tf_weapon_rocketlauncher_airstrike, CTFRocketLauncher_AirStrike );
+PRECACHE_WEAPON_REGISTER( tf_weapon_rocketlauncher_airstrike );
+
+// Server specific.
+#ifndef CLIENT_DLL
+BEGIN_DATADESC( CTFRocketLauncher_AirStrike )
+END_DATADESC()
+#endif
+// AIRSTRIKE END
+
+//CREATE_SIMPLE_WEAPON_TABLE( TFRocketLauncher_AirStrike, tf_weapon_rocketlauncher_airstrike )
+//CREATE_SIMPLE_WEAPON_TABLE( TFRocketLauncher_Mortar, tf_weapon_rocketlauncher_mortar )
+//=============================================================================
+//
+// Mortar tables.
+//
+IMPLEMENT_NETWORKCLASS_ALIASED( TFRocketLauncher_Mortar, DT_WeaponRocketLauncher_Mortar )
+
+BEGIN_NETWORK_TABLE( CTFRocketLauncher_Mortar, DT_WeaponRocketLauncher_Mortar )
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CTFRocketLauncher_Mortar )
+END_PREDICTION_DATA()
+
+#ifdef STAGING_ONLY
+LINK_ENTITY_TO_CLASS( tf_weapon_rocketlauncher_mortar, CTFRocketLauncher_Mortar );
+PRECACHE_WEAPON_REGISTER( tf_weapon_rocketlauncher_mortar );
+#endif // STAGING_ONLY
+
+// Server specific.
+#ifndef CLIENT_DLL
+BEGIN_DATADESC( CTFRocketLauncher_Mortar )
+END_DATADESC()
+#endif
+
+//=============================================================================
+//
+// Crossbow tables.
+//
+IMPLEMENT_NETWORKCLASS_ALIASED( TFCrossbow, DT_Crossbow )
+
+BEGIN_NETWORK_TABLE( CTFCrossbow, DT_Crossbow )
+#ifdef CLIENT_DLL
+ RecvPropFloat( RECVINFO( m_flRegenerateDuration ) ),
+ RecvPropFloat( RECVINFO( m_flLastUsedTimestamp ) ),
+#else
+ SendPropFloat( SENDINFO( m_flRegenerateDuration ), 0, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO( m_flLastUsedTimestamp ), 0, SPROP_NOSCALE ),
+#endif
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CTFCrossbow )
+END_PREDICTION_DATA()
+
+LINK_ENTITY_TO_CLASS( tf_weapon_crossbow, CTFCrossbow );
+PRECACHE_WEAPON_REGISTER( tf_weapon_crossbow );
+
+// Server specific.
+#ifndef CLIENT_DLL
+BEGIN_DATADESC( CTFCrossbow )
+END_DATADESC()
+#endif
+
+#ifdef STAGING_ONLY
+ConVar tf_airstrike_dmg_scale( "tf_airstrike_dmg_scale", "0.65", FCVAR_REPLICATED, "How much damage the mini rockets do compared to regular rocket" );
+ConVar tf_mortar_allow_fulltracking( "tf_mortar_allow_fulltracking", "0.0", FCVAR_REPLICATED, "Enable to allow full tracking / infinte redirects for Mortar Launcher" );
+#endif // STAGING_ONLY
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : -
+//-----------------------------------------------------------------------------
+CTFRocketLauncher::CTFRocketLauncher()
+{
+ m_bReloadsSingly = true;
+ m_nReloadPitchStep = 0;
+
+#ifdef GAME_DLL
+ m_bIsOverloading = false;
+#endif //GAME_DLL
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : -
+//-----------------------------------------------------------------------------
+CTFRocketLauncher::~CTFRocketLauncher()
+{
+}
+
+#ifndef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRocketLauncher::Precache()
+{
+ BaseClass::Precache();
+ PrecacheParticleSystem( "rocketbackblast" );
+
+ // FIXME: DO WE STILL NEED THESE??
+ PrecacheScriptSound( "MVM.GiantSoldierRocketShoot" );
+ PrecacheScriptSound( "MVM.GiantSoldierRocketShootCrit" );
+ PrecacheScriptSound( "MVM.GiantSoldierRocketExplode" );
+
+ PrecacheScriptSound( "Weapon_Airstrike.AltFire" );
+ PrecacheScriptSound( "Weapon_Airstrike.Fail" );
+ //Building_Sentrygun.FireRocket
+}
+#endif
+
+void CTFRocketLauncher::ModifyEmitSoundParams( EmitSound_t &params )
+{
+ bool bBaseReloadSound = V_strcmp( params.m_pSoundName, "Weapon_RPG.Reload" ) == 0;
+ if ( AutoFiresFullClip() && ( bBaseReloadSound || V_strcmp( params.m_pSoundName, "Weapon_DumpsterRocket.Reload" ) == 0 ) )
+ {
+ float fMaxAmmoInClip = GetMaxClip1();
+ float fAmmoPercentage = static_cast< float >( m_nReloadPitchStep ) / fMaxAmmoInClip;
+
+ // Play a sound that gets higher pitched as more ammo is added
+ if ( bBaseReloadSound )
+ {
+ params.m_pSoundName = "Weapon_DumpsterRocket.Reload_FP";
+ }
+ else
+ {
+ params.m_pSoundName = "Weapon_DumpsterRocket.Reload";
+ }
+
+ params.m_nPitch *= RemapVal( fAmmoPercentage, 0.0f, ( fMaxAmmoInClip - 1.0f ) / fMaxAmmoInClip, 0.79f, 1.19f );
+ params.m_nFlags |= SND_CHANGE_PITCH;
+
+ m_nReloadPitchStep = MIN( GetMaxClip1() - 1, m_nReloadPitchStep + 1 );
+
+ // The last rocket goes in right when this sound happens so that you can launch it before a misfire
+ IncrementAmmo();
+ m_bReloadedThroughAnimEvent = true;
+ }
+}
+
+void CTFRocketLauncher::Misfire( void )
+{
+ BaseClass::Misfire();
+
+#ifdef GAME_DLL
+ if ( CanOverload() )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( !pPlayer )
+ return;
+
+ CTFBaseRocket *pRocket = dynamic_cast< CTFBaseRocket* >( BaseClass::FireProjectile( pPlayer ) );
+ if ( pRocket )
+ {
+ trace_t tr;
+ UTIL_TraceLine( pRocket->GetAbsOrigin(), pPlayer->EyePosition(), MASK_SOLID, pRocket, COLLISION_GROUP_NONE, &tr );
+ pRocket->Explode( &tr, pPlayer );
+ }
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+bool CTFRocketLauncher::CheckReloadMisfire( void )
+{
+ if ( !CanOverload() )
+ return false;
+
+#ifdef GAME_DLL
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+
+ if ( m_bIsOverloading )
+ {
+ if ( Clip1() > 0 )
+ {
+ Misfire();
+ return true;
+ }
+ else
+ {
+ m_bIsOverloading = false;
+ }
+ }
+ else if ( Clip1() >= GetMaxClip1() || ( Clip1() > 0 && pPlayer && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) == 0 ) )
+ {
+ Misfire();
+ m_bIsOverloading = true;
+ return true;
+ }
+#endif // GAME_DLL
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+bool CTFRocketLauncher::ShouldBlockPrimaryFire()
+{
+ return !AutoFiresFullClip();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseEntity *CTFRocketLauncher::FireProjectile( CTFPlayer *pPlayer )
+{
+ m_flShowReloadHintAt = gpGlobals->curtime + 30;
+ CBaseEntity *pRocket = BaseClass::FireProjectile( pPlayer );
+
+ m_nReloadPitchStep = MAX( 0, m_nReloadPitchStep - 1 );
+
+#ifdef GAME_DLL
+ int iProjectile = 0;
+ CALL_ATTRIB_HOOK_INT( iProjectile, override_projectile_type );
+ if ( iProjectile == 0 )
+ {
+ iProjectile = GetWeaponProjectileType();
+ }
+ if ( pPlayer->IsPlayerClass( TF_CLASS_SOLDIER ) && IsCurrentAttackARandomCrit() && ( iProjectile == TF_PROJECTILE_ROCKET ) )
+ {
+ // Track consecutive crit shots for achievements
+ m_iConsecutiveCrits++;
+ if ( m_iConsecutiveCrits == 2 )
+ {
+ pPlayer->AwardAchievement( ACHIEVEMENT_TF_SOLDIER_SHOOT_MULT_CRITS );
+ }
+ }
+ else
+ {
+ m_iConsecutiveCrits = 0;
+ }
+ m_bIsOverloading = false;
+#endif
+
+ if ( TFGameRules()->GameModeUsesUpgrades() )
+ {
+ PlayUpgradedShootSound( "Weapon_Upgrade.DamageBonus" );
+ }
+
+#ifdef STAGING_ONLY
+#ifdef GAME_DLL
+ if ( pRocket && pPlayer && pPlayer->RocketJumped() )
+ {
+ int iRocketsApplyImpuse = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iRocketsApplyImpuse, mod_rocket_launch_impulse );
+ if ( iRocketsApplyImpuse )
+ {
+ // Apply force in opposite direction of rocket
+ Vector vecDir = pRocket->GetAbsVelocity();
+ Vector vecFlightDir = -vecDir;
+ VectorNormalize( vecFlightDir );
+
+ // Apply more force if looking down
+ QAngle angEye = EyeAngles();
+ float flForce = ( angEye.x > 60.f ) ? 700.f : 400.f;
+ Vector vecForce = vecFlightDir * flForce;
+
+ // DevMsg( "x.Ang: %f\tForce: %f\n", angEye.x, flForce );
+
+ // Prevent insane speeds
+ float flSpeed = vecForce.NormalizeInPlace();
+ const float flLimit = Min( 800.f, flSpeed );
+ pPlayer->ApplyAbsVelocityImpulse( flLimit * vecForce );
+ }
+ }
+#endif // GAME_DLL
+#endif // STAGING_ONLY
+
+ return pRocket;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRocketLauncher::ItemPostFrame( void )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( !pOwner )
+ return;
+
+ BaseClass::ItemPostFrame();
+
+#ifdef GAME_DLL
+
+ if ( m_flShowReloadHintAt && m_flShowReloadHintAt < gpGlobals->curtime )
+ {
+ if ( Clip1() < GetMaxClip1() )
+ {
+ pOwner->HintMessage( HINT_SOLDIER_RPG_RELOAD );
+ }
+ m_flShowReloadHintAt = 0;
+ }
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFRocketLauncher::DefaultReload( int iClipSize1, int iClipSize2, int iActivity )
+{
+ m_flShowReloadHintAt = 0;
+ return BaseClass::DefaultReload( iClipSize1, iClipSize2, iActivity );
+}
+
+#ifdef CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFRocketLauncher::CreateMuzzleFlashEffects( C_BaseEntity *pAttachEnt, int nIndex )
+{
+ BaseClass::CreateMuzzleFlashEffects( pAttachEnt, nIndex );
+
+ // Don't do backblast effects in first person
+ C_TFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( pOwner->IsLocalPlayer() )
+ return;
+
+ ParticleProp()->Init( this );
+ ParticleProp()->Create( "rocketbackblast", PATTACH_POINT_FOLLOW, "backblast" );
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFRocketLauncher::GetWeaponProjectileType( void ) const
+{
+ return BaseClass::GetWeaponProjectileType();
+}
+
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+// CTFRocketLauncher_AirStrike BEGIN
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+CTFRocketLauncher_AirStrike::CTFRocketLauncher_AirStrike()
+{
+ //m_iSecondaryShotsFired = 0;
+}
+
+#ifdef GAME_DLL
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+void CTFRocketLauncher_AirStrike::OnPlayerKill( CTFPlayer *pVictim, const CTakeDamageInfo &info )
+{
+ BaseClass::OnPlayerKill( pVictim, info );
+
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ int iDecap = pOwner->m_Shared.GetDecapitations() + 1;
+ if ( pVictim )
+ {
+ iDecap += pVictim->m_Shared.GetDecapitations();
+ }
+ pOwner->m_Shared.SetDecapitations( iDecap );
+
+ int iClipSizeOnKills = 0;
+ CALL_ATTRIB_HOOK_INT( iClipSizeOnKills, clipsize_increase_on_kill );
+ if ( iClipSizeOnKills && ( iDecap >= iClipSizeOnKills ) )
+ {
+ pOwner->AwardAchievement( ACHIEVEMENT_TF_SOLDIER_AIRSTRIKE_MAX_CLIP );
+ }
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+#endif
+
+//-----------------------------------------------------------------------------
+int CTFRocketLauncher_AirStrike::GetCount( void )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return 0;
+
+ return pOwner->m_Shared.GetDecapitations();
+}
+
+////----------------------------------------------------------------------------------------------------------------------------------------------------------
+//void CTFRocketLauncher_AirStrike::PrimaryAttack( void )
+//{
+// CTFPlayer *pPlayer = GetTFPlayerOwner();
+// if ( !pPlayer )
+// return;
+//
+// // If the player is blast jumping and hasn't fired a shot yet, we can initiate
+// if ( pPlayer->m_Shared.InCond( TF_COND_BLASTJUMPING ) && m_iSecondaryShotsFired == 0 )
+// {
+// FireSecondaryRockets();
+// }
+// else
+// {
+// BaseClass::PrimaryAttack();
+// }
+//}
+////----------------------------------------------------------------------------------------------------------------------------------------------------------
+//bool CTFRocketLauncher_AirStrike::CanHolster( void )
+//{
+// if ( m_iSecondaryShotsFired > 0 )
+// return false;
+//
+// return BaseClass::CanHolster();
+//}
+////-----------------------------------------------------------------------------
+//void CTFRocketLauncher_AirStrike::ItemPostFrame( void )
+//{
+// // If allowed
+// FireSecondaryRockets();
+// BaseClass::ItemPostFrame();
+//}
+////-----------------------------------------------------------------------------
+//void CTFRocketLauncher_AirStrike::ItemBusyFrame( void )
+//{
+// // If allowed
+// FireSecondaryRockets();
+// BaseClass::ItemBusyFrame();
+//}
+//
+////-----------------------------------------------------------------------------
+//void CTFRocketLauncher_AirStrike::FireSecondaryRockets()
+//{
+//#ifdef STAGING_ONLY
+// if ( m_flNextPrimaryAttack >= gpGlobals->curtime )
+// return;
+//
+// CTFPlayer *pPlayer = GetTFPlayerOwner();
+// if ( !pPlayer )
+// return;
+//
+// if ( !( pPlayer->m_nButtons & IN_ATTACK ) && m_iSecondaryShotsFired == 0 )
+// return;
+//
+// int iAirBombardment = 0;
+// CALL_ATTRIB_HOOK_INT( iAirBombardment, rj_air_bombardment );
+// if ( !iAirBombardment )
+// return;
+//
+// // This function and its checks are only on the server
+// if ( !pPlayer->m_Shared.InCond( TF_COND_BLASTJUMPING ) )
+// {
+//#ifdef CLIENT_DLL
+// // play fail sound locally
+// //pPlayer->EmitSound( "Weapon_Airstrike.Fail" );
+//#endif
+// m_iSecondaryShotsFired = 0;
+// return;
+// }
+//
+// if ( m_iClip1 <= 0 && m_iSecondaryShotsFired == 0 )
+// return;
+//
+// if ( m_bReloadsSingly )
+// {
+// m_iReloadMode.Set( TF_RELOAD_START );
+// }
+//
+// float flFireDelay = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay;
+// flFireDelay += GetFireDelay();
+// CALL_ATTRIB_HOOK_FLOAT( flFireDelay, mult_postfiredelay );
+// CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayer, flFireDelay, hwn_mult_postfiredelay );
+//
+// SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+// pPlayer->SetAnimation( PLAYER_ATTACK1 );
+// pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
+// m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay / 2.0f;
+//
+// // Want a different sound
+// pPlayer->EmitSound( "Weapon_Airstrike.AltFire" );
+//
+//#ifdef GAME_DLL
+// // Server only - create the rocket.
+// Vector vecSrc;
+// QAngle angForward;
+// Vector vecOffset( 23.5f, 12.0f, -3.0f );
+// GetProjectileFireSetup( pPlayer, vecOffset, &vecSrc, &angForward, false );
+//
+// CTFProjectile_SentryRocket *pProjectile = CTFProjectile_SentryRocket::Create( vecSrc, angForward, this, pPlayer );
+//
+// if ( pProjectile )
+// {
+// pProjectile->SetCritical( IsCurrentAttackACrit() );
+// pProjectile->SetDamage( GetProjectileDamage() * tf_airstrike_dmg_scale.GetFloat() );
+// pProjectile->SetDamageForceScale( tf_airstrike_dmg_scale.GetFloat() );
+// }
+//
+// if ( m_iSecondaryShotsFired == 0 )
+// {
+// RemoveProjectileAmmo( pPlayer );
+// }
+//
+// m_iSecondaryShotsFired++;
+// if ( m_iSecondaryShotsFired >= 3 )
+// {
+// // Decrement ammo and reset
+// m_iSecondaryShotsFired = 0;
+// // Give normal delay between shots here
+// m_flNextPrimaryAttack = gpGlobals->curtime + flFireDelay;
+// }
+//#endif
+//
+//#endif
+//}
+
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+// CTFRocketLauncher_Mortar BEGIN
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+//CTFRocketLauncher_Mortar::CTFRocketLauncher_Mortar()
+//{
+//
+//}
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+CBaseEntity *CTFRocketLauncher_Mortar::FireProjectile( CTFPlayer *pPlayer )
+{
+ // Fire the rocket
+ CBaseEntity* pRocket = BaseClass::FireProjectile( pPlayer );
+ // Add it to my list
+#ifdef GAME_DLL
+ m_vecRockets.AddToTail( pRocket );
+#endif
+
+ return pRocket;
+}
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+void CTFRocketLauncher_Mortar::SecondaryAttack( void )
+{
+ RedirectRockets();
+}
+//-----------------------------------------------------------------------------
+void CTFRocketLauncher_Mortar::ItemPostFrame( void )
+{
+#ifdef GAME_DLL
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( pOwner && pOwner->m_nButtons & IN_ATTACK2 )
+ {
+ // If allowed
+ RedirectRockets();
+ }
+#endif
+ BaseClass::ItemPostFrame();
+}
+
+//-----------------------------------------------------------------------------
+void CTFRocketLauncher_Mortar::ItemBusyFrame( void )
+{
+#ifdef GAME_DLL
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( pOwner && pOwner->m_nButtons & IN_ATTACK2 )
+ {
+ // If allowed
+ RedirectRockets();
+ }
+#endif
+ BaseClass::ItemBusyFrame();
+}
+
+
+//-----------------------------------------------------------------------------
+void CTFRocketLauncher_Mortar::RedirectRockets( void )
+{
+#ifdef GAME_DLL
+ if ( m_vecRockets.Count() <= 0 )
+ return;
+
+ CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() );
+ if ( !pOwner )
+ return;
+
+ Vector vecEye = pOwner->EyePosition();
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( pOwner->EyeAngles(), &vecForward, &vecRight, &vecUp );
+
+ trace_t tr;
+ UTIL_TraceLine( vecEye, vecEye + vecForward * MAX_TRACE_LENGTH, MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr );
+ float flVel = 1100.0f;
+
+ FOR_EACH_VEC_BACK( m_vecRockets, i )
+ {
+ CBaseEntity* pRocket = m_vecRockets[i].Get();
+ // Remove targets that have disappeared
+ if ( !pRocket || pRocket->GetOwnerEntity() != GetOwnerEntity() )
+ {
+ m_vecRockets.Remove( i );
+ continue;
+ }
+
+ // Give the rocket a new target
+ Vector vecDir = pRocket->WorldSpaceCenter() - tr.endpos;
+ VectorNormalize( vecDir );
+
+ Vector vecVel = pRocket->GetAbsVelocity();
+ vecVel = -flVel * vecDir;
+ pRocket->SetAbsVelocity( vecVel );
+
+ QAngle newAngles;
+ VectorAngles( -vecDir, newAngles );
+ pRocket->SetAbsAngles( newAngles );
+
+#ifdef STAGING_ONLY
+ if ( !tf_mortar_allow_fulltracking.GetBool() )
+ {
+ // only allow a single redirect
+ m_vecRockets.Remove( i );
+ }
+#else
+ m_vecRockets.Remove( i );
+#endif
+ }
+#endif
+}
+
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+// CROSSBOW BEGIN
+//----------------------------------------------------------------------------------------------------------------------------------------------------------
+bool CTFCrossbow::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ // Allow Crossbow to silently reload like the flaregun
+ if ( m_iClip1 == 0 )
+ {
+ // These Values need to match the anim times since all this stuff is actually driven by animation sequence time in the base code
+ float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay );
+
+ float flReloadTime = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeReload;
+ CALL_ATTRIB_HOOK_FLOAT( flReloadTime, mult_reload_time );
+ CALL_ATTRIB_HOOK_FLOAT( flReloadTime, mult_reload_time_hidden );
+ CALL_ATTRIB_HOOK_FLOAT( flReloadTime, fast_reload );
+
+ float flIdleTime = GetLastPrimaryAttackTime() + flFireDelay + flReloadTime;
+ if ( GetWeaponIdleTime() < flIdleTime )
+ {
+ SetWeaponIdleTime( flIdleTime );
+ m_flNextPrimaryAttack = flIdleTime;
+ }
+
+ IncrementAmmo();
+ }
+
+ return BaseClass::Holster( pSwitchingTo );
+}
+//-----------------------------------------------------------------------------
+void CTFCrossbow::SecondaryAttack( void )
+{
+ // If this is the jarate bolt crossbow, make sure we are allowed to do it
+ int iMilkBolt = 0;
+ CALL_ATTRIB_HOOK_INT( iMilkBolt, fires_milk_bolt );
+ if ( iMilkBolt )
+ {
+ CTFPlayer *pPlayer = GetTFPlayerOwner();
+ if ( !pPlayer )
+ return;
+
+ if ( !CanAttack() )
+ return;
+
+ if ( m_flNextPrimaryAttack > gpGlobals->curtime )
+ return;
+
+ // Can we attack
+ if ( GetProgress() >= 1.0f )
+ {
+ // Call Primary Attack and modify the projectile
+ m_bMilkNextAttack = true;
+ PrimaryAttack();
+ m_flRegenerateDuration = iMilkBolt;
+ m_flLastUsedTimestamp = gpGlobals->curtime;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFCrossbow::ModifyProjectile( CBaseEntity* pProj )
+{
+#ifdef GAME_DLL
+ if ( m_bMilkNextAttack )
+ {
+ CTFProjectile_Arrow* pMainArrow = assert_cast<CTFProjectile_Arrow*>( pProj );
+ if ( pMainArrow )
+ {
+ pMainArrow->SetApplyMilkOnHit();
+ }
+ }
+#endif
+
+ m_bMilkNextAttack = false;
+}
+//-----------------------------------------------------------------------------
+void CTFCrossbow::ItemPostFrame( void )
+{
+ BaseClass::ItemPostFrame();
+ m_bMilkNextAttack = false;
+}
+//-----------------------------------------------------------------------------
+float CTFCrossbow::GetProjectileSpeed( void )
+{
+ return RemapValClamped( 0.75f, 0.0f, 1.f, 1800, 2600 ); // Temp, if we want to ramp.
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFCrossbow::GetProjectileGravity( void )
+{
+ return RemapValClamped( 0.75f, 0.0f, 1.f, 0.5, 0.1 ); // Temp, if we want to ramp.
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFCrossbow::IsViewModelFlipped( void )
+{
+ return !BaseClass::IsViewModelFlipped(); // Invert because arrows are backwards by default.
+}
+//-----------------------------------------------------------------------------
+void CTFCrossbow::WeaponRegenerate( void )
+{
+ BaseClass::WeaponRegenerate();
+ m_flLastUsedTimestamp = 0;
+}
+//-----------------------------------------------------------------------------
+inline float CTFCrossbow::GetProgress( void )
+{
+ int iMilkBolt = 0;
+ CALL_ATTRIB_HOOK_INT( iMilkBolt, fires_milk_bolt );
+ if ( iMilkBolt == 0 )
+ return 0;
+
+ float meltedTime = gpGlobals->curtime - m_flLastUsedTimestamp;
+ return meltedTime / m_flRegenerateDuration;
+} \ No newline at end of file