summaryrefslogtreecommitdiff
path: root/game/server/tf/tf_weapon_builder.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/tf_weapon_builder.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/tf/tf_weapon_builder.cpp')
-rw-r--r--game/server/tf/tf_weapon_builder.cpp1320
1 files changed, 1320 insertions, 0 deletions
diff --git a/game/server/tf/tf_weapon_builder.cpp b/game/server/tf/tf_weapon_builder.cpp
new file mode 100644
index 0000000..c3d32e5
--- /dev/null
+++ b/game/server/tf/tf_weapon_builder.cpp
@@ -0,0 +1,1320 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The "weapon" used to build objects
+//
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "tf_player.h"
+#include "entitylist.h"
+#include "in_buttons.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+#include "engine/IEngineSound.h"
+#include "tf_obj.h"
+#include "sendproxy.h"
+#include "tf_weapon_builder.h"
+#include "vguiscreen.h"
+#include "tf_gamerules.h"
+#include "tf_obj_teleporter.h"
+#include "tf_obj_sapper.h"
+
+extern ISoundEmitterSystemBase *soundemitterbase;
+
+extern ConVar tf2_object_hard_limits;
+extern ConVar tf_fastbuild;
+
+
+EXTERN_SEND_TABLE(DT_BaseCombatWeapon)
+
+BEGIN_NETWORK_TABLE_NOBASE( CTFWeaponBuilder, DT_BuilderLocalData )
+ SendPropInt( SENDINFO( m_iObjectType ), BUILDER_OBJECT_BITS, SPROP_UNSIGNED ),
+ SendPropEHandle( SENDINFO( m_hObjectBeingBuilt ) ),
+ SendPropArray3( SENDINFO_ARRAY3( m_aBuildableObjectTypes ), SendPropBool( SENDINFO_ARRAY( m_aBuildableObjectTypes ) ) ),
+END_NETWORK_TABLE()
+
+IMPLEMENT_SERVERCLASS_ST(CTFWeaponBuilder, DT_TFWeaponBuilder)
+ SendPropInt( SENDINFO( m_iBuildState ), 4, SPROP_UNSIGNED ),
+ SendPropDataTable( "BuilderLocalData", 0, &REFERENCE_SEND_TABLE( DT_BuilderLocalData ), SendProxy_SendLocalWeaponDataTable ),
+ SendPropInt( SENDINFO( m_iObjectMode ) , 4, SPROP_UNSIGNED ),
+ SendPropFloat( SENDINFO( m_flWheatleyTalkingUntil) ),
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_weapon_builder, CTFWeaponBuilder );
+PRECACHE_WEAPON_REGISTER( tf_weapon_builder );
+
+//
+
+IMPLEMENT_SERVERCLASS_ST( CTFWeaponSapper, DT_TFWeaponSapper )
+ SendPropFloat( SENDINFO( m_flChargeBeginTime ) ),
+END_SEND_TABLE()
+
+LINK_ENTITY_TO_CLASS( tf_weapon_sapper, CTFWeaponSapper );
+PRECACHE_WEAPON_REGISTER( tf_weapon_sapper );
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFWeaponBuilder::CTFWeaponBuilder()
+{
+ m_iObjectType.Set( BUILDER_INVALID_OBJECT );
+ m_iObjectMode = 0;
+ m_bAttack3Down = false;
+
+ //Sapper VO Pack stuff
+ WheatleyReset( true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFWeaponBuilder::~CTFWeaponBuilder()
+{
+ StopPlacement();
+ if (m_pkvWavList)
+ {
+ m_pkvWavList->deleteThis();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::SetSubType( int iSubType )
+{
+ m_iObjectType = iSubType;
+
+ // m_iViewModelIndex is set by the base Precache(), which didn't know what
+ // type of object we built, so it didn't get the right viewmodel index.
+ // Now that our data is filled in, go and get the right index.
+ const char *pszViewModel = GetViewModel(0);
+ if ( pszViewModel && pszViewModel[0] )
+ {
+ m_iViewModelIndex = CBaseEntity::PrecacheModel( pszViewModel );
+ }
+
+ if ( m_iObjectType == OBJ_ATTACHMENT_SAPPER )
+ {
+ if ( IsWheatleySapper() )
+ {
+ if (m_pkvWavList)
+ {
+ m_pkvWavList->deleteThis();
+ }
+ m_pkvWavList = new KeyValues("sappervo");
+ }
+ }
+
+ BaseClass::SetSubType( iSubType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::Precache( void )
+{
+ BaseClass::Precache();
+
+ // Precache all the viewmodels we could possibly be building
+ for ( int iObj=0; iObj < OBJ_LAST; iObj++ )
+ {
+ const CObjectInfo *pInfo = GetObjectInfo( iObj );
+
+ if ( pInfo )
+ {
+ if ( pInfo->m_pViewModel )
+ {
+ PrecacheModel( pInfo->m_pViewModel );
+ }
+
+ if ( pInfo->m_pPlayerModel )
+ {
+ PrecacheModel( pInfo->m_pPlayerModel );
+ }
+ }
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFWeaponBuilder::CanDeploy( void )
+{
+ CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
+ if (!pPlayer)
+ return false;
+
+ if ( pPlayer->m_Shared.IsCarryingObject() )
+ return BaseClass::CanDeploy();
+
+ if ( pPlayer->CanBuild( m_iObjectType, m_iObjectMode ) != CB_CAN_BUILD )
+ {
+ return false;
+ }
+
+ return BaseClass::CanDeploy();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFWeaponBuilder::Deploy( void )
+{
+ bool bDeploy = BaseClass::Deploy();
+
+ if ( bDeploy )
+ {
+ SetCurrentState( BS_PLACING );
+ StartPlacement();
+ m_flNextPrimaryAttack = gpGlobals->curtime + 0.35f;
+ m_flNextSecondaryAttack = gpGlobals->curtime; // asap
+
+ CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
+ if (!pPlayer)
+ return false;
+
+ pPlayer->SetNextAttack( gpGlobals->curtime );
+
+ m_iWorldModelIndex = modelinfo->GetModelIndex( GetWorldModel() );
+
+ m_flNextDenySound = 0;
+
+ // Set off the hint here, because we don't know until now if our building
+ // is rotate-able or not.
+ if ( m_hObjectBeingBuilt && !m_hObjectBeingBuilt->MustBeBuiltOnAttachmentPoint() )
+ {
+ // set the alt-fire hint so it gets removed when we holster
+ m_iAltFireHint = HINT_ALTFIRE_ROTATE_BUILDING;
+ pPlayer->StartHintTimer( m_iAltFireHint );
+ }
+
+ pPlayer->PlayWearableAnimsForPlaybackEvent( WAP_START_BUILDING );
+ }
+
+ return bDeploy;
+}
+
+Activity CTFWeaponBuilder::GetDrawActivity( void )
+{
+ // sapper used to call different draw animations , one when invis and one when not.
+ // now you can go invis *while* deploying, so let's always use the one-handed deploy.
+ if ( GetType() == OBJ_ATTACHMENT_SAPPER )
+ {
+ return ACT_VM_DRAW_DEPLOYED;
+ }
+
+ return BaseClass::GetDrawActivity();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop placement when holstering
+//-----------------------------------------------------------------------------
+bool CTFWeaponBuilder::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return false;
+
+ if ( pOwner->m_Shared.IsCarryingObject() )
+ return false;
+
+ if ( m_iObjectType == OBJ_ATTACHMENT_SAPPER )
+ {
+ if( IsWheatleySapper() )
+ {
+ pOwner->ClearSappingTracking();
+ if ( pOwner->m_Shared.GetState() == TF_STATE_DYING)
+ {
+ if ( RandomInt( 0, 4) == 0 )
+ {
+ WheatleyEmitSound( "PSap.DeathLong", true );
+ }
+ else
+ {
+ WheatleyEmitSound( "PSap.Death", true );
+ }
+ }
+ else
+ {
+ float flSoundDuration;
+ if ( gpGlobals->curtime - m_flWheatleyLastDeploy < 1.5 && gpGlobals->curtime - m_flWheatleyLastDeploy > -1.0 )
+ {
+ flSoundDuration = WheatleyEmitSound( "PSap.HolsterFast");
+ }
+ else
+ {
+ flSoundDuration = WheatleyEmitSound( "PSap.Holster");
+ }
+ m_flWheatleyLastHolster = gpGlobals->curtime + flSoundDuration;
+ }
+ }
+ }
+
+ m_flNextVoicePakIdleStartTime = -1.0f;
+
+ if ( m_iBuildState == BS_PLACING || m_iBuildState == BS_PLACING_INVALID )
+ {
+ SetCurrentState( BS_IDLE );
+ }
+ StopPlacement();
+
+ pOwner->PlayWearableAnimsForPlaybackEvent( WAP_STOP_BUILDING );
+
+ return BaseClass::Holster(pSwitchingTo);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::ItemPostFrame( void )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ // If we're building, and our team has lost, stop placing the object
+ if ( m_hObjectBeingBuilt.Get() &&
+ TFGameRules()->State_Get() == GR_STATE_TEAM_WIN &&
+ pOwner->GetTeamNumber() != TFGameRules()->GetWinningTeam() )
+ {
+ StopPlacement();
+ return;
+ }
+
+ // Check that I still have enough resources to build this item
+ if ( pOwner->CanBuild( m_iObjectType, m_iObjectMode ) != CB_CAN_BUILD )
+ {
+ SwitchOwnersWeaponToLast();
+ }
+
+ if ( ( pOwner->m_nButtons & IN_ATTACK ) && ( m_flNextPrimaryAttack <= gpGlobals->curtime ) )
+ {
+ PrimaryAttack();
+ }
+
+ if ( pOwner->m_nButtons & IN_ATTACK2 )
+ {
+ if ( m_flNextSecondaryAttack <= gpGlobals->curtime )
+ {
+ SecondaryAttack();
+ }
+ }
+ else
+ {
+ m_bInAttack2 = false;
+ }
+
+ // Attrib
+ int iMarkForDeathOnPickup = 0;
+ if ( pOwner->m_Shared.IsCarryingObject () )
+ {
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, iMarkForDeathOnPickup, mark_for_death_on_building_pickup );
+ if ( iMarkForDeathOnPickup )
+ {
+ pOwner->m_Shared.AddCond( TF_COND_MARKEDFORDEATH_SILENT, 3.f );
+ }
+ }
+
+ WeaponIdle();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start placing or building the currently selected object
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::PrimaryAttack( void )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ if ( !CanAttack() )
+ return;
+
+ // Necessary so that we get the latest building position for the test, otherwise
+ // we are one frame behind.
+ UpdatePlacementState();
+
+ // What state should we move to?
+ switch( m_iBuildState )
+ {
+ case BS_IDLE:
+ {
+ // Idle state starts selection
+ SetCurrentState( BS_SELECTING );
+ }
+ break;
+
+ case BS_SELECTING:
+ {
+ // Do nothing, client handles selection
+ return;
+ }
+ break;
+
+ case BS_PLACING:
+ {
+ if ( m_hObjectBeingBuilt )
+ {
+ int iFlags = m_hObjectBeingBuilt->GetObjectFlags();
+
+ // Tricky, because this can re-calc the object position and change whether its a valid
+ // pos or not. Best not to do this only in debug, but we can be pretty sure that this
+ // will give the same result as was calculated in UpdatePlacementState() above.
+ Assert( IsValidPlacement() );
+
+ // If we're placing an attachment, like a sapper, play a placement animation on the owner
+ if ( m_hObjectBeingBuilt->MustBeBuiltOnAttachmentPoint() )
+ {
+ pOwner->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_GRENADE );
+ }
+
+ CBaseEntity *pBuiltOnObject = m_hObjectBeingBuilt->GetBuiltOnObject();
+
+ if ( pBuiltOnObject && m_iObjectType == OBJ_ATTACHMENT_SAPPER )
+ {
+ m_vLastKnownSapPos = pBuiltOnObject->GetAbsOrigin();
+ m_hLastSappedBuilding = pBuiltOnObject;
+ }
+ StartBuilding();
+
+ if ( m_iObjectType == OBJ_ATTACHMENT_SAPPER )
+ {
+ // tell players a sapper was just placed (so bots can react)
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TEAM_ANY, COLLECT_ONLY_LIVING_PLAYERS );
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ playerVector[i]->OnSapperPlaced( pBuiltOnObject );
+
+ // if we just placed a sapper on a teleporter...try to sap the match, too?
+ if ( pBuiltOnObject )
+ {
+ CObjectTeleporter *pTeleporter = dynamic_cast<CObjectTeleporter*>( pBuiltOnObject );
+ if ( pTeleporter && pTeleporter->GetMatchingTeleporter() && !pTeleporter->GetMatchingTeleporter()->HasSapper() )
+ {
+ // Start placing another
+ SetCurrentState( BS_PLACING );
+ StartPlacement();
+
+ if ( m_hObjectBeingBuilt.Get() )
+ {
+ m_hObjectBeingBuilt->UpdateAttachmentPlacement( pTeleporter->GetMatchingTeleporter() );
+ StartBuilding();
+ }
+ }
+ }
+ }
+
+ // Should we switch away?
+ if ( iFlags & OF_ALLOW_REPEAT_PLACEMENT )
+ {
+ // Start placing another
+ SetCurrentState( BS_PLACING );
+ StartPlacement();
+ }
+ else
+ {
+ SwitchOwnersWeaponToLast();
+ }
+ }
+ }
+ break;
+
+ case BS_PLACING_INVALID:
+ {
+ if ( m_flNextDenySound < gpGlobals->curtime )
+ {
+ CSingleUserRecipientFilter filter( pOwner );
+ EmitSound( filter, entindex(), "Player.DenyWeaponSelection" );
+
+ m_flNextDenySound = gpGlobals->curtime + 0.5;
+ }
+ }
+ break;
+ }
+
+ m_flNextPrimaryAttack = gpGlobals->curtime + 0.2f;
+}
+
+void CTFWeaponBuilder::SecondaryAttack( void )
+{
+ if ( m_bInAttack2 )
+ return;
+
+ // require a re-press
+ m_bInAttack2 = true;
+
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ UpdatePlacementState();
+
+ if ( !pOwner->IsPlayerClass( TF_CLASS_ENGINEER ) && pOwner->DoClassSpecialSkill() )
+ {
+ // Spies do the special skill first.
+ }
+ else if ( m_iBuildState == BS_PLACING || m_iBuildState == BS_PLACING_INVALID )
+ {
+ if ( m_hObjectBeingBuilt )
+ {
+ pOwner->StopHintTimer( HINT_ALTFIRE_ROTATE_BUILDING );
+ m_hObjectBeingBuilt->RotateBuildAngles();
+ }
+ }
+ else if ( pOwner->DoClassSpecialSkill() )
+ {
+ // Engineers do the special skill last.
+ }
+
+ m_flNextSecondaryAttack = gpGlobals->curtime + 0.2f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the builder to the specified state
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::SetCurrentState( int iState )
+{
+ m_iBuildState = iState;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the owner's weapon and last weapon appropriately when we need to
+// switch away from the builder weapon.
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::SwitchOwnersWeaponToLast()
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ // for engineer, switch to wrench and set last weapon appropriately
+ if ( pOwner->IsPlayerClass( TF_CLASS_ENGINEER ) )
+ {
+ // Switch to wrench if possible. if not, then best weapon
+ CBaseCombatWeapon *pWpn = pOwner->Weapon_GetSlot( 2 );
+
+ // Don't store last weapon when we autoswitch off builder
+ CBaseCombatWeapon *pLastWpn = pOwner->GetLastWeapon();
+
+ if ( pWpn )
+ {
+ pOwner->Weapon_Switch( pWpn );
+ }
+ else
+ {
+ pOwner->SwitchToNextBestWeapon( NULL );
+ }
+
+ if ( pWpn == pLastWpn )
+ {
+ // We had the wrench out before we started building. Go ahead and set out last
+ // weapon to our primary weapon.
+ pWpn = pOwner->Weapon_GetSlot( 0 );
+ pOwner->Weapon_SetLast( pWpn );
+ }
+ else
+ {
+ pOwner->Weapon_SetLast( pLastWpn );
+ }
+ }
+ else
+ {
+ // for all other classes, just switch to last weapon used
+ pOwner->Weapon_Switch( pOwner->GetLastWeapon() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: updates the building postion and checks the new postion
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::UpdatePlacementState( void )
+{
+ // This updates the building position
+ bool bValidPos = IsValidPlacement();
+
+ // If we're in placement mode, update the placement model
+ switch( m_iBuildState )
+ {
+ case BS_PLACING:
+ case BS_PLACING_INVALID:
+ {
+ if ( bValidPos )
+ {
+ SetCurrentState( BS_PLACING );
+ }
+ else
+ {
+ SetCurrentState( BS_PLACING_INVALID );
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Idle updates the position of the build placement model
+//-----------------------------------------------------------------------------
+/*#define SAPPER_VOPAK_DEFAULT_WAIT 0.0f*/
+void CTFWeaponBuilder::WeaponIdle( void )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ WheatleySapperIdle( pOwner );
+
+ if ( HasWeaponIdleTimeElapsed() )
+ {
+ SendWeaponAnim( ACT_VM_IDLE );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Special Item Idle
+//-----------------------------------------------------------------------------
+bool CTFWeaponBuilder::IsWheatleySapper( void )
+{
+ float flVoicePak = 0.0;
+ CALL_ATTRIB_HOOK_FLOAT( flVoicePak, sapper_voice_pak );
+ return (flVoicePak == 1.0);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Special Item Reset
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::WheatleyReset( bool bResetIntro )
+{
+ if ( IsWheatleySapper() )
+ {
+ WheatleyEmitSound( "PSap.null" );
+ }
+ if ( bResetIntro )
+ {
+ m_bWheatleyIntroPlayed = false;
+ }
+ m_flNextVoicePakIdleStartTime = -1.0f;
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ m_flWheatleyTalkingUntil = 0.00;
+ m_flWheatleyLastDamage = 0.00;
+ m_iWheatleyVOSequenceOffset = 0;
+ m_flWheatleyLastDeploy = 0.00;
+ m_flWheatleyLastHolster = 0.00;
+}
+
+bool CTFWeaponBuilder::IsWheatleyTalking( void )
+{
+ return gpGlobals->curtime <= m_flWheatleyTalkingUntil;
+}
+
+float CTFWeaponBuilder::WheatleyEmitSound( const char *snd, bool bEmitToAll /*= false*/, bool bNoRepeats /*= false */ )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ CSoundParameters params;
+ if ( !soundemitterbase->GetParametersForSound( snd, params, GENDER_NONE ) )
+ {
+ return 0.00;
+ }
+ //Should we check to see if it's already been played?
+ if ( bNoRepeats && m_pkvWavList )
+ {
+ if ( m_pkvWavList->GetInt( params.soundname , 0 ) )
+ {
+ return 0;
+ }
+ else
+ {
+ m_pkvWavList->SetInt( params.soundname , 1);
+ }
+ }
+ //Look for special cases that require us to pick the next lines from a sequential list
+ if ( Q_strcmp( params.soundname, "vo/items/wheatley_sapper/wheatley_sapper_idle38.mp3") == NULL )
+ {
+ SetWheatleyState( TF_PSAPSTATE_SPECIALIDLE_HARMLESS );
+ m_iWheatleyVOSequenceOffset = 0;
+ }
+ else if ( Q_strcmp( params.soundname, "vo/items/wheatley_sapper/wheatley_sapper_idle41.mp3") == NULL )
+ {
+ SetWheatleyState( TF_PSAPSTATE_SPECIALIDLE_HACK );
+ m_iWheatleyVOSequenceOffset = 0;
+ }
+ else if ( Q_strcmp( params.soundname, "vo/items/wheatley_sapper/wheatley_sapper_idle35.mp3") == NULL )
+ {
+ SetWheatleyState( TF_PSAPSTATE_SPECIALIDLE_KNIFE );
+ m_iWheatleyVOSequenceOffset = 0;
+ }
+
+ //Play the sound
+ // When playing a sound to all players, do it at the last known sapper location
+ // This is not played on the object itself (building or sapper) cause it may not exist in the case of death and follow up audio
+ // Also having it played on this entity itself prevents multiple VO playing in the case of mass sapping
+ if ( bEmitToAll )
+ {
+ //int entIndex = 0;
+ CBroadcastNonOwnerRecipientFilter filter( pOwner );
+ EmitSound( filter, entindex(), snd, &m_vLastKnownSapPos );
+ }
+
+ // GetSoundDuration is not supported on Linux or for MP3s. So lets just put in a good number
+ //float flSoundDuration = enginesound->GetSoundDuration( params.soundname );
+ float flSoundDuration = 3.0f;
+ CSingleUserRecipientFilter filter( pOwner );
+ EmitSound( filter, entindex(), params );
+ m_flWheatleyTalkingUntil = gpGlobals->curtime + flSoundDuration;
+ return flSoundDuration;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set Wheatley Sapper State
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::SetWheatleyState( int iNewState )
+{
+ m_iSapState = iNewState;
+}
+
+int CTFWeaponBuilder::GetWheatleyIdleWait()
+{
+ return RandomInt( WHEATLEY_IDLE_WAIT_SECS_MIN, WHEATLEY_IDLE_WAIT_SECS_MAX );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set Wheatley Sapper State
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::WheatleyDamage( void )
+{
+ if ( (gpGlobals->curtime - m_flWheatleyLastDamage) > 10.0)
+ {
+ if ( RandomInt(0,2) == 0 )
+ {
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if (pOwner)
+ {
+ pOwner->ClearSappingEvent();
+ }
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ m_flWheatleyLastDamage = gpGlobals->curtime;
+ WheatleyEmitSound( "PSap.Damage" );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Special Item Idle
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::WheatleySapperIdle( CTFPlayer *pOwner )
+{
+ if ( pOwner && m_iObjectType == OBJ_ATTACHMENT_SAPPER && IsWheatleySapper())
+ {
+ //Is Wheatley coming out of the player's pocket?
+ if( m_flNextVoicePakIdleStartTime < 0.0f )
+ {
+ pOwner->ClearSappingTracking();
+ float flSoundDuration;
+ if ( gpGlobals->curtime - m_flWheatleyLastHolster < 2.0 && gpGlobals->curtime - m_flWheatleyLastHolster >= -1.00 )
+ {
+ flSoundDuration = WheatleyEmitSound( "Psap.DeployAgain" );
+ }
+ else
+ {
+ flSoundDuration = WheatleyEmitSound( (m_bWheatleyIntroPlayed) ? "Psap.Deploy" : "Psap.DeployIntro" );
+ }
+ m_flWheatleyLastDeploy = gpGlobals->curtime + flSoundDuration;
+ if ( (!m_bWheatleyIntroPlayed) && (RandomInt(0,2) == 0) )
+ {
+ SetWheatleyState( TF_PSAPSTATE_INTRO );
+ m_bWheatleyIntroPlayed = true;
+ m_iWheatleyVOSequenceOffset = 0;
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + flSoundDuration + 3.0;
+ }
+ else
+ {
+ m_bWheatleyIntroPlayed = true;
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + flSoundDuration + GetWheatleyIdleWait();
+ }
+ }
+ //Is there a sapper event? (sapper placed / sapper finished)
+ else if( pOwner->GetSappingEvent() != TF_SAPEVENT_NONE)
+ {
+ char *pVoicePakString = NULL;
+ switch ( pOwner->GetSappingEvent() )
+ {
+ case TF_SAPEVENT_PLACED:
+ if (RandomInt(0,1) == 0)
+ {
+ if ( RandomInt(0,3) == 0 )
+ {
+ pVoicePakString = "PSap.AttachedPW";
+ SetWheatleyState( TF_PSAPSTATE_WAITINGHACK );
+ }
+ else
+ {
+ pVoicePakString = "PSap.Attached";
+ SetWheatleyState( TF_PSAPSTATE_WAITINGHACKPW );
+ }
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 0.2;
+ }
+ else
+ {
+ pVoicePakString = "PSap.Hacking";
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ }
+ break;
+ case TF_SAPEVENT_DONE:
+ if ( IsWheatleyTalking() )
+ {
+ if ( m_hLastSappedBuilding && m_hLastSappedBuilding.Get() )
+ {
+ //Building Alive, Sapper died
+ pVoicePakString = "PSap.Death";
+ }
+ else
+ {
+ pVoicePakString = "PSap.HackedLoud";
+ }
+
+ if ( RandomInt( 0, 3 ) == 0 )
+ {
+ SetWheatleyState( TF_PSAPSTATE_WAITINGFOLLOWUP);
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 1.3;
+ }
+ else
+ {
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ }
+ }
+ else
+ {
+ SetWheatleyState( TF_PSAPSTATE_WAITINGHACKED );
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 0.5;
+ }
+ break;
+ default:
+ break;
+ }
+ pOwner->ClearSappingEvent();
+ if ( pVoicePakString )
+ {
+ float flSoundDuration = WheatleyEmitSound( pVoicePakString, true );
+ m_flNextVoicePakIdleStartTime += flSoundDuration;
+ }
+ }
+ //Are we in the intro sequence?
+ else if ( m_iSapState == TF_PSAPSTATE_INTRO && gpGlobals->curtime > m_flNextVoicePakIdleStartTime )
+ {
+ if ( !IsWheatleyTalking() )
+ {
+ char szVoicePakString[128];
+ szVoicePakString[0] = '\0';
+ if ( m_iWheatleyVOSequenceOffset >= 0 && m_iWheatleyVOSequenceOffset <=3 )
+ {
+ V_sprintf_safe( szVoicePakString, "PSap.IdleIntro0%i", ++m_iWheatleyVOSequenceOffset);
+ float flSoundDuration = WheatleyEmitSound( szVoicePakString );
+ if ( m_iWheatleyVOSequenceOffset == 4 )
+ {
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait() + flSoundDuration;
+ }
+ else
+ {
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 1.0 + flSoundDuration;
+ }
+ }
+ else
+ {
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ m_iWheatleyVOSequenceOffset = 0;
+ }
+ }
+ }
+ //Does a generic timed event need to be serviced?
+ else if( gpGlobals->curtime > m_flNextVoicePakIdleStartTime )
+ {
+ bool bNoRepeats = false;
+ bool bEmitAll = false;
+ char *pVoicePakString = NULL;
+ //Sapped! vo
+ if ( m_iSapState == TF_PSAPSTATE_WAITINGHACKED )
+ {
+ bEmitAll = true;
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ if ( IsWheatleyTalking() )
+ {
+ pVoicePakString = "PSap.HackedLoud";
+ }
+ else
+ {
+ pVoicePakString = "PSap.Hacked";
+ }
+ if ( RandomInt( 0, 3 ) == 0 )
+ {
+ SetWheatleyState( TF_PSAPSTATE_WAITINGFOLLOWUP);
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 1.3;
+ }
+ else
+ {
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ }
+ }
+ //Waiting to start the password guessing vo
+ else if ( m_iSapState == TF_PSAPSTATE_WAITINGHACKPW )
+ {
+ bEmitAll = true;
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ pVoicePakString = "PSap.HackingPW";
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ }
+ //Waiting to start regular hacking vo
+ else if ( m_iSapState == TF_PSAPSTATE_WAITINGHACK )
+ {
+ bEmitAll = true;
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ if ( RandomInt( 0, 2 ) == 0 )
+ {
+ pVoicePakString = "PSap.HackingShort";
+ }
+ else
+ {
+ pVoicePakString = "PSap.Hacking";
+ }
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ }
+ //Waiting to start successful hack followup vo
+ else if ( m_iSapState == TF_PSAPSTATE_WAITINGFOLLOWUP )
+ {
+ bEmitAll = true;
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ pVoicePakString = "PSap.HackedFollowup";
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ }
+ //If Wheatley's talking, skip & check again later
+ else if ( IsWheatleyTalking() )
+ {
+ pVoicePakString = NULL;
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 5.0;
+ }
+ //Are we in the SPECIAL IDLE SEQUENCE "HACK"?
+ else if ( m_iSapState == TF_PSAPSTATE_SPECIALIDLE_HACK )
+ {
+ switch ( m_iWheatleyVOSequenceOffset )
+ {
+ case 0:
+ pVoicePakString = "PSap.IdleHack02";
+ m_iWheatleyVOSequenceOffset++;
+ break;
+ default:
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ m_iWheatleyVOSequenceOffset = 0;
+ break;
+ }
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ }
+ //Are we in the SPECIAL IDLE SEQUENCE "KNIFE"?
+ else if ( m_iSapState == TF_PSAPSTATE_SPECIALIDLE_KNIFE )
+ {
+ switch ( m_iWheatleyVOSequenceOffset )
+ {
+ case 0:
+ pVoicePakString = "PSap.IdleKnife02";
+ m_iWheatleyVOSequenceOffset++;
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + 0.3;
+ break;
+ case 1:
+ pVoicePakString = "PSap.IdleKnife03";
+ m_iWheatleyVOSequenceOffset++;
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ break;
+ default:
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ m_iWheatleyVOSequenceOffset = 0;
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ break;
+ }
+ }
+ //Are we in the SPECIAL IDLE SEQUENCE "HARMLESS"?
+ else if ( m_iSapState == TF_PSAPSTATE_SPECIALIDLE_HARMLESS )
+ {
+ switch ( m_iWheatleyVOSequenceOffset )
+ {
+ case 0:
+ pVoicePakString = "PSap.IdleHarmless02";
+ m_iWheatleyVOSequenceOffset++;
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ break;
+ default:
+ SetWheatleyState( TF_PSAPSTATE_IDLE );
+ m_iWheatleyVOSequenceOffset = 0;
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ break;
+ }
+ }
+ //Is the player stealthed?
+ else if ( pOwner->m_Shared.IsStealthed() )
+ {
+ if ( RandomInt(0,1) == 0 )
+ {
+ pVoicePakString = "PSap.Sneak";
+ }
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ }
+ else if ( m_iSapState == TF_PSAPSTATE_IDLE )
+ {
+ pVoicePakString = "PSap.Idle";
+ bNoRepeats = true;
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ }
+ else
+ {
+ pVoicePakString = NULL;
+ }
+ if (!pVoicePakString)
+ {
+ m_flNextVoicePakIdleStartTime = gpGlobals->curtime + GetWheatleyIdleWait();
+ return;
+ }
+ float flSoundDuration = WheatleyEmitSound( pVoicePakString, bEmitAll, bNoRepeats );
+ m_flNextVoicePakIdleStartTime += flSoundDuration;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Start placing the object
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::StartPlacement( void )
+{
+ StopPlacement();
+
+ CTFPlayer *pTFPlayer = ToTFPlayer( GetOwner() );
+ if ( !pTFPlayer )
+ return;
+
+ if ( pTFPlayer->m_Shared.IsCarryingObject() )
+ {
+ m_hObjectBeingBuilt = pTFPlayer->m_Shared.GetCarriedObject();
+ m_hObjectBeingBuilt->StopFollowingEntity();
+ }
+ else
+ {
+ m_hObjectBeingBuilt = (CBaseObject*)CreateEntityByName( GetObjectInfo( m_iObjectType )->m_pClassName );
+ }
+
+ if ( m_hObjectBeingBuilt )
+ {
+ // Set the builder before Spawn() so attributes can hook correctly
+ m_hObjectBeingBuilt->SetBuilder( pTFPlayer );
+
+ bool bIsCarried = m_hObjectBeingBuilt->IsCarried();
+
+ // split this off from the block at the bottom because we need to know what type of building
+ // this is before we spawn so things like the teleporters have the correct placement models
+ // but we need to set the starting construction health after we've called spawn
+ if ( !bIsCarried )
+ {
+ m_hObjectBeingBuilt->SetObjectMode( m_iObjectMode );
+ }
+
+ m_hObjectBeingBuilt->Spawn();
+ m_hObjectBeingBuilt->StartPlacement( pTFPlayer );
+
+ if ( !bIsCarried )
+ {
+ m_hObjectBeingBuilt->m_iHealth = OBJECT_CONSTRUCTION_STARTINGHEALTH;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::StopPlacement( void )
+{
+ if ( m_hObjectBeingBuilt )
+ {
+ if ( m_hObjectBeingBuilt->IsCarried() )
+ {
+ m_hObjectBeingBuilt->MakeCarriedObject( ToTFPlayer( GetOwner() ) );
+ }
+ else
+ {
+ m_hObjectBeingBuilt->StopPlacement();
+ }
+ m_hObjectBeingBuilt = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::WeaponReset( void )
+{
+ //Check to see if the active weapon is the wheatley sapper, and, if so, reset him
+ if ( m_iObjectType == OBJ_ATTACHMENT_SAPPER )
+ {
+ if ( IsWheatleySapper() )
+ {
+ CTFPlayer *pPlayer = ToTFPlayer( GetOwner() );
+ if ( pPlayer )
+ {
+ pPlayer->ClearSappingTracking();
+ }
+ WheatleyReset();
+ }
+ }
+
+ BaseClass::WeaponReset();
+
+ StopPlacement();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Move the placement model to the current position. Return false if it's an invalid position
+//-----------------------------------------------------------------------------
+bool CTFWeaponBuilder::IsValidPlacement( void )
+{
+ if ( !m_hObjectBeingBuilt )
+ return false;
+
+ CBaseObject *pObj = m_hObjectBeingBuilt.Get();
+
+ pObj->UpdatePlacement();
+
+ return m_hObjectBeingBuilt->IsValidPlacement();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Player holding this weapon has started building something
+// Assumes we are in a valid build position
+//-----------------------------------------------------------------------------
+void CTFWeaponBuilder::StartBuilding( void )
+{
+ CBaseObject *pObj = m_hObjectBeingBuilt.Get();
+
+ Assert( pObj );
+
+ pObj->StartBuilding( GetOwner() );
+
+ m_hObjectBeingBuilt = NULL;
+
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( pOwner )
+ {
+ pOwner->RemoveInvisibility();
+ pOwner->m_Shared.SetCarriedObject( NULL );
+
+ if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() )
+ {
+ if ( pObj->ObjectType() == OBJ_ATTACHMENT_SAPPER )
+ {
+ // Let human players place player-targeted sappers in modes that allow upgrades
+ if ( !pOwner->IsBot() && pObj->GetBuiltOnObject() && pObj->GetBuiltOnObject()->IsPlayer() )
+ {
+ int iRoboSapper = 0;
+ CALL_ATTRIB_HOOK_INT_ON_OTHER( pOwner, iRoboSapper, robo_sapper );
+
+ int nMode = iRoboSapper ? MODE_SAPPER_ANTI_ROBOT_RADIUS : MODE_SAPPER_ANTI_ROBOT;
+ pObj->SetObjectMode( nMode );
+
+ pOwner->RemoveAmmo( 1, TF_AMMO_GRENADES2 );
+ StartEffectBarRegen();
+ }
+ }
+#ifdef STAGING_ONLY
+ // Traps use TF_AMMO_GRENADES1
+ else if ( pObj->GetType() == OBJ_SPY_TRAP )
+ {
+ pOwner->RemoveAmmo( 1, TF_AMMO_GRENADES1 );
+ }
+#endif // STAGING_ONLY
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this weapon has some ammo
+//-----------------------------------------------------------------------------
+bool CTFWeaponBuilder::HasAmmo( void )
+{
+ CTFPlayer *pOwner = ToTFPlayer( GetOwner() );
+ if ( !pOwner )
+ return false;
+
+ int iCost = pOwner->m_Shared.CalculateObjectCost( pOwner, m_iObjectType );
+ return ( pOwner->GetBuildResources() >= iCost );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFWeaponBuilder::GetSlot( void ) const
+{
+ return GetObjectInfo( m_iObjectType )->m_SelectionSlot;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFWeaponBuilder::GetPosition( void ) const
+{
+ return GetObjectInfo( m_iObjectType )->m_SelectionPosition;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : char const
+//-----------------------------------------------------------------------------
+const char *CTFWeaponBuilder::GetPrintName( void ) const
+{
+ return GetObjectInfo( m_iObjectType )->m_pStatusName;
+}
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+bool CTFWeaponBuilder::CanBuildObjectType( int iObjectType )
+{
+ if ( iObjectType < 0 || iObjectType >= OBJ_LAST )
+ return false;
+
+ return m_aBuildableObjectTypes.Get( iObjectType );
+}
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+void CTFWeaponBuilder::SetObjectTypeAsBuildable( int iObjectType )
+{
+ if ( iObjectType < 0 || iObjectType >= OBJ_LAST )
+ return;
+
+ m_aBuildableObjectTypes.Set( iObjectType, true );
+ SetSubType( iObjectType );
+}
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+Activity CTFWeaponBuilder::TranslateViewmodelHandActivity( Activity actBase )
+{
+ if ( GetObjectInfo( m_iObjectType )->m_bUseItemInfo )
+ {
+ return BaseClass::TranslateViewmodelHandActivity( actBase );
+ }
+ else
+ {
+ return actBase;
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Purpose:
+// -----------------------------------------------------------------------------
+const char *CTFWeaponBuilder::GetViewModel( int iViewModel ) const
+{
+ if ( m_iObjectType != BUILDER_INVALID_OBJECT )
+ {
+ if ( GetObjectInfo( m_iObjectType )->m_bUseItemInfo )
+ return BaseClass::GetViewModel();
+
+ return GetObjectInfo( m_iObjectType )->m_pViewModel;
+ }
+
+ return BaseClass::GetViewModel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFWeaponBuilder::GetWorldModel( void ) const
+{
+ if ( m_iObjectType != BUILDER_INVALID_OBJECT )
+ {
+ return GetObjectInfo( m_iObjectType )->m_pPlayerModel;
+ }
+
+ return BaseClass::GetWorldModel();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFWeaponBuilder::AllowsAutoSwitchTo( void ) const
+{
+ // ask the object we're building
+ return GetObjectInfo( m_iObjectType )->m_bAutoSwitchTo;
+}
+
+// ****************************************************************************
+// SAPPER
+// ****************************************************************************
+CTFWeaponSapper::CTFWeaponSapper()
+{
+ m_flChargeBeginTime = 0;
+ m_bAttackDown = false;
+}
+//-----------------------------------------------------------------------------
+void CTFWeaponSapper::ItemPostFrame( void )
+{
+#ifdef STAGING_ONLY
+ CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() );
+ if ( pPlayer )
+ {
+ float flSapperDeployTime = 0;
+ CALL_ATTRIB_HOOK_FLOAT( flSapperDeployTime, sapper_deploy_time );
+ if ( flSapperDeployTime )
+ {
+ //IsValidPlacement
+ if ( !( pPlayer->m_nButtons & IN_ATTACK ) || !IsValidPlacement() )
+ {
+ m_bAttackDown = false;
+ m_flChargeBeginTime = 0;
+ }
+ else if ( m_bAttackDown == false && ( pPlayer->m_nButtons & IN_ATTACK ) )
+ {
+ m_bAttackDown = true;
+ m_flChargeBeginTime = gpGlobals->curtime;
+ }
+
+ if ( ( m_bAttackDown == true && m_flChargeBeginTime + flSapperDeployTime < gpGlobals->curtime ) || !( pPlayer->m_nButtons & IN_ATTACK ) )
+ {
+ BaseClass::ItemPostFrame();
+ }
+ return;
+ }
+ }
+#endif // STAGING_ONLY
+ BaseClass::ItemPostFrame();
+}
+//-----------------------------------------------------------------------------
+const char *CTFWeaponSapper::GetViewModel( int iViewModel ) const
+{
+ // Skip over Builder's version
+ return CTFWeaponBase::GetViewModel();
+}
+//-----------------------------------------------------------------------------
+const char *CTFWeaponSapper::GetWorldModel( void ) const
+{
+ // Skip over Builder's version
+ return CTFWeaponBase::GetWorldModel();
+}
+//-----------------------------------------------------------------------------
+Activity CTFWeaponSapper::TranslateViewmodelHandActivity( Activity actBase )
+{
+ return BaseClass::TranslateViewmodelHandActivity( actBase );
+
+ // Skip over Builder's version
+ //return CTFWeaponBase::TranslateViewmodelHandActivity( actBase );
+}
+