//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #include "cbase.h" #include "tf_weapon_wrench.h" #include "decals.h" #include "baseobject_shared.h" #include "tf_viewmodel.h" // Client specific. #ifdef CLIENT_DLL #include "c_tf_player.h" #include "in_buttons.h" #include "tf_hud_menu_eureka_teleport.h" // NVNT haptics system interface #include "haptics/ihaptics.h" // Server specific. #else #include "tf_player.h" #include "variant_t.h" #include "tf_gamerules.h" #include "particle_parse.h" #include "tf_fx.h" #include "tf_obj_sentrygun.h" #endif // Maximum time between robo arm hits to maintain the three-hit-combo #define ROBOARM_COMBO_TIMEOUT 1.0f //============================================================================= // // Weapon Wrench tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFWrench, DT_TFWeaponWrench ) BEGIN_NETWORK_TABLE( CTFWrench, DT_TFWeaponWrench ) END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFWrench ) END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( tf_weapon_wrench, CTFWrench ); PRECACHE_WEAPON_REGISTER( tf_weapon_wrench ); //============================================================================= // // Robot Arm tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFRobotArm, DT_TFWeaponRobotArm ) BEGIN_NETWORK_TABLE( CTFRobotArm, DT_TFWeaponRobotArm ) #ifdef GAME_DLL SendPropEHandle(SENDINFO(m_hRobotArm)), #else RecvPropEHandle(RECVINFO(m_hRobotArm)), #endif END_NETWORK_TABLE() #ifdef CLIENT_DLL BEGIN_PREDICTION_DATA( CTFRobotArm ) // DEFINE_PRED_FIELD( name, fieldtype, flags ) DEFINE_PRED_FIELD( m_iComboCount, FIELD_INTEGER, 0 ), DEFINE_PRED_FIELD( m_flLastComboHit, FIELD_FLOAT, 0 ), END_PREDICTION_DATA() #endif LINK_ENTITY_TO_CLASS( tf_weapon_robot_arm, CTFRobotArm ); PRECACHE_WEAPON_REGISTER( tf_weapon_robot_arm ); IMPLEMENT_NETWORKCLASS_ALIASED( TFWearableRobotArm, DT_TFWearableRobotArm ) BEGIN_NETWORK_TABLE( CTFWearableRobotArm, DT_TFWearableRobotArm ) END_NETWORK_TABLE() LINK_ENTITY_TO_CLASS( tf_wearable_robot_arm, CTFWearableRobotArm ); //============================================================================= // // Weapon Wrench functions. // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFWrench::CTFWrench() : m_bReloadDown( false ) {} //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWrench::Spawn() { BaseClass::Spawn(); } #ifdef GAME_DLL void CTFWrench::OnFriendlyBuildingHit( CBaseObject *pObject, CTFPlayer *pPlayer, Vector hitLoc ) { bool bHelpTeammateBuildStructure = pObject->IsBuilding() && pObject->GetOwner() != GetOwner(); // Did this object hit do any work? repair or upgrade? bool bUsefulHit = pObject->InputWrenchHit( pPlayer, this, hitLoc ); // award achievement if we helped a teammate build a structure if ( bUsefulHit && bHelpTeammateBuildStructure ) { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( pOwner && pOwner->IsPlayerClass( TF_CLASS_ENGINEER ) ) { pOwner->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_HELP_BUILD_STRUCTURE ); } } CDisablePredictionFiltering disabler; if ( pObject->IsDisposableBuilding() ) { CSingleUserRecipientFilter singleFilter( pPlayer ); EmitSound( singleFilter, pObject->entindex(), "Player.UseDeny" ); } else { if ( bUsefulHit ) { // play success sound WeaponSound( SPECIAL1 ); } else { // play failure sound WeaponSound( SPECIAL2 ); } } } #endif void CTFWrench::Smack( void ) { // see if we can hit an object with a higher range // Get the current player. CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; if ( !CanAttack() ) return; // Setup a volume for the melee weapon to be swung - approx size, so all melee behave the same. static Vector vecSwingMins( -18, -18, -18 ); static Vector vecSwingMaxs( 18, 18, 18 ); // Setup the swing range. Vector vecForward; AngleVectors( pPlayer->EyeAngles(), &vecForward ); Vector vecSwingStart = pPlayer->Weapon_ShootPosition(); Vector vecSwingEnd = vecSwingStart + vecForward * 70; // only trace against objects // See if we hit anything. trace_t trace; CTraceFilterIgnorePlayers traceFilter( NULL, COLLISION_GROUP_NONE ); UTIL_TraceLine( vecSwingStart, vecSwingEnd, MASK_SOLID, &traceFilter, &trace ); if ( trace.fraction >= 1.0 ) { UTIL_TraceHull( vecSwingStart, vecSwingEnd, vecSwingMins, vecSwingMaxs, MASK_SOLID, &traceFilter, &trace ); } // We hit, setup the smack. if ( trace.fraction < 1.0f && trace.m_pEnt && trace.m_pEnt->IsBaseObject() && trace.m_pEnt->GetTeamNumber() == pPlayer->GetTeamNumber() ) { #ifdef GAME_DLL OnFriendlyBuildingHit( dynamic_cast< CBaseObject * >( trace.m_pEnt ), pPlayer, trace.endpos ); #else // NVNT if the local player is the owner of this wrench // Notify the haptics system we just repaired something. if(pPlayer==C_TFPlayer::GetLocalTFPlayer() && haptics) haptics->ProcessHapticEvent(2,"Weapons","tf_weapon_wrench_fix"); #endif } else { // if we cannot, Smack as usual for player hits BaseClass::Smack(); } } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWrench::ItemPostFrame() { BaseClass::ItemPostFrame(); if ( !CanAttack() ) { return; } CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); if ( !pOwner ) { return; } // Just pressed reload? if ( pOwner->m_nButtons & IN_RELOAD && !m_bReloadDown ) { m_bReloadDown = true; int iAltFireTeleportToSpawn = 0; CALL_ATTRIB_HOOK_INT( iAltFireTeleportToSpawn, alt_fire_teleport_to_spawn ); if ( iAltFireTeleportToSpawn ) { // Tell the teleport menu to show CHudEurekaEffectTeleportMenu *pTeleportMenu = ( CHudEurekaEffectTeleportMenu * )GET_HUDELEMENT( CHudEurekaEffectTeleportMenu ); if ( pTeleportMenu ) { pTeleportMenu->WantsToTeleport(); } } } else if ( !(pOwner->m_nButtons & IN_RELOAD) && m_bReloadDown ) { m_bReloadDown = false; } } #endif //----------------------------------------------------------------------------- // Purpose: Kill all buildings when wrench is changed. //----------------------------------------------------------------------------- #ifdef GAME_DLL void CTFWrench::Equip( CBaseCombatCharacter *pOwner ) { // STAGING_ENGY CTFPlayer *pPlayer = ToTFPlayer( pOwner ); if ( pPlayer ) { // if switching too gunslinger, blow up other sentry int iMiniSentry = 0; CALL_ATTRIB_HOOK_INT( iMiniSentry, wrench_builds_minisentry ); if ( iMiniSentry ) { // Just detonate Sentries CObjectSentrygun *pSentry = dynamic_cast( pPlayer->GetObjectOfType( OBJ_SENTRYGUN ) ); if ( pSentry ) { pSentry->DetonateObject(); } } } BaseClass::Equip( pOwner ); } //----------------------------------------------------------------------------- // Purpose: Kill all buildings when wrench is changed. //----------------------------------------------------------------------------- void CTFWrench::Detach( void ) { // STAGING_ENGY CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( pPlayer ) { bool bDetonateObjects = true; // In MvM mode, leave engineer's buildings after he dies if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { if ( pPlayer->GetTeamNumber() != TF_TEAM_PVE_DEFENDERS ) { bDetonateObjects = false; } } // Only detonate if we are unequipping gunslinger if ( bDetonateObjects ) { // if switching off of gunslinger detonate int iMiniSentry = 0; CALL_ATTRIB_HOOK_INT( iMiniSentry, wrench_builds_minisentry ); if ( iMiniSentry ) { // Just detonate Sentries CObjectSentrygun *pSentry = dynamic_cast( pPlayer->GetObjectOfType( OBJ_SENTRYGUN ) ); if ( pSentry ) { pSentry->DetonateObject(); } } } } BaseClass::Detach(); } //----------------------------------------------------------------------------- // Purpose: Apply health upgrade to our existing buildings //----------------------------------------------------------------------------- void CTFWrench::ApplyBuildingHealthUpgrade( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; for ( int i = pPlayer->GetObjectCount()-1; i >= 0; i-- ) { CBaseObject *pObj = pPlayer->GetObject(i); if ( pObj ) { pObj->ApplyHealthUpgrade(); } } } #endif // STAGING_ENGY ConVar tf_construction_build_rate_multiplier( "tf_construction_build_rate_multiplier", "1.5f", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY ); float CTFWrench::GetConstructionValue( void ) { float flValue = tf_construction_build_rate_multiplier.GetFloat(); CALL_ATTRIB_HOOK_FLOAT( flValue, mult_construction_value ); return flValue; } float CTFWrench::GetRepairValue( void ) { float flValue = 1.0; CALL_ATTRIB_HOOK_FLOAT( flValue, mult_repair_value ); #ifdef GAME_DLL if ( GetOwner() ) { CBaseCombatWeapon* pWpn = GetOwner()->Weapon_GetSlot( TF_WPN_TYPE_PRIMARY ); if ( pWpn ) { CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, flValue, mult_repair_value ); } } #endif return flValue; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWrench::Holster( CBaseCombatWeapon *pSwitchingTo ) { return BaseClass::Holster( pSwitchingTo ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CTFRobotArm::CTFRobotArm() { m_iComboCount = 0; m_flLastComboHit = 0.f; m_bBigIdle = false; m_bBigHit = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRobotArm::Precache() { BaseClass::Precache(); extern const char *g_HACK_GunslingerEngineerArmsOverride; PrecacheModel( g_HACK_GunslingerEngineerArmsOverride ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- #ifdef GAME_DLL void CTFRobotArm::Equip( CBaseCombatCharacter* pOwner ) { BaseClass::Equip( pOwner ); if ( !IsPDQ() ) return; CTFWearable* pArmItem = dynamic_cast( CreateEntityByName( "tf_wearable_robot_arm" ) ); if ( pArmItem ) { pArmItem->AddSpawnFlags( SF_NORESPAWN ); pArmItem->SetAlwaysAllow( true ); DispatchSpawn( pArmItem ); pArmItem->GiveTo( pOwner ); pArmItem->AddHiddenBodyGroup( "rightarm" ); pArmItem->SetOwnerEntity( pOwner ); m_hRobotArm.Set( pArmItem ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRobotArm::Drop( const Vector &vecVelocity ) { RemoveRobotArm(); BaseClass::Drop( vecVelocity ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRobotArm::UpdateOnRemove( void ) { RemoveRobotArm(); BaseClass::UpdateOnRemove(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRobotArm::RemoveRobotArm( void ) { if ( m_hRobotArm ) { m_hRobotArm->RemoveFrom( GetOwnerEntity() ); m_hRobotArm = NULL; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRobotArm::OnActiveStateChanged( int iOldState ) { if ( m_iState == WEAPON_NOT_CARRIED ) { RemoveRobotArm(); } } #endif // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- void CTFRobotArm::PrimaryAttack() { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; if ( gpGlobals->curtime - m_flLastComboHit > ROBOARM_COMBO_TIMEOUT ) { m_iComboCount = 0; } if ( m_iComboCount == 2 && CanAttack() ) { pPlayer->m_Shared.SetNextMeleeCrit( MELEE_CRIT ); } BaseClass::PrimaryAttack(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRobotArm::Smack( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; trace_t trace; bool btrace = DoSwingTrace( trace ); if ( btrace && trace.DidHitNonWorldEntity() && trace.m_pEnt && trace.m_pEnt->IsPlayer() && trace.m_pEnt->GetTeamNumber() != pPlayer->GetTeamNumber() ) { m_iComboCount++; m_flLastComboHit = gpGlobals->curtime; if ( m_iComboCount == 3 ) { m_iComboCount = 0; m_bBigIdle = true; m_bBigHit = true; } } else { m_iComboCount = 0; } BaseClass::Smack(); m_bBigHit = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRobotArm::DoViewModelAnimation( void ) { if ( m_iComboCount == 2 ) { SendWeaponAnim( ACT_ITEM2_VM_SWINGHARD ); } else { SendWeaponAnim( ACT_ITEM2_VM_HITCENTER ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- #ifdef GAME_DLL int CTFRobotArm::GetDamageCustom() { if ( m_bBigHit ) { return TF_DMG_CUSTOM_COMBO_PUNCH; } else { return BaseClass::GetDamageCustom(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFRobotArm::GetForceScale( void ) { if ( m_bBigHit ) { return 500.f; } else { return BaseClass::GetForceScale(); } } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFRobotArm::WeaponIdle( void ) { #ifdef GAME_DLL if ( m_bBigIdle ) { m_bBigIdle = false; SendWeaponAnim( ACT_ITEM2_VM_IDLE_2 ); m_flTimeWeaponIdle = gpGlobals->curtime + SequenceDuration(); return; } #endif BaseClass::WeaponIdle(); }