//========= Copyright Valve Corporation, All rights reserved. ============// // // Weapons. // //============================================================================= #include "cbase.h" #include "in_buttons.h" #include "takedamageinfo.h" #include "tf_weaponbase.h" #include "ammodef.h" #include "tf_gamerules.h" #include "eventlist.h" #include "econ_item_system.h" #include "activitylist.h" #include "gcsdk/gcmsg.h" #include "econ_gcmessages.h" #include "tf_gcmessages.h" #include "tf_weapon_wrench.h" #include "passtime_convars.h" // Server specific. #if !defined( CLIENT_DLL ) #include "tf_player.h" #include "tf_weapon_medigun.h" #include "tf_gamestats.h" #include "tf_player.h" #include "tf_gamerules.h" #include "tf_gamestats.h" #include "ilagcompensationmanager.h" #include "collisionutils.h" #include "tf_team.h" #include "tf_obj.h" #include "tf_weapon_grenade_pipebomb.h" #include "particle_parse.h" #include "tf_weaponbase_grenadeproj.h" #include "tf_weapon_compound_bow.h" #include "tf_projectile_arrow.h" #include "tf_gamestats.h" #include "bot/tf_bot_manager.h" #include "bot/tf_bot.h" #include "halloween/halloween_base_boss.h" #include "tf_fx.h" #include "tf_gamestats.h" // Client specific. #else #include "c_tf_player.h" #include "tf_viewmodel.h" #include "hud_crosshair.h" #include "c_tf_playerresource.h" #include "clientmode_tf.h" #include "r_efx.h" #include "dlight.h" #include "effect_dispatch_data.h" #include "c_te_effect_dispatch.h" #include "toolframework_client.h" #include "hud_chat.h" #include "econ_notifications.h" #include "prediction.h" // for spy material proxy #include "tf_proxyentity.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialvar.h" extern CTFWeaponInfo *GetTFWeaponInfo( int iWeapon ); #endif #include "gc_clientsystem.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern ConVar tf_useparticletracers; ConVar tf_scout_hype_pep_mod( "tf_scout_hype_pep_mod", "1.0", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar tf_scout_hype_pep_max( "tf_scout_hype_pep_max", "99.0", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar tf_scout_hype_pep_min_damage( "tf_scout_hype_pep_min_damage", "5.0", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar tf_weapon_criticals_nopred( "tf_weapon_criticals_nopred", "1.0", FCVAR_REPLICATED | FCVAR_CHEAT ); #ifdef _DEBUG ConVar tf_weapon_criticals_anticheat( "tf_weapon_criticals_anticheat", "1.0", FCVAR_REPLICATED ); ConVar tf_weapon_criticals_debug( "tf_weapon_criticals_debug", "0.0", FCVAR_REPLICATED ); extern ConVar tf_weapon_criticals_force_random; #endif // _DEBUG extern ConVar tf_weapon_criticals_bucket_cap; extern ConVar tf_weapon_criticals_bucket_bottom; #ifdef CLIENT_DLL extern ConVar cl_crosshair_file; extern ConVar cl_flipviewmodels; #endif #ifdef STAGING_ONLY ConVar tf_weapon_force_allow_inspect( "tf_weapon_force_allow_inspect", "0", FCVAR_REPLICATED | FCVAR_ARCHIVE, "Allow the inspect animation on any weapon" ); #endif //============================================================================= // // Global functions. // //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool IsAmmoType( int iAmmoType, const char *pAmmoName ) { return GetAmmoDef()->Index( pAmmoName ) == iAmmoType; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void FindHullIntersection( const Vector &vecSrc, trace_t &tr, const Vector &mins, const Vector &maxs, CBaseEntity *pEntity ) { int i, j, k; trace_t tmpTrace; Vector vecEnd; float distance = 1e6f; Vector minmaxs[2] = {mins, maxs}; Vector vecHullEnd = tr.endpos; vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2); UTIL_TraceLine( vecSrc, vecHullEnd, MASK_SOLID, pEntity, COLLISION_GROUP_NONE, &tmpTrace ); if ( tmpTrace.fraction < 1.0 ) { tr = tmpTrace; return; } for ( i = 0; i < 2; i++ ) { for ( j = 0; j < 2; j++ ) { for ( k = 0; k < 2; k++ ) { vecEnd.x = vecHullEnd.x + minmaxs[i][0]; vecEnd.y = vecHullEnd.y + minmaxs[j][1]; vecEnd.z = vecHullEnd.z + minmaxs[k][2]; UTIL_TraceLine( vecSrc, vecEnd, MASK_SOLID, pEntity, COLLISION_GROUP_NONE, &tmpTrace ); if ( tmpTrace.fraction < 1.0 ) { float thisDistance = (tmpTrace.endpos - vecSrc).Length(); if ( thisDistance < distance ) { tr = tmpTrace; distance = thisDistance; } } } } } } //============================================================================= // // TFWeaponBase tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFWeaponBase, DT_TFWeaponBase ) #ifdef GAME_DLL void* SendProxy_SendActiveLocalWeaponDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID ); void* SendProxy_SendNonLocalWeaponDataTable( const SendProp *pProp, const void *pStruct, const void *pVarData, CSendProxyRecipients *pRecipients, int objectID ); #endif //----------------------------------------------------------------------------- // Purpose: Only sent to the player holding it. //----------------------------------------------------------------------------- BEGIN_NETWORK_TABLE_NOBASE( CTFWeaponBase, DT_LocalTFWeaponData ) #if defined( CLIENT_DLL ) RecvPropTime( RECVINFO( m_flLastCritCheckTime ) ), RecvPropTime( RECVINFO( m_flReloadPriorNextFire ) ), RecvPropTime( RECVINFO( m_flLastFireTime ) ), RecvPropTime( RECVINFO( m_flEffectBarRegenTime ) ), RecvPropFloat( RECVINFO( m_flObservedCritChance ) ), #else SendPropTime( SENDINFO( m_flLastCritCheckTime ) ), SendPropTime( SENDINFO( m_flReloadPriorNextFire ) ), SendPropTime( SENDINFO( m_flLastFireTime ) ), SendPropTime( SENDINFO( m_flEffectBarRegenTime ) ), SendPropFloat( SENDINFO( m_flObservedCritChance ), 16, SPROP_NOSCALE, 0.0, 100.0 ), #endif END_NETWORK_TABLE() //----------------------------------------------------------------------------- // Purpose: Variables sent at low precision to non-holding observers. //----------------------------------------------------------------------------- BEGIN_NETWORK_TABLE_NOBASE( CTFWeaponBase, DT_TFWeaponDataNonLocal ) END_NETWORK_TABLE() BEGIN_NETWORK_TABLE( CTFWeaponBase, DT_TFWeaponBase ) // Client specific. #ifdef CLIENT_DLL RecvPropBool( RECVINFO( m_bLowered ) ), RecvPropInt( RECVINFO( m_iReloadMode ) ), RecvPropBool( RECVINFO( m_bResetParity ) ), RecvPropBool( RECVINFO( m_bReloadedThroughAnimEvent ) ), RecvPropBool( RECVINFO( m_bDisguiseWeapon ) ), RecvPropDataTable("LocalActiveTFWeaponData", 0, 0, &REFERENCE_RECV_TABLE(DT_LocalTFWeaponData)), RecvPropDataTable("NonLocalTFWeaponData", 0, 0, &REFERENCE_RECV_TABLE(DT_TFWeaponDataNonLocal)), RecvPropFloat( RECVINFO(m_flEnergy) ), RecvPropEHandle( RECVINFO( m_hExtraWearable ) ), RecvPropEHandle( RECVINFO( m_hExtraWearableViewModel ) ), RecvPropBool( RECVINFO( m_bBeingRepurposedForTaunt ) ), RecvPropInt( RECVINFO( m_nKillComboClass ) ), RecvPropInt( RECVINFO( m_nKillComboCount ) ), RecvPropFloat( RECVINFO( m_flInspectAnimTime ) ), RecvPropInt( RECVINFO( m_nInspectStage ) ), #else // Server specific. SendPropBool( SENDINFO( m_bLowered ) ), SendPropBool( SENDINFO( m_bResetParity ) ), SendPropInt( SENDINFO( m_iReloadMode ), 4, SPROP_UNSIGNED ), SendPropBool( SENDINFO( m_bReloadedThroughAnimEvent ) ), SendPropBool( SENDINFO( m_bDisguiseWeapon ) ), SendPropDataTable("LocalActiveTFWeaponData", 0, &REFERENCE_SEND_TABLE(DT_LocalTFWeaponData), SendProxy_SendActiveLocalWeaponDataTable ), SendPropDataTable("NonLocalTFWeaponData", 0, &REFERENCE_SEND_TABLE(DT_TFWeaponDataNonLocal), SendProxy_SendNonLocalWeaponDataTable ), SendPropFloat( SENDINFO(m_flEnergy) ), SendPropEHandle( SENDINFO( m_hExtraWearable ) ), SendPropEHandle( SENDINFO( m_hExtraWearableViewModel ) ), SendPropBool( SENDINFO( m_bBeingRepurposedForTaunt ) ), SendPropInt( SENDINFO( m_nKillComboClass ), 4, SPROP_UNSIGNED ), SendPropInt( SENDINFO( m_nKillComboCount ), 2, SPROP_UNSIGNED ), SendPropFloat( SENDINFO( m_flInspectAnimTime ) ), SendPropInt( SENDINFO( m_nInspectStage ), -1, SPROP_VARINT ), #endif END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFWeaponBase ) #ifdef CLIENT_DLL DEFINE_PRED_FIELD( m_bLowered, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_iReloadMode, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bReloadedThroughAnimEvent, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD( m_bDisguiseWeapon, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD_TOL( m_flLastCritCheckTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ), DEFINE_PRED_FIELD_TOL( m_flReloadPriorNextFire, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ), DEFINE_PRED_FIELD_TOL( m_flLastFireTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ), DEFINE_PRED_FIELD( m_bCurrentAttackIsCrit, FIELD_BOOLEAN, 0 ), DEFINE_PRED_FIELD( m_iCurrentSeed, FIELD_INTEGER, 0 ), DEFINE_PRED_FIELD( m_flEnergy, FIELD_FLOAT, FTYPEDESC_INSENDTABLE ), DEFINE_PRED_FIELD_TOL( m_flEffectBarRegenTime, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, TD_MSECTOLERANCE ), DEFINE_PRED_FIELD( m_bBeingRepurposedForTaunt, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), #endif END_PREDICTION_DATA() LINK_ENTITY_TO_CLASS( tf_weapon_base, CTFWeaponBase ); // Server specific. #if !defined( CLIENT_DLL ) BEGIN_DATADESC( CTFWeaponBase ) DEFINE_THINKFUNC( FallThink ), END_DATADESC() // Client specific #else ConVar cl_crosshaircolor( "cl_crosshaircolor", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); ConVar cl_dynamiccrosshair( "cl_dynamiccrosshair", "1", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); ConVar cl_scalecrosshair( "cl_scalecrosshair", "1", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); ConVar cl_crosshairalpha( "cl_crosshairalpha", "200", FCVAR_CLIENTDLL | FCVAR_ARCHIVE ); int g_iScopeTextureID = 0; int g_iScopeDustTextureID = 0; #endif ConVar tf_weapon_criticals( "tf_weapon_criticals", "1", FCVAR_REPLICATED | FCVAR_NOTIFY, "Whether or not random crits are enabled" ); //============================================================================= // // TFWeaponBase shared functions. // // ----------------------------------------------------------------------------- // Purpose: Constructor. // ----------------------------------------------------------------------------- CTFWeaponBase::CTFWeaponBase() { SetPredictionEligible( true ); // Nothing collides with these, but they get touch calls. AddSolidFlags( FSOLID_TRIGGER ); // Weapons can fire underwater. m_bFiresUnderwater = true; m_bAltFiresUnderwater = true; // Initialize the weapon modes. m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; m_iReloadMode.Set( TF_RELOAD_START ); m_iAltFireHint = 0; m_bInAttack = false; m_bInAttack2 = false; m_flCritTime = 0; m_flLastCritCheckTime = 0; m_flLastRapidFireCritCheckTime = 0; m_iLastCritCheckFrame = 0; m_flObservedCritChance = 0.f; m_flLastFireTime = 0; m_flEffectBarRegenTime = 0; m_bCurrentAttackIsCrit = false; m_bCurrentCritIsRandom = false; m_bCurrentAttackIsDuringDemoCharge = false; m_iCurrentSeed = -1; m_flReloadPriorNextFire = 0; m_flLastDeployTime = 0; m_bDisguiseWeapon = false; m_flEnergy = Energy_GetMaxEnergy(); m_iAmmoToAdd = 0; #ifdef GAME_DLL m_iHitsInTime = 1; m_iFiredInTime = 1; m_iKillStreak = 0; m_flClipScale = 1.f; #endif // GAME_DLL #ifdef CLIENT_DLL m_iCachedModelIndex = 0; m_iEjectBrassAttachpoint = -2; m_bInitViewmodelOffset = false; m_vecViewmodelOffset = vec3_origin; #endif // CLIENT_DLL m_bBeingRepurposedForTaunt = false; m_nKillComboClass = 0; ClearKillComboCount(); m_flLastPrimaryAttackTime = 0.f; m_eStrangeType = STRANGE_UNKNOWN; m_eStatTrakModuleType = MODULE_UNKNOWN; m_flInspectAnimTime = -1.f; m_nInspectStage = INSPECT_INVALID; } CTFWeaponBase::~CTFWeaponBase() { #ifdef CLIENT_DLL RemoveWorldmodelStatTrak(); RemoveViewmodelStatTrak(); #endif } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- void CTFWeaponBase::Spawn() { // Called manually, because CBaseCombatWeapon::Spawn doesn't call back. InitializeAttributes(); m_bBeingRepurposedForTaunt = false; m_nKillComboClass = 0; ClearKillComboCount(); // Base class spawn. BaseClass::Spawn(); // Set this here to allow players to shoot dropped weapons. SetCollisionGroup( COLLISION_GROUP_WEAPON ); // Get the weapon information. WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( GetClassname() ); Assert( hWpnInfo != GetInvalidWeaponInfoHandle() ); CTFWeaponInfo *pWeaponInfo = dynamic_cast( GetFileWeaponInfoFromHandle( hWpnInfo ) ); Assert( pWeaponInfo && "Failed to get CTFWeaponInfo in weapon spawn" ); m_pWeaponInfo = pWeaponInfo; if ( GetPlayerOwner() ) { ChangeTeam( GetPlayerOwner()->GetTeamNumber() ); } #ifdef GAME_DLL // Move it up a little bit, otherwise it'll be at the guy's feet, and its sound origin // will be in the ground so its EmitSound calls won't do anything. Vector vecOrigin = GetAbsOrigin(); SetAbsOrigin( Vector( vecOrigin.x, vecOrigin.y, vecOrigin.z + 5.0f ) ); m_flRegenTime = 0.0f; m_hLastDrainVictim = NULL; m_lastDrainVictimTimer.Invalidate(); #endif m_szTracerName[0] = '\0'; CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem ) { CEconItemDefinition* pData = pItem->GetStaticData(); if ( pData && pData->GetSubType() ) { SetSubType( pData->GetSubType() ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::Activate( void ) { BaseClass::Activate(); // Reset our clip, in case we've had it modified GiveDefaultAmmo(); } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- void CTFWeaponBase::FallInit( void ) { } //----------------------------------------------------------------------------- // Purpose: // Input : - //----------------------------------------------------------------------------- void CTFWeaponBase::Precache() { BaseClass::Precache(); if ( GetMuzzleFlashModel() ) { PrecacheModel( GetMuzzleFlashModel() ); } const CTFWeaponInfo *pTFInfo = &GetTFWpnData(); if ( pTFInfo->m_szExplosionSound && pTFInfo->m_szExplosionSound[0] ) { CBaseEntity::PrecacheScriptSound( pTFInfo->m_szExplosionSound ); } if ( pTFInfo->m_szBrassModel[0] ) { PrecacheModel( pTFInfo->m_szBrassModel ); } if ( GetMuzzleFlashParticleEffect() ) { PrecacheParticleSystem( GetMuzzleFlashParticleEffect() ); } if ( pTFInfo->m_szExplosionEffect && pTFInfo->m_szExplosionEffect[0] ) { PrecacheParticleSystem( pTFInfo->m_szExplosionEffect ); } if ( pTFInfo->m_szExplosionPlayerEffect && pTFInfo->m_szExplosionPlayerEffect[0] ) { PrecacheParticleSystem( pTFInfo->m_szExplosionPlayerEffect ); } if ( pTFInfo->m_szExplosionWaterEffect && pTFInfo->m_szExplosionWaterEffect[0] ) { PrecacheParticleSystem( pTFInfo->m_szExplosionWaterEffect ); } const char *pszTracerEffect = pTFInfo->m_szTracerEffect; const CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem->IsValid() ) { const char *pszItemTracerEffect = pItem->GetStaticData()->GetTracerEffect( GetTeamNumber() ); if ( pszItemTracerEffect ) { pszTracerEffect = pszItemTracerEffect; } } if ( pszTracerEffect && pszTracerEffect[0] ) { char pTracerEffect[128]; char pTracerEffectCrit[128]; Q_snprintf( pTracerEffect, sizeof(pTracerEffect), "%s_red", pszTracerEffect ); Q_snprintf( pTracerEffectCrit, sizeof(pTracerEffectCrit), "%s_red_crit", pszTracerEffect ); PrecacheParticleSystem( pTracerEffect ); PrecacheParticleSystem( pTracerEffectCrit ); Q_snprintf( pTracerEffect, sizeof(pTracerEffect), "%s_blue", pszTracerEffect ); Q_snprintf( pTracerEffectCrit, sizeof(pTracerEffectCrit), "%s_blue_crit", pszTracerEffect ); PrecacheParticleSystem( pTracerEffect ); PrecacheParticleSystem( pTracerEffectCrit ); } if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) { CBaseEntity::PrecacheScriptSound( "Weapon_Upgrade.DamageBonus1" ); CBaseEntity::PrecacheScriptSound( "Weapon_Upgrade.DamageBonus2" ); CBaseEntity::PrecacheScriptSound( "Weapon_Upgrade.DamageBonus3" ); CBaseEntity::PrecacheScriptSound( "Weapon_Upgrade.DamageBonus4" ); } PrecacheModel( "models/weapons/c_models/stattrack.mdl" ); } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- const CTFWeaponInfo &CTFWeaponBase::GetTFWpnData() const { const FileWeaponInfo_t *pWeaponInfo = &GetWpnData(); const CTFWeaponInfo *pTFInfo = dynamic_cast< const CTFWeaponInfo* >( pWeaponInfo ); Assert( pTFInfo ); return *pTFInfo; } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- int CTFWeaponBase::GetWeaponID( void ) const { Assert( false ); return TF_WEAPON_NONE; } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- bool CTFWeaponBase::IsWeapon( int iWeapon ) const { return GetWeaponID() == iWeapon; } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- int CTFWeaponBase::GetMaxClip1( void ) const { if ( IsEnergyWeapon() ) { return Energy_GetMaxEnergy(); } // Handle the itemdef mod first... float flClip = BaseClass::GetMaxClip1(); if ( flClip >= 0 ) { CALL_ATTRIB_HOOK_INT( flClip, mult_clipsize ); } // Now handle in-game sources, otherwise we get weird numbers on things like the FAN if ( flClip >= 0 ) { #ifdef GAME_DLL flClip *= m_flClipScale; #endif CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( pPlayer ) { // Blast weps (low clip counts) if ( IsBlastImpactWeapon() ) { // MvM-specific upgrade attribute that handles rocket and grenade launchers int nProjectiles = 0; CALL_ATTRIB_HOOK_INT( nProjectiles, mult_clipsize_upgrade_atomic ); // Clipsize increase on kills int iClipSizeOnKills = 0; CALL_ATTRIB_HOOK_INT( iClipSizeOnKills, clipsize_increase_on_kill ); if ( iClipSizeOnKills ) { nProjectiles += Min( pPlayer->m_Shared.GetDecapitations(), iClipSizeOnKills ); // max extra projectiles } if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_HASTE ) { flClip *= 2; } if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_PRECISION ) { flClip *= 1.5f; } return ( flClip + nProjectiles ); } else { CALL_ATTRIB_HOOK_INT( flClip, mult_clipsize_upgrade ); if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_HASTE ) { flClip *= 2; } } } } return flClip; } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- int CTFWeaponBase::GetDefaultClip1( void ) const { return GetMaxClip1(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::UsesPrimaryAmmo( void ) { if ( IsEnergyWeapon() ) return false; else return CBaseCombatWeapon::UsesPrimaryAmmo(); } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- const char *CTFWeaponBase::GetViewModel( int iViewModel ) const { if ( GetPlayerOwner() == NULL ) return BaseClass::GetViewModel(); CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); int iHandModelIndex = 0; if ( pPlayer ) { //CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayer, iHandModelIndex, override_hand_model_index ); // this is a cleaner way of doing it, but... CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayer, iHandModelIndex, wrench_builds_minisentry ); // ...the gunslinger is the only thing that uses this attribute for now } const CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pPlayer && pItem->IsValid() && pItem->GetStaticData()->ShouldAttachToHands() ) { // Should always be valid, because players without classes shouldn't be carrying items const char *pszHandModel = pPlayer->GetPlayerClass()->GetHandModelName( iHandModelIndex ); Assert( pszHandModel ); return pszHandModel; } return GetTFWpnData().szViewModel; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CTFWeaponBase::GetWorldModel( void ) const { const CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem->IsValid() ) { if ( pItem->GetWorldDisplayModel() ) return pItem->GetWorldDisplayModel(); int iClass = 0; int iTeam = 0; CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( pPlayer ) { iClass = pPlayer->GetPlayerClass()->GetClassIndex(); iTeam = pPlayer->GetTeamNumber(); } return pItem->GetPlayerDisplayModel( iClass, iTeam ); } return BaseClass::GetWorldModel(); } bool CTFWeaponBase::IsInspectActivity( int iActivity ) { return iActivity == GetInspectActivity( INSPECT_START ) || iActivity == GetInspectActivity( INSPECT_IDLE ) || iActivity == GetInspectActivity( INSPECT_END ); } bool CTFWeaponBase::SendWeaponAnim( int iActivity ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return BaseClass::SendWeaponAnim( iActivity ); if ( m_nInspectStage != INSPECT_INVALID ) { if ( iActivity == GetActivity() ) return true; // ignore idle anim while inspect anim is still playing if ( iActivity == ACT_VM_IDLE ) { return true; } // allow other activity to override the inspect if ( !IsInspectActivity( iActivity ) ) { m_flInspectAnimTime = -1.f; m_nInspectStage = INSPECT_INVALID; return BaseClass::SendWeaponAnim( iActivity ); } // let the idle loop while the inspect key is pressed if ( pPlayer->IsInspecting() && m_nInspectStage == INSPECT_IDLE ) return true; } return BaseClass::SendWeaponAnim( iActivity ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::Equip( CBaseCombatCharacter *pOwner ) { SetOwner( pOwner ); SetOwnerEntity( pOwner ); ReapplyProvision(); BaseClass::Equip( pOwner ); // If we attach to our hands, we need to update our viewmodel when we get a new owner. UpdateHands(); CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem->IsValid() ) { m_bFlipViewModel = pItem->GetStaticData()->ShouldFlipViewmodels(); // Also precache the vision filtered display models here. if ( pItem->GetVisionFilteredDisplayModel() ) { if ( modelinfo->GetModelIndex( pItem->GetVisionFilteredDisplayModel() ) == -1 ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Vision Filtered Display Model Late Precache", __FUNCTION__ ); CBaseEntity::PrecacheModel( pItem->GetVisionFilteredDisplayModel() ); } } #ifdef GAME_DLL UpdateExtraWearables(); CTFPlayer *pTFPlayer = ToTFPlayer( pOwner ); if ( pTFPlayer ) { pTFPlayer->ReapplyItemUpgrades(pItem); } #endif // GAME_DLL } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::UpdateHands( void ) { const CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem->IsValid() && pItem->GetStaticData()->ShouldAttachToHands() ) { m_iViewModelIndex = CBaseEntity::PrecacheModel( GetViewModel() ); } } #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::UpdateExtraWearables() { CTFWearable *pOldWearable = m_hExtraWearable.Get(); CTFWearable *pOldWearableVM = m_hExtraWearableViewModel.Get(); if ( pOldWearable || pOldWearableVM ) { CBaseCombatCharacter *pOwner = GetOwner(); if ( pOwner ) { if ( !( pOldWearable && pOldWearable->GetTeamNumber() != pOwner->GetTeamNumber() ) && !( pOldWearableVM && pOldWearableVM->GetTeamNumber() != pOwner->GetTeamNumber() ) ) { // No need to destroy and recreate them, because they already match the owner's team return; } } RemoveExtraWearables(); } bool bHasViewModel = false; CEconItemView *pEconItemView = GetAttributeContainer()->GetItem(); if ( pEconItemView->GetExtraWearableViewModel() ) { CTFWearable* pExtraWearableItem = dynamic_cast( CreateEntityByName( "tf_wearable_vm" ) ); if ( pExtraWearableItem ) { if ( modelinfo->GetModelIndex( pEconItemView->GetExtraWearableViewModel() ) == -1 ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - View Model Late Precache", __FUNCTION__ ); // Precaching may be needed here, because we allow virtually everything to be loaded on demand now. pExtraWearableItem->PrecacheModel( pEconItemView->GetExtraWearableViewModel() ); } pExtraWearableItem->AddSpawnFlags( SF_NORESPAWN ); pExtraWearableItem->SetAlwaysAllow( true ); DispatchSpawn( pExtraWearableItem ); pExtraWearableItem->GiveTo( GetOwner() ); pExtraWearableItem->SetModel( pEconItemView->GetExtraWearableViewModel() ); bHasViewModel = true; pExtraWearableItem->SetWeaponAssociatedWith( this ); ExtraWearableViewModelEquipped( pExtraWearableItem ); } } if ( pEconItemView->GetExtraWearableModel() ) { CTFWearable* pExtraWearableItem = dynamic_cast( CreateEntityByName( "tf_wearable" ) ); if ( pExtraWearableItem ) { if ( modelinfo->GetModelIndex( pEconItemView->GetExtraWearableModel() ) == -1 ) { tmZone(TELEMETRY_LEVEL0, TMZF_NONE, "%s - Model Late Precache", __FUNCTION__); // Precaching may be needed here, because we allow virtually everything to be loaded on demand now. pExtraWearableItem->PrecacheModel( pEconItemView->GetExtraWearableModel() ); } pExtraWearableItem->AddSpawnFlags( SF_NORESPAWN ); pExtraWearableItem->SetAlwaysAllow( true ); DispatchSpawn( pExtraWearableItem ); pExtraWearableItem->GiveTo( GetOwner() ); pExtraWearableItem->SetModel( pEconItemView->GetExtraWearableModel() ); if ( bHasViewModel ) { // If it has a view model we need to have the weapon control visibility of this wearable pExtraWearableItem->SetWeaponAssociatedWith( this ); } ExtraWearableEquipped( pExtraWearableItem ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::ExtraWearableEquipped( CTFWearable *pExtraWearableItem ) { Assert( m_hExtraWearable == NULL ); Assert( pExtraWearableItem != NULL ); m_hExtraWearable.Set( pExtraWearableItem ); CBasePlayer *pPlayerOwner = dynamic_cast( GetOwner() ); if ( pPlayerOwner ) { pExtraWearableItem->Equip( pPlayerOwner ); } } void CTFWeaponBase::ExtraWearableViewModelEquipped( CTFWearable *pExtraWearableItem ) { Assert( m_hExtraWearableViewModel == NULL ); Assert( pExtraWearableItem != NULL ); m_hExtraWearableViewModel.Set( pExtraWearableItem ); CBasePlayer *pPlayerOwner = dynamic_cast( GetOwner() ); if ( pPlayerOwner ) { pExtraWearableItem->Equip( pPlayerOwner ); } } #else void CTFWeaponBase::UpdateExtraWearablesVisibility() { if ( m_hExtraWearable.Get() ) { m_hExtraWearable->ValidateModelIndex(); m_hExtraWearable->UpdateVisibility(); m_hExtraWearable->CreateShadow(); } if ( m_hExtraWearableViewModel.Get() ) { m_hExtraWearableViewModel->UpdateVisibility(); } if ( m_viewmodelStatTrakAddon.Get() ) { m_viewmodelStatTrakAddon->UpdateVisibility(); } if ( m_worldmodelStatTrakAddon.Get() ) { m_worldmodelStatTrakAddon->UpdateVisibility(); m_worldmodelStatTrakAddon->CreateShadow(); } } #endif // GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::RemoveExtraWearables( void ) { if ( m_hExtraWearable ) { m_hExtraWearable->RemoveFrom( GetOwnerEntity() ); m_hExtraWearable = NULL; } if ( m_hExtraWearableViewModel ) { m_hExtraWearableViewModel->RemoveFrom( GetOwnerEntity() ); m_hExtraWearableViewModel = NULL; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::Drop( const Vector &vecVelocity ) { #ifndef CLIENT_DLL if ( m_iAltFireHint ) { CBasePlayer *pPlayer = GetPlayerOwner(); if ( pPlayer ) { pPlayer->StopHintTimer( m_iAltFireHint ); } } #endif BaseClass::Drop( vecVelocity ); ReapplyProvision(); RemoveExtraWearables(); #ifndef CLIENT_DLL // Never allow weapons to lie around on the ground UTIL_Remove( this ); #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::UpdateOnRemove( void ) { RemoveExtraWearables(); BaseClass::UpdateOnRemove(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::CanHolster( void ) const { // Honorbound weapons are unable to be holstered until they have killed someone // since the last time they were brought out. We ignore this logic for the first // block of time after a weapon is taken out to allow quickswitching. // only check the first block of time logic if the weapon is active weapon // Can always holster if you have enough life cause we'll take that away CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( pPlayer && ( pPlayer->GetActiveWeapon() != this || gpGlobals->curtime >= pPlayer->m_Shared.m_flFirstPrimaryAttack ) ) { if ( IsHonorBound() && pPlayer->m_Shared.m_iKillCountSinceLastDeploy == 0 && pPlayer->GetHealth() <= 50 ) { #ifdef CLIENT_DLL pPlayer->EmitSound( "Player.DenyWeaponSelection" ); #endif return false; } } return BaseClass::CanHolster(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::Holster( CBaseCombatWeapon *pSwitchingTo ) { #ifndef CLIENT_DLL CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( pPlayer && m_iAltFireHint ) { pPlayer->StopHintTimer( m_iAltFireHint ); } // Honorbound hurt yourself if ( pPlayer && ( pPlayer->GetActiveWeapon() != this || gpGlobals->curtime >= pPlayer->m_Shared.m_flFirstPrimaryAttack ) ) { if ( IsHonorBound() && pPlayer->m_Shared.m_iKillCountSinceLastDeploy == 0 && pPlayer->GetHealth() > 0 && pPlayer->IsAlive() ) { pPlayer->TakeDamage( CTakeDamageInfo( pPlayer, pPlayer, vec3_origin, pPlayer->WorldSpaceCenter(), 50.f, GetDamageType() | DMG_PREVENT_PHYSICS_FORCE ) ); } } #endif m_iReloadMode.Set( TF_RELOAD_START ); return BaseClass::Holster( pSwitchingTo ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::Deploy( void ) { #ifndef CLIENT_DLL if ( m_iAltFireHint ) { CBasePlayer *pPlayer = GetPlayerOwner(); if ( pPlayer ) { pPlayer->StartHintTimer( m_iAltFireHint ); } } #endif m_iReloadMode.Set( TF_RELOAD_START ); float flOriginalPrimaryAttack = m_flNextPrimaryAttack; float flOriginalSecondaryAttack = m_flNextSecondaryAttack; bool bDeploy = BaseClass::Deploy(); if ( bDeploy ) { CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( !pPlayer ) return false; float flWeaponSwitchTime = 0.5f; // Overrides the anim length for calculating ready time. float flDeployTimeMultiplier = 1.0f; CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayer, flDeployTimeMultiplier, mult_deploy_time ); CALL_ATTRIB_HOOK_FLOAT( flDeployTimeMultiplier, mult_single_wep_deploy_time ); // don't apply mult_switch_from_wep_deploy_time attribute if the last weapon hasn't been deployed for more than 0.67 second to match to weapon script switch time // unless the player latched to a hook target, then allow switching right away CTFWeaponBase *pLastWeapon = dynamic_cast< CTFWeaponBase* >( pPlayer->GetLastWeapon() ); if ( pPlayer->GetGrapplingHookTarget() != NULL || ( pLastWeapon && gpGlobals->curtime - pLastWeapon->m_flLastDeployTime > flWeaponSwitchTime ) ) { CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pLastWeapon, flDeployTimeMultiplier, mult_switch_from_wep_deploy_time ); } if ( pPlayer->m_Shared.InCond( TF_COND_BLASTJUMPING ) ) { CALL_ATTRIB_HOOK_FLOAT( flDeployTimeMultiplier, mult_rocketjump_deploy_time ); } int iIsSword = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pLastWeapon, iIsSword, is_a_sword ); CALL_ATTRIB_HOOK_INT( iIsSword, is_a_sword ); if ( iIsSword ) { // swords deploy and holster 75% slower flDeployTimeMultiplier *= 1.75f; } #ifdef STAGING_ONLY if ( pPlayer->m_Shared.InCond( TF_COND_TRANQ_SPY_BOOST ) ) { flDeployTimeMultiplier /= 2.0f; } #endif // STAGING_ONLY if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_AGILITY ) { flDeployTimeMultiplier /= 5.0f; } int numHealers = pPlayer->m_Shared.GetNumHealers(); if ( numHealers == 0 ) { CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayer, flDeployTimeMultiplier, mod_medic_healed_deploy_time ); } flDeployTimeMultiplier = MAX( flDeployTimeMultiplier, 0.00001f ); float flDeployTime = flWeaponSwitchTime * flDeployTimeMultiplier; float flPlaybackRate = Clamp( ( 1.f / flDeployTimeMultiplier ) * ( 0.67f / flWeaponSwitchTime ), -4.f, 12.f ); // clamp between the range that's defined in send table if ( pPlayer->GetViewModel(0) ) { pPlayer->GetViewModel(0)->SetPlaybackRate( flPlaybackRate ); } if ( pPlayer->GetViewModel(1) ) { pPlayer->GetViewModel(1)->SetPlaybackRate( flPlaybackRate ); } // Don't override primary attacks that are already further out than this. This prevents // people exploiting weapon switches to allow weapons to fire faster. m_flNextPrimaryAttack = MAX( flOriginalPrimaryAttack, gpGlobals->curtime + flDeployTime ); m_flNextSecondaryAttack = MAX( flOriginalSecondaryAttack, m_flNextPrimaryAttack.Get() ); pPlayer->SetNextAttack( m_flNextPrimaryAttack ); m_flLastDeployTime = gpGlobals->curtime; #ifdef GAME_DLL // Reset our deploy-lifetime kill counter. pPlayer->m_Shared.m_iKillCountSinceLastDeploy = 0; pPlayer->m_Shared.m_flFirstPrimaryAttack = m_flNextPrimaryAttack; #endif // GAME_DLL } return bDeploy; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::ForceWeaponSwitch() const { // allow knockout rune to force switch to melee CTFPlayer *pOwner = GetTFPlayerOwner(); if ( pOwner && pOwner->m_Shared.GetCarryingRuneType() == RUNE_KNOCKOUT ) { int iClass = pOwner->GetPlayerClass()->GetClassIndex(); const CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem && pItem->GetStaticData()->GetLoadoutSlot( iClass ) == LOADOUT_POSITION_MELEE ) { return true; } } // should force switch to this item int iForceWeaponSwitch = 0; CALL_ATTRIB_HOOK_INT( iForceWeaponSwitch, force_weapon_switch ); return iForceWeaponSwitch != 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::Detach( void ) { BaseClass::Detach(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::OnActiveStateChanged( int iOldState ) { UpdateHiddenParentBodygroup( m_iState == WEAPON_IS_ACTIVE ); // See if we need to reapply our provider based on our active state. int iProvideMode = 0; CALL_ATTRIB_HOOK_INT( iProvideMode, provide_on_active ); if ( 1 == iProvideMode ) { ReapplyProvision(); } // Check for a speed mod change. CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( pPlayer ) { pPlayer->TeamFortress_SetSpeed(); } CEconItemView *pScriptItem = GetAttributeContainer()->GetItem(); if ( pScriptItem && pScriptItem->GetStaticData()->GetHideBodyGroupsDeployedOnly() ) { #ifdef CLIENT_DLL if ( pPlayer ) { pPlayer->SetBodygroupsDirty(); } #else int iState = 0; if ( WeaponState() == WEAPON_IS_ACTIVE ) { iState = 1; } if ( pPlayer ) { UpdateBodygroups( pPlayer, iState ); } #endif } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::VisibleInWeaponSelection( void ) { if ( BaseClass::VisibleInWeaponSelection() == false ) { return false; } if ( TFGameRules()->IsInTraining() ) { ConVarRef training_can_select_weapon_primary ( "training_can_select_weapon_primary" ); ConVarRef training_can_select_weapon_secondary ( "training_can_select_weapon_secondary" ); ConVarRef training_can_select_weapon_melee ( "training_can_select_weapon_melee" ); ConVarRef training_can_select_weapon_building ( "training_can_select_weapon_building" ); ConVarRef training_can_select_weapon_pda ( "training_can_select_weapon_pda" ); ConVarRef training_can_select_weapon_item1 ( "training_can_select_weapon_item1" ); ConVarRef training_can_select_weapon_item2 ( "training_can_select_weapon_item2" ); bool bVisible = true; switch ( GetTFWpnData().m_iWeaponType ) { case TF_WPN_TYPE_PRIMARY: bVisible = training_can_select_weapon_primary.GetBool(); break; case TF_WPN_TYPE_SECONDARY: bVisible = training_can_select_weapon_secondary.GetBool(); break; case TF_WPN_TYPE_MELEE: bVisible = training_can_select_weapon_melee.GetBool(); break; case TF_WPN_TYPE_BUILDING: bVisible = training_can_select_weapon_building.GetBool(); break; case TF_WPN_TYPE_PDA: bVisible = training_can_select_weapon_pda.GetBool(); break; case TF_WPN_TYPE_ITEM1: bVisible = training_can_select_weapon_item1.GetBool(); break; case TF_WPN_TYPE_ITEM2: bVisible = training_can_select_weapon_item2.GetBool(); break; } // switch return bVisible; } return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::UpdateHiddenParentBodygroup( bool bHide ) { #ifdef GAME_DLL CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if (!pPlayer) return; const CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem->IsValid() ) { // Old style hidden bodygroups (weapon only): int iHiddenBG = pItem->GetStaticData()->GetHiddenParentBodygroup( GetTeamNumber() ); if ( iHiddenBG != -1 ) { pPlayer->SetBodygroup( iHiddenBG, bHide ); } } #endif } void CTFWeaponBase::Misfire( void ) { CalcIsAttackCritical(); } void CTFWeaponBase::FireFullClipAtOnce( void ) { AssertMsg( 0, "weapon that has AutoFiresFullClipAllAtOnce should implement this function" ); } //----------------------------------------------------------------------------- // Purpose: // Output : //----------------------------------------------------------------------------- void CTFWeaponBase::PrimaryAttack( void ) { // Set the weapon mode. m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; if ( !CanAttack() ) return; BaseClass::PrimaryAttack(); if ( m_bReloadsSingly ) { m_iReloadMode.Set( TF_RELOAD_START ); } m_flLastPrimaryAttackTime = gpGlobals->curtime; #ifdef STAGING_ONLY // Remove Cond if I attack CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) ) { pPlayer->m_Shared.RemoveCond( TF_COND_NO_COMBAT_SPEED_BOOST ); } #endif } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- void CTFWeaponBase::SecondaryAttack( void ) { // Set the weapon mode. m_iWeaponMode = TF_WEAPON_SECONDARY_MODE; #ifdef STAGING_ONLY // Remove Cond if I attack CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_NO_COMBAT_SPEED_BOOST ) ) { pPlayer->m_Shared.RemoveCond( TF_COND_NO_COMBAT_SPEED_BOOST ); } #endif // Don't hook secondary for now. return; } //----------------------------------------------------------------------------- // Purpose: Most calls use the prediction seed //----------------------------------------------------------------------------- void CTFWeaponBase::CalcIsAttackCritical( void) { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; if ( gpGlobals->framecount == m_iLastCritCheckFrame ) return; m_iLastCritCheckFrame = gpGlobals->framecount; m_bCurrentCritIsRandom = false; #if !defined( CLIENT_DLL ) // in training mode, the all bot team does not get crits if ( TFGameRules()->IsInTraining() ) { if ( pPlayer->IsBot() && TheTFBots().IsAllBotTeam( pPlayer->GetTeamNumber() ) ) { // Support critboosted even in no crit mode m_bCurrentAttackIsCrit = CalcIsAttackCriticalHelperNoCrits(); return; } } if ( TFGameRules()->IsPVEModeActive() && TFGameRules()->IsPVEModeControlled( pPlayer ) ) { // no crits for enemies in PvE // Support critboosted even in no crit mode m_bCurrentAttackIsCrit = CalcIsAttackCriticalHelperNoCrits(); return; } #endif if ( (TFGameRules()->State_Get() == GR_STATE_TEAM_WIN) && (TFGameRules()->GetWinningTeam() == pPlayer->GetTeamNumber()) ) { m_bCurrentAttackIsCrit = true; } else if ( !AreRandomCritsEnabled() ) { // Support critboosted even in no crit mode m_bCurrentAttackIsCrit = CalcIsAttackCriticalHelperNoCrits(); } else { // call the weapon-specific helper method m_bCurrentAttackIsCrit = CalcIsAttackCriticalHelper(); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool CTFWeaponBase::CalcIsAttackCriticalHelperNoCrits() { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return false; return pPlayer->m_Shared.IsCritBoosted(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- ETFDmgCustom CTFWeaponBase::GetPenetrateType() const { int iMode = 0; CALL_ATTRIB_HOOK_INT( iMode, projectile_penetration ); #ifdef STAGING_ONLY // Prototype hack if ( !iMode ) { CTFPlayer *pPlayer = ToTFPlayer( GetOwnerEntity() ); if ( pPlayer ) { CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer, iMode, ability_master_sniper ); } } #endif // STAGING_ONLY return iMode >= 1 ? TF_DMG_CUSTOM_PENETRATE_ALL_PLAYERS : TF_DMG_CUSTOM_NONE; } //----------------------------------------------------------------------------- // Purpose: Weapon-specific helper method to calculate if attack is crit //----------------------------------------------------------------------------- bool CTFWeaponBase::CalcIsAttackCriticalHelper() { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return false; float flCritChance = 0.f; float flPlayerCritMult = pPlayer->GetCritMult(); if ( !CanFireCriticalShot() ) return false; // Crit boosted players fire all crits if ( pPlayer->m_Shared.IsCritBoosted() ) return true; // For rapid fire weapons, allow crits while period is active bool bRapidFire = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_bUseRapidFireCrits; if ( bRapidFire && m_flCritTime > gpGlobals->curtime ) return true; // --- Random crits from this point on --- // Monitor and enforce short-term random crit rate - via bucket // Figure out how much to add/remove from token bucket int nProjectilesPerShot = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nBulletsPerShot; if ( nProjectilesPerShot >= 1 ) { CALL_ATTRIB_HOOK_FLOAT( nProjectilesPerShot, mult_bullets_per_shot ); } else { nProjectilesPerShot = 1; } // Damage float flDamage = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage; CALL_ATTRIB_HOOK_FLOAT( flDamage, mult_dmg ); flDamage *= nProjectilesPerShot; AddToCritBucket( flDamage ); bool bCrit = false; m_bCurrentCritIsRandom = true; int iRandom = 0; if ( bRapidFire ) { // only perform one crit check per second for rapid fire weapons if ( tf_weapon_criticals_nopred.GetBool() ) { if ( gpGlobals->curtime < m_flLastRapidFireCritCheckTime + 1.f ) return false; m_flLastRapidFireCritCheckTime = gpGlobals->curtime; } else { if ( gpGlobals->curtime < m_flLastCritCheckTime + 1.f ) return false; m_flLastCritCheckTime = gpGlobals->curtime; } // get the total crit chance (ratio of total shots fired we want to be crits) float flTotalCritChance = clamp( TF_DAMAGE_CRIT_CHANCE_RAPID * flPlayerCritMult, 0.01f, 0.99f ); // get the fixed amount of time that we start firing crit shots for float flCritDuration = TF_DAMAGE_CRIT_DURATION_RAPID; // calculate the amount of time, on average, that we want to NOT fire crit shots for in order to achieve the total crit chance we want float flNonCritDuration = ( flCritDuration / flTotalCritChance ) - flCritDuration; // calculate the chance per second of non-crit fire that we should transition into critting such that on average we achieve the total crit chance we want float flStartCritChance = 1 / flNonCritDuration; CALL_ATTRIB_HOOK_FLOAT( flStartCritChance, mult_crit_chance ); // if base entity seed has changed since last calculation, reseed with new seed int iMask = ( entindex() << 8 ) | ( pPlayer->entindex() ); int iSeed = CBaseEntity::GetPredictionRandomSeed() ^ iMask; if ( iSeed != m_iCurrentSeed ) { m_iCurrentSeed = iSeed; RandomSeed( m_iCurrentSeed ); } // see if we should start firing crit shots iRandom = RandomInt( 0, WEAPON_RANDOM_RANGE-1 ); if ( iRandom < flStartCritChance * WEAPON_RANDOM_RANGE ) { bCrit = true; flCritChance = flStartCritChance; } } else { // single-shot weapon, just use random pct per shot flCritChance = TF_DAMAGE_CRIT_CHANCE * flPlayerCritMult; CALL_ATTRIB_HOOK_FLOAT( flCritChance, mult_crit_chance ); // mess with the crit chance seed so it's not based solely on the prediction seed int iMask = ( entindex() << 8 ) | ( pPlayer->entindex() ); int iSeed = CBaseEntity::GetPredictionRandomSeed() ^ iMask; if ( iSeed != m_iCurrentSeed ) { m_iCurrentSeed = iSeed; RandomSeed( m_iCurrentSeed ); } iRandom = RandomInt( 0, WEAPON_RANDOM_RANGE - 1 ); bCrit = ( iRandom < flCritChance * WEAPON_RANDOM_RANGE ); } #ifdef _DEBUG if ( tf_weapon_criticals_debug.GetBool() ) { #ifdef GAME_DLL DevMsg( "Roll (server): %i out of %f (crit: %d)\n", iRandom, ( flCritChance * WEAPON_RANDOM_RANGE ), bCrit ); #else if ( prediction->IsFirstTimePredicted() ) { DevMsg( "\tRoll (client): %i out of %f (crit: %d)\n", iRandom, ( flCritChance * WEAPON_RANDOM_RANGE ), bCrit ); } #endif // GAME_DLL } // Force seed to always say yes if ( tf_weapon_criticals_force_random.GetInt() ) { bCrit = true; } #endif // _DEBUG // Track each check #ifdef GAME_DLL m_nCritChecks++; #else if ( prediction->IsFirstTimePredicted() ) { m_nCritChecks++; } #endif // GAME_DLL // Seed says crit. Run it by the manager. if ( bCrit ) { bool bAntiCheat = true; #ifdef _DEBUG bAntiCheat = tf_weapon_criticals_anticheat.GetBool(); #endif // _DEBUG // Monitor and enforce long-term random crit rate - via stats if ( bAntiCheat ) { if ( !CanFireRandomCriticalShot( flCritChance ) ) return false; // Make sure rapid fire weapons can pay the cost of the entire period up-front if ( bRapidFire ) { flDamage *= TF_DAMAGE_CRIT_DURATION_RAPID / m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay; // Never try to drain more than cap int nBucketCap = tf_weapon_criticals_bucket_cap.GetInt(); if ( flDamage * TF_DAMAGE_CRIT_MULTIPLIER > nBucketCap ) flDamage = (float)nBucketCap / TF_DAMAGE_CRIT_MULTIPLIER; } bCrit = IsAllowedToWithdrawFromCritBucket( flDamage ); } if ( bCrit && bRapidFire ) { m_flCritTime = gpGlobals->curtime + TF_DAMAGE_CRIT_DURATION_RAPID; } } return bCrit; } //----------------------------------------------------------------------------- // Purpose: Return true if this weapon has some ammo //----------------------------------------------------------------------------- bool CTFWeaponBase::HasAmmo( void ) { if ( IsEnergyWeapon() ) return true; else return BaseClass::HasAmmo(); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CTFWeaponBase::Reload( void ) { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return false; if ( IsEnergyWeapon() && !Energy_FullyCharged() ) { return ReloadSingly(); } // If we're not already reloading, check to see if we have ammo to reload and check to see if we are max ammo. if ( m_iReloadMode == TF_RELOAD_START ) { // If I don't have any spare ammo, I can't reload if ( GetOwner()->GetAmmoCount(m_iPrimaryAmmoType) <= 0 ) return false; if ( !CanOverload() && Clip1() >= GetMaxClip1() ) return false; } // Reload one object at a time. if ( m_bReloadsSingly ) return ReloadSingly(); // Normal reload. DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::AbortReload( void ) { BaseClass::AbortReload(); m_iReloadMode.Set( TF_RELOAD_START ); // Make sure our reloading bodygroup is hidden (shells/grenades/etc) int indexR = FindBodygroupByName( "reload" ); if ( indexR >= 0 ) { SetBodygroup( indexR, 0 ); } } //----------------------------------------------------------------------------- // Is the weapon reloading right now? bool CTFWeaponBase::IsReloading() const { return m_iReloadMode != TF_RELOAD_START; } bool CTFWeaponBase::AutoFiresFullClip( void ) const { int nAutoFiresFullClip = 0; CALL_ATTRIB_HOOK_INT( nAutoFiresFullClip, auto_fires_full_clip ); return ( nAutoFiresFullClip != 0 ); } bool CTFWeaponBase::AutoFiresFullClipAllAtOnce( void ) const { int nAutoFiresFullClipAllAtOnce = 0; CALL_ATTRIB_HOOK_INT( nAutoFiresFullClipAllAtOnce, auto_fires_full_clip_all_at_once ); return ( nAutoFiresFullClipAllAtOnce != 0 ); } bool CTFWeaponBase::CanOverload( void ) const { int nCanOverload = 0; CALL_ATTRIB_HOOK_INT( nCanOverload, can_overload ); return ( nCanOverload != 0 ); } float CTFWeaponBase::ApplyFireDelay( float flDelay ) const { float flDelayMult = 1.0f; CALL_ATTRIB_HOOK_FLOAT( flDelayMult, mult_postfiredelay ); float flComboBoost = 0.0f; CALL_ATTRIB_HOOK_FLOAT( flComboBoost, kill_combo_fire_rate_boost ); flComboBoost *= GetKillComboCount(); flDelayMult -= flComboBoost; // Haste Powerup Rune adds multiplier to fire delay time CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( pPlayer && pPlayer->m_Shared.GetCarryingRuneType() == RUNE_HASTE ) { flDelayMult *= 0.5f; } else if ( pPlayer && ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_KING || pPlayer->m_Shared.InCond( TF_COND_KING_BUFFED ) ) ) { flDelayMult *= 0.75f; } return flDelay * flDelayMult; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CTFWeaponBase::ReloadSingly( void ) { // Don't reload. if ( m_flNextPrimaryAttack > gpGlobals->curtime ) return false; // Get the current player. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return false; // Anti Reload Cancelling Exploit (Beggers Bazooka) // Force attack if we try to reload when we have ammo in the clip if ( AutoFiresFullClip() && Clip1() > 0 && m_iReloadMode == TF_RELOAD_START ) { PrimaryAttack(); m_bFiringWholeClip = true; #ifdef CLIENT_DLL pPlayer->SetFiredWeapon( true ); #endif return false; } int nAutoFiresWhenFull = 0; CALL_ATTRIB_HOOK_INT( nAutoFiresWhenFull, auto_fires_when_full ); if ( nAutoFiresWhenFull && ( Clip1() == GetMaxClip1() || pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) ) { PrimaryAttack(); m_bFiringWholeClip = true; #ifdef CLIENT_DLL pPlayer->SetFiredWeapon( true ); #endif return false; } // check to see if we're ready to reload switch ( m_iReloadMode ) { case TF_RELOAD_START: { // Play weapon and player animations. if ( SendWeaponAnim( ACT_RELOAD_START ) ) { SetReloadTimer( SequenceDuration() ); } else { // Update the reload timers with script values. UpdateReloadTimers( true ); } // Next reload the shells. m_iReloadMode.Set( TF_RELOADING ); m_iReloadStartClipAmount = Clip1(); return true; } case TF_RELOADING: { // Did we finish the reload start? Now we can reload a rocket. if ( m_flTimeWeaponIdle > gpGlobals->curtime ) return false; // Play weapon reload animations and sound. if ( Clip1() == m_iReloadStartClipAmount ) { pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD ); } else { pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD_LOOP ); } m_bReloadedThroughAnimEvent = false; if ( SendWeaponAnim( ACT_VM_RELOAD ) ) { if ( GetWeaponID() == TF_WEAPON_GRENADELAUNCHER ) { SetReloadTimer( GetTFWpnData().m_WeaponData[TF_WEAPON_PRIMARY_MODE].m_flTimeReload ); } else { SetReloadTimer( SequenceDuration() ); } } else { // Update the reload timers. UpdateReloadTimers( false ); } // Play reload #ifdef CLIENT_DLL if ( ShouldPlayClientReloadSound() ) WeaponSound( RELOAD ); #else WeaponSound( RELOAD ); #endif // Next continue to reload shells? m_iReloadMode.Set( TF_RELOADING_CONTINUE ); return true; } case TF_RELOADING_CONTINUE: { // Did we finish the reload start? Now we can finish reloading the rocket. if ( m_flTimeWeaponIdle > gpGlobals->curtime ) return false; IncrementAmmo(); if ( IsEnergyWeapon() ) { if ( Energy_FullyCharged() ) { m_iReloadMode.Set( TF_RELOAD_FINISH ); } else { m_iReloadMode.Set( TF_RELOADING ); } } else { if ( ( !CanOverload() && ( Clip1() == GetMaxClip1() || pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 ) ) ) { m_iReloadMode.Set( TF_RELOAD_FINISH ); } else { m_iReloadMode.Set( TF_RELOADING ); } } return true; } case TF_RELOAD_FINISH: default: { if ( SendWeaponAnim( ACT_RELOAD_FINISH ) ) { // We're done, allow primary attack as soon as we like unless we're an energy weapon. // if ( IsEnergyWeapon() ) // { // SetReloadTimer( SequenceDuration() ); // } } pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD_END ); m_iReloadMode.Set( TF_RELOAD_START ); return true; } } } void CTFWeaponBase::IncrementAmmo( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); // If we have ammo, remove ammo and add it to clip if ( !m_bReloadedThroughAnimEvent ) { if ( IsEnergyWeapon() ) { Energy_Recharge(); } else if ( !CheckReloadMisfire() ) { if ( pPlayer && pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) > 0 ) { m_iClip1 = MIN( ( m_iClip1 + 1 ), GetMaxClip1() ); pPlayer->RemoveAmmo( 1, m_iPrimaryAmmoType ); } } } } //----------------------------------------------------------------------------- // Purpose: // Input : *pEvent - // *pOperator - //----------------------------------------------------------------------------- void CTFWeaponBase::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) { if ( (pEvent->type & AE_TYPE_NEWEVENTSYSTEM) /*&& (pEvent->type & AE_TYPE_SERVER)*/ ) { if ( pEvent->event == AE_WPN_INCREMENTAMMO ) { IncrementAmmo(); m_bReloadedThroughAnimEvent = true; return; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CTFWeaponBase::GetInventoryModel( void ) { // Return the world model when displaying this item in the inventory const model_t *pWorldModel = modelinfo->GetModel( m_iWorldModelIndex ); if ( pWorldModel ) return modelinfo->GetModelName( pWorldModel ); return NULL;//BaseClass::GetInventoryModel(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::NeedsReloadForAmmo1( int iClipSize1 ) const { CBaseCombatCharacter *pOwner = GetOwner(); if ( pOwner ) { // If you don't have clips, then don't try to reload them. if ( UsesClipsForAmmo1() ) { // need to reload primary clip? int primary = MIN( iClipSize1 - m_iClip1, pOwner->GetAmmoCount( m_iPrimaryAmmoType ) ); if ( primary != 0 ) return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::NeedsReloadForAmmo2( int iClipSize2 ) const { CBaseCombatCharacter *pOwner = GetOwner(); if ( pOwner ) { if ( UsesClipsForAmmo2() ) { // need to reload secondary clip? int secondary = MIN( iClipSize2 - m_iClip2, pOwner->GetAmmoCount( m_iSecondaryAmmoType ) ); if ( secondary != 0 ) return true; } } return false; } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- bool CTFWeaponBase::DefaultReload( int iClipSize1, int iClipSize2, int iActivity ) { // The the owning local player. CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return false; // Setup and check for reload. bool bReloadPrimary = NeedsReloadForAmmo1( iClipSize1 ); bool bReloadSecondary = NeedsReloadForAmmo2( iClipSize2 ); // We didn't reload. if ( !( bReloadPrimary || bReloadSecondary ) ) return false; // Play reload #ifdef CLIENT_DLL if ( ShouldPlayClientReloadSound() ) WeaponSound( RELOAD ); #else WeaponSound( RELOAD ); #endif // Play the player's reload animation pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD ); float flReloadTime; // First, see if we have a reload animation if ( SendWeaponAnim( iActivity ) ) { // We consider the reload finished 0.2 sec before the anim is, so that players don't keep accidentally aborting their reloads flReloadTime = SequenceDuration() - 0.2; } else { // No reload animation. Use the script time. flReloadTime = GetTFWpnData().m_WeaponData[TF_WEAPON_PRIMARY_MODE].m_flTimeReload; if ( bReloadSecondary ) { flReloadTime = GetTFWpnData().m_WeaponData[TF_WEAPON_SECONDARY_MODE].m_flTimeReload; } } SetReloadTimer( flReloadTime ); m_bInReload = true; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::UpdateReloadTimers( bool bStart ) { // Starting a reload? if ( bStart ) { // Get the reload start time. SetReloadTimer( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeReloadStart ); } // In reload. else { SetReloadTimer( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeReload ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::SetReloadTimer( float flReloadTime ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; float flBaseReloadTime = flReloadTime; CALL_ATTRIB_HOOK_FLOAT( flReloadTime, mult_reload_time ); CALL_ATTRIB_HOOK_FLOAT( flReloadTime, mult_reload_time_hidden ); CALL_ATTRIB_HOOK_FLOAT( flReloadTime, fast_reload ); CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayer, flReloadTime, hwn_mult_reload_time ); //int iPanicAttack = 0; //CALL_ATTRIB_HOOK_INT( iPanicAttack, panic_attack ); //if ( iPanicAttack ) //{ // if ( pPlayer->GetHealth() < pPlayer->GetMaxHealth() * 0.33f ) // { // flReloadTime *= 0.3f; // } // else if ( pPlayer->GetHealth() < pPlayer->GetMaxHealth() * 0.66f ) // { // flReloadTime *= 0.6f; // } //} // Haste Powerup Rune adds multiplier to reload time if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_HASTE ) { flReloadTime *= 0.5f; } else if ( pPlayer->m_Shared.GetCarryingRuneType() == RUNE_KING || pPlayer->m_Shared.InCond( TF_COND_KING_BUFFED ) ) { flReloadTime *= 0.75f; } int numHealers = pPlayer->m_Shared.GetNumHealers(); if ( numHealers == 1 ) { CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pPlayer, flReloadTime, mult_reload_time_while_healed ); } flReloadTime = MAX( flReloadTime, 0.00001f ); if ( pPlayer->GetViewModel(0) ) { pPlayer->GetViewModel(0)->SetPlaybackRate( flBaseReloadTime / flReloadTime ); } if ( pPlayer->GetViewModel(1) ) { pPlayer->GetViewModel(1)->SetPlaybackRate( flBaseReloadTime / flReloadTime ); } m_flReloadPriorNextFire = m_flNextPrimaryAttack; float flTime = gpGlobals->curtime + flReloadTime; // Set next player attack time (weapon independent). pPlayer->m_flNextAttack = flTime; // Set next weapon attack times (based on reloading). m_flNextPrimaryAttack = Max( flTime, (float)m_flReloadPriorNextFire); // Don't push out secondary attack, because our secondary fire // systems are all separate from primary fire (sniper zooming, demoman pipebomb detonating, etc) //m_flNextSecondaryAttack = flTime; // Set next idle time (based on reloading). SetWeaponIdleTime( flTime ); } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- bool CTFWeaponBase::PlayEmptySound() { CPASAttenuationFilter filter( this ); filter.UsePredictionRules(); // TFTODO: Add default empty sound here! // EmitSound( filter, entindex(), "Default.ClipEmpty_Rifle" ); return false; } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- void CTFWeaponBase::SendReloadEvents() { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; // Make the player play his reload animation. pPlayer->DoAnimationEvent( PLAYERANIMEVENT_RELOAD ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::ItemBusyFrame( void ) { // Call into the base ItemBusyFrame. BaseClass::ItemBusyFrame(); CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) { return; } if ( ( pOwner->m_nButtons & IN_ATTACK2 ) && /*m_bInReload == false &&*/ m_bInAttack2 == false ) { pOwner->DoClassSpecialSkill(); m_bInAttack2 = true; } else if ( !(pOwner->m_nButtons & IN_ATTACK2) && m_bInAttack2 ) { m_bInAttack2 = false; } // Interrupt a reload on reload singly weapons. if ( ( pOwner->m_nButtons & IN_ATTACK ) && Clip1() > 0 ) { bool bAbortReload = false; if ( m_bReloadsSingly ) { if ( m_iReloadMode != TF_RELOAD_START ) { m_iReloadMode.Set( TF_RELOAD_START ); bAbortReload = true; } } else if ( m_bInReload ) { // We don't let them abort before the next fire point, so they can't use reload to fire before they would have fired if they hadn't reloaded if ( gpGlobals->curtime >= m_flReloadPriorNextFire ) { bAbortReload = true; } } if ( bAbortReload ) { AbortReload(); m_bInReload = false; pOwner->m_flNextAttack = gpGlobals->curtime; m_flNextPrimaryAttack = Max( gpGlobals->curtime, m_flReloadPriorNextFire ); SetWeaponIdleTime( gpGlobals->curtime + m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeIdle ); } } #ifdef GAME_DLL // If we have an active-weapon-only regen, we accumulate regen time while active, so that // they can't avoid the regen/degen by weapon switching rapidly. ApplyItemRegen(); #endif CheckEffectBarRegen(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::ItemPostFrame( void ) { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) { return; } bool bNeedsReload = NeedsReloadForAmmo1( GetMaxClip1() ) || ( IsEnergyWeapon() && !Energy_FullyCharged() ); // If we're not shooting, and we want to autoreload, press our reload key if ( !AutoFiresFullClip() && pOwner->ShouldAutoReload() && UsesClipsForAmmo1() && !(pOwner->m_nButtons & (IN_ATTACK|IN_ATTACK2)) && bNeedsReload ) { pOwner->m_nButtons |= IN_RELOAD; } // debounce InAttack flags if ( m_bInAttack && !( pOwner->m_nButtons & IN_ATTACK ) ) { m_bInAttack = false; } if ( m_bInAttack2 && !( pOwner->m_nButtons & IN_ATTACK2 ) ) { m_bInAttack2 = false; } #ifdef GAME_DLL // If we have an active-weapon-only regen, we accumulate regen time while active, so that // they can't avoid the regen/degen by weapon switching rapidly. ApplyItemRegen(); #endif CheckEffectBarRegen(); // If we're lowered, we're not allowed to fire if ( m_bLowered ) return; // Call the base item post frame. BaseClass::ItemPostFrame(); // Check for reload singly interrupts. if ( m_bReloadsSingly ) { ReloadSinglyPostFrame(); } if ( AutoFiresFullClip() && AutoFiresFullClipAllAtOnce() && m_iClip1 == GetMaxClip1() ) { FireFullClipAtOnce(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFWeaponBase::GetInspectActivity( TFWeaponInspectStage inspectStage ) { static int s_inspectActivities[][INSPECT_STAGE_COUNT] = { // LOADOUT_POSITION_PRIMARY { ACT_PRIMARY_VM_INSPECT_START, ACT_PRIMARY_VM_INSPECT_IDLE, ACT_PRIMARY_VM_INSPECT_END }, //LOADOUT_POSITION_SECONDARY { ACT_SECONDARY_VM_INSPECT_START, ACT_SECONDARY_VM_INSPECT_IDLE, ACT_SECONDARY_VM_INSPECT_END }, //LOADOUT_POSITION_MELEE { ACT_MELEE_VM_INSPECT_START, ACT_MELEE_VM_INSPECT_IDLE, ACT_MELEE_VM_INSPECT_END }, }; loadout_positions_t iLoadoutSlot = LOADOUT_POSITION_INVALID; CTFPlayer *pOwner = GetTFPlayerOwner(); const CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pOwner && pItem ) { int iClass = pOwner->GetPlayerClass()->GetClassIndex(); iLoadoutSlot = (loadout_positions_t)pItem->GetStaticData()->GetLoadoutSlot( iClass ); } if ( iLoadoutSlot < LOADOUT_POSITION_PRIMARY || iLoadoutSlot > LOADOUT_POSITION_MELEE ) { // do primary animation for any loadout that we don't support yet iLoadoutSlot = LOADOUT_POSITION_PRIMARY; } return s_inspectActivities[iLoadoutSlot][inspectStage]; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::CanInspect() const { #ifdef STAGING_ONLY if ( tf_weapon_force_allow_inspect.GetBool() ) return true; #endif float flInspect = 0.f; CALL_ATTRIB_HOOK_FLOAT( flInspect, weapon_allow_inspect ); return flInspect != 0.f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::HandleInspect() { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return; if ( !CanInspect() ) return; // first time pressing inspecting key if ( !m_bInspecting && pPlayer->IsInspecting() ) { m_nInspectStage = INSPECT_INVALID; m_flInspectAnimTime = -1.f; if ( SendWeaponAnim( GetInspectActivity( INSPECT_START ) ) ) { m_flInspectAnimTime = gpGlobals->curtime + SequenceDuration(); m_nInspectStage = INSPECT_START; } } else if ( !pPlayer->IsInspecting() && m_nInspectStage == INSPECT_IDLE ) { // transition from idle to end when the inspect button is released if ( SendWeaponAnim( GetInspectActivity( INSPECT_END ) ) ) { m_flInspectAnimTime = gpGlobals->curtime + SequenceDuration(); m_nInspectStage = INSPECT_END; } } else if ( m_nInspectStage != INSPECT_INVALID ) // inspecting { if ( gpGlobals->curtime > m_flInspectAnimTime ) { if ( m_nInspectStage == INSPECT_START ) { TFWeaponInspectStage inspectStage = pPlayer->IsInspecting() ? INSPECT_IDLE : INSPECT_END; // transition from start to idle, or end if the inspect button is released if ( SendWeaponAnim( GetInspectActivity( inspectStage ) ) ) { m_flInspectAnimTime = gpGlobals->curtime + SequenceDuration(); m_nInspectStage = inspectStage; } } else if ( m_nInspectStage == INSPECT_END ) { m_flInspectAnimTime = -1.f; m_nInspectStage = INSPECT_INVALID; SendWeaponAnim( ACT_VM_IDLE ); } } } m_bInspecting = pPlayer->IsInspecting(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::ItemHolsterFrame( void ) { BaseClass::ItemHolsterFrame(); CheckEffectBarRegen(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::ReloadSinglyPostFrame( void ) { if ( m_flTimeWeaponIdle > gpGlobals->curtime ) return; // if the clip is empty and we have ammo remaining, if ( IsEnergyWeapon() ) { Reload(); } else if ( ( !AutoFiresFullClip() && Clip1() == 0 && GetOwner()->GetAmmoCount( m_iPrimaryAmmoType ) > 0 ) || // or we are already in the process of reloading but not finished ( m_iReloadMode != TF_RELOAD_START ) ) { // reload/continue reloading Reload(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::WeaponShouldBeLowered( void ) { // Can't be in the middle of another animation if ( GetIdealActivity() != ACT_VM_IDLE_LOWERED && GetIdealActivity() != ACT_VM_IDLE && GetIdealActivity() != ACT_VM_IDLE_TO_LOWERED && GetIdealActivity() != ACT_VM_LOWERED_TO_IDLE ) return false; if ( m_bLowered ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::Ready( void ) { // If we don't have the anim, just hide for now if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) == ACTIVITY_NOT_AVAILABLE ) { RemoveEffects( EF_NODRAW ); } m_bLowered = false; SendWeaponAnim( ACT_VM_IDLE ); // Prevent firing until our weapon is back up CTFPlayer *pPlayer = GetTFPlayerOwner(); pPlayer->m_flNextAttack = gpGlobals->curtime + SequenceDuration(); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::Lower( void ) { AbortReload(); // If we don't have the anim, just hide for now if ( SelectWeightedSequence( ACT_VM_IDLE_LOWERED ) == ACTIVITY_NOT_AVAILABLE ) { AddEffects( EF_NODRAW ); } m_bLowered = true; SendWeaponAnim( ACT_VM_IDLE_LOWERED ); return true; } //----------------------------------------------------------------------------- // Purpose: Show/hide weapon and corresponding view model if any // Input : visible - //----------------------------------------------------------------------------- void CTFWeaponBase::SetWeaponVisible( bool visible ) { if ( visible ) { RemoveEffects( EF_NODRAW ); } else { AddEffects( EF_NODRAW ); } #ifdef CLIENT_DLL UpdateVisibility(); // Force an update PreDataUpdate( DATA_UPDATE_DATATABLE_CHANGED ); #endif } //----------------------------------------------------------------------------- // Purpose: Allows the weapon to choose proper weapon idle animation //----------------------------------------------------------------------------- void CTFWeaponBase::WeaponIdle( void ) { //See if we should idle high or low if ( WeaponShouldBeLowered() ) { // Move to lowered position if we're not there yet if ( GetActivity() != ACT_VM_IDLE_LOWERED && GetActivity() != ACT_VM_IDLE_TO_LOWERED && GetActivity() != ACT_TRANSITION ) { SendWeaponAnim( ACT_VM_IDLE_LOWERED ); } else if ( HasWeaponIdleTimeElapsed() ) { // Keep idling low SendWeaponAnim( ACT_VM_IDLE_LOWERED ); } } else { // See if we need to raise immediately if ( GetActivity() == ACT_VM_IDLE_LOWERED ) { SendWeaponAnim( ACT_VM_IDLE ); } else if ( HasWeaponIdleTimeElapsed() ) { if ( !( m_bReloadsSingly && m_iReloadMode != TF_RELOAD_START ) ) { SendWeaponAnim( ACT_VM_IDLE ); m_flTimeWeaponIdle = gpGlobals->curtime + SequenceDuration(); } #ifdef GAME_DLL m_iHitsInTime = 1; m_iFiredInTime = 1; #endif // GAME_DLL } } } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- const char *CTFWeaponBase::GetMuzzleFlashModel( void ) { const char *pszModel = GetTFWpnData().m_szMuzzleFlashModel; if ( Q_strlen( pszModel ) > 0 ) { return pszModel; } return NULL; } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- const char *CTFWeaponBase::GetMuzzleFlashParticleEffect( void ) { const char *pszPEffect = GetTFWpnData().m_szMuzzleFlashParticleEffect; CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem->IsValid() ) { const char *pszItemMuzzleEffect = pItem->GetStaticData()->GetMuzzleFlash( GetTeamNumber() ); if ( pszItemMuzzleEffect ) { pszPEffect = pszItemMuzzleEffect; } } if ( Q_strlen( pszPEffect ) > 0 ) { return pszPEffect; } return NULL; } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- float CTFWeaponBase::GetMuzzleFlashModelLifetime( void ) { return GetTFWpnData().m_flMuzzleFlashModelDuration; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *CTFWeaponBase::GetTracerType( void ) { const char* pszTracerEffect = GetTFWpnData().m_szTracerEffect; if ( tf_useparticletracers.GetBool() ) { CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem->IsValid() ) { // Look for a replacement effect specified in the item's visual attributes. const char *pszItemTracerEffect = pItem->GetStaticData()->GetTracerEffect( GetTeamNumber() ); if ( pszItemTracerEffect ) { pszTracerEffect = pszItemTracerEffect; } } if ( pszTracerEffect && pszTracerEffect[0] ) { if ( !m_szTracerName[0] ) { Q_snprintf( m_szTracerName, MAX_TRACER_NAME, "%s_%s", pszTracerEffect, (GetOwner() && GetOwner()->GetTeamNumber() == TF_TEAM_RED ) ? "red" : "blue" ); } return m_szTracerName; } } if ( GetWeaponID() == TF_WEAPON_MINIGUN ) return "BrightTracer"; return BaseClass::GetTracerType(); } //============================================================================= // // TFWeaponBase functions (Server specific). // #if !defined( CLIENT_DLL ) // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- void CTFWeaponBase::CheckRespawn() { // Do not respawn. return; } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBase::Respawn() { // make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code // will decide when to make the weapon visible and touchable. CBaseEntity *pNewWeapon = CBaseEntity::Create( GetClassname(), g_pGameRules->VecWeaponRespawnSpot( this ), GetAbsAngles(), GetOwner() ); if ( pNewWeapon ) { pNewWeapon->AddEffects( EF_NODRAW );// invisible for now pNewWeapon->SetTouch( NULL );// no touch pNewWeapon->SetThink( &CTFWeaponBase::AttemptToMaterialize ); UTIL_DropToFloor( this, MASK_SOLID ); // not a typo! We want to know when the weapon the player just picked up should respawn! This new entity we created is the replacement, // but when it should respawn is based on conditions belonging to the weapon that was taken. pNewWeapon->SetNextThink( gpGlobals->curtime + g_pGameRules->FlWeaponRespawnTime( this ) ); } else { Msg( "Respawn failed to create %s!\n", GetClassname() ); } return pNewWeapon; } // ----------------------------------------------------------------------------- // Purpose: Make a weapon visible and tangible. // ----------------------------------------------------------------------------- void CTFWeaponBase::Materialize() { if ( IsEffectActive( EF_NODRAW ) ) { RemoveEffects( EF_NODRAW ); DoMuzzleFlash(); } AddSolidFlags( FSOLID_TRIGGER ); SetThink ( &CTFWeaponBase::SUB_Remove ); SetNextThink( gpGlobals->curtime + 1 ); } // ----------------------------------------------------------------------------- // Purpose: The item is trying to materialize, should it do so now or wait longer? // ----------------------------------------------------------------------------- void CTFWeaponBase::AttemptToMaterialize() { float flTime = g_pGameRules->FlWeaponTryRespawn( this ); if ( flTime == 0 ) { Materialize(); return; } SetNextThink( gpGlobals->curtime + flTime ); } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- void CTFWeaponBase::SetDieThink( bool bDie ) { if( bDie ) { SetContextThink( &CTFWeaponBase::Die, gpGlobals->curtime + 30.0f, "DieContext" ); } else { SetContextThink( NULL, gpGlobals->curtime, "DieContext" ); } } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- void CTFWeaponBase::Die( void ) { UTIL_Remove( this ); } void CTFWeaponBase::WeaponReset( void ) { m_iReloadMode.Set( TF_RELOAD_START ); m_bResetParity = !m_bResetParity; m_flEnergy = Energy_GetMaxEnergy(); } //----------------------------------------------------------------------------- // Purpose: // ---------------------------------------------------------------------------- const Vector &CTFWeaponBase::GetBulletSpread( void ) { static Vector cone = VECTOR_CONE_15DEGREES; return cone; } //----------------------------------------------------------------------------- // Purpose: // ---------------------------------------------------------------------------- void CTFWeaponBase::OnBulletFire( int iEnemyPlayersHit ) { if ( !iEnemyPlayersHit ) { m_iConsecutiveKills = 0; } else { m_iHitsInTime++; m_flLastHitTime = gpGlobals->curtime; } m_iFiredInTime++; } void CTFWeaponBase::OnPlayerKill( CTFPlayer *pVictim, const CTakeDamageInfo &info ) { m_iConsecutiveKills++; if ( pVictim ) { int nClassIndex = pVictim->GetPlayerClass()->GetClassIndex(); float fKillComboFireRateBoost = 0.0f; CALL_ATTRIB_HOOK_FLOAT( fKillComboFireRateBoost, kill_combo_fire_rate_boost ); if ( fKillComboFireRateBoost != 0.0f ) { AddKillCombo( nClassIndex ); if ( GetKillComboCount() == 1 ) { // Yell when we switch class combo type CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( pOwner ) { pOwner->SpeakConceptIfAllowed( MP_CONCEPT_COMBO_KILLED, CFmtStr( "victimclass:%s", g_aPlayerClassNames_NonLocalized[ nClassIndex ] ).Access() ); } } } } } #else void TE_DynamicLight( IRecipientFilter& filter, float delay, const Vector* org, int r, int g, int b, int exponent, float radius, float time, float decay, int nLightIndex = LIGHT_INDEX_TE_DYNAMIC ); //============================================================================= // // TFWeaponBase functions (Client specific). // bool CTFWeaponBase::IsFirstPersonView() { C_TFPlayer *pPlayerOwner = GetTFPlayerOwner(); if ( pPlayerOwner == NULL ) { return false; } return pPlayerOwner->InFirstPersonView(); } bool CTFWeaponBase::UsingViewModel() { C_TFPlayer *pPlayerOwner = GetTFPlayerOwner(); bool bIsFirstPersonView = IsFirstPersonView(); bool bUsingViewModel = bIsFirstPersonView && ( pPlayerOwner != NULL ) && !pPlayerOwner->ShouldDrawThisPlayer(); return bUsingViewModel; } C_BaseAnimating *CTFWeaponBase::GetAppropriateWorldOrViewModel() { C_TFPlayer *pPlayerOwner = GetTFPlayerOwner(); if ( pPlayerOwner && UsingViewModel() ) { // For w_* models the viewmodel itself is just arms+hands. And attached to them is the actual weapon. const CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem->IsValid() && pItem->GetStaticData()->ShouldAttachToHands() ) { C_BaseAnimating *pVMAttach = GetViewmodelAttachment(); if ( pVMAttach != NULL ) { return pVMAttach; } } // Nope - it's a standard viewmodel. C_BaseAnimating *pViewModel = pPlayerOwner->GetViewModel(); if ( pViewModel != NULL ) { return pViewModel; } // No viewmodel, so just return the normal model. return this; } else { return this; } } void CTFWeaponBase::CreateMuzzleFlashEffects( C_BaseEntity *pAttachEnt, int nIndex ) { Vector vecOrigin; QAngle angAngles; if ( !pAttachEnt ) return; if ( UsingViewModel() && !g_pClientMode->ShouldDrawViewModel() ) { // Prevent effects when the ViewModel is hidden with r_drawviewmodel=0 return; } int iMuzzleFlashAttachment = pAttachEnt->LookupAttachment( "muzzle" ); const char *pszMuzzleFlashEffect = NULL; const char *pszMuzzleFlashModel = GetMuzzleFlashModel(); const char *pszMuzzleFlashParticleEffect = GetMuzzleFlashParticleEffect(); // Pick the right muzzleflash (3rd / 1st person) // (this uses IsFirstPersonView() rather than UsingViewModel() because even when NOT using the viewmodel, in 1st-person mode we still want the 1st-person muzzleflash effect) if ( IsFirstPersonView() ) { pszMuzzleFlashEffect = GetMuzzleFlashEffectName_1st(); } else { pszMuzzleFlashEffect = GetMuzzleFlashEffectName_3rd(); } // If we have an attachment, then stick a light on it. if ( iMuzzleFlashAttachment > 0 && (pszMuzzleFlashEffect || pszMuzzleFlashModel || pszMuzzleFlashParticleEffect ) ) { pAttachEnt->GetAttachment( iMuzzleFlashAttachment, vecOrigin, angAngles ); // Muzzleflash light /* CLocalPlayerFilter filter; TE_DynamicLight( filter, 0.0f, &vecOrigin, 255, 192, 64, 5, 70.0f, 0.05f, 70.0f / 0.05f, LIGHT_INDEX_MUZZLEFLASH ); */ if ( pszMuzzleFlashEffect ) { // Using an muzzle flash dispatch effect CEffectData muzzleFlashData; muzzleFlashData.m_vOrigin = vecOrigin; muzzleFlashData.m_vAngles = angAngles; muzzleFlashData.m_hEntity = pAttachEnt->GetRefEHandle(); muzzleFlashData.m_nAttachmentIndex = iMuzzleFlashAttachment; //muzzleFlashData.m_nHitBox = GetDODWpnData().m_iMuzzleFlashType; //muzzleFlashData.m_flMagnitude = GetDODWpnData().m_flMuzzleFlashScale; muzzleFlashData.m_flMagnitude = 0.2; DispatchEffect( pszMuzzleFlashEffect, muzzleFlashData ); } if ( pszMuzzleFlashModel ) { float flEffectLifetime = GetMuzzleFlashModelLifetime(); // Using a model as a muzzle flash. if ( m_hMuzzleFlashModel[nIndex] ) { // Increase the lifetime of the muzzleflash m_hMuzzleFlashModel[nIndex]->SetLifetime( flEffectLifetime ); } else { m_hMuzzleFlashModel[nIndex] = C_MuzzleFlashModel::CreateMuzzleFlashModel( pszMuzzleFlashModel, pAttachEnt, iMuzzleFlashAttachment, flEffectLifetime ); // FIXME: This is an incredibly brutal hack to get muzzle flashes positioned correctly for recording m_hMuzzleFlashModel[nIndex]->SetIs3rdPersonFlash( nIndex == 1 ); } } if ( pszMuzzleFlashParticleEffect ) { DispatchMuzzleFlash( pszMuzzleFlashParticleEffect, pAttachEnt ); } } } void CTFWeaponBase::DispatchMuzzleFlash( const char* effectName, C_BaseEntity* pAttachEnt ) { DispatchParticleEffect( effectName, PATTACH_POINT_FOLLOW, pAttachEnt, "muzzle" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::ShouldDraw( void ) { C_BaseCombatCharacter *pOwner = GetOwner(); if ( !pOwner ) return true; C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return true; if ( pOwner->IsPlayer() ) { CTFPlayer *pTFOwner = ToTFPlayer( GetOwner() ); if ( !pTFOwner ) return true; if ( pTFOwner->m_Shared.IsControlStunned() ) return false; // Ghosts dont have weapons if ( pTFOwner->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) return false; if ( pTFOwner->m_Shared.GetDisguiseWeapon() ) { if ( pTFOwner->m_Shared.InCond( TF_COND_DISGUISED ) ) { int iLocalPlayerTeam = pLocalPlayer->GetTeamNumber(); if ( pLocalPlayer->m_bIsCoaching && pLocalPlayer->m_hStudent ) { iLocalPlayerTeam = pLocalPlayer->m_hStudent->GetTeamNumber(); } // If we are disguised we may want to draw the disguise weapon. if ( iLocalPlayerTeam != pOwner->GetTeamNumber() && (iLocalPlayerTeam != TEAM_SPECTATOR) ) { // We are a disguised enemy, so only draw the disguise weapon. if ( pTFOwner->m_Shared.GetDisguiseWeapon() != this ) { return false; } } else { // We are a disguised friendly. Don't draw the disguise weapon. if ( m_bDisguiseWeapon ) { return false; } } } else { // We are not disguised. Never draw the disguise weapon. if ( m_bDisguiseWeapon ) { return false; } } } } return BaseClass::ShouldDraw(); } void CTFWeaponBase::UpdateVisibility( void ) { BaseClass::UpdateVisibility(); UpdateExtraWearablesVisibility(); C_TFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); if ( pOwner ) { pOwner->SetBodygroupsDirty(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CTFWeaponBase::InternalDrawModel( int flags ) { C_TFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); bool bNotViewModel = ( pOwner->ShouldDrawThisPlayer() ); bool bUseInvulnMaterial = ( bNotViewModel && pOwner && pOwner->m_Shared.IsInvulnerable() && ( !pOwner->m_Shared.InCond( TF_COND_INVULNERABLE_HIDE_UNLESS_DAMAGED ) || gpGlobals->curtime < pOwner->GetLastDamageTime() + 2.0f ) ); if ( bUseInvulnMaterial ) { modelrender->ForcedMaterialOverride( *pOwner->GetInvulnMaterialRef() ); } int ret = BaseClass::InternalDrawModel( flags ); if ( bUseInvulnMaterial ) { modelrender->ForcedMaterialOverride( NULL ); } return ret; } void CTFWeaponBase::ProcessMuzzleFlashEvent( void ) { C_BaseAnimating *pAttachEnt = GetAppropriateWorldOrViewModel(); C_TFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); if ( pOwner == NULL ) return; bool bDrawMuzzleFlashOnViewModel = ( pAttachEnt != this ); { CRecordEffectOwner recordOwner( pOwner, bDrawMuzzleFlashOnViewModel ); CreateMuzzleFlashEffects( pAttachEnt, 0 ); } // Quasi-evil int nModelIndex = GetModelIndex(); int nWorldModelIndex = GetWorldModelIndex(); bool bInToolRecordingMode = ToolsEnabled() && clienttools->IsInRecordingMode(); if ( bInToolRecordingMode && nModelIndex != nWorldModelIndex && pOwner->IsLocalPlayer() ) { CRecordEffectOwner recordOwner( pOwner, false ); SetModelIndex( nWorldModelIndex ); CreateMuzzleFlashEffects( this, 1 ); SetModelIndex( nModelIndex ); } } //----------------------------------------------------------------------------- // Purpose: // ---------------------------------------------------------------------------- bool CTFWeaponBase::ShouldPredict() { if ( GetOwner() && GetOwner() == C_BasePlayer::GetLocalPlayer() ) { return true; } return BaseClass::ShouldPredict(); } //----------------------------------------------------------------------------- // Purpose: // ---------------------------------------------------------------------------- void CTFWeaponBase::WeaponReset( void ) { UpdateVisibility(); } //----------------------------------------------------------------------------- // Purpose: // Input : updateType - //----------------------------------------------------------------------------- void CTFWeaponBase::PostDataUpdate( DataUpdateType_t updateType ) { // We need to do this before the C_BaseAnimating code starts to drive // clientside animation sequences on this model, which will be using bad sequences for the world model. int iDesiredModelIndex = 0; C_BasePlayer *pOwner = ToBasePlayer(GetOwner()); if ( !pOwner->ShouldDrawThisPlayer() ) { iDesiredModelIndex = m_iViewModelIndex; } else { iDesiredModelIndex = GetWorldModelIndex(); // Our world models never animate SetSequence( 0 ); } if ( GetModelIndex() != iDesiredModelIndex ) { SetModelIndex( iDesiredModelIndex ); } BaseClass::PostDataUpdate( updateType ); } //----------------------------------------------------------------------------- // Purpose: // ---------------------------------------------------------------------------- void CTFWeaponBase::OnPreDataChanged( DataUpdateType_t type ) { BaseClass::OnPreDataChanged( type ); m_bOldResetParity = m_bResetParity; } //----------------------------------------------------------------------------- // Purpose: // ---------------------------------------------------------------------------- void CTFWeaponBase::OnDataChanged( DataUpdateType_t type ) { BaseClass::OnDataChanged( type ); if ( type == DATA_UPDATE_CREATED ) { ListenForGameEvent( "localplayer_changeteam" ); } if ( GetPredictable() && !ShouldPredict() ) { ShutdownPredictable(); } //If its a world (held or dropped) model then set the correct skin color here. if ( m_nModelIndex == GetWorldModelIndex() ) { m_nSkin = GetSkin(); } if ( m_bResetParity != m_bOldResetParity ) { WeaponReset(); } //Here we go... //Since we can't get a repro for the invisible weapon thing, I'll fix it right up here: CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); // Our owner is alive and not a loser. if ( pOwner && pOwner->IsAlive() && !pOwner->m_Shared.IsLoser() && !pOwner->m_Shared.InCond( TF_COND_COMPETITIVE_LOSER ) && ( pOwner->GetActiveWeapon() == this ) ) { // And he is NOT taunting or control stunned. if ( !pOwner->m_Shared.InCond ( TF_COND_TAUNTING ) && !pOwner->m_Shared.InCond ( TF_COND_HALLOWEEN_KART ) && (!pOwner->m_Shared.IsControlStunned() || !pOwner->m_Shared.IsLoserStateStunned() || !HideWhileStunned()) ) { if ( IsEffectActive( EF_NODRAW ) ) { RemoveEffects( EF_NODRAW ); UpdateVisibility(); } } } UpdateParticleSystems(); if ( m_iOldTeam != m_iTeamNum ) { // Recompute our tracer name m_szTracerName[0] = '\0'; } //if ( m_hExtraWearable.Get() && m_hExtraWearable->IsVisible() != IsVisible() ) if ( m_hExtraWearable.Get() ) { m_hExtraWearable->UpdateVisibility(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::FireGameEvent( IGameEvent *event ) { // If we were the active weapon, we need to update our visibility // because we may switch visibility due to Spy disguises. const char *pszEventName = event->GetName(); if ( Q_strcmp( pszEventName, "localplayer_changeteam" ) == 0 ) { UpdateVisibility(); } } //----------------------------------------------------------------------------- // Purpose: // ---------------------------------------------------------------------------- int CTFWeaponBase::GetWorldModelIndex( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); // Guitar Riff taunt support. if ( pPlayer ) { bool bReplaceModel = true; const char* pszCustomTauntProp = NULL; if ( pPlayer->m_Shared.InCond( TF_COND_TAUNTING ) && ( pPlayer->m_Shared.GetTauntIndex() == TAUNT_MISC_ITEM || pPlayer->m_Shared.GetTauntIndex() == TAUNT_LONG ) ) { int iClass = pPlayer->GetPlayerClass()->GetClassIndex(); CEconItemView *pMiscItemView = pPlayer->GetTauntEconItemView(); if ( pMiscItemView && pMiscItemView->GetStaticData()->GetTauntData() ) { // if prop has its own animation, don't replace weapon model if ( !pMiscItemView->GetStaticData()->GetTauntData()->GetPropIntroScene( iClass ) ) { pszCustomTauntProp = pMiscItemView->GetStaticData()->GetTauntData()->GetProp( iClass ); } } } if ( pszCustomTauntProp ) { m_iWorldModelIndex = modelinfo->GetModelIndex( pszCustomTauntProp ); } else { bReplaceModel = false; } if ( bReplaceModel ) { return m_iWorldModelIndex; } } if ( m_iCachedModelIndex == 0 ) { // Remember our normal world model index so we can quickly replace it later. m_iCachedModelIndex = modelinfo->GetModelIndex( GetWorldModel() ); } // We aren't taunting, so we want to use the cached model index. if ( m_iWorldModelIndex != m_iCachedModelIndex ) { m_iWorldModelIndex = m_iCachedModelIndex; } if ( IsEffectActive( EF_NODRAW ) && ShouldDraw() ) // Early-out if we are visible. { // Some taunts (guitar, bubble wand, etc.) hide us at the end of the animation. // We want to re-enable drawing if we should be drawing now but aren't. SetWeaponVisible( true ); } if ( pPlayer ) { // if we're a spy and we're disguised, we also // want to disguise our weapon's world model CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return 0; int iLocalTeam = pLocalPlayer->GetTeamNumber(); // We only show disguise weapon to the enemy team when owner is disguised bool bUseDisguiseWeapon = ( pPlayer->GetTeamNumber() != iLocalTeam && iLocalTeam > LAST_SHARED_TEAM ); if ( bUseDisguiseWeapon && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) ) { CTFWeaponBase *pDisguiseWeapon = pPlayer->m_Shared.GetDisguiseWeapon(); if ( !pDisguiseWeapon ) return BaseClass::GetWorldModelIndex(); if ( pDisguiseWeapon == this ) return BaseClass::GetWorldModelIndex(); else return pDisguiseWeapon->GetWorldModelIndex(); } } return BaseClass::GetWorldModelIndex(); } bool CTFWeaponBase::ShouldDrawCrosshair( void ) { const char *crosshairfile = cl_crosshair_file.GetString(); if ( !crosshairfile || !crosshairfile[0] ) { // Default crosshair. return GetTFWpnData().m_WeaponData[TF_WEAPON_PRIMARY_MODE].m_bDrawCrosshair; } // Custom crosshair. return true; } void CTFWeaponBase::Redraw() { if ( ShouldDrawCrosshair() && g_pClientMode->ShouldDrawCrosshair() ) { DrawCrosshair(); } } #endif acttable_t s_acttablePrimary[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_PRIMARY, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_PRIMARY, false }, { ACT_MP_DEPLOYED, ACT_MP_DEPLOYED_PRIMARY, false }, { ACT_MP_CROUCH_DEPLOYED, ACT_MP_CROUCHWALK_DEPLOYED, false }, { ACT_MP_CROUCH_DEPLOYED_IDLE, ACT_MP_CROUCH_DEPLOYED_IDLE, false }, { ACT_MP_RUN, ACT_MP_RUN_PRIMARY, false }, { ACT_MP_WALK, ACT_MP_WALK_PRIMARY, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_PRIMARY, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_PRIMARY, false }, { ACT_MP_JUMP, ACT_MP_JUMP_PRIMARY, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_PRIMARY, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_PRIMARY, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_PRIMARY, false }, { ACT_MP_SWIM, ACT_MP_SWIM_PRIMARY, false }, { ACT_MP_SWIM_DEPLOYED, ACT_MP_SWIM_DEPLOYED_PRIMARY, false }, { ACT_MP_DOUBLEJUMP_CROUCH, ACT_MP_DOUBLEJUMP_CROUCH_PRIMARY, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_ATTACK_STAND_PRIMARY, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE_DEPLOYED, ACT_MP_ATTACK_STAND_PRIMARY_DEPLOYED, false }, { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_MP_ATTACK_CROUCH_PRIMARY, false }, { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE_DEPLOYED, ACT_MP_ATTACK_CROUCH_PRIMARY_DEPLOYED, false }, { ACT_MP_ATTACK_SWIM_PRIMARYFIRE, ACT_MP_ATTACK_SWIM_PRIMARY, false }, { ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE, ACT_MP_ATTACK_AIRWALK_PRIMARY, false }, { ACT_MP_RELOAD_STAND, ACT_MP_RELOAD_STAND_PRIMARY, false }, { ACT_MP_RELOAD_STAND_LOOP, ACT_MP_RELOAD_STAND_PRIMARY_LOOP, false }, { ACT_MP_RELOAD_STAND_END, ACT_MP_RELOAD_STAND_PRIMARY_END, false }, { ACT_MP_RELOAD_CROUCH, ACT_MP_RELOAD_CROUCH_PRIMARY, false }, { ACT_MP_RELOAD_CROUCH_LOOP,ACT_MP_RELOAD_CROUCH_PRIMARY_LOOP, false }, { ACT_MP_RELOAD_CROUCH_END, ACT_MP_RELOAD_CROUCH_PRIMARY_END, false }, { ACT_MP_RELOAD_SWIM, ACT_MP_RELOAD_SWIM_PRIMARY, false }, { ACT_MP_RELOAD_SWIM_LOOP, ACT_MP_RELOAD_SWIM_PRIMARY_LOOP, false }, { ACT_MP_RELOAD_SWIM_END, ACT_MP_RELOAD_SWIM_PRIMARY_END, false }, { ACT_MP_RELOAD_AIRWALK, ACT_MP_RELOAD_AIRWALK_PRIMARY, false }, { ACT_MP_RELOAD_AIRWALK_LOOP, ACT_MP_RELOAD_AIRWALK_PRIMARY_LOOP, false }, { ACT_MP_RELOAD_AIRWALK_END,ACT_MP_RELOAD_AIRWALK_PRIMARY_END, false }, { ACT_MP_GESTURE_FLINCH, ACT_MP_GESTURE_FLINCH_PRIMARY, false }, { ACT_MP_GRENADE1_DRAW, ACT_MP_PRIMARY_GRENADE1_DRAW, false }, { ACT_MP_GRENADE1_IDLE, ACT_MP_PRIMARY_GRENADE1_IDLE, false }, { ACT_MP_GRENADE1_ATTACK, ACT_MP_PRIMARY_GRENADE1_ATTACK, false }, { ACT_MP_GRENADE2_DRAW, ACT_MP_PRIMARY_GRENADE2_DRAW, false }, { ACT_MP_GRENADE2_IDLE, ACT_MP_PRIMARY_GRENADE2_IDLE, false }, { ACT_MP_GRENADE2_ATTACK, ACT_MP_PRIMARY_GRENADE2_ATTACK, false }, { ACT_MP_ATTACK_STAND_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_CROUCH_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_SWIM_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_AIRWALK_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_GESTURE_VC_HANDMOUTH, ACT_MP_GESTURE_VC_HANDMOUTH_PRIMARY, false }, { ACT_MP_GESTURE_VC_FINGERPOINT, ACT_MP_GESTURE_VC_FINGERPOINT_PRIMARY, false }, { ACT_MP_GESTURE_VC_FISTPUMP, ACT_MP_GESTURE_VC_FISTPUMP_PRIMARY, false }, { ACT_MP_GESTURE_VC_THUMBSUP, ACT_MP_GESTURE_VC_THUMBSUP_PRIMARY, false }, { ACT_MP_GESTURE_VC_NODYES, ACT_MP_GESTURE_VC_NODYES_PRIMARY, false }, { ACT_MP_GESTURE_VC_NODNO, ACT_MP_GESTURE_VC_NODNO_PRIMARY, false }, }; acttable_t s_acttableSecondary[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_SECONDARY, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_SECONDARY, false }, { ACT_MP_RUN, ACT_MP_RUN_SECONDARY, false }, { ACT_MP_WALK, ACT_MP_WALK_SECONDARY, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_SECONDARY, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_SECONDARY, false }, { ACT_MP_JUMP, ACT_MP_JUMP_SECONDARY, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_SECONDARY, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_SECONDARY, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_SECONDARY, false }, { ACT_MP_SWIM, ACT_MP_SWIM_SECONDARY, false }, { ACT_MP_DOUBLEJUMP_CROUCH, ACT_MP_DOUBLEJUMP_CROUCH_SECONDARY, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_ATTACK_STAND_SECONDARY, false }, { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_MP_ATTACK_CROUCH_SECONDARY, false }, { ACT_MP_ATTACK_SWIM_PRIMARYFIRE, ACT_MP_ATTACK_SWIM_SECONDARY, false }, { ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE, ACT_MP_ATTACK_AIRWALK_SECONDARY, false }, { ACT_MP_RELOAD_STAND, ACT_MP_RELOAD_STAND_SECONDARY, false }, { ACT_MP_RELOAD_STAND_LOOP, ACT_MP_RELOAD_STAND_SECONDARY_LOOP, false }, { ACT_MP_RELOAD_STAND_END, ACT_MP_RELOAD_STAND_SECONDARY_END, false }, { ACT_MP_RELOAD_CROUCH, ACT_MP_RELOAD_CROUCH_SECONDARY, false }, { ACT_MP_RELOAD_CROUCH_LOOP,ACT_MP_RELOAD_CROUCH_SECONDARY_LOOP,false }, { ACT_MP_RELOAD_CROUCH_END, ACT_MP_RELOAD_CROUCH_SECONDARY_END, false }, { ACT_MP_RELOAD_SWIM, ACT_MP_RELOAD_SWIM_SECONDARY, false }, { ACT_MP_RELOAD_SWIM_LOOP, ACT_MP_RELOAD_SWIM_SECONDARY_LOOP, false }, { ACT_MP_RELOAD_SWIM_END, ACT_MP_RELOAD_SWIM_SECONDARY_END, false }, { ACT_MP_RELOAD_AIRWALK, ACT_MP_RELOAD_AIRWALK_SECONDARY, false }, { ACT_MP_RELOAD_AIRWALK_LOOP, ACT_MP_RELOAD_AIRWALK_SECONDARY_LOOP, false }, { ACT_MP_RELOAD_AIRWALK_END,ACT_MP_RELOAD_AIRWALK_SECONDARY_END,false }, { ACT_MP_GESTURE_FLINCH, ACT_MP_GESTURE_FLINCH_SECONDARY, false }, { ACT_MP_GRENADE1_DRAW, ACT_MP_SECONDARY_GRENADE1_DRAW, false }, { ACT_MP_GRENADE1_IDLE, ACT_MP_SECONDARY_GRENADE1_IDLE, false }, { ACT_MP_GRENADE1_ATTACK, ACT_MP_SECONDARY_GRENADE1_ATTACK, false }, { ACT_MP_GRENADE2_DRAW, ACT_MP_SECONDARY_GRENADE2_DRAW, false }, { ACT_MP_GRENADE2_IDLE, ACT_MP_SECONDARY_GRENADE2_IDLE, false }, { ACT_MP_GRENADE2_ATTACK, ACT_MP_SECONDARY_GRENADE2_ATTACK, false }, { ACT_MP_ATTACK_STAND_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_CROUCH_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_SWIM_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_AIRWALK_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_GESTURE_VC_HANDMOUTH, ACT_MP_GESTURE_VC_HANDMOUTH_SECONDARY, false }, { ACT_MP_GESTURE_VC_FINGERPOINT, ACT_MP_GESTURE_VC_FINGERPOINT_SECONDARY, false }, { ACT_MP_GESTURE_VC_FISTPUMP, ACT_MP_GESTURE_VC_FISTPUMP_SECONDARY, false }, { ACT_MP_GESTURE_VC_THUMBSUP, ACT_MP_GESTURE_VC_THUMBSUP_SECONDARY, false }, { ACT_MP_GESTURE_VC_NODYES, ACT_MP_GESTURE_VC_NODYES_SECONDARY, false }, { ACT_MP_GESTURE_VC_NODNO, ACT_MP_GESTURE_VC_NODNO_SECONDARY, false }, }; acttable_t s_acttablePrimary2[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_PRIMARY, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_PRIMARY, false }, { ACT_MP_DEPLOYED, ACT_MP_DEPLOYED_PRIMARY, false }, { ACT_MP_CROUCH_DEPLOYED, ACT_MP_CROUCHWALK_DEPLOYED, false }, { ACT_MP_CROUCH_DEPLOYED_IDLE, ACT_MP_CROUCH_DEPLOYED_IDLE, false }, { ACT_MP_RUN, ACT_MP_RUN_PRIMARY, false }, { ACT_MP_WALK, ACT_MP_WALK_PRIMARY, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_PRIMARY, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_PRIMARY, false }, { ACT_MP_JUMP, ACT_MP_JUMP_PRIMARY, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_PRIMARY, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_PRIMARY, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_PRIMARY, false }, { ACT_MP_SWIM, ACT_MP_SWIM_PRIMARY, false }, { ACT_MP_SWIM_DEPLOYED, ACT_MP_SWIM_DEPLOYED_PRIMARY, false }, { ACT_MP_DOUBLEJUMP_CROUCH, ACT_MP_DOUBLEJUMP_CROUCH_PRIMARY, false }, { ACT_MP_ATTACK_STAND_PRIMARY_SUPER, ACT_MP_ATTACK_STAND_PRIMARY_SUPER, false }, { ACT_MP_ATTACK_CROUCH_PRIMARY_SUPER, ACT_MP_ATTACK_CROUCH_PRIMARY_SUPER, false }, { ACT_MP_ATTACK_SWIM_PRIMARY_SUPER, ACT_MP_ATTACK_SWIM_PRIMARY_SUPER, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_ATTACK_STAND_PRIMARY_ALT, false }, { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_MP_ATTACK_CROUCH_PRIMARY_ALT, false }, { ACT_MP_ATTACK_SWIM_PRIMARYFIRE, ACT_MP_ATTACK_SWIM_PRIMARY_ALT, false }, { ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE, ACT_MP_ATTACK_AIRWALK_PRIMARY, false }, { ACT_MP_RELOAD_STAND, ACT_MP_RELOAD_STAND_PRIMARY_ALT, false }, { ACT_MP_RELOAD_STAND_LOOP, ACT_MP_RELOAD_STAND_PRIMARY_LOOP_ALT, false }, { ACT_MP_RELOAD_STAND_END, ACT_MP_RELOAD_STAND_PRIMARY_END_ALT, false }, { ACT_MP_RELOAD_CROUCH, ACT_MP_RELOAD_CROUCH_PRIMARY_ALT, false }, { ACT_MP_RELOAD_CROUCH_LOOP,ACT_MP_RELOAD_CROUCH_PRIMARY_LOOP_ALT, false }, { ACT_MP_RELOAD_CROUCH_END, ACT_MP_RELOAD_CROUCH_PRIMARY_END_ALT, false }, { ACT_MP_RELOAD_SWIM, ACT_MP_RELOAD_SWIM_PRIMARY_ALT, false }, { ACT_MP_RELOAD_SWIM_LOOP, ACT_MP_RELOAD_SWIM_PRIMARY_LOOP, false }, { ACT_MP_RELOAD_SWIM_END, ACT_MP_RELOAD_SWIM_PRIMARY_END, false }, { ACT_MP_RELOAD_AIRWALK, ACT_MP_RELOAD_AIRWALK_PRIMARY_ALT, false }, { ACT_MP_RELOAD_AIRWALK_LOOP, ACT_MP_RELOAD_AIRWALK_PRIMARY_LOOP_ALT, false }, { ACT_MP_RELOAD_AIRWALK_END,ACT_MP_RELOAD_AIRWALK_PRIMARY_END_ALT, false }, { ACT_MP_ATTACK_STAND_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_CROUCH_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_SWIM_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_AIRWALK_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_GESTURE_VC_HANDMOUTH, ACT_MP_GESTURE_VC_HANDMOUTH_PRIMARY, false }, { ACT_MP_GESTURE_VC_FINGERPOINT, ACT_MP_GESTURE_VC_FINGERPOINT_PRIMARY, false }, { ACT_MP_GESTURE_VC_FISTPUMP, ACT_MP_GESTURE_VC_FISTPUMP_PRIMARY, false }, { ACT_MP_GESTURE_VC_THUMBSUP, ACT_MP_GESTURE_VC_THUMBSUP_PRIMARY, false }, { ACT_MP_GESTURE_VC_NODYES, ACT_MP_GESTURE_VC_NODYES_PRIMARY, false }, { ACT_MP_GESTURE_VC_NODNO, ACT_MP_GESTURE_VC_NODNO_PRIMARY, false }, }; acttable_t s_acttableSecondary2[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_SECONDARY2, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_SECONDARY2, false }, { ACT_MP_RUN, ACT_MP_RUN_SECONDARY2, false }, { ACT_MP_WALK, ACT_MP_WALK_SECONDARY2, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_SECONDARY2, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_SECONDARY2, false }, { ACT_MP_JUMP, ACT_MP_JUMP_SECONDARY2, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_SECONDARY2, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_SECONDARY2, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_SECONDARY2, false }, { ACT_MP_SWIM, ACT_MP_SWIM_SECONDARY2, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_ATTACK_STAND_SECONDARY2, false }, { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_MP_ATTACK_CROUCH_SECONDARY2, false }, { ACT_MP_ATTACK_SWIM_PRIMARYFIRE, ACT_MP_ATTACK_SWIM_SECONDARY2, false }, { ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE, ACT_MP_ATTACK_AIRWALK_SECONDARY2, false }, { ACT_MP_RELOAD_STAND, ACT_MP_RELOAD_STAND_SECONDARY2, false }, { ACT_MP_RELOAD_STAND_LOOP, ACT_MP_RELOAD_STAND_SECONDARY2_LOOP, false }, { ACT_MP_RELOAD_STAND_END, ACT_MP_RELOAD_STAND_SECONDARY2_END, false }, { ACT_MP_RELOAD_CROUCH, ACT_MP_RELOAD_CROUCH_SECONDARY2, false }, { ACT_MP_RELOAD_CROUCH_LOOP,ACT_MP_RELOAD_CROUCH_SECONDARY2_LOOP,false }, { ACT_MP_RELOAD_CROUCH_END, ACT_MP_RELOAD_CROUCH_SECONDARY2_END, false }, { ACT_MP_RELOAD_SWIM, ACT_MP_RELOAD_SWIM_SECONDARY2, false }, { ACT_MP_RELOAD_SWIM_LOOP, ACT_MP_RELOAD_SWIM_SECONDARY2_LOOP, false }, { ACT_MP_RELOAD_SWIM_END, ACT_MP_RELOAD_SWIM_SECONDARY2_END, false }, { ACT_MP_RELOAD_AIRWALK, ACT_MP_RELOAD_AIRWALK_SECONDARY2, false }, { ACT_MP_RELOAD_AIRWALK_LOOP, ACT_MP_RELOAD_AIRWALK_SECONDARY2_LOOP, false }, { ACT_MP_RELOAD_AIRWALK_END,ACT_MP_RELOAD_AIRWALK_SECONDARY2_END,false }, { ACT_MP_ATTACK_STAND_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_CROUCH_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_SWIM_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_ATTACK_AIRWALK_GRENADE, ACT_MP_ATTACK_STAND_GRENADE, false }, { ACT_MP_GESTURE_VC_HANDMOUTH, ACT_MP_GESTURE_VC_HANDMOUTH_SECONDARY, false }, { ACT_MP_GESTURE_VC_FINGERPOINT, ACT_MP_GESTURE_VC_FINGERPOINT_SECONDARY, false }, { ACT_MP_GESTURE_VC_FISTPUMP, ACT_MP_GESTURE_VC_FISTPUMP_SECONDARY, false }, { ACT_MP_GESTURE_VC_THUMBSUP, ACT_MP_GESTURE_VC_THUMBSUP_SECONDARY, false }, { ACT_MP_GESTURE_VC_NODYES, ACT_MP_GESTURE_VC_NODYES_SECONDARY, false }, { ACT_MP_GESTURE_VC_NODNO, ACT_MP_GESTURE_VC_NODNO_SECONDARY, false }, }; acttable_t s_acttableMelee[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_MELEE, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_MELEE, false }, { ACT_MP_RUN, ACT_MP_RUN_MELEE, false }, { ACT_MP_WALK, ACT_MP_WALK_MELEE, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_MELEE, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_MELEE, false }, { ACT_MP_JUMP, ACT_MP_JUMP_MELEE, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_MELEE, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_MELEE, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_MELEE, false }, { ACT_MP_SWIM, ACT_MP_SWIM_MELEE, false }, { ACT_MP_DOUBLEJUMP_CROUCH, ACT_MP_DOUBLEJUMP_CROUCH_MELEE, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_ATTACK_STAND_MELEE, false }, { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_MP_ATTACK_CROUCH_MELEE, false }, { ACT_MP_ATTACK_SWIM_PRIMARYFIRE, ACT_MP_ATTACK_SWIM_MELEE, false }, { ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE, ACT_MP_ATTACK_AIRWALK_MELEE, false }, { ACT_MP_ATTACK_STAND_SECONDARYFIRE, ACT_MP_ATTACK_STAND_MELEE_SECONDARY, false }, { ACT_MP_ATTACK_CROUCH_SECONDARYFIRE, ACT_MP_ATTACK_CROUCH_MELEE_SECONDARY,false }, { ACT_MP_ATTACK_SWIM_SECONDARYFIRE, ACT_MP_ATTACK_SWIM_MELEE, false }, { ACT_MP_ATTACK_AIRWALK_SECONDARYFIRE, ACT_MP_ATTACK_AIRWALK_MELEE, false }, { ACT_MP_GESTURE_FLINCH, ACT_MP_GESTURE_FLINCH_MELEE, false }, { ACT_MP_GRENADE1_DRAW, ACT_MP_MELEE_GRENADE1_DRAW, false }, { ACT_MP_GRENADE1_IDLE, ACT_MP_MELEE_GRENADE1_IDLE, false }, { ACT_MP_GRENADE1_ATTACK, ACT_MP_MELEE_GRENADE1_ATTACK, false }, { ACT_MP_GRENADE2_DRAW, ACT_MP_MELEE_GRENADE2_DRAW, false }, { ACT_MP_GRENADE2_IDLE, ACT_MP_MELEE_GRENADE2_IDLE, false }, { ACT_MP_GRENADE2_ATTACK, ACT_MP_MELEE_GRENADE2_ATTACK, false }, { ACT_MP_GESTURE_VC_HANDMOUTH, ACT_MP_GESTURE_VC_HANDMOUTH_MELEE, false }, { ACT_MP_GESTURE_VC_FINGERPOINT, ACT_MP_GESTURE_VC_FINGERPOINT_MELEE, false }, { ACT_MP_GESTURE_VC_FISTPUMP, ACT_MP_GESTURE_VC_FISTPUMP_MELEE, false }, { ACT_MP_GESTURE_VC_THUMBSUP, ACT_MP_GESTURE_VC_THUMBSUP_MELEE, false }, { ACT_MP_GESTURE_VC_NODYES, ACT_MP_GESTURE_VC_NODYES_MELEE, false }, { ACT_MP_GESTURE_VC_NODNO, ACT_MP_GESTURE_VC_NODNO_MELEE, false }, }; acttable_t s_acttableItem1[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_ITEM1, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_ITEM1, false }, { ACT_MP_RUN, ACT_MP_RUN_ITEM1, false }, { ACT_MP_WALK, ACT_MP_WALK_ITEM1, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_ITEM1, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_ITEM1, false }, { ACT_MP_JUMP, ACT_MP_JUMP_ITEM1, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_ITEM1, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_ITEM1, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_ITEM1, false }, { ACT_MP_SWIM, ACT_MP_SWIM_ITEM1, false }, { ACT_MP_DOUBLEJUMP_CROUCH, ACT_MP_DOUBLEJUMP_CROUCH_ITEM1, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_ATTACK_STAND_ITEM1, false }, { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_MP_ATTACK_CROUCH_ITEM1, false }, { ACT_MP_ATTACK_SWIM_PRIMARYFIRE, ACT_MP_ATTACK_SWIM_ITEM1, false }, { ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE, ACT_MP_ATTACK_AIRWALK_ITEM1, false }, { ACT_MP_ATTACK_STAND_SECONDARYFIRE, ACT_MP_ATTACK_STAND_ITEM1_SECONDARY, false }, { ACT_MP_ATTACK_CROUCH_SECONDARYFIRE, ACT_MP_ATTACK_CROUCH_ITEM1_SECONDARY,false }, { ACT_MP_ATTACK_SWIM_SECONDARYFIRE, ACT_MP_ATTACK_SWIM_ITEM1, false }, { ACT_MP_ATTACK_AIRWALK_SECONDARYFIRE, ACT_MP_ATTACK_AIRWALK_ITEM1, false }, { ACT_MP_DEPLOYED, ACT_MP_DEPLOYED_ITEM1, false }, { ACT_MP_DEPLOYED_IDLE, ACT_MP_DEPLOYED_IDLE_ITEM1, false }, { ACT_MP_CROUCH_DEPLOYED, ACT_MP_CROUCHWALK_DEPLOYED_ITEM1, false }, { ACT_MP_CROUCH_DEPLOYED_IDLE, ACT_MP_CROUCH_DEPLOYED_IDLE_ITEM1, false }, //{ ACT_MP_ATTACK_STAND_PRIMARY_DEPLOYED, ACT_MP_ATTACK_STAND_PRIMARY_DEPLOYED_ITEM1, false }, //{ ACT_MP_ATTACK_CROUCH_PRIMARY_DEPLOYED,ACT_MP_ATTACK_CROUCH_PRIMARY_DEPLOYED_ITEM1, false }, { ACT_MP_GESTURE_FLINCH, ACT_MP_GESTURE_FLINCH_ITEM1, false }, { ACT_MP_GRENADE1_DRAW, ACT_MP_ITEM1_GRENADE1_DRAW, false }, { ACT_MP_GRENADE1_IDLE, ACT_MP_ITEM1_GRENADE1_IDLE, false }, { ACT_MP_GRENADE1_ATTACK, ACT_MP_ITEM1_GRENADE1_ATTACK, false }, { ACT_MP_GRENADE2_DRAW, ACT_MP_ITEM1_GRENADE2_DRAW, false }, { ACT_MP_GRENADE2_IDLE, ACT_MP_ITEM1_GRENADE2_IDLE, false }, { ACT_MP_GRENADE2_ATTACK, ACT_MP_ITEM1_GRENADE2_ATTACK, false }, { ACT_MP_GESTURE_VC_HANDMOUTH, ACT_MP_GESTURE_VC_HANDMOUTH_ITEM1, false }, { ACT_MP_GESTURE_VC_FINGERPOINT, ACT_MP_GESTURE_VC_FINGERPOINT_ITEM1, false }, { ACT_MP_GESTURE_VC_FISTPUMP, ACT_MP_GESTURE_VC_FISTPUMP_ITEM1, false }, { ACT_MP_GESTURE_VC_THUMBSUP, ACT_MP_GESTURE_VC_THUMBSUP_ITEM1, false }, { ACT_MP_GESTURE_VC_NODYES, ACT_MP_GESTURE_VC_NODYES_ITEM1, false }, { ACT_MP_GESTURE_VC_NODNO, ACT_MP_GESTURE_VC_NODNO_ITEM1, false }, }; acttable_t s_acttableItem2[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_ITEM2, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_ITEM2, false }, { ACT_MP_RUN, ACT_MP_RUN_ITEM2, false }, { ACT_MP_WALK, ACT_MP_WALK_ITEM2, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_ITEM2, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_ITEM2, false }, { ACT_MP_JUMP, ACT_MP_JUMP_ITEM2, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_ITEM2, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_ITEM2, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_ITEM2, false }, { ACT_MP_SWIM, ACT_MP_SWIM_ITEM2, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_ITEM2, false }, { ACT_MP_DOUBLEJUMP_CROUCH, ACT_MP_DOUBLEJUMP_CROUCH_ITEM2, false }, { ACT_MP_RELOAD_STAND, ACT_MP_RELOAD_STAND_ITEM2, false }, { ACT_MP_RELOAD_CROUCH, ACT_MP_RELOAD_CROUCH_ITEM2, false }, { ACT_MP_RELOAD_SWIM, ACT_MP_RELOAD_SWIM_ITEM2, false }, { ACT_MP_RELOAD_AIRWALK, ACT_MP_RELOAD_AIRWALK_ITEM2, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_ATTACK_STAND_ITEM2, false }, { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_MP_ATTACK_CROUCH_ITEM2, false }, { ACT_MP_ATTACK_SWIM_PRIMARYFIRE, ACT_MP_ATTACK_SWIM_ITEM2, false }, { ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE, ACT_MP_ATTACK_AIRWALK_ITEM2, false }, { ACT_MP_DEPLOYED, ACT_MP_DEPLOYED_ITEM2, false }, { ACT_MP_DEPLOYED_IDLE, ACT_MP_DEPLOYED_IDLE_ITEM2, false }, { ACT_MP_CROUCH_DEPLOYED, ACT_MP_CROUCHWALK_DEPLOYED_ITEM2, false }, { ACT_MP_CROUCH_DEPLOYED_IDLE, ACT_MP_CROUCH_DEPLOYED_IDLE_ITEM2, false }, //{ ACT_MP_ATTACK_STAND_PRIMARY_DEPLOYED, ACT_MP_ATTACK_STAND_PRIMARY_DEPLOYED_ITEM2, false }, //{ ACT_MP_ATTACK_CROUCH_PRIMARY_DEPLOYED,ACT_MP_ATTACK_CROUCH_PRIMARY_DEPLOYED_ITEM2, false }, { ACT_MP_ATTACK_STAND_SECONDARYFIRE, ACT_MP_ATTACK_STAND_ITEM2_SECONDARY, false }, { ACT_MP_ATTACK_CROUCH_SECONDARYFIRE, ACT_MP_ATTACK_CROUCH_ITEM2_SECONDARY,false }, { ACT_MP_ATTACK_SWIM_SECONDARYFIRE, ACT_MP_ATTACK_SWIM_ITEM2, false }, { ACT_MP_ATTACK_AIRWALK_SECONDARYFIRE, ACT_MP_ATTACK_AIRWALK_ITEM2, false }, { ACT_MP_GESTURE_FLINCH, ACT_MP_GESTURE_FLINCH_ITEM2, false }, { ACT_MP_GRENADE1_DRAW, ACT_MP_ITEM2_GRENADE1_DRAW, false }, { ACT_MP_GRENADE1_IDLE, ACT_MP_ITEM2_GRENADE1_IDLE, false }, { ACT_MP_GRENADE1_ATTACK, ACT_MP_ITEM2_GRENADE1_ATTACK, false }, { ACT_MP_GRENADE2_DRAW, ACT_MP_ITEM2_GRENADE2_DRAW, false }, { ACT_MP_GRENADE2_IDLE, ACT_MP_ITEM2_GRENADE2_IDLE, false }, { ACT_MP_GRENADE2_ATTACK, ACT_MP_ITEM2_GRENADE2_ATTACK, false }, { ACT_MP_GESTURE_VC_HANDMOUTH, ACT_MP_GESTURE_VC_HANDMOUTH_ITEM2, false }, { ACT_MP_GESTURE_VC_FINGERPOINT, ACT_MP_GESTURE_VC_FINGERPOINT_ITEM2, false }, { ACT_MP_GESTURE_VC_FISTPUMP, ACT_MP_GESTURE_VC_FISTPUMP_ITEM2, false }, { ACT_MP_GESTURE_VC_THUMBSUP, ACT_MP_GESTURE_VC_THUMBSUP_ITEM2, false }, { ACT_MP_GESTURE_VC_NODYES, ACT_MP_GESTURE_VC_NODYES_ITEM2, false }, { ACT_MP_GESTURE_VC_NODNO, ACT_MP_GESTURE_VC_NODNO_ITEM2, false }, }; acttable_t s_acttableBuilding[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_BUILDING, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_BUILDING, false }, { ACT_MP_RUN, ACT_MP_RUN_BUILDING, false }, { ACT_MP_WALK, ACT_MP_WALK_BUILDING, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_BUILDING, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_BUILDING, false }, { ACT_MP_JUMP, ACT_MP_JUMP_BUILDING, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_BUILDING, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_BUILDING, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_BUILDING, false }, { ACT_MP_SWIM, ACT_MP_SWIM_BUILDING, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_ATTACK_STAND_BUILDING, false }, { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_MP_ATTACK_CROUCH_BUILDING, false }, { ACT_MP_ATTACK_SWIM_PRIMARYFIRE, ACT_MP_ATTACK_SWIM_BUILDING, false }, { ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE, ACT_MP_ATTACK_AIRWALK_BUILDING, false }, { ACT_MP_ATTACK_STAND_GRENADE, ACT_MP_ATTACK_STAND_GRENADE_BUILDING, false }, { ACT_MP_ATTACK_CROUCH_GRENADE, ACT_MP_ATTACK_STAND_GRENADE_BUILDING, false }, { ACT_MP_ATTACK_SWIM_GRENADE, ACT_MP_ATTACK_STAND_GRENADE_BUILDING, false }, { ACT_MP_ATTACK_AIRWALK_GRENADE, ACT_MP_ATTACK_STAND_GRENADE_BUILDING, false }, { ACT_MP_GESTURE_VC_HANDMOUTH, ACT_MP_GESTURE_VC_HANDMOUTH_BUILDING, false }, { ACT_MP_GESTURE_VC_FINGERPOINT, ACT_MP_GESTURE_VC_FINGERPOINT_BUILDING, false }, { ACT_MP_GESTURE_VC_FISTPUMP, ACT_MP_GESTURE_VC_FISTPUMP_BUILDING, false }, { ACT_MP_GESTURE_VC_THUMBSUP, ACT_MP_GESTURE_VC_THUMBSUP_BUILDING, false }, { ACT_MP_GESTURE_VC_NODYES, ACT_MP_GESTURE_VC_NODYES_BUILDING, false }, { ACT_MP_GESTURE_VC_NODNO, ACT_MP_GESTURE_VC_NODNO_BUILDING, false }, }; acttable_t s_acttablePDA[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_PDA, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_PDA, false }, { ACT_MP_RUN, ACT_MP_RUN_PDA, false }, { ACT_MP_WALK, ACT_MP_WALK_PDA, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_PDA, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_PDA, false }, { ACT_MP_JUMP, ACT_MP_JUMP_PDA, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_PDA, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_PDA, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_PDA, false }, { ACT_MP_SWIM, ACT_MP_SWIM_PDA, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_ATTACK_STAND_PDA, false }, { ACT_MP_ATTACK_SWIM_PRIMARYFIRE, ACT_MP_ATTACK_SWIM_PDA, false }, { ACT_MP_GESTURE_VC_HANDMOUTH, ACT_MP_GESTURE_VC_HANDMOUTH_PDA, false }, { ACT_MP_GESTURE_VC_FINGERPOINT, ACT_MP_GESTURE_VC_FINGERPOINT_PDA, false }, { ACT_MP_GESTURE_VC_FISTPUMP, ACT_MP_GESTURE_VC_FISTPUMP_PDA, false }, { ACT_MP_GESTURE_VC_THUMBSUP, ACT_MP_GESTURE_VC_THUMBSUP_PDA, false }, { ACT_MP_GESTURE_VC_NODYES, ACT_MP_GESTURE_VC_NODYES_PDA, false }, { ACT_MP_GESTURE_VC_NODNO, ACT_MP_GESTURE_VC_NODNO_PDA, false }, }; acttable_t s_acttableMeleeAllclass[] = { { ACT_MP_STAND_IDLE, ACT_MP_STAND_MELEE_ALLCLASS, false }, { ACT_MP_CROUCH_IDLE, ACT_MP_CROUCH_MELEE_ALLCLASS, false }, { ACT_MP_RUN, ACT_MP_RUN_MELEE_ALLCLASS, false }, { ACT_MP_WALK, ACT_MP_WALK_MELEE_ALLCLASS, false }, { ACT_MP_AIRWALK, ACT_MP_AIRWALK_MELEE_ALLCLASS, false }, { ACT_MP_CROUCHWALK, ACT_MP_CROUCHWALK_MELEE_ALLCLASS, false }, { ACT_MP_JUMP, ACT_MP_JUMP_MELEE_ALLCLASS, false }, { ACT_MP_JUMP_START, ACT_MP_JUMP_START_MELEE_ALLCLASS, false }, { ACT_MP_JUMP_FLOAT, ACT_MP_JUMP_FLOAT_MELEE_ALLCLASS, false }, { ACT_MP_JUMP_LAND, ACT_MP_JUMP_LAND_MELEE_ALLCLASS, false }, { ACT_MP_SWIM, ACT_MP_SWIM_MELEE_ALLCLASS, false }, { ACT_MP_DOUBLEJUMP_CROUCH, ACT_MP_DOUBLEJUMP_CROUCH_MELEE, false }, { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_MP_ATTACK_STAND_MELEE_ALLCLASS, false }, { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_MP_ATTACK_CROUCH_MELEE_ALLCLASS, false }, { ACT_MP_ATTACK_SWIM_PRIMARYFIRE, ACT_MP_ATTACK_SWIM_MELEE_ALLCLASS, false }, { ACT_MP_ATTACK_AIRWALK_PRIMARYFIRE, ACT_MP_ATTACK_AIRWALK_MELEE_ALLCLASS, false }, { ACT_MP_ATTACK_STAND_SECONDARYFIRE, ACT_MP_ATTACK_STAND_MELEE_SECONDARY, false }, { ACT_MP_ATTACK_CROUCH_SECONDARYFIRE, ACT_MP_ATTACK_CROUCH_MELEE_SECONDARY,false }, { ACT_MP_ATTACK_SWIM_SECONDARYFIRE, ACT_MP_ATTACK_SWIM_MELEE_ALLCLASS, false }, { ACT_MP_ATTACK_AIRWALK_SECONDARYFIRE, ACT_MP_ATTACK_AIRWALK_MELEE_ALLCLASS, false }, { ACT_MP_GESTURE_FLINCH, ACT_MP_GESTURE_FLINCH_MELEE, false }, { ACT_MP_GRENADE1_DRAW, ACT_MP_MELEE_GRENADE1_DRAW, false }, { ACT_MP_GRENADE1_IDLE, ACT_MP_MELEE_GRENADE1_IDLE, false }, { ACT_MP_GRENADE1_ATTACK, ACT_MP_MELEE_GRENADE1_ATTACK, false }, { ACT_MP_GRENADE2_DRAW, ACT_MP_MELEE_GRENADE2_DRAW, false }, { ACT_MP_GRENADE2_IDLE, ACT_MP_MELEE_GRENADE2_IDLE, false }, { ACT_MP_GRENADE2_ATTACK, ACT_MP_MELEE_GRENADE2_ATTACK, false }, { ACT_MP_GESTURE_VC_HANDMOUTH, ACT_MP_GESTURE_VC_HANDMOUTH_MELEE, false }, { ACT_MP_GESTURE_VC_FINGERPOINT, ACT_MP_GESTURE_VC_FINGERPOINT_MELEE, false }, { ACT_MP_GESTURE_VC_FISTPUMP, ACT_MP_GESTURE_VC_FISTPUMP_MELEE, false }, { ACT_MP_GESTURE_VC_THUMBSUP, ACT_MP_GESTURE_VC_THUMBSUP_MELEE, false }, { ACT_MP_GESTURE_VC_NODYES, ACT_MP_GESTURE_VC_NODYES_MELEE, false }, { ACT_MP_GESTURE_VC_NODNO, ACT_MP_GESTURE_VC_NODNO_MELEE, false }, }; ConVar mp_forceactivityset( "mp_forceactivityset", "-1", FCVAR_CHEAT|FCVAR_REPLICATED|FCVAR_DEVELOPMENTONLY ); int CTFWeaponBase::GetActivityWeaponRole() const { int iWeaponRole = GetTFWpnData().m_iWeaponType; const CEconItemView *pEconItemView = GetAttributeContainer()->GetItem(); if ( pEconItemView ) { int iMaybeOverrideAnimSlot = pEconItemView->GetAnimationSlot(); if ( iMaybeOverrideAnimSlot >= 0 ) { iWeaponRole = iMaybeOverrideAnimSlot; } } if ( mp_forceactivityset.GetInt() >= 0 ) { iWeaponRole = mp_forceactivityset.GetInt(); } return iWeaponRole; } acttable_t *CTFWeaponBase::ActivityList( int &iActivityCount ) { int iWeaponRole = GetActivityWeaponRole(); #ifdef CLIENT_DLL CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( pPlayer && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && pPlayer->IsEnemyPlayer() ) { CTFWeaponBase *pDisguiseWeapon = pPlayer->m_Shared.GetDisguiseWeapon(); if ( pDisguiseWeapon && pDisguiseWeapon != this ) { return pDisguiseWeapon->ActivityList( iActivityCount ); } } #endif acttable_t *pTable; switch( iWeaponRole ) { case TF_WPN_TYPE_PRIMARY: default: iActivityCount = ARRAYSIZE( s_acttablePrimary ); pTable = s_acttablePrimary; break; case TF_WPN_TYPE_SECONDARY: iActivityCount = ARRAYSIZE( s_acttableSecondary ); pTable = s_acttableSecondary; break; case TF_WPN_TYPE_MELEE: iActivityCount = ARRAYSIZE( s_acttableMelee ); pTable = s_acttableMelee; break; case TF_WPN_TYPE_BUILDING: iActivityCount = ARRAYSIZE( s_acttableBuilding ); pTable = s_acttableBuilding; break; case TF_WPN_TYPE_PDA: iActivityCount = ARRAYSIZE( s_acttablePDA ); pTable = s_acttablePDA; break; case TF_WPN_TYPE_ITEM1: iActivityCount = ARRAYSIZE( s_acttableItem1 ); pTable = s_acttableItem1; break; case TF_WPN_TYPE_ITEM2: iActivityCount = ARRAYSIZE( s_acttableItem2 ); pTable = s_acttableItem2; break; case TF_WPN_TYPE_MELEE_ALLCLASS: iActivityCount = ARRAYSIZE( s_acttableMeleeAllclass ); pTable = s_acttableMeleeAllclass; break; case TF_WPN_TYPE_SECONDARY2: iActivityCount = ARRAYSIZE( s_acttableSecondary2 ); pTable = s_acttableSecondary2; break; case TF_WPN_TYPE_PRIMARY2: iActivityCount = ARRAYSIZE( s_acttablePrimary2 ); pTable = s_acttablePrimary2; break; } return pTable; } typedef struct { int baseAct; int weaponAct; int weaponRole; } viewmodelacttable_t; // Remaps viewmodel activities to specific ones for the weapon role. // Needed this for weapons that bonemerge themselves to the hand models to create their viewmodel. // The hand model needs to have all the animations, and be able to choose the right anims to play for the active weapon. // We use this acttable to remap the base viewmodel anims to the right one for the weapon. viewmodelacttable_t s_viewmodelacttable[] = { { ACT_VM_DRAW, ACT_PRIMARY_VM_DRAW, TF_WPN_TYPE_PRIMARY }, { ACT_VM_HOLSTER, ACT_PRIMARY_VM_HOLSTER, TF_WPN_TYPE_PRIMARY }, { ACT_VM_IDLE, ACT_PRIMARY_VM_IDLE, TF_WPN_TYPE_PRIMARY }, { ACT_VM_PULLBACK, ACT_PRIMARY_VM_PULLBACK, TF_WPN_TYPE_PRIMARY }, { ACT_VM_PRIMARYATTACK, ACT_PRIMARY_VM_PRIMARYATTACK, TF_WPN_TYPE_PRIMARY }, { ACT_VM_SECONDARYATTACK, ACT_PRIMARY_VM_SECONDARYATTACK, TF_WPN_TYPE_PRIMARY }, { ACT_VM_RELOAD, ACT_PRIMARY_VM_RELOAD, TF_WPN_TYPE_PRIMARY }, { ACT_RELOAD_START, ACT_PRIMARY_RELOAD_START, TF_WPN_TYPE_PRIMARY }, { ACT_RELOAD_FINISH, ACT_PRIMARY_RELOAD_FINISH, TF_WPN_TYPE_PRIMARY }, { ACT_VM_DRYFIRE, ACT_PRIMARY_VM_DRYFIRE, TF_WPN_TYPE_PRIMARY }, { ACT_VM_IDLE_TO_LOWERED, ACT_PRIMARY_VM_IDLE_TO_LOWERED, TF_WPN_TYPE_PRIMARY }, { ACT_VM_IDLE_LOWERED, ACT_PRIMARY_VM_IDLE_LOWERED, TF_WPN_TYPE_PRIMARY }, { ACT_VM_LOWERED_TO_IDLE, ACT_PRIMARY_VM_LOWERED_TO_IDLE, TF_WPN_TYPE_PRIMARY }, { ACT_MP_ATTACK_STAND_PREFIRE, ACT_PRIMARY_ATTACK_STAND_PREFIRE, TF_WPN_TYPE_PRIMARY }, { ACT_MP_ATTACK_STAND_POSTFIRE, ACT_PRIMARY_ATTACK_STAND_POSTFIRE, TF_WPN_TYPE_PRIMARY }, { ACT_MP_ATTACK_STAND_STARTFIRE, ACT_PRIMARY_ATTACK_STAND_STARTFIRE, TF_WPN_TYPE_PRIMARY }, { ACT_MP_ATTACK_CROUCH_PREFIRE, ACT_PRIMARY_ATTACK_CROUCH_PREFIRE, TF_WPN_TYPE_PRIMARY }, { ACT_MP_ATTACK_CROUCH_POSTFIRE, ACT_PRIMARY_ATTACK_CROUCH_POSTFIRE, TF_WPN_TYPE_PRIMARY }, { ACT_MP_ATTACK_SWIM_PREFIRE, ACT_PRIMARY_ATTACK_SWIM_PREFIRE, TF_WPN_TYPE_PRIMARY }, { ACT_MP_ATTACK_SWIM_POSTFIRE, ACT_PRIMARY_ATTACK_SWIM_POSTFIRE, TF_WPN_TYPE_PRIMARY }, { ACT_VM_DRAW, ACT_SECONDARY_VM_DRAW, TF_WPN_TYPE_SECONDARY }, { ACT_VM_HOLSTER, ACT_SECONDARY_VM_HOLSTER, TF_WPN_TYPE_SECONDARY }, { ACT_VM_IDLE, ACT_SECONDARY_VM_IDLE, TF_WPN_TYPE_SECONDARY }, { ACT_VM_PULLBACK, ACT_SECONDARY_VM_PULLBACK, TF_WPN_TYPE_SECONDARY }, { ACT_VM_PRIMARYATTACK, ACT_SECONDARY_VM_PRIMARYATTACK, TF_WPN_TYPE_SECONDARY }, { ACT_VM_SECONDARYATTACK, ACT_SECONDARY_VM_SECONDARYATTACK, TF_WPN_TYPE_SECONDARY }, { ACT_VM_RELOAD, ACT_SECONDARY_VM_RELOAD, TF_WPN_TYPE_SECONDARY }, { ACT_RELOAD_START, ACT_SECONDARY_RELOAD_START, TF_WPN_TYPE_SECONDARY }, { ACT_RELOAD_FINISH, ACT_SECONDARY_RELOAD_FINISH, TF_WPN_TYPE_SECONDARY }, { ACT_VM_DRYFIRE, ACT_SECONDARY_VM_DRYFIRE, TF_WPN_TYPE_SECONDARY }, { ACT_VM_IDLE_TO_LOWERED, ACT_SECONDARY_VM_IDLE_TO_LOWERED, TF_WPN_TYPE_SECONDARY }, { ACT_VM_IDLE_LOWERED, ACT_SECONDARY_VM_IDLE_LOWERED, TF_WPN_TYPE_SECONDARY }, { ACT_VM_LOWERED_TO_IDLE, ACT_SECONDARY_VM_LOWERED_TO_IDLE, TF_WPN_TYPE_SECONDARY }, { ACT_MP_ATTACK_STAND_PREFIRE, ACT_SECONDARY_ATTACK_STAND_PREFIRE, TF_WPN_TYPE_SECONDARY }, { ACT_MP_ATTACK_STAND_POSTFIRE, ACT_SECONDARY_ATTACK_STAND_POSTFIRE, TF_WPN_TYPE_SECONDARY }, { ACT_MP_ATTACK_STAND_STARTFIRE, ACT_SECONDARY_ATTACK_STAND_STARTFIRE, TF_WPN_TYPE_SECONDARY }, { ACT_MP_ATTACK_CROUCH_PREFIRE, ACT_SECONDARY_ATTACK_CROUCH_PREFIRE, TF_WPN_TYPE_SECONDARY }, { ACT_MP_ATTACK_CROUCH_POSTFIRE, ACT_SECONDARY_ATTACK_CROUCH_POSTFIRE, TF_WPN_TYPE_SECONDARY }, { ACT_MP_ATTACK_SWIM_PREFIRE, ACT_SECONDARY_ATTACK_SWIM_PREFIRE, TF_WPN_TYPE_SECONDARY }, { ACT_MP_ATTACK_SWIM_POSTFIRE, ACT_SECONDARY_ATTACK_SWIM_POSTFIRE, TF_WPN_TYPE_SECONDARY }, { ACT_VM_DRAW, ACT_MELEE_VM_DRAW, TF_WPN_TYPE_MELEE }, { ACT_VM_HOLSTER, ACT_MELEE_VM_HOLSTER, TF_WPN_TYPE_MELEE }, { ACT_VM_IDLE, ACT_MELEE_VM_IDLE, TF_WPN_TYPE_MELEE }, { ACT_VM_PULLBACK, ACT_MELEE_VM_PULLBACK, TF_WPN_TYPE_MELEE }, { ACT_VM_PRIMARYATTACK, ACT_MELEE_VM_PRIMARYATTACK, TF_WPN_TYPE_MELEE }, { ACT_VM_SECONDARYATTACK, ACT_MELEE_VM_SECONDARYATTACK, TF_WPN_TYPE_MELEE }, { ACT_VM_RELOAD, ACT_MELEE_VM_RELOAD, TF_WPN_TYPE_MELEE }, { ACT_VM_DRYFIRE, ACT_MELEE_VM_DRYFIRE, TF_WPN_TYPE_MELEE }, { ACT_VM_IDLE_TO_LOWERED, ACT_MELEE_VM_IDLE_TO_LOWERED, TF_WPN_TYPE_MELEE }, { ACT_VM_IDLE_LOWERED, ACT_MELEE_VM_IDLE_LOWERED, TF_WPN_TYPE_MELEE }, { ACT_VM_LOWERED_TO_IDLE, ACT_MELEE_VM_LOWERED_TO_IDLE, TF_WPN_TYPE_MELEE }, { ACT_VM_HITCENTER, ACT_MELEE_VM_HITCENTER, TF_WPN_TYPE_MELEE }, { ACT_VM_SWINGHARD, ACT_MELEE_VM_SWINGHARD, TF_WPN_TYPE_MELEE }, { ACT_MP_ATTACK_STAND_PREFIRE, ACT_MELEE_ATTACK_STAND_PREFIRE, TF_WPN_TYPE_MELEE }, { ACT_MP_ATTACK_STAND_POSTFIRE, ACT_MELEE_ATTACK_STAND_POSTFIRE, TF_WPN_TYPE_MELEE }, { ACT_MP_ATTACK_STAND_STARTFIRE, ACT_MELEE_ATTACK_STAND_STARTFIRE, TF_WPN_TYPE_MELEE }, { ACT_MP_ATTACK_CROUCH_PREFIRE, ACT_MELEE_ATTACK_CROUCH_PREFIRE, TF_WPN_TYPE_MELEE }, { ACT_MP_ATTACK_CROUCH_POSTFIRE, ACT_MELEE_ATTACK_CROUCH_POSTFIRE, TF_WPN_TYPE_MELEE }, { ACT_MP_ATTACK_SWIM_PREFIRE, ACT_MELEE_ATTACK_SWIM_PREFIRE, TF_WPN_TYPE_MELEE }, { ACT_MP_ATTACK_SWIM_POSTFIRE, ACT_MELEE_ATTACK_SWIM_POSTFIRE, TF_WPN_TYPE_MELEE }, // Scout Pack -- Bat Special State Support { ACT_VM_DRAW_SPECIAL, ACT_VM_DRAW_SPECIAL, TF_WPN_TYPE_MELEE }, { ACT_VM_HOLSTER_SPECIAL, ACT_VM_HOLSTER_SPECIAL, TF_WPN_TYPE_MELEE }, { ACT_VM_IDLE_SPECIAL, ACT_VM_IDLE_SPECIAL, TF_WPN_TYPE_MELEE }, { ACT_VM_PULLBACK_SPECIAL, ACT_VM_PULLBACK_SPECIAL, TF_WPN_TYPE_MELEE }, { ACT_VM_PRIMARYATTACK_SPECIAL, ACT_VM_PRIMARYATTACK_SPECIAL, TF_WPN_TYPE_MELEE }, { ACT_VM_SECONDARYATTACK_SPECIAL, ACT_VM_SECONDARYATTACK_SPECIAL, TF_WPN_TYPE_MELEE }, { ACT_VM_HITCENTER_SPECIAL, ACT_VM_HITCENTER_SPECIAL, TF_WPN_TYPE_MELEE }, { ACT_VM_SWINGHARD_SPECIAL, ACT_VM_SWINGHARD_SPECIAL, TF_WPN_TYPE_MELEE }, { ACT_VM_IDLE_TO_LOWERED_SPECIAL, ACT_VM_IDLE_TO_LOWERED_SPECIAL, TF_WPN_TYPE_MELEE }, { ACT_VM_IDLE_LOWERED_SPECIAL, ACT_VM_IDLE_LOWERED_SPECIAL, TF_WPN_TYPE_MELEE }, { ACT_VM_LOWERED_TO_IDLE_SPECIAL, ACT_VM_LOWERED_TO_IDLE_SPECIAL, TF_WPN_TYPE_MELEE }, // Spy Pack -- New Knife Anims { ACT_BACKSTAB_VM_DOWN, ACT_BACKSTAB_VM_DOWN, TF_WPN_TYPE_MELEE }, { ACT_BACKSTAB_VM_UP, ACT_BACKSTAB_VM_UP, TF_WPN_TYPE_MELEE }, { ACT_BACKSTAB_VM_IDLE, ACT_BACKSTAB_VM_IDLE, TF_WPN_TYPE_MELEE }, { ACT_VM_DRAW, ACT_PDA_VM_DRAW, TF_WPN_TYPE_PDA }, { ACT_VM_HOLSTER, ACT_PDA_VM_HOLSTER, TF_WPN_TYPE_PDA }, { ACT_VM_IDLE, ACT_PDA_VM_IDLE, TF_WPN_TYPE_PDA }, { ACT_VM_PULLBACK, ACT_PDA_VM_PULLBACK, TF_WPN_TYPE_PDA }, { ACT_VM_PRIMARYATTACK, ACT_PDA_VM_PRIMARYATTACK, TF_WPN_TYPE_PDA }, { ACT_VM_SECONDARYATTACK, ACT_PDA_VM_SECONDARYATTACK, TF_WPN_TYPE_PDA }, { ACT_VM_RELOAD, ACT_PDA_VM_RELOAD, TF_WPN_TYPE_PDA }, { ACT_VM_DRYFIRE, ACT_PDA_VM_DRYFIRE, TF_WPN_TYPE_PDA }, { ACT_VM_IDLE_TO_LOWERED, ACT_PDA_VM_IDLE_TO_LOWERED, TF_WPN_TYPE_PDA }, { ACT_VM_IDLE_LOWERED, ACT_PDA_VM_IDLE_LOWERED, TF_WPN_TYPE_PDA }, { ACT_VM_LOWERED_TO_IDLE, ACT_PDA_VM_LOWERED_TO_IDLE, TF_WPN_TYPE_PDA }, // ITEM1 { ACT_VM_DRAW, ACT_ITEM1_VM_DRAW, TF_WPN_TYPE_ITEM1 }, { ACT_VM_HOLSTER, ACT_ITEM1_VM_HOLSTER, TF_WPN_TYPE_ITEM1 }, { ACT_VM_IDLE, ACT_ITEM1_VM_IDLE, TF_WPN_TYPE_ITEM1 }, { ACT_VM_PULLBACK, ACT_ITEM1_VM_PULLBACK, TF_WPN_TYPE_ITEM1 }, { ACT_VM_PRIMARYATTACK, ACT_ITEM1_VM_PRIMARYATTACK, TF_WPN_TYPE_ITEM1 }, { ACT_VM_SECONDARYATTACK, ACT_ITEM1_VM_SECONDARYATTACK, TF_WPN_TYPE_ITEM1 }, { ACT_VM_RELOAD, ACT_ITEM1_VM_RELOAD, TF_WPN_TYPE_ITEM1 }, { ACT_RELOAD_START, ACT_ITEM1_RELOAD_START, TF_WPN_TYPE_ITEM1 }, { ACT_RELOAD_FINISH, ACT_ITEM1_RELOAD_FINISH, TF_WPN_TYPE_ITEM1 }, { ACT_VM_DRYFIRE, ACT_ITEM1_VM_DRYFIRE, TF_WPN_TYPE_ITEM1 }, { ACT_VM_IDLE_TO_LOWERED, ACT_ITEM1_VM_IDLE_TO_LOWERED, TF_WPN_TYPE_ITEM1 }, { ACT_VM_IDLE_LOWERED, ACT_ITEM1_VM_IDLE_LOWERED, TF_WPN_TYPE_ITEM1 }, { ACT_VM_LOWERED_TO_IDLE, ACT_ITEM1_VM_LOWERED_TO_IDLE, TF_WPN_TYPE_ITEM1 }, { ACT_MP_ATTACK_STAND_PREFIRE, ACT_ITEM1_ATTACK_STAND_PREFIRE, TF_WPN_TYPE_ITEM1 }, { ACT_MP_ATTACK_STAND_POSTFIRE, ACT_ITEM1_ATTACK_STAND_POSTFIRE, TF_WPN_TYPE_ITEM1 }, { ACT_MP_ATTACK_STAND_STARTFIRE, ACT_ITEM1_ATTACK_STAND_STARTFIRE, TF_WPN_TYPE_ITEM1 }, { ACT_MP_ATTACK_CROUCH_PREFIRE, ACT_ITEM1_ATTACK_CROUCH_PREFIRE, TF_WPN_TYPE_ITEM1 }, { ACT_MP_ATTACK_CROUCH_POSTFIRE, ACT_ITEM1_ATTACK_CROUCH_POSTFIRE, TF_WPN_TYPE_ITEM1 }, { ACT_MP_ATTACK_SWIM_PREFIRE, ACT_ITEM1_ATTACK_SWIM_PREFIRE, TF_WPN_TYPE_ITEM1 }, { ACT_MP_ATTACK_SWIM_POSTFIRE, ACT_ITEM1_ATTACK_SWIM_POSTFIRE, TF_WPN_TYPE_ITEM1 }, // ITEM2 { ACT_VM_DRAW, ACT_ITEM2_VM_DRAW, TF_WPN_TYPE_ITEM2 }, { ACT_VM_HOLSTER, ACT_ITEM2_VM_HOLSTER, TF_WPN_TYPE_ITEM2 }, { ACT_VM_IDLE, ACT_ITEM2_VM_IDLE, TF_WPN_TYPE_ITEM2 }, { ACT_VM_PULLBACK, ACT_ITEM2_VM_PULLBACK, TF_WPN_TYPE_ITEM2 }, { ACT_VM_PRIMARYATTACK, ACT_ITEM2_VM_PRIMARYATTACK, TF_WPN_TYPE_ITEM2 }, { ACT_VM_SECONDARYATTACK, ACT_ITEM2_VM_SECONDARYATTACK, TF_WPN_TYPE_ITEM2 }, { ACT_VM_RELOAD, ACT_ITEM2_VM_RELOAD, TF_WPN_TYPE_ITEM2 }, { ACT_VM_DRYFIRE, ACT_ITEM2_VM_DRYFIRE, TF_WPN_TYPE_ITEM2 }, { ACT_VM_IDLE_TO_LOWERED, ACT_ITEM2_VM_IDLE_TO_LOWERED, TF_WPN_TYPE_ITEM2 }, { ACT_VM_IDLE_LOWERED, ACT_ITEM2_VM_IDLE_LOWERED, TF_WPN_TYPE_ITEM2 }, { ACT_VM_LOWERED_TO_IDLE, ACT_ITEM2_VM_LOWERED_TO_IDLE, TF_WPN_TYPE_ITEM2 }, { ACT_MP_ATTACK_STAND_PREFIRE, ACT_ITEM2_ATTACK_STAND_PREFIRE, TF_WPN_TYPE_ITEM2 }, { ACT_MP_ATTACK_STAND_POSTFIRE, ACT_ITEM2_ATTACK_STAND_POSTFIRE, TF_WPN_TYPE_ITEM2 }, { ACT_MP_ATTACK_STAND_STARTFIRE, ACT_ITEM2_ATTACK_STAND_STARTFIRE, TF_WPN_TYPE_ITEM2 }, { ACT_MP_ATTACK_CROUCH_PREFIRE, ACT_ITEM2_ATTACK_CROUCH_PREFIRE, TF_WPN_TYPE_ITEM2 }, { ACT_MP_ATTACK_CROUCH_POSTFIRE, ACT_ITEM2_ATTACK_CROUCH_POSTFIRE, TF_WPN_TYPE_ITEM2 }, { ACT_MP_ATTACK_SWIM_PREFIRE, ACT_ITEM2_ATTACK_SWIM_PREFIRE, TF_WPN_TYPE_ITEM2 }, { ACT_MP_ATTACK_SWIM_POSTFIRE, ACT_ITEM2_ATTACK_SWIM_POSTFIRE, TF_WPN_TYPE_ITEM2 }, { ACT_VM_DRAW, ACT_MELEE_ALLCLASS_VM_DRAW, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_HOLSTER, ACT_MELEE_ALLCLASS_VM_HOLSTER, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_IDLE, ACT_MELEE_ALLCLASS_VM_IDLE, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_PULLBACK, ACT_MELEE_ALLCLASS_VM_PULLBACK, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_PRIMARYATTACK, ACT_MELEE_ALLCLASS_VM_PRIMARYATTACK, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_SECONDARYATTACK, ACT_MELEE_ALLCLASS_VM_SECONDARYATTACK, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_RELOAD, ACT_MELEE_ALLCLASS_VM_RELOAD, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_DRYFIRE, ACT_MELEE_ALLCLASS_VM_DRYFIRE, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_IDLE_TO_LOWERED, ACT_MELEE_ALLCLASS_VM_IDLE_TO_LOWERED, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_IDLE_LOWERED, ACT_MELEE_ALLCLASS_VM_IDLE_LOWERED, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_LOWERED_TO_IDLE, ACT_MELEE_ALLCLASS_VM_LOWERED_TO_IDLE, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_HITCENTER, ACT_MELEE_ALLCLASS_VM_HITCENTER, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_SWINGHARD, ACT_MELEE_ALLCLASS_VM_SWINGHARD, TF_WPN_TYPE_MELEE_ALLCLASS }, { ACT_VM_DRAW, ACT_SECONDARY2_VM_DRAW, TF_WPN_TYPE_SECONDARY2 }, { ACT_VM_HOLSTER, ACT_SECONDARY2_VM_HOLSTER, TF_WPN_TYPE_SECONDARY2 }, { ACT_VM_IDLE, ACT_SECONDARY2_VM_IDLE, TF_WPN_TYPE_SECONDARY2 }, { ACT_VM_PULLBACK, ACT_SECONDARY2_VM_PULLBACK, TF_WPN_TYPE_SECONDARY2 }, { ACT_VM_PRIMARYATTACK, ACT_SECONDARY2_VM_PRIMARYATTACK, TF_WPN_TYPE_SECONDARY2 }, { ACT_VM_RELOAD, ACT_SECONDARY2_VM_RELOAD, TF_WPN_TYPE_SECONDARY2 }, { ACT_RELOAD_START, ACT_SECONDARY2_RELOAD_START, TF_WPN_TYPE_SECONDARY2 }, { ACT_RELOAD_FINISH, ACT_SECONDARY2_RELOAD_FINISH, TF_WPN_TYPE_SECONDARY2 }, { ACT_VM_DRYFIRE, ACT_SECONDARY2_VM_DRYFIRE, TF_WPN_TYPE_SECONDARY2 }, { ACT_VM_IDLE_TO_LOWERED, ACT_SECONDARY2_VM_IDLE_TO_LOWERED, TF_WPN_TYPE_SECONDARY2 }, { ACT_VM_IDLE_LOWERED, ACT_SECONDARY2_VM_IDLE_LOWERED, TF_WPN_TYPE_SECONDARY2 }, { ACT_VM_LOWERED_TO_IDLE, ACT_SECONDARY2_VM_LOWERED_TO_IDLE, TF_WPN_TYPE_SECONDARY2 }, { ACT_VM_DRAW, ACT_PRIMARY_VM_DRAW, TF_WPN_TYPE_PRIMARY2 }, { ACT_VM_HOLSTER, ACT_PRIMARY_VM_HOLSTER, TF_WPN_TYPE_PRIMARY2 }, { ACT_VM_IDLE, ACT_PRIMARY_VM_IDLE, TF_WPN_TYPE_PRIMARY2 }, { ACT_VM_PULLBACK, ACT_PRIMARY_VM_PULLBACK, TF_WPN_TYPE_PRIMARY2 }, { ACT_VM_PRIMARYATTACK, ACT_PRIMARY_VM_PRIMARYATTACK, TF_WPN_TYPE_PRIMARY2 }, { ACT_VM_RELOAD, ACT_PRIMARY_VM_RELOAD_3, TF_WPN_TYPE_PRIMARY2 }, { ACT_RELOAD_START, ACT_PRIMARY_RELOAD_START_3, TF_WPN_TYPE_PRIMARY2 }, { ACT_RELOAD_FINISH, ACT_PRIMARY_RELOAD_FINISH_3, TF_WPN_TYPE_PRIMARY2 }, { ACT_VM_DRYFIRE, ACT_PRIMARY_VM_DRYFIRE, TF_WPN_TYPE_PRIMARY2 }, { ACT_VM_IDLE_TO_LOWERED, ACT_PRIMARY_VM_IDLE_TO_LOWERED, TF_WPN_TYPE_PRIMARY2 }, { ACT_VM_IDLE_LOWERED, ACT_PRIMARY_VM_IDLE_LOWERED, TF_WPN_TYPE_PRIMARY2 }, { ACT_VM_LOWERED_TO_IDLE, ACT_PRIMARY_VM_LOWERED_TO_IDLE, TF_WPN_TYPE_PRIMARY2 }, }; // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- Activity CTFWeaponBase::TranslateViewmodelHandActivityInternal( Activity actBase ) { CEconItemView *pEconItemView = GetAttributeContainer()->GetItem(); if ( pEconItemView && pEconItemView->IsValid() && GetOwnerEntity() ) { Activity translatedActivity = pEconItemView->GetStaticData()->GetActivityOverride( GetOwnerEntity()->GetTeamNumber(), actBase ); if ( translatedActivity != actBase ) return translatedActivity; } int iWeaponRole = GetViewModelWeaponRole(); if ( pEconItemView ) { int iMaybeOverrideAnimSlot = pEconItemView->GetAnimationSlot(); if ( iMaybeOverrideAnimSlot >= 0 ) { iWeaponRole = iMaybeOverrideAnimSlot; } } viewmodelacttable_t *pTable = s_viewmodelacttable; for ( int i = 0; i < ARRAYSIZE(s_viewmodelacttable); i++ ) { const viewmodelacttable_t &act = pTable[i]; if ( actBase == act.baseAct && act.weaponRole == iWeaponRole ) return (Activity)act.weaponAct; } return actBase; } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- CBasePlayer *CTFWeaponBase::GetPlayerOwner() const { return dynamic_cast( GetOwner() ); } // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- CTFPlayer *CTFWeaponBase::GetTFPlayerOwner() const { return dynamic_cast( GetOwner() ); } #ifdef CLIENT_DLL // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- C_BaseEntity *CTFWeaponBase::GetWeaponForEffect() { return GetAppropriateWorldOrViewModel(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- ShadowType_t CTFWeaponBase::ShadowCastType( void ) { // Some weapons (fists) don't actually get set to NODRAW when holstered so we // need some extra checks if ( IsEffectActive( EF_NODRAW | EF_NOSHADOW ) || m_iState != WEAPON_IS_ACTIVE ) return SHADOWS_NONE; return BaseClass::ShadowCastType(); } #endif // ----------------------------------------------------------------------------- // Purpose: // ----------------------------------------------------------------------------- bool CTFWeaponBase::CanAttack() { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( pPlayer ) return pPlayer->CanAttack( GetCanAttackFlags() ); return false; } //----------------------------------------------------------------------------- bool CTFWeaponBase::CanFireCriticalShot( bool bIsHeadshot ) { #ifdef GAME_DLL CTFPlayer *player = GetTFPlayerOwner(); if ( TFGameRules()->IsPVEModeControlled( player ) ) { // scenario bots cant crit (unless they always do) CTFBot *bot = ToTFBot( player ); return ( bot && bot->HasAttribute( CTFBot::ALWAYS_CRIT ) ); } #ifdef TF_CREEP_MODE if ( TFGameRules()->IsCreepWaveMode() && player ) { CTFBot *bot = ToTFBot( player ); if ( bot && bot->HasAttribute( CTFBot::IS_NPC ) ) { // creeps can't crit return false; } } #endif // TF_CREEP_MODE #endif return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::CanFireRandomCriticalShot( float flCritChance ) { #ifdef GAME_DLL // Todo: Create a version of this in tf_weaponbase_melee CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return false; PlayerStats_t *pPlayerStats = CTF_GameStats.FindPlayerStats( pPlayer ); if ( pPlayerStats ) { // Compare total damage done against total crit damage done. If this // ratio is out of range for the expected crit chance, deny the crit. int nRandomRangedCritDamage = pPlayerStats->statsCurrentRound.m_iStat[TFSTAT_DAMAGE_RANGED_CRIT_RANDOM]; int nTotalDamage = pPlayerStats->statsCurrentRound.m_iStat[TFSTAT_DAMAGE_RANGED]; // Early out if ( !nTotalDamage ) return true; float flNormalizedDamage = (float)nRandomRangedCritDamage / TF_DAMAGE_CRIT_MULTIPLIER; m_flObservedCritChance.Set( flNormalizedDamage / ( flNormalizedDamage + ( nTotalDamage - nRandomRangedCritDamage ) ) ); // DevMsg ( "SERVER: CritChance: %f Observed: %f\n", flCritChance, m_flObservedCritChance.Get() ); } #else // DevMsg ( "CLIENT: CritChance: %f Observed: %f\n", flCritChance, m_flObservedCritChance.Get() ); #endif // GAME_DLL if ( m_flObservedCritChance.Get() > flCritChance + 0.1f ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- char const *CTFWeaponBase::GetShootSound( int iIndex ) const { const CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem->IsValid() ) { int nTeam = GetTeamNumber(); if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && nTeam == TF_TEAM_PVE_INVADERS ) { CTFPlayer *pPlayer = ToTFPlayer( GetOwner() ); if ( pPlayer && pPlayer->IsMiniBoss() ) { // Not a real team - just a define used in replacing visuals via itemdefs ("visuals_mvm") nTeam = TF_TEAM_PVE_INVADERS_GIANTS; } } const char *pszSound = pItem->GetStaticData()->GetWeaponReplacementSound( nTeam, (WeaponSound_t)iIndex ); if ( pszSound ) return pszSound; } return BaseClass::GetShootSound(iIndex); } //----------------------------------------------------------------------------- // Purpose: Owner is stunned. //----------------------------------------------------------------------------- void CTFWeaponBase::OnControlStunned( void ) { // Abort reloading. AbortReload(); // Hide the weapon. SetWeaponVisible( false ); } #if defined( CLIENT_DLL ) static ConVar cl_bobcycle( "cl_bobcycle","0.8", FCVAR_CHEAT ); static ConVar cl_bobup( "cl_bobup","0.5", FCVAR_CHEAT ); //----------------------------------------------------------------------------- // Purpose: Helper function to calculate head bob //----------------------------------------------------------------------------- float CalcViewModelBobHelper( CBasePlayer *player, BobState_t *pBobState ) { Assert( pBobState ); if ( !pBobState ) return 0; float cycle; // Don't allow zeros, because we divide by them. float flBobup = cl_bobup.GetFloat(); if ( flBobup <= 0 ) { flBobup = 0.01; } float flBobCycle = cl_bobcycle.GetFloat(); if ( flBobCycle <= 0 ) { flBobCycle = 0.01; } //NOTENOTE: For now, let this cycle continue when in the air, because it snaps badly without it if ( ( !gpGlobals->frametime ) || ( player == NULL ) ) { //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) return 0.0f;// just use old value } //Find the speed of the player float speed = player->GetLocalVelocity().Length2D(); float flmaxSpeedDelta = MAX( 0, (gpGlobals->curtime - pBobState->m_flLastBobTime ) * 320.0f ); // don't allow too big speed changes speed = clamp( speed, pBobState->m_flLastSpeed-flmaxSpeedDelta, pBobState->m_flLastSpeed+flmaxSpeedDelta ); speed = clamp( speed, -320.f, 320.f ); pBobState->m_flLastSpeed = speed; //FIXME: This maximum speed value must come from the server. // MaxSpeed() is not sufficient for dealing with sprinting - jdw float bob_offset = RemapVal( speed, 0, 320, 0.0f, 1.0f ); pBobState->m_flBobTime += ( gpGlobals->curtime - pBobState->m_flLastBobTime ) * bob_offset; pBobState->m_flLastBobTime = gpGlobals->curtime; //Calculate the vertical bob cycle = pBobState->m_flBobTime - (int)(pBobState->m_flBobTime/flBobCycle)*flBobCycle; cycle /= flBobCycle; if ( cycle < flBobup ) { cycle = M_PI * cycle / flBobup; } else { cycle = M_PI + M_PI*(cycle-flBobup)/(1.0 - flBobup); } pBobState->m_flVerticalBob = speed*0.005f; pBobState->m_flVerticalBob = pBobState->m_flVerticalBob*0.3 + pBobState->m_flVerticalBob*0.7*sin(cycle); pBobState->m_flVerticalBob = clamp( pBobState->m_flVerticalBob, -7.0f, 4.0f ); //Calculate the lateral bob cycle = pBobState->m_flBobTime - (int)(pBobState->m_flBobTime/flBobCycle*2)*flBobCycle*2; cycle /= flBobCycle*2; if ( cycle < flBobup ) { cycle = M_PI * cycle / flBobup; } else { cycle = M_PI + M_PI*(cycle-flBobup)/(1.0 - flBobup); } pBobState->m_flLateralBob = speed*0.005f; pBobState->m_flLateralBob = pBobState->m_flLateralBob*0.3 + pBobState->m_flLateralBob*0.7*sin(cycle); pBobState->m_flLateralBob = clamp( pBobState->m_flLateralBob, -7.0f, 4.0f ); //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) return 0.0f; } //----------------------------------------------------------------------------- // Purpose: Helper function to add head bob //----------------------------------------------------------------------------- void AddViewModelBobHelper( Vector &origin, QAngle &angles, BobState_t *pBobState ) { Assert( pBobState ); if ( !pBobState ) return; Vector forward, right; AngleVectors( angles, &forward, &right, NULL ); // Apply bob, but scaled down to 40% VectorMA( origin, pBobState->m_flVerticalBob * 0.4f, forward, origin ); // Z bob a bit more origin[2] += pBobState->m_flVerticalBob * 0.1f; // bob the angles angles[ ROLL ] += pBobState->m_flVerticalBob * 0.5f; angles[ PITCH ] -= pBobState->m_flVerticalBob * 0.4f; angles[ YAW ] -= pBobState->m_flLateralBob * 0.3f; VectorMA( origin, pBobState->m_flLateralBob * 0.2f, right, origin ); } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float CTFWeaponBase::CalcViewmodelBob( void ) { CBasePlayer *player = ToBasePlayer( GetOwner() ); //Assert( player ); BobState_t *pBobState = GetBobState(); if ( pBobState ) return ::CalcViewModelBobHelper( player, pBobState ); else return 0; } //----------------------------------------------------------------------------- // Purpose: // Input : &origin - // &angles - // viewmodelindex - //----------------------------------------------------------------------------- void CTFWeaponBase::AddViewmodelBob( CBaseViewModel *viewmodel, Vector &origin, QAngle &angles ) { // call helper functions to do the calculation BobState_t *pBobState = GetBobState(); if ( pBobState ) { CalcViewmodelBob(); ::AddViewModelBobHelper( origin, angles, pBobState ); } } //----------------------------------------------------------------------------- // Purpose: Returns the head bob state for this weapon, which is stored // in the view model. Note that this this function can return // NULL if the player is dead or the view model is otherwise not present. //----------------------------------------------------------------------------- BobState_t *CTFWeaponBase::GetBobState() { // get the view model for this weapon CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( pOwner == NULL ) return NULL; CBaseViewModel *baseViewModel = pOwner->GetViewModel( m_nViewModelIndex ); if ( baseViewModel == NULL ) return NULL; CTFViewModel *viewModel = dynamic_cast(baseViewModel); Assert( viewModel ); // get the bob state out of the view model return &( viewModel->GetBobState() ); } #endif // defined( CLIENT_DLL ) //----------------------------------------------------------------------------- // Purpose: Used for spy invisiblity material, skin overrides, and team colors //----------------------------------------------------------------------------- int CTFWeaponBase::GetSkin() { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( !pPlayer ) return 0; int iTeamNumber = pPlayer->GetTeamNumber(); #if defined( CLIENT_DLL ) // Run client-only "is the viewer on the same team as the wielder" logic. Assumed to // always be false on the server. CTFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return 0; int iLocalTeam = pLocalPlayer->GetTeamNumber(); // We only show disguise weapon to the enemy team when owner is disguised bool bUseDisguiseWeapon = ( iTeamNumber != iLocalTeam && iLocalTeam > LAST_SHARED_TEAM ); if ( bUseDisguiseWeapon && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) ) { if ( pLocalPlayer != pPlayer ) { iTeamNumber = pPlayer->m_Shared.GetDisguiseTeam(); } } #endif // defined( CLIENT_DLL ) // See if the item wants to override the skin int nSkin = GetSkinOverride(); // give custom gameplay code a chance to set whatever if ( nSkin == -1 ) { const CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( pItem->IsValid() ) { nSkin = pItem->GetSkin( iTeamNumber ); // if we didn't have custom code, fall back to the item definition } } // If it didn't, fall back to the base skins if ( nSkin == -1 ) { switch( iTeamNumber ) { case TF_TEAM_RED: nSkin = 0; break; case TF_TEAM_BLUE: nSkin = 1; break; default: nSkin = 0; break; } } return nSkin; } #if defined( CLIENT_DLL ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::OnFireEvent( C_BaseViewModel *pViewModel, const Vector& origin, const QAngle& angles, int event, const char *options ) { if ( event == 6002 && ShouldEjectBrass() ) { if ( UsingViewModel() && !g_pClientMode->ShouldDrawViewModel() ) { // Prevent effects when the ViewModel is hidden with r_drawviewmodel=0 return true; } CEffectData data; // Look for 'eject_brass' attachment first instead of using options which is a seemingly magic number if ( m_iEjectBrassAttachpoint == -2 ) { m_iEjectBrassAttachpoint = pViewModel->LookupAttachment( "eject_brass" ); } if ( m_iEjectBrassAttachpoint > 0 ) { pViewModel->GetAttachment( m_iEjectBrassAttachpoint, data.m_vOrigin, data.m_vAngles ); } else { pViewModel->GetAttachment( atoi(options), data.m_vOrigin, data.m_vAngles ); } data.m_nDamageType = GetAttributeContainer()->GetItem() ? GetAttributeContainer()->GetItem()->GetItemDefIndex() : 0; data.m_nHitBox = GetWeaponID(); DispatchEffect( "TF_EjectBrass", data ); return true; } if ( event == AE_WPN_INCREMENTAMMO ) { IncrementAmmo(); m_bReloadedThroughAnimEvent = true; return true; } return BaseClass::OnFireEvent( pViewModel, origin, angles, event, options ); } //----------------------------------------------------------------------------- // Purpose: Used for spy invisiblity material //----------------------------------------------------------------------------- class CWeaponInvisProxy : public CBaseInvisMaterialProxy { public: virtual void OnBind( C_BaseEntity *pBaseEntity ) OVERRIDE; }; extern ConVar tf_teammate_max_invis; //----------------------------------------------------------------------------- // Purpose: // Input : //----------------------------------------------------------------------------- void CWeaponInvisProxy::OnBind( C_BaseEntity *pBaseEntity ) { if( !m_pPercentInvisible ) return; C_BaseEntity *pMoveParent = pBaseEntity->GetMoveParent(); if ( !pMoveParent ) { m_pPercentInvisible->SetFloatValue( 0.0f ); return; } if ( !pMoveParent->IsPlayer() ) { C_TFPlayer *pOwningPlayer = ToTFPlayer( pMoveParent->GetOwnerEntity() ); if ( pOwningPlayer ) { // mimic the owner's invisibility float flInvis = pOwningPlayer->GetEffectiveInvisibilityLevel(); m_pPercentInvisible->SetFloatValue( flInvis ); } else { m_pPercentInvisible->SetFloatValue( 0.0f ); } return; } CTFPlayer *pPlayer = ToTFPlayer( pMoveParent ); Assert( pPlayer ); float flInvis = pPlayer->GetEffectiveInvisibilityLevel(); m_pPercentInvisible->SetFloatValue( flInvis ); } EXPOSE_INTERFACE( CWeaponInvisProxy, IMaterialProxy, "weapon_invis" IMATERIAL_PROXY_INTERFACE_VERSION ); #endif // CLIENT_DLL #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- ConVar tf_dev_marked_for_death_lifetime( "tf_dev_marked_for_death_lifetime", "15.0", FCVAR_DEVELOPMENTONLY ); ConVar tf_dev_health_on_damage_recover_percentage( "tf_dev_health_on_damage_recover_percentage", "0.35", FCVAR_DEVELOPMENTONLY ); void CTFWeaponBase::ApplyOnHitAttributes( CBaseEntity *pVictimBaseEntity, CTFPlayer *pAttacker, const CTakeDamageInfo &info ) { if ( !pAttacker ) return; CTFPlayer *pVictim = ToTFPlayer( pVictimBaseEntity ); // Ammo on hit int iModAmmoOnHit = 0; CALL_ATTRIB_HOOK_INT( iModAmmoOnHit, add_onhit_addammo ); if ( iModAmmoOnHit > 0 ) { // this will save the value so we can add it after we're doing firing // the projectile and have subtracted the ammo for the current shot float flPercentage = (float)iModAmmoOnHit / 100.0f; // No ammo for disguised Spies that are NOT stealthed so you can't use this to check for Spies if ( pVictim && pVictim->IsPlayerClass( TF_CLASS_SPY ) && pVictim->m_Shared.InCond( TF_COND_DISGUISED ) && !( pVictim->m_Shared.IsStealthed() || pVictim->m_Shared.InCond( TF_COND_STEALTHED_BLINK ) ) ) { flPercentage = 0.0f; } m_iAmmoToAdd += (int)( flPercentage * info.GetDamage() ); } int iExtraDamageOnHit = 0; CALL_ATTRIB_HOOK_INT( iExtraDamageOnHit, extra_damage_on_hit ); if ( iExtraDamageOnHit ) { // Adds 'Heads'. Reusing this data field int iDecap = pAttacker->m_Shared.GetDecapitations(); pAttacker->m_Shared.SetDecapitations( Min( 200, iDecap + iExtraDamageOnHit ) ); } // Everything else is only for player enemies or Halloween bosses // We don't want buildables or the tank doing things like giving health or increasing ubercharge if ( !( pVictim || dynamic_cast< CHalloweenBaseBoss* >( pVictimBaseEntity ) ) ) { return; } bool bIsSpyRevealed = false; if ( pVictim ) { // Reveal cloaked Spy on hit if ( pVictim->IsPlayerClass( TF_CLASS_SPY ) && pVictim->m_Shared.IsStealthed() ) { int iRevealCloakedSpyOnHit = 0; CALL_ATTRIB_HOOK_INT( iRevealCloakedSpyOnHit, reveal_cloaked_victim_on_hit ); if ( iRevealCloakedSpyOnHit > 0 ) { pVictim->RemoveInvisibility(); bIsSpyRevealed = true; } } // Reveal disguised Spy on hit if ( pVictim->IsPlayerClass( TF_CLASS_SPY ) && pVictim->m_Shared.InCond( TF_COND_DISGUISED ) ) { int iRevealDisguisedSpyOnHit = 0; CALL_ATTRIB_HOOK_INT( iRevealDisguisedSpyOnHit, reveal_disguised_victim_on_hit ); if ( iRevealDisguisedSpyOnHit > 0 ) { pVictim->RemoveDisguise(); bIsSpyRevealed = true; } } if ( bIsSpyRevealed ) { color32 colorHit = { 255, 255, 255, 255 }; UTIL_ScreenFade( pVictim, colorHit, 0.25f, 0.1f, FFADE_IN ); // pVictim->EmitSound( "Weapon_DRG_Wrench.RevealSpy" ); } // On hit attributes don't work when you shoot disguised spies if ( pVictim->m_Shared.InCond( TF_COND_DISGUISED ) ) return; } // Or from burn damage if ( (info.GetDamageType() & DMG_BURN) ) return; // Heal on hits int iModHealthOnHit = 0; CALL_ATTRIB_HOOK_INT( iModHealthOnHit, add_onhit_addhealth ); if ( iModHealthOnHit ) { // Scale Health mod with damage dealt, input being the maximum amount of health possible float flScale = Clamp( info.GetDamage() / info.GetBaseDamage(), 0.f, 1.0f ); iModHealthOnHit = (int)((float)iModHealthOnHit * flScale ); } // Charge meter on hit float flChargeRefill = 0.0f; CALL_ATTRIB_HOOK_FLOAT( flChargeRefill, charge_meter_on_hit ); if ( flChargeRefill > 0 ) { pAttacker->m_Shared.SetDemomanChargeMeter( pAttacker->m_Shared.GetDemomanChargeMeter() + flChargeRefill * 100.0f ); } // Speed on hit int iSpeedBoostOnHit = 0; CALL_ATTRIB_HOOK_INT( iSpeedBoostOnHit, speed_boost_on_hit ); if ( iSpeedBoostOnHit ) { pAttacker->m_Shared.AddCond( TF_COND_SPEED_BOOST, iSpeedBoostOnHit ); } if ( pVictim ) { if ( pVictim->m_Shared.InCond( TF_COND_MAD_MILK ) ) { int nAmount = info.GetDamage() * 0.6f; iModHealthOnHit += nAmount; CTFPlayer *pProvider = ToTFPlayer( pVictim->m_Shared.GetConditionProvider( TF_COND_MAD_MILK ) ); if ( pProvider ) { // Only give points for the portion they're responsible for if ( pProvider != pAttacker ) { CTF_GameStats.Event_PlayerHealedOtherAssist( pProvider, nAmount ); } // Show in the medic's UI as primary healing IGameEvent *event = gameeventmanager->CreateEvent( "player_healed" ); if ( event ) { event->SetInt( "priority", 1 ); // HLTV event priority event->SetInt( "patient", pAttacker->GetUserID() ); event->SetInt( "healer", pProvider->GetUserID() ); event->SetInt( "amount", iModHealthOnHit ); gameeventmanager->FireEvent( event ); } // Give them a little bit of Uber CWeaponMedigun *pMedigun = static_cast( pProvider->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ) ); if ( pMedigun ) { int iHealedAmount = Max( Min( (int)pAttacker->GetMaxHealth() - (int)pAttacker->GetHealth(), nAmount ), 0 ); // On Mediguns, per frame, the amount of uber added is based on // Default heal rate is 24per second, we scale based on that and frametime pMedigun->AddCharge( (iHealedAmount / 24.0f ) * gpGlobals->frametime ); } } } } if ( pAttacker->m_Shared.InCond( TF_COND_REGENONDAMAGEBUFF ) ) { int nAmount = info.GetDamage() * tf_dev_health_on_damage_recover_percentage.GetFloat(); iModHealthOnHit += nAmount; // Increment provider's healing assist stat CTFPlayer *pProvider = ToTFPlayer( pAttacker->m_Shared.GetConditionProvider( TF_COND_REGENONDAMAGEBUFF ) ); if ( pProvider && pProvider != pAttacker ) { // Only give points for the portion they're responsible for CTF_GameStats.Event_PlayerHealedOtherAssist( pProvider, nAmount ); } } if ( iModHealthOnHit ) { if ( iModHealthOnHit > 0 ) { int iHealed = pAttacker->TakeHealth( iModHealthOnHit, DMG_GENERIC ); // Increment attacker's healing stat if ( iHealed ) { CTF_GameStats.Event_PlayerHealedOther( pAttacker, iHealed ); } } else { pAttacker->TakeDamage( CTakeDamageInfo( pAttacker, this, (iModHealthOnHit * -1), DMG_GENERIC ) ); } IGameEvent *event = gameeventmanager->CreateEvent( "player_healonhit" ); if ( event ) { event->SetInt( "amount", iModHealthOnHit ); event->SetInt( "entindex", pAttacker->entindex() ); item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX; if ( GetAttributeContainer() && GetAttributeContainer()->GetItem() ) { healingItemDef = GetAttributeContainer()->GetItem()->GetItemDefIndex(); } event->SetInt( "weapon_def_index", healingItemDef ); gameeventmanager->FireEvent( event ); } } // Add ubercharge on hit if ( pAttacker->IsPlayerClass( TF_CLASS_MEDIC ) ) { float flUberChargeBonus = 0; CALL_ATTRIB_HOOK_FLOAT( flUberChargeBonus, add_onhit_ubercharge ); if ( flUberChargeBonus ) { CWeaponMedigun *pMedigun = (CWeaponMedigun *)pAttacker->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ); if ( pMedigun ) { pMedigun->AddCharge( flUberChargeBonus ); } } } // Lower rage on hit. if ( pAttacker->IsPlayerClass( TF_CLASS_SOLDIER ) || pAttacker->IsPlayerClass( TF_CLASS_PYRO ) ) { int iRageOnHit = 0; CALL_ATTRIB_HOOK_INT( iRageOnHit, rage_on_hit ); pAttacker->m_Shared.ModifyRage( iRageOnHit ); } // rune charge on hit if ( pAttacker->m_Shared.CanRuneCharge() ) { const float flMaxRuneCharge = 400.f; float flAdd = (float)info.GetDamage() * ( 100.f / flMaxRuneCharge ); pAttacker->m_Shared.SetRuneCharge( pAttacker->m_Shared.GetRuneCharge() + flAdd ); } // Increase Boost on hit int iBoostOnDamage = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pAttacker, iBoostOnDamage, boost_on_damage ); if ( iBoostOnDamage != 0 ) { float fHype = MIN( tf_scout_hype_pep_max.GetFloat(), pAttacker->m_Shared.GetScoutHypeMeter() + ( MAX( tf_scout_hype_pep_min_damage.GetFloat(), info.GetDamage() ) / tf_scout_hype_pep_mod.GetFloat() ) ); pAttacker->m_Shared.SetScoutHypeMeter( fHype ); pAttacker->TeamFortress_SetSpeed(); } // Procs! if( pVictim ) { // Detemine weapon speed float flFireDelay = ApplyFireDelay( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay ); // Proc chance for AOE Heal float flPPM = 0.f; CALL_ATTRIB_HOOK_FLOAT( flPPM, aoe_heal_chance ); float flProcChance = flFireDelay * (flPPM / 60.f); if( RandomFloat() < flProcChance ) { pAttacker->m_Shared.AddCond( TF_COND_RADIUSHEAL_ON_DAMAGE, 1.0f ); } // Proc chance for crit boost flPPM = 0.f; CALL_ATTRIB_HOOK_FLOAT( flPPM, crits_on_damage ); flProcChance = flFireDelay * (flPPM / 60.f); if( RandomFloat() < flProcChance ) { pAttacker->m_Shared.AddCond( TF_COND_CRITBOOSTED_CARD_EFFECT, 3 ); } // Proc chance for stun flPPM = 0.f; CALL_ATTRIB_HOOK_FLOAT( flPPM, stun_on_damage ); flProcChance = flFireDelay * (flPPM / 60.f); if( RandomFloat() < flProcChance ) { pVictim->m_Shared.StunPlayer( 3.0, 1.f, TF_STUN_MOVEMENT | TF_STUN_CONTROLS, pAttacker ); } // Proc chance for AOE Blast flPPM = 0.f; CALL_ATTRIB_HOOK_FLOAT ( flPPM, aoe_blast_on_damage ); flProcChance = flFireDelay * (flPPM / 60.f); if ( (RandomFloat() < flProcChance) ) { // Stun the source float flStunDuration = 2.f; float flStunAmt = 1.f; pVictim->m_Shared.StunPlayer( flStunDuration, flStunAmt, TF_STUN_MOVEMENT | TF_STUN_CONTROLS | TF_STUN_NO_EFFECTS, pAttacker ); pVictim->m_Shared.MakeBleed( ToTFPlayer( pAttacker ), NULL, flStunDuration, 75 ); // Generate an explosion and look for nearby bots float flDmgRange = 100.f; const int nMaxEnts = 12; CBaseEntity *pObjects[ nMaxEnts ]; CTFPlayer* pPrevTFPlayer = NULL; int nCount = UTIL_EntitiesInSphere( pObjects, nMaxEnts, pVictim->GetAbsOrigin(), flDmgRange, FL_CLIENT ); for ( int i = 0; i < nCount; i++ ) { if ( !pObjects[i] ) continue; if ( !pObjects[i]->IsAlive() ) continue; if ( pObjects[i]->GetTeamNumber() != pVictim->GetTeamNumber() ) continue; if ( !FVisible( pObjects[i], MASK_OPAQUE ) ) continue; CTFPlayer *pTFPlayer = static_cast( pObjects[i] ); if ( !pTFPlayer ) continue; if ( pTFPlayer == pVictim ) continue; if ( !pTFPlayer->IsBot() ) continue; if ( pVictim->m_Shared.InCond( TF_COND_PHASE ) || pVictim->m_Shared.InCond( TF_COND_PASSTIME_INTERCEPTION ) ) continue; if ( pVictim->m_Shared.IsInvulnerable() ) continue; // Stun pTFPlayer->m_Shared.StunPlayer( flStunDuration, flStunAmt, TF_STUN_MOVEMENT | TF_STUN_CONTROLS | TF_STUN_NO_EFFECTS, pAttacker ); // DoT pTFPlayer->m_Shared.MakeBleed( ToTFPlayer( pAttacker ), NULL, flStunDuration, 75.f ); // Shoot a beam at them CPVSFilter filter( pTFPlayer->WorldSpaceCenter() ); Vector vStart = pPrevTFPlayer == NULL ? pVictim->EyePosition() : pPrevTFPlayer->EyePosition(); Vector vEnd = pTFPlayer->EyePosition(); te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd }; TE_TFParticleEffectComplex( filter, 0.0f, "dxhr_arm_muzzleflash", vStart, QAngle( 0, 0, 0 ), NULL, &controlPoint, pTFPlayer, PATTACH_CUSTOMORIGIN ); pTFPlayer->EmitSound( "Weapon_Upgrade.ExplosiveHeadshot" ); pPrevTFPlayer = pTFPlayer; } } } // Damage bonus on hit // Disabled because we have no attributes that use it /* float flAddDamageDoneBonusOnHit = 0; CALL_ATTRIB_HOOK_FLOAT( flAddDamageDoneBonusOnHit, addperc_ondmgdone_tmpbuff ); if ( flAddDamageDoneBonusOnHit ) { pAttacker->m_Shared.AddTmpDamageBonus( flAddDamageDoneBonusOnHit, 10.0 ); } */ if ( pVictim ) { int iRageStun = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pAttacker, iRageStun, generate_rage_on_dmg ); if ( iRageStun && pAttacker->m_Shared.IsRageDraining() ) { // MvM: Heavies can purchase a rage-based knockback+stun effect if ( pAttacker->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) { int iStunFlags = TF_STUN_MOVEMENT | TF_STUN_NO_EFFECTS; pVictim->m_Shared.StunPlayer( 0.25f, 1.f, iStunFlags, pAttacker ); } } // Slow enemy on hit, unless they're being healed by a medic if ( !pVictim->m_Shared.InCond( TF_COND_HEALTH_BUFF ) ) { float flSlowEnemy = 0.0; CALL_ATTRIB_HOOK_FLOAT( flSlowEnemy, mult_onhit_enemyspeed ); if ( flSlowEnemy ) { if ( RandomFloat() < flSlowEnemy ) { // Adjust the stun amount based on distance to the target // close range full stun, falls off to zero at 1536 (1024 window size) Vector vecDistance = pVictim->GetAbsOrigin() - pAttacker->GetAbsOrigin(); float flStunAmount = RemapValClamped( vecDistance.LengthSqr(), (512.0f * 512.0f), (1536.0f * 1536.0f), 0.60f, 0.0f ); pVictim->m_Shared.StunPlayer( 0.2, flStunAmount, TF_STUN_MOVEMENT, pAttacker ); } } flSlowEnemy = 0.0; CALL_ATTRIB_HOOK_FLOAT( flSlowEnemy, mult_onhit_enemyspeed_major ); if ( flSlowEnemy ) { pVictim->m_Shared.StunPlayer( flSlowEnemy, 0.4, TF_STUN_MOVEMENT, pAttacker ); } } // Mark for death on hit. int iMarkForDeath = 0; CALL_ATTRIB_HOOK_INT( iMarkForDeath, mark_for_death ); if ( iMarkForDeath ) { // Note: this logic isn't perfect, and can do non-obvious things in certain situations. For example, // imagine that we've got two scouts -- if the first scout marks someone, and then the second scout marks // the same guy, and then the first scout marks someone else, the original victim will lose his marked- // for-death status. Conditions don't have any concept of owner. This could be manually tracked for this // condition if it becomes a problem. if ( pAttacker->m_pMarkedForDeathTarget != NULL && pAttacker->m_pMarkedForDeathTarget->m_Shared.InCond( TF_COND_MARKEDFORDEATH ) ) { pAttacker->m_pMarkedForDeathTarget->m_Shared.RemoveCond( TF_COND_MARKEDFORDEATH ); } float flDuration = pVictim->IsMiniBoss() ? tf_dev_marked_for_death_lifetime.GetFloat() / 2 : tf_dev_marked_for_death_lifetime.GetFloat(); pVictim->m_Shared.AddCond( TF_COND_MARKEDFORDEATH, flDuration, pAttacker ); pAttacker->m_pMarkedForDeathTarget = pVictim; // ACHIEVEMENT_TF_MVM_SCOUT_MARK_FOR_DEATH if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { if ( pAttacker->IsPlayerClass( TF_CLASS_SCOUT ) && ( GetWeaponID() == TF_WEAPON_BAT_WOOD ) ) { if ( pVictim->IsBot() && ( pVictim->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) { IGameEvent *event = gameeventmanager->CreateEvent( "mvm_scout_marked_for_death" ); if ( event ) { event->SetInt( "player", pAttacker->entindex() ); gameeventmanager->FireEvent( event ); } } } } } // Stun airborne enemies who are half a body length higher than attacker bool bIsVictimAirborne = !( pVictim->GetFlags() & FL_ONGROUND ) && ( pVictim->GetWaterLevel() == WL_NotInWater ); int iStunWaistHighAirborne = 0; CALL_ATTRIB_HOOK_INT( iStunWaistHighAirborne, stun_waist_high_airborne ); if ( iStunWaistHighAirborne > 0 && bIsVictimAirborne ) { if ( pVictim->WorldSpaceCenter().z >= pAttacker->EyePosition().z ) { // right in the jimmy! pVictim->m_Shared.StunPlayer( iStunWaistHighAirborne, 0.5f, TF_STUN_LOSER_STATE | TF_STUN_BOTH, pAttacker ); pVictim->EmitSound( "Halloween.PlayerScream" ); } } } } //----------------------------------------------------------------------------- // Purpose: When owner of this weapon is hit //----------------------------------------------------------------------------- void CTFWeaponBase::ApplyOnInjuredAttributes( CTFPlayer *pVictim, CTFPlayer *pAttacker, const CTakeDamageInfo &info ) { if ( CanDeploy() ) { int iBecomeFireproofOnHitByFire = 0; CALL_ATTRIB_HOOK_INT( iBecomeFireproofOnHitByFire, become_fireproof_on_hit_by_fire ); if ( iBecomeFireproofOnHitByFire > 0 && info.GetDamageType() & DMG_BURN ) { pVictim->m_Shared.AddCond( TF_COND_FIRE_IMMUNE, 1.0 ); if ( pVictim->m_Shared.InCond( TF_COND_BURNING ) ) { pVictim->EmitSound( "TFPlayer.FlameOut" ); pVictim->m_Shared.RemoveCond( TF_COND_BURNING ); } // STAGING_SPY pVictim->m_Shared.AddCond( TF_COND_AFTERBURN_IMMUNE, iBecomeFireproofOnHitByFire ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::ApplyPostHitEffects( const CTakeDamageInfo &info, CTFPlayer *pVictim ) { bool bDidDrain = false; CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() ); if ( !pAttacker || !pVictim ) return; // only drain a victim once per shot, even with penetrating weapons if ( pVictim != m_hLastDrainVictim || m_lastDrainVictimTimer.IsElapsed() ) { // Subtract victim's Medigun charge on hit int iSubtractVictimMedigunChargeOnHit = 0; CALL_ATTRIB_HOOK_INT( iSubtractVictimMedigunChargeOnHit, subtract_victim_medigun_charge_onhit ); if ( iSubtractVictimMedigunChargeOnHit > 0 ) { CWeaponMedigun *pMedigun = (CWeaponMedigun *)pVictim->Weapon_OwnsThisID( TF_WEAPON_MEDIGUN ); if ( pMedigun && !pMedigun->IsReleasingCharge() ) { // STAGING_ENGY // Scale drain after 512 Hu to 1536Hu ( 50% drain at 1024, 0 drain at 1536 units ) Vector toEnt = pVictim->GetAbsOrigin() - pAttacker->GetAbsOrigin(); if ( toEnt.LengthSqr() > Square( 512.0f ) ) { iSubtractVictimMedigunChargeOnHit *= RemapValClamped( toEnt.LengthSqr(), (512.0f * 512.0f), (1536.0f * 1536.0f), 1.0f, 0.0f ); } pMedigun->SubtractCharge( iSubtractVictimMedigunChargeOnHit / 100.0f ); bDidDrain = true; } } // Subtract victim's cloak on hit int iSubtractVictimCloakOnHit = 0; CALL_ATTRIB_HOOK_INT( iSubtractVictimCloakOnHit, subtract_victim_cloak_on_hit ); if ( iSubtractVictimCloakOnHit > 0 && pVictim->IsPlayerClass( TF_CLASS_SPY ) ) { // STAGING_ENGY // Scale drain after 512 Hu to 1536Hu ( 50% drain at 1024, 0 drain at 1536 units ) Vector toEnt = pVictim->GetAbsOrigin() - pAttacker->GetAbsOrigin(); if ( toEnt.LengthSqr() > Square( 512.0f ) ) { iSubtractVictimCloakOnHit *= RemapValClamped( toEnt.LengthSqr(), (512.0f * 512.0f), (1536.0f * 1536.0f), 1.0f, 0.0f ); } float flCloak = pVictim->m_Shared.GetSpyCloakMeter(); flCloak -= iSubtractVictimCloakOnHit; if ( flCloak < 0.0f ) { flCloak = 0.0f; } pVictim->m_Shared.SetSpyCloakMeter( flCloak ); bDidDrain = true; } // don't play effects to attacker if he hit a disguised/cloaked spy if ( !pVictim->m_Shared.InCond( TF_COND_DISGUISED ) && !pVictim->m_Shared.IsStealthed() ) { if ( bDidDrain ) { DispatchParticleEffect( "drg_pomson_impact_drain", PATTACH_POINT, pVictim, "head", GetParticleColor( 1 ), GetParticleColor( 2 ) ); if ( pAttacker ) { // play drain sound effect, louder for the attacker EmitSound_t params; params.m_flSoundTime = 0; params.m_pSoundName = "Weapon_Pomson.DrainedVictim"; params.m_pflSoundDuration = 0; CPASFilter filter( pVictim->GetAbsOrigin() ); filter.RemoveRecipient( pAttacker ); EmitSound( filter, pVictim->entindex(), params ); CSingleUserRecipientFilter attackerFilter( pAttacker ); EmitSound( attackerFilter, pAttacker->entindex(), params ); } m_hLastDrainVictim = pVictim; m_lastDrainVictimTimer.Start( 0.3f ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { // Deliberately disabled to prevent players picking up fallen weapons. return; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::DisguiseWeaponThink( void ) { // Periodically check to make sure we are valid. // Disguise weapons are attached to a player, but not managed through the owned weapons list. CTFPlayer *pTFOwner = ToTFPlayer( GetOwner() ); if ( !pTFOwner ) { // We must have an owner to be valid. Drop( Vector( 0,0,0 ) ); return; } if ( pTFOwner->m_Shared.GetDisguiseWeapon() != this ) { // The owner's disguise weapon must be us, otherwise we are invalid. Drop( Vector( 0,0,0 ) ); return; } SetContextThink( &CTFWeaponBase::DisguiseWeaponThink, gpGlobals->curtime + 0.5, "DisguiseWeaponThink" ); } #endif // GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::IsViewModelFlipped( void ) { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return false; #ifdef GAME_DLL if ( m_bFlipViewModel != pPlayer->m_bFlipViewModels ) { return true; } #else if ( m_bFlipViewModel != cl_flipviewmodels.GetBool() ) { return true; } #endif return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::ReapplyProvision( void ) { // Disguise items never provide if ( m_bDisguiseWeapon ) { #ifdef GAME_DLL UpdateModelToClass(); #endif return; } int iProvideMode = 0; CALL_ATTRIB_HOOK_INT( iProvideMode, provide_on_active ); if ( 1 == iProvideMode ) { if ( m_iState == WEAPON_IS_ACTIVE ) { // We are active, provide to our owner. BaseClass::ReapplyProvision(); } else { // We aren't active so stop providing to our owner. GetAttributeManager()->StopProvidingTo( GetPlayerOwner() ); m_hOldProvidee = NULL; } } else { BaseClass::ReapplyProvision(); } } //----------------------------------------------------------------------------- // Purpose: Return the origin & angles for a projectile fired from the player's gun //----------------------------------------------------------------------------- void CTFWeaponBase::GetProjectileFireSetup( CTFPlayer *pPlayer, Vector vecOffset, Vector *vecSrc, QAngle *angForward, bool bHitTeammates /* = true */, float flEndDist /* = 2000 */) { // @todo third person code!! // Flip the firing offset if our view model is flipped. if ( IsViewModelFlipped() ) { vecOffset.y *= -1; } int iCenterFireProjectile = 0; CALL_ATTRIB_HOOK_INT( iCenterFireProjectile, centerfire_projectile ); if ( iCenterFireProjectile == 1 ) { vecOffset.y = 0; } QAngle angSpread = GetSpreadAngles(); Vector vecForward, vecRight, vecUp; AngleVectors( angSpread, &vecForward, &vecRight, &vecUp ); Vector vecShootPos = pPlayer->Weapon_ShootPosition(); // Estimate end point Vector endPos = vecShootPos + vecForward * flEndDist; // Trace forward and find what's in front of us, and aim at that trace_t tr; if ( bHitTeammates ) { CTraceFilterSimple traceFilter( pPlayer, COLLISION_GROUP_NONE ); ITraceFilter *pFilterChain = NULL; CTraceFilterIgnoreFriendlyCombatItems traceFilterCombatItem( pPlayer, COLLISION_GROUP_NONE, GetTeamNumber() ); if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) { // Ignore teammates and their (physical) upgrade items in MvM pFilterChain = &traceFilterCombatItem; } CTraceFilterChain traceFilterChain( &traceFilter, pFilterChain ); UTIL_TraceLine( vecShootPos, endPos, MASK_SOLID, &traceFilterChain, &tr ); } else { CTraceFilterIgnoreTeammates filter( pPlayer, COLLISION_GROUP_NONE, pPlayer->GetTeamNumber() ); UTIL_TraceLine( vecShootPos, endPos, MASK_SOLID, &filter, &tr ); } // Offset actual start point *vecSrc = vecShootPos + (vecForward * vecOffset.x) + (vecRight * vecOffset.y) + (vecUp * vecOffset.z); // Find angles that will get us to our desired end point // Only use the trace end if it wasn't too close, which results // in visually bizarre forward angles if ( tr.fraction > 0.1 ) { VectorAngles( tr.endpos - *vecSrc, *angForward ); } else { VectorAngles( endPos - *vecSrc, *angForward ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- QAngle CTFWeaponBase::GetSpreadAngles( void ) { CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() ); Assert( pOwner ); QAngle angEyes = pOwner->EyeAngles(); float flSpreadAngle = 0.0f; CALL_ATTRIB_HOOK_FLOAT( flSpreadAngle, projectile_spread_angle ); if ( flSpreadAngle ) { QAngle angSpread = RandomAngle( -flSpreadAngle, flSpreadAngle ); angSpread.z = 0.0f; if ( TFGameRules() && TFGameRules()->GameModeUsesUpgrades() ) { if ( CanOverload() && AutoFiresFullClip() && Clip1() == 1 && !m_bFiringWholeClip ) { float flTimeSinceLastAttack = gpGlobals->curtime - GetLastPrimaryAttackTime(); if ( flTimeSinceLastAttack < 0.9f ) { // Punish upgraded single-fire spam for this class of weapon float flPenaltyAngle = RemapValClamped( flTimeSinceLastAttack, 0.4f, 0.9f, 6.f, 1.f ); angSpread += RandomAngle( -flPenaltyAngle, flPenaltyAngle ); } } } angEyes += angSpread; } return angEyes; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::CanPerformSecondaryAttack() const { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); // Demo shields are allowed to charge whenever if ( pOwner->m_Shared.HasDemoShieldEquipped() ) return true; return BaseClass::CanPerformSecondaryAttack(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::AreRandomCritsEnabled( void ) { if ( TFGameRules() ) { if ( TFGameRules()->IsPowerupMode() ) return false; const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( TFGameRules()->GetCurrentMatchGroup() ); if ( pMatchDesc ) return pMatchDesc->m_params.m_bRandomWeaponCrits; } return tf_weapon_criticals.GetBool(); } #ifdef GAME_DLL //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::ChangeTeam( int iTeamNum ) { BaseClass::ChangeTeam( iTeamNum ); // We need to set the team for our econ item view as well if ( GetAttributeContainer() && GetAttributeContainer()->GetItem() ) { GetAttributeContainer()->GetItem()->SetTeamNumber( GetTeamNumber() ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::DeflectProjectiles() { CTFPlayer *pOwner = ToTFPlayer( GetPlayerOwner() ); if ( !pOwner ) return false; if ( pOwner->GetWaterLevel() == WL_Eyes ) return false; lagcompensation->StartLagCompensation( pOwner, pOwner->GetCurrentCommand() ); Vector vecEye = pOwner->EyePosition(); Vector vecForward, vecRight, vecUp; AngleVectors( pOwner->EyeAngles(), &vecForward, &vecRight, &vecUp ); Vector vecSize = GetDeflectionSize(); float flMaxElement = 0.0f; for ( int i = 0; i < 3; ++i ) { flMaxElement = MAX( flMaxElement, vecSize[i] ); } Vector vecCenter = vecEye + vecForward * flMaxElement; // Get a list of entities in the box defined by vecSize at VecCenter. // We will then try to deflect everything in the box. const int maxCollectedEntities = 64; CBaseEntity *pObjects[ maxCollectedEntities ]; int count = UTIL_EntitiesInBox( pObjects, maxCollectedEntities, vecCenter - vecSize, vecCenter + vecSize, FL_CLIENT | FL_GRENADE ); // NDebugOverlay::Box( vecCenter, -vecSize, vecSize, 0, 255, 0, 40, 3 ); bool bDeflected = false; bool bDeflectedPlayer = false; int iEnemyTeam = GetEnemyTeam( pOwner->GetTeamNumber() ); bool bTruce = TFGameRules() && TFGameRules()->IsTruceActive() && pOwner->IsTruceValidForEnt(); for ( int i = 0; i < count; i++ ) { if ( pObjects[i] == pOwner ) continue; if ( pObjects[i]->IsPlayer() && pObjects[i]->GetTeamNumber() == TEAM_SPECTATOR ) continue; if ( !pObjects[i]->IsDeflectable() && !FClassnameIs( pObjects[i], "prop_physics" ) ) continue; if ( pOwner->FVisible( pObjects[i], MASK_SOLID ) == false ) continue; if ( bTruce && ( pObjects[i]->GetTeamNumber() == iEnemyTeam ) ) continue; if ( pObjects[i]->IsPlayer() == true ) { CTFPlayer *pTarget = ToTFPlayer( pObjects[i] ); if ( pTarget ) { bool bRes = DeflectPlayer( pTarget, pOwner, vecForward, vecCenter, vecSize ); bDeflectedPlayer |= bRes; bDeflected |= bRes; } } else { bDeflected |= DeflectEntity( pObjects[i], pOwner, vecForward, vecCenter, vecSize ); } } if ( bDeflected ) { pOwner->SpeakConceptIfAllowed( MP_CONCEPT_DEFLECTED, "victim:0" ); PlayDeflectionSound( bDeflectedPlayer ); } lagcompensation->FinishLagCompensation( pOwner ); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::DeflectPlayer( CTFPlayer *pTarget, CTFPlayer *pOwner, Vector &vecForward, Vector &vecCenter, Vector &vecSize ) { return true; } //----------------------------------------------------------------------------- // This filter checks against friendly players, buildings, shields //----------------------------------------------------------------------------- class CTraceFilterDeflection : public CTraceFilterSimple { public: DECLARE_CLASS( CTraceFilterDeflection, CTraceFilterSimple ); CTraceFilterDeflection( const IHandleEntity *passentity, int collisionGroup, int iIgnoreTeam ) : CTraceFilterSimple( passentity, collisionGroup ), m_iIgnoreTeam( iIgnoreTeam ) { } virtual bool ShouldHitEntity( IHandleEntity *passentity, int contentsMask ) OVERRIDE { CBaseEntity *pEntity = EntityFromEntityHandle( passentity ); if ( !pEntity ) return false; if ( pEntity->IsPlayer() ) return false; if ( pEntity->IsBaseObject() ) return false; if ( pEntity->IsCombatItem() ) return false; return BaseClass::ShouldHitEntity( passentity, contentsMask ); } int m_iIgnoreTeam; }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::DeflectEntity( CBaseEntity *pTarget, CTFPlayer *pOwner, Vector &vecForward, Vector &vecCenter, Vector &vecSize ) { Assert( pTarget ); Assert( pOwner ); Vector vecEye = pOwner->EyePosition(); Vector vecVel = pTarget->GetAbsVelocity(); // apply an impulse instead if this is a prop physics object if ( FClassnameIs( pTarget, "prop_physics" ) ) { IPhysicsObject *pPhysicsObject = pTarget->VPhysicsGetObject(); if ( pPhysicsObject && pTarget->CollisionProp() ) { Vector vecDir = pTarget->WorldSpaceCenter() - vecEye; VectorNormalize( vecDir ); float flVel = 50.0f * CTFWeaponBase::DeflectionForce( pTarget->CollisionProp()->OBBSize(), 90, 12.0f ); pPhysicsObject->ApplyForceOffset( vecDir * flVel, vecEye ); } return true; } AngularImpulse angularimp; CTraceFilterDeflection filter( pOwner, COLLISION_GROUP_NONE, pOwner->GetTeamNumber() ); trace_t tr; UTIL_TraceLine( vecEye, vecEye + vecForward * MAX_TRACE_LENGTH, MASK_SOLID, &filter, &tr ); Vector vecDir = pTarget->WorldSpaceCenter() - tr.endpos; VectorNormalize( vecDir ); // Send the entity back where it came. // If we want per-entity physical deflection behavior this could move into ::Deflected IPhysicsObject *pPhysicsObject = pTarget->VPhysicsGetObject(); if ( pPhysicsObject ) { pPhysicsObject->GetVelocity( &vecVel, &angularimp ); } float flVel = vecVel.Length(); vecVel = -flVel * vecDir; if ( pPhysicsObject ) { if ( pPhysicsObject->IsMotionEnabled() == false ) { vecDir = pOwner->WorldSpaceCenter() - pTarget->WorldSpaceCenter(); VectorNormalize( vecDir ); vecVel = -flVel * vecDir; } pPhysicsObject->EnableMotion( true ); pPhysicsObject->SetVelocity( &vecVel, &angularimp ); } else { pTarget->SetAbsVelocity( vecVel ); } // Perform entity specific deflection behavior like team changing. pTarget->Deflected( pOwner, vecDir ); QAngle newAngles; VectorAngles( -vecDir, newAngles ); pTarget->SetAbsAngles( newAngles ); pOwner->AwardAchievement( ACHIEVEMENT_TF_PYRO_REFLECT_PROJECTILES ); CDisablePredictionFiltering disabler; DispatchParticleEffect( "deflect_fx", PATTACH_ABSORIGIN_FOLLOW, pTarget ); return true; } //----------------------------------------------------------------------------- // Purpose: Static deflection helper. //----------------------------------------------------------------------------- float CTFWeaponBase::DeflectionForce( const Vector &size, float damage, float scale ) { float force = damage * ((48 * 48 * 82.0) / (size.x * size.y * size.z)) * scale; if ( force > 1000.0) { force = 1000.0; } return force; } //----------------------------------------------------------------------------- // Purpose: Static deflection helper. //----------------------------------------------------------------------------- void CTFWeaponBase::SendObjectDeflectedEvent( CTFPlayer *pNewOwner, CTFPlayer *pPrevOwner, int iWeaponID, CBaseAnimating *pObject ) { if ( pNewOwner && pPrevOwner ) { IGameEvent * event = gameeventmanager->CreateEvent( "object_deflected" ); if ( event ) { event->SetInt( "userid", pNewOwner->GetUserID() ); event->SetInt( "ownerid", pPrevOwner->GetUserID() ); event->SetInt( "weaponid", iWeaponID ); // Community request. We don't use object_entindex, but some server plugins do. event->SetInt( "object_entindex", pObject ? pObject->entindex() : 0 ); gameeventmanager->FireEvent( event ); } } } //----------------------------------------------------------------------------- // Purpose: Separate Regen function to handle item-specific cases //----------------------------------------------------------------------------- void CTFWeaponBase::ApplyItemRegen( void ) { CTFPlayer *pOwner = ToTFPlayer( GetOwner() ); if ( !pOwner ) return; m_flRegenTime += gpGlobals->frametime; if ( m_flRegenTime > 1.0f ) { m_flRegenTime -= 1.0; float flRegenAmount = 0; CALL_ATTRIB_HOOK_FLOAT( flRegenAmount, active_item_health_regen ); if ( (int)flRegenAmount != 0 ) { pOwner->TakeDamage( CTakeDamageInfo( pOwner, pOwner, vec3_origin, WorldSpaceCenter(), (int)flRegenAmount * -1, DMG_GENERIC ) ); IGameEvent *event = gameeventmanager->CreateEvent( "player_healonhit" ); if ( event ) { event->SetInt( "amount", (int)flRegenAmount ); event->SetInt( "entindex", pOwner->entindex() ); item_definition_index_t healingItemDef = INVALID_ITEM_DEF_INDEX; if ( GetAttributeContainer() && GetAttributeContainer()->GetItem() ) { healingItemDef = GetAttributeContainer()->GetItem()->GetItemDefIndex(); } event->SetInt( "weapon_def_index", healingItemDef ); gameeventmanager->FireEvent( event ); } } } } kill_eater_event_t CTFWeaponBase::GetKillEaterKillEventType() const { uint32 unEventType = kKillEaterEvent_PlayerKill; CALL_ATTRIB_HOOK_INT( unEventType, kill_eater_kill_type ); return (kill_eater_event_t)unEventType; } #endif // GAME_DLL bool CTFWeaponBase::IsSilentKiller() { int iSilentKiller = 0; CALL_ATTRIB_HOOK_INT( iSilentKiller, set_silent_killer ); if ( iSilentKiller == 1 ) return true; else return false; } //----------------------------------------------------------------------------- // Purpose: Ensures that a player's correct body groups are enabled on client respawn. //----------------------------------------------------------------------------- void CTFWeaponBase::UpdateWeaponBodyGroups( CTFPlayer* pPlayer, bool bHandleDeployedBodygroups ) { if ( !pPlayer ) return; for ( int i = 0; i < pPlayer->WeaponCount(); i++) { CTFWeaponBase *pWpn = ( CTFWeaponBase *) pPlayer->GetWeapon(i); if ( !pWpn ) continue; // If this weapon if repurposed for a taunt, dont modify bodygroups. This is so // things like the Heavy's boxing gloves can change to a different model (ie. a guitar) // and then his hands will draw like normal if( pWpn->IsBeingRepurposedForTaunt() ) continue; // Dynamic models which are not yet rendering do not modify bodygroups if ( pWpn->IsDynamicModelLoading() ) continue; // These are updated later or have already been updated. CEconItemView *pScriptItem = pWpn->GetAttributeContainer()->GetItem(); const bool bHideBodygroupsDeployedOnly = pScriptItem ? pScriptItem->GetStaticData()->GetHideBodyGroupsDeployedOnly() : false; if ( bHideBodygroupsDeployedOnly != bHandleDeployedBodygroups ) continue; // If we're supposed to hide bodygroups when deployed and we aren't deployed, don't do anything. if ( bHideBodygroupsDeployedOnly && pPlayer->GetActiveWeapon() != pWpn ) continue; pWpn->UpdateBodygroups( pPlayer, 1 ); } } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- // Purpose: Weapon Level Notification //----------------------------------------------------------------------------- class CTFKillEaterNotification : public CEconNotification { public: CTFKillEaterNotification( const CSteamID& KillerID, const wchar_t *wszWeaponName, const wchar_t *wszLevelName ) : CEconNotification() { SetLifetime( 20.0f ); SetSteamID( KillerID ); SetText( "#TF_HUD_Event_KillEater_Leveled" ); AddStringToken( "weapon_name", wszWeaponName ); AddStringToken( "rank_name", wszLevelName ); SetSoundFilename( "misc/happy_birthday.wav" ); } virtual EType NotificationType() { return eType_Basic; } }; //----------------------------------------------------------------------------- // Purpose: GC Msg handler to receive the server response that we've killed a player. //----------------------------------------------------------------------------- class CGCPlayerKilledResponse : public GCSDK::CGCClientJob { public: CGCPlayerKilledResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CProtoBufMsg msg( pNetPacket ); if ( !steamapicontext || !steamapicontext->SteamFriends() || !steamapicontext->SteamUser() || !steamapicontext->SteamUtils() ) return true; item_definition_index_t unItemDef = msg.Body().item_def(); const CEconItemDefinition* pItemDef = ItemSystem()->GetStaticDataForItemByDefIndex( unItemDef ); if ( !pItemDef ) return true; const char *pszKillerName = InventoryManager()->PersonaName_Get( msg.Body().killer_account_id() ); if ( !pszKillerName ) return true; wchar_t wszPlayerName[1024]; g_pVGuiLocalize->ConvertANSIToUnicode( pszKillerName, wszPlayerName, sizeof(wszPlayerName) ); wchar_t* wszWeaponName = g_pVGuiLocalize->Find( pItemDef->GetItemBaseName() ); uint32 unLevelBlock = msg.Body().level_type(); const char *pszLevelBlockName = GetItemSchema()->GetKillEaterScoreTypeLevelingDataName( unLevelBlock ); const CItemLevelingDefinition *pLevelDef = GetItemSchema()->GetItemLevelForScore( pszLevelBlockName, msg.Body().num_kills() ); if ( !pLevelDef ) return true; wchar_t* wszLevelName = g_pVGuiLocalize->Find( pLevelDef->GetNameLocalizationKey() ); // Kyle says: the notifications were annoying people so instead of doing a full // flashy thing we display a HUD message for everyone except the guy whose // weapon it is. Basically, *you* get the flashy notification that your // weapon leveled up, but everyone else just gets the HUD text. if ( steamapicontext->SteamUser()->GetSteamID().GetAccountID() == msg.Body().killer_account_id() ) { NotificationQueue_Add( new CTFKillEaterNotification( CSteamID( msg.Body().killer_account_id(), GetUniverse(), k_EAccountTypeIndividual ), wszWeaponName, wszLevelName ) ); } // Everyone gets the HUD notification text. CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); if ( pHUDChat ) { wchar_t wszNotification[1024]=L""; g_pVGuiLocalize->ConstructString_safe( wszNotification, g_pVGuiLocalize->Find( "#TF_HUD_Event_KillEater_Leveled_Chat" ), 3, wszPlayerName, wszWeaponName, wszLevelName ); char szAnsi[1024]; g_pVGuiLocalize->ConvertUnicodeToANSI( wszNotification, szAnsi, sizeof(szAnsi) ); pHUDChat->Printf( CHAT_FILTER_NONE, "%s", szAnsi ); } return true; } }; GC_REG_JOB( GCSDK::CGCClient, CGCPlayerKilledResponse, "CGCPlayerKilledResponse", k_EMsgGC_IncrementKillCountResponse, GCSDK::k_EServerTypeGCClient ); #endif bool WeaponID_IsSniperRifle( int iWeaponID ) { #ifdef STAGING_ONLY if ( iWeaponID == TF_WEAPON_SNIPERRIFLE || iWeaponID == TF_WEAPON_SNIPERRIFLE_DECAP || iWeaponID == TF_WEAPON_SNIPERRIFLE_CLASSIC || iWeaponID == TF_WEAPON_SNIPERRIFLE_REVOLVER ) #else if ( iWeaponID == TF_WEAPON_SNIPERRIFLE || iWeaponID == TF_WEAPON_SNIPERRIFLE_DECAP || iWeaponID == TF_WEAPON_SNIPERRIFLE_CLASSIC ) #endif return true; else return false; } bool WeaponID_IsSniperRifleOrBow( int iWeaponID ) { if ( iWeaponID == TF_WEAPON_COMPOUND_BOW ) return true; else return WeaponID_IsSniperRifle( iWeaponID ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFWeaponBase::Energy_GetMaxEnergy( void ) const { // This is a terrible hack to support clip size upgrades. // Basically -- figure out the desired number of shots, // and return the amount of energy required for that. int iNumShots = ENERGY_WEAPON_MAX_CHARGE / Energy_GetShotCost(); CALL_ATTRIB_HOOK_FLOAT( iNumShots, mult_clipsize_upgrade ); return ( iNumShots * Energy_GetShotCost() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::Energy_FullyCharged( void ) const { if ( m_flEnergy >= Energy_GetMaxEnergy() ) return true; else return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::Energy_HasEnergy( void ) { if ( m_flEnergy >= Energy_GetShotCost() ) return true; else return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::Energy_DrainEnergy( void ) { Energy_DrainEnergy( Energy_GetShotCost() ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::Energy_DrainEnergy( float flDrain ) { m_flEnergy -= flDrain; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::Energy_Recharge( void ) { m_flEnergy += Energy_GetRechargeCost(); if ( Energy_FullyCharged() ) { m_flEnergy = Energy_GetMaxEnergy(); return true; } else return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::WeaponRegenerate( void ) { m_flEnergy = Energy_GetMaxEnergy(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::FinishReload( void ) { if ( IsEnergyWeapon() ) { m_bInReload = false; return; } BaseClass::FinishReload(); CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( pPlayer ) { int iAttr = 0; CALL_ATTRIB_HOOK_INT( iAttr, last_shot_crits ); if ( iAttr ) { if ( m_iClip1 == 1 ) { pPlayer->m_Shared.AddCond( TF_COND_CRITBOOSTED ); } else { pPlayer->m_Shared.RemoveCond( TF_COND_CRITBOOSTED ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::CheckReload( void ) { if ( IsEnergyWeapon() ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); if ( !pOwner ) return; if ( !Energy_HasEnergy() ) { Reload(); return; } if ((m_bInReload) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) { if ( pOwner->m_nButtons & (IN_ATTACK | IN_ATTACK2) && Energy_HasEnergy() ) { m_bInReload = false; return; } if ( !Energy_FullyCharged() ) { Reload(); } else { FinishReload(); m_flNextPrimaryAttack = gpGlobals->curtime; m_flNextSecondaryAttack = gpGlobals->curtime; } } } else { BaseClass::CheckReload(); } } //----------------------------------------------------------------------------- // Purpose: Get the current bar state (will return a value from 0.0 to 1.0) //----------------------------------------------------------------------------- float CTFWeaponBase::GetEffectBarProgress( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( pPlayer && (pPlayer->GetAmmoCount( GetEffectBarAmmo() ) < pPlayer->GetMaxAmmo( GetEffectBarAmmo() )) ) { float flTime = GetEffectBarRechargeTime(); float flProgress = (flTime - (m_flEffectBarRegenTime - gpGlobals->curtime)) / flTime; return flProgress; } return 1.f; } //----------------------------------------------------------------------------- // Purpose: Start the regeneration bar charging from this moment in time //----------------------------------------------------------------------------- void CTFWeaponBase::StartEffectBarRegen( void ) { // Only reset regen if its less then curr time or we were full CTFPlayer *pPlayer = GetTFPlayerOwner(); bool bWasFull = false; if ( pPlayer && (pPlayer->GetAmmoCount( GetEffectBarAmmo() ) + 1 == pPlayer->GetMaxAmmo( GetEffectBarAmmo() ) ) ) { bWasFull = true; } if ( m_flEffectBarRegenTime < gpGlobals->curtime || bWasFull ) { m_flEffectBarRegenTime = gpGlobals->curtime + GetEffectBarRechargeTime(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::CheckEffectBarRegen( void ) { if ( !m_flEffectBarRegenTime ) return; // If we're full stop the timer. Fixes a bug with "double" throws after respawning or touching a supply cab CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( pPlayer->GetAmmoCount( GetEffectBarAmmo() ) == pPlayer->GetMaxAmmo( GetEffectBarAmmo() ) ) { m_flEffectBarRegenTime = 0; return; } if ( m_flEffectBarRegenTime < gpGlobals->curtime ) { m_flEffectBarRegenTime = 0; EffectBarRegenFinished(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBase::EffectBarRegenFinished( void ) { CTFPlayer *pPlayer = GetTFPlayerOwner(); if ( pPlayer && (pPlayer->GetAmmoCount( GetEffectBarAmmo() ) < pPlayer->GetMaxAmmo( GetEffectBarAmmo() )) ) { #ifdef GAME_DLL pPlayer->GiveAmmo( 1, GetEffectBarAmmo(), true ); #endif #ifdef GAME_DLL // If we still have more ammo space, recharge if ( pPlayer->GetAmmoCount( GetEffectBarAmmo() ) < pPlayer->GetMaxAmmo( GetEffectBarAmmo() ) ) #else // On the client, we assume we'll get 1 more ammo as soon as the server updates us, so only restart if that still won't make us full. if ( pPlayer->GetAmmoCount( GetEffectBarAmmo() ) + 1 < pPlayer->GetMaxAmmo( GetEffectBarAmmo() ) ) #endif { StartEffectBarRegen(); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- Vector CTFWeaponBase::GetParticleColor( int iColor ) { CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); if ( !pOwner ) return Vector(0,0,0); CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( !pItem->IsValid() ) return Vector(0,0,0); int iModifiedRGB = pItem->GetModifiedRGBValue( pOwner->GetTeamNumber() == TF_TEAM_BLUE ); if ( iModifiedRGB > 0 ) { Color clr = Color( ((iModifiedRGB & 0xFF0000) >> 16), ((iModifiedRGB & 0xFF00) >> 8), (iModifiedRGB & 0xFF) ); float fColorMod = 1.f; if ( iColor == 2 ) { fColorMod = 0.5f; } Vector vResult; vResult.x = clamp( fColorMod * clr.r() * (1.f/255), 0.f, 1.0f ); vResult.y = clamp( fColorMod * clr.g() * (1.f/255), 0.f, 1.0f ); vResult.z = clamp( fColorMod * clr.b() * (1.f/255), 0.f, 1.0f ); return vResult; } if ( iColor == 1 ) { if ( pOwner->GetTeamNumber() == TF_TEAM_RED ) return TF_PARTICLE_WEAPON_RED_1; else return TF_PARTICLE_WEAPON_BLUE_1; } else { if ( pOwner->GetTeamNumber() == TF_TEAM_RED ) return TF_PARTICLE_WEAPON_RED_2; else return TF_PARTICLE_WEAPON_BLUE_2; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBase::CanBeCritBoosted( void ) { int iNoCritBoost = 0; CALL_ATTRIB_HOOK_FLOAT( iNoCritBoost, no_crit_boost ); return iNoCritBoost == 0; } bool CTFWeaponBase::CanHaveRevengeCrits( void ) { int iSapperCrits = 0; CALL_ATTRIB_HOOK_INT( iSapperCrits, sapper_kills_collect_crits ); if ( iSapperCrits != 0 ) return true; int iExtinguishCrits = 0; CALL_ATTRIB_HOOK_INT( iExtinguishCrits, extinguish_revenge ); if ( iExtinguishCrits != 0 ) return true; int iRevengeCrits = 0; CALL_ATTRIB_HOOK_INT( iRevengeCrits, sentry_killed_revenge ); if ( iRevengeCrits ) return true; return false; } //----------------------------------------------------------------------------- // Purpose: This is an accent sound that plays in addition to the base shoot sound //----------------------------------------------------------------------------- void CTFWeaponBase::PlayUpgradedShootSound( const char *pszSound ) { if ( TFGameRules()->GameModeUsesUpgrades() ) { CTFPlayer *pOwner = ToTFPlayer( GetOwnerEntity() ); if ( pOwner ) { float flDmgMod = 1.f; CALL_ATTRIB_HOOK_FLOAT( flDmgMod, mult_dmg ); if ( flDmgMod > 1.f ) { // This is pretty hacky as it assumes a cap of +100% damage for picking // sounds -- anything more and the 1-4 scale below falls apart. int nLevel = RemapValClamped( flDmgMod, 1.f, 1.8f, 1.f, 4.f ); const char *pszSoundname = CFmtStr( "%s%d", pszSound, nLevel ); CSoundParameters params; if ( !GetParametersForSound( pszSoundname, params, NULL ) ) return; CPASAttenuationFilter filter( GetOwner(), params.soundlevel ); if ( IsPredicted() && CBaseEntity::GetPredictionPlayer() ) { filter.UsePredictionRules(); } EmitSound( filter, pOwner->entindex(), pszSoundname ); } } } } //----------------------------------------------------------------------------- // Purpose: Is this honorbound weapon? //----------------------------------------------------------------------------- bool CTFWeaponBase::IsHonorBound( void ) const { int iHonorbound = 0; CALL_ATTRIB_HOOK_INT( iHonorbound, honorbound ); return iHonorbound != 0; } EWeaponStrangeType_t CTFWeaponBase::GetStrangeType() { // verify stattrak module and add if necessary if ( m_eStrangeType == STRANGE_UNKNOWN ) { CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( !pItem ) return STRANGE_UNKNOWN; int iStrangeType = -1; for ( int i = 0; i < GetKillEaterAttrCount(); i++ ) { if ( pItem->FindAttribute( GetKillEaterAttr_Score( i ) ) ) { iStrangeType = i; break; } } m_eStrangeType = iStrangeType == -1 ? STRANGE_NOT_STRANGE : STRANGE_IS_STRANGE; } return m_eStrangeType; } bool CTFWeaponBase::BHasStatTrakModule() { if ( m_eStatTrakModuleType == MODULE_UNKNOWN ) { CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( !pItem ) return false; EWeaponStrangeType_t eStrangeType = GetStrangeType(); if ( eStrangeType != STRANGE_IS_STRANGE) { m_eStatTrakModuleType = MODULE_NONE; return false; } // Does it have a module CAttribute_String attrModule; static CSchemaAttributeDefHandle pAttr_module( "weapon_uses_stattrak_module" ); if ( pItem->FindAttribute( pAttr_module, &attrModule ) && attrModule.has_value() ) { m_eStatTrakModuleType = MODULE_FOUND; return true;; } } if ( m_eStatTrakModuleType != MODULE_FOUND ) return false; return true; } #ifdef CLIENT_DLL //----------------------------------------------------------------------------- void CTFWeaponBase::UpdateAllViewmodelAddons( void ) { C_TFPlayer *pPlayer = ToTFPlayer( GetOwner() ); // Remove any view model add ons if we're spectating. if ( !pPlayer ) { RemoveViewmodelStatTrak(); return; } // econ-related addons follow, so bail out if we can't get at the econitemview CEconItemView *pItem = GetAttributeContainer()->GetItem(); if ( !pItem ) { RemoveViewmodelStatTrak(); return; } if ( GetStrangeType() > -1 ) { CSteamID HolderSteamID; pPlayer->GetSteamID( &HolderSteamID ); AddStatTrakModel( pItem, m_eStrangeType, HolderSteamID.GetAccountID() ); } else { RemoveViewmodelStatTrak(); } } // StatTrak Module Testing void CTFWeaponBase::AddStatTrakModel( CEconItemView *pItem, int nStatTrakType, AccountID_t holderAcctId ) { // Already has module, just early out if ( m_viewmodelStatTrakAddon && m_viewmodelStatTrakAddon.Get() && m_viewmodelStatTrakAddon->GetMoveParent() ) { return; } // Something is missing, remove and return if ( !pItem ) { RemoveViewmodelStatTrak(); RemoveWorldmodelStatTrak(); return; } if ( GetStrangeType() != STRANGE_IS_STRANGE ) { RemoveViewmodelStatTrak(); RemoveWorldmodelStatTrak(); return; } if ( !BHasStatTrakModule() ) { RemoveViewmodelStatTrak(); RemoveWorldmodelStatTrak(); return; } // Get Module Data CAttribute_String attrModule; static CSchemaAttributeDefHandle pAttr_module( "weapon_uses_stattrak_module" ); if ( !pItem->FindAttribute( pAttr_module, &attrModule ) || !attrModule.has_value() ) { RemoveViewmodelStatTrak(); RemoveWorldmodelStatTrak(); return; } float flScale = 1.0f; CALL_ATTRIB_HOOK_FLOAT( flScale, weapon_stattrak_module_scale ); // Skin int nSkin = pItem->GetTeamNumber() - TF_TEAM_RED; if ( pItem->GetAccountID() != holderAcctId ) { nSkin += 2; // sad skin } // View Model / third person if ( GetViewmodelAttachment() ) { // Already has a module, early out if ( !( m_viewmodelStatTrakAddon && m_viewmodelStatTrakAddon.Get() && m_viewmodelStatTrakAddon->GetMoveParent() ) ) { RemoveViewmodelStatTrak(); CTFWeaponAttachmentModel *pStatTrakEnt = new class CTFWeaponAttachmentModel; if ( pStatTrakEnt ) { pStatTrakEnt->InitializeAsClientEntity( attrModule.value().c_str(), RENDER_GROUP_VIEW_MODEL_OPAQUE ); pStatTrakEnt->Init( GetViewmodelAttachment(), this, true ); pStatTrakEnt->m_nSkin = nSkin; m_viewmodelStatTrakAddon = pStatTrakEnt; if ( cl_flipviewmodels.GetBool() ) { pStatTrakEnt->SetBodygroup( 1, 1 ); // use a special mirror-image stattrak module that appears correct for lefties flScale *= -1.0f; // flip scale } pStatTrakEnt->SetModelScale( flScale ); //RemoveEffects( EF_NODRAW ); } } } // World Model if ( !(m_worldmodelStatTrakAddon && m_worldmodelStatTrakAddon.Get() && m_worldmodelStatTrakAddon->GetMoveParent() ) ) { RemoveWorldmodelStatTrak(); CTFWeaponAttachmentModel *pStatTrakEnt = new class CTFWeaponAttachmentModel; if ( pStatTrakEnt ) { pStatTrakEnt->InitializeAsClientEntity( attrModule.value().c_str(), RENDER_GROUP_OPAQUE_ENTITY ); pStatTrakEnt->SetModelScale( flScale ); pStatTrakEnt->Init( this, this, false ); pStatTrakEnt->m_nSkin = nSkin; m_worldmodelStatTrakAddon = pStatTrakEnt; // //if ( !cl_flipviewmodels.GetBool() ) // //{ // // pStatTrakEnt->SetBodygroup( 0, 1 ); // use a special mirror-image stattrak module that appears correct for lefties // //} //RemoveEffects( EF_NODRAW ); } } } //----------------------------------------------------------------------------- void CTFWeaponBase::RemoveViewmodelStatTrak( void ) { if ( m_viewmodelStatTrakAddon.Get() ) { m_viewmodelStatTrakAddon->Remove(); m_viewmodelStatTrakAddon = NULL; } } //----------------------------------------------------------------------------- void CTFWeaponBase::RemoveWorldmodelStatTrak( void ) { if ( m_worldmodelStatTrakAddon ) { m_worldmodelStatTrakAddon->Remove(); m_worldmodelStatTrakAddon = NULL; } } //----------------------------------------------------------------------------- const Vector& CTFWeaponBase::GetViewmodelOffset() { if ( !m_bInitViewmodelOffset ) { CAttribute_String attr_min_viewmodel_offset; CALL_ATTRIB_HOOK_STRING( attr_min_viewmodel_offset, min_viewmodel_offset ); const char* pszMinViewmodelOffset = attr_min_viewmodel_offset.value().c_str(); if ( pszMinViewmodelOffset && *pszMinViewmodelOffset ) { UTIL_StringToVector( m_vecViewmodelOffset.Base(), pszMinViewmodelOffset ); } m_bInitViewmodelOffset = true; } return m_vecViewmodelOffset; } //----------------------------------------------------------------------------- // CTFWeaponAttachmentModel //----------------------------------------------------------------------------- void CTFWeaponAttachmentModel::Init( CBaseEntity *pParent, CTFWeaponBase *pAssociatedWeapon, bool bIsViewModel ) { SetParent( pParent ); SetLocalOrigin( vec3_origin ); UpdatePartitionListEntry(); CollisionProp()->MarkPartitionHandleDirty(); //UpdateVisibility(); SetWeaponAssociatedWith( pAssociatedWeapon ); AddEffects( EF_BONEMERGE ); AddEffects( EF_BONEMERGE_FASTCULL ); AddEffects( EF_NODRAW ); m_bIsViewModelAttachment = bIsViewModel; } //----------------------------------------------------------------------------- bool CTFWeaponAttachmentModel::ShouldDraw( void ) { // Follow my associated weapon if ( !m_hWeaponAssociatedWith.Get() ) return false; // some code is overriding the weapon model (taunt), don't show the attachment model if ( m_hWeaponAssociatedWith->IsUsingOverrideModel() ) return false; if ( m_hWeaponAssociatedWith->IsFirstPersonView() && !m_bIsViewModelAttachment ) { return false; } bool bShouldDraw = m_hWeaponAssociatedWith->ShouldDraw(); if ( bShouldDraw ) { return !m_bIsViewModelAttachment; } return false; //if ( pWeapon ) //{ // // If the weapon isn't active, don't draw // if ( pOwner && pOwner->GetActiveWeapon() != pWeapon ) // { // return false; // } // if ( !IsViewModelWearable() ) // { // // If it's the 3rd person wearable, don't draw it when the weapon is hidden // if ( !pWeapon->ShouldDraw() ) // { // return false; // } // } // // If the weapon is being repurposed for a taunt dont draw. // // The Brutal Legend taunt changes your weapon's model to be the guitar, // // but we dont want things like bot-killer skulls or festive lights // // to continue to draw // if ( pWeapon->IsBeingRepurposedForTaunt() ) // { // return false; // } //} // } #endif // CLIENT_DLL