diff options
Diffstat (limited to 'game/shared/tf/tf_weaponbase.cpp')
| -rw-r--r-- | game/shared/tf/tf_weaponbase.cpp | 6558 |
1 files changed, 6558 insertions, 0 deletions
diff --git a/game/shared/tf/tf_weaponbase.cpp b/game/shared/tf/tf_weaponbase.cpp new file mode 100644 index 0000000..b8ed436 --- /dev/null +++ b/game/shared/tf/tf_weaponbase.cpp @@ -0,0 +1,6558 @@ +//========= 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<CTFWeaponInfo*>( 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<CTFWearable*>( 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<CTFWearable*>( 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<CBasePlayer *>( 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<CBasePlayer *>( 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<float>( 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<CBasePlayer*>( GetOwner() ); +} + +// ----------------------------------------------------------------------------- +// Purpose: +// ----------------------------------------------------------------------------- +CTFPlayer *CTFWeaponBase::GetTFPlayerOwner() const +{ + return dynamic_cast<CTFPlayer*>( 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<CTFViewModel *>(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<CWeaponMedigun *>( 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<CTFPlayer *>( 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<CMsgGCIncrementKillCountResponse> 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 |