//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= // // Purpose: Weapon Base Gun // //============================================================================= #include "cbase.h" #include "tf_weaponbase_gun.h" #include "tf_fx_shared.h" #include "effect_dispatch_data.h" #include "takedamageinfo.h" #include "tf_projectile_nail.h" #if !defined( CLIENT_DLL ) // Server specific. #include "tf_gamestats.h" #include "tf_player.h" #include "tf_fx.h" #include "te_effect_dispatch.h" #include "tf_projectile_rocket.h" #include "tf_weapon_grenade_pipebomb.h" #include "te.h" #else // Client specific. #include "c_tf_player.h" #include "c_te_effect_dispatch.h" #endif //============================================================================= // // TFWeaponBase Gun tables. // IMPLEMENT_NETWORKCLASS_ALIASED( TFWeaponBaseGun, DT_TFWeaponBaseGun ) BEGIN_NETWORK_TABLE( CTFWeaponBaseGun, DT_TFWeaponBaseGun ) END_NETWORK_TABLE() BEGIN_PREDICTION_DATA( CTFWeaponBaseGun ) END_PREDICTION_DATA() // Server specific. #if !defined( CLIENT_DLL ) BEGIN_DATADESC( CTFWeaponBaseGun ) DEFINE_THINKFUNC( ZoomOutIn ), DEFINE_THINKFUNC( ZoomOut ), DEFINE_THINKFUNC( ZoomIn ), END_DATADESC() #endif //============================================================================= // // TFWeaponBase Gun functions. // //----------------------------------------------------------------------------- // Purpose: Constructor. //----------------------------------------------------------------------------- CTFWeaponBaseGun::CTFWeaponBaseGun() { m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::PrimaryAttack( void ) { // Check for ammunition. if ( m_iClip1 <= 0 && m_iClip1 != -1 ) return; // Are we capable of firing again? if ( m_flNextPrimaryAttack > gpGlobals->curtime ) return; // Get the player owning the weapon. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; if ( !CanAttack() ) return; CalcIsAttackCritical(); #ifndef CLIENT_DLL pPlayer->RemoveInvisibility(); pPlayer->RemoveDisguise(); // Minigun has custom handling if ( GetWeaponID() != TF_WEAPON_MINIGUN ) { pPlayer->SpeakWeaponFire(); } CTF_GameStats.Event_PlayerFiredWeapon( pPlayer, IsCurrentAttackACrit() ); #endif // Set the weapon mode. m_iWeaponMode = TF_WEAPON_PRIMARY_MODE; SendWeaponAnim( ACT_VM_PRIMARYATTACK ); pPlayer->SetAnimation( PLAYER_ATTACK1 ); FireProjectile( pPlayer ); // Set next attack times. m_flNextPrimaryAttack = gpGlobals->curtime + m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay; // 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 = gpGlobals->curtime + m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flTimeFireDelay; // Set the idle animation times based on the sequence duration, so that we play full fire animations // that last longer than the refire rate may allow. if ( Clip1() > 0 ) { SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() ); } else { SetWeaponIdleTime( gpGlobals->curtime + SequenceDuration() ); } // Check the reload mode and behave appropriately. if ( m_bReloadsSingly ) { m_iReloadMode.Set( TF_RELOAD_START ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::SecondaryAttack( void ) { // semi-auto behaviour if ( m_bInAttack2 ) return; // Get the player owning the weapon. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; pPlayer->DoClassSpecialSkill(); m_bInAttack2 = true; m_flNextSecondaryAttack = gpGlobals->curtime + 0.5; } CBaseEntity *CTFWeaponBaseGun::FireProjectile( CTFPlayer *pPlayer ) { int iProjectile = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_iProjectile; CBaseEntity *pProjectile = NULL; switch( iProjectile ) { case TF_PROJECTILE_BULLET: FireBullet( pPlayer ); break; case TF_PROJECTILE_ROCKET: pProjectile = FireRocket( pPlayer ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_SYRINGE: pProjectile = FireNail( pPlayer, iProjectile ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_PIPEBOMB: pProjectile = FirePipeBomb( pPlayer, false ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_PIPEBOMB_REMOTE: pProjectile = FirePipeBomb( pPlayer, true ); pPlayer->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY ); break; case TF_PROJECTILE_NONE: default: // do nothing! DevMsg( "Weapon does not have a projectile type set\n" ); break; } if ( m_iClip1 != -1 ) { m_iClip1 -= m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_iAmmoPerShot; } else { if ( m_iWeaponMode == TF_WEAPON_PRIMARY_MODE ) { pPlayer->RemoveAmmo( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_iAmmoPerShot, m_iPrimaryAmmoType ); } else { pPlayer->RemoveAmmo( m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_iAmmoPerShot, m_iSecondaryAmmoType ); } } DoFireEffects(); UpdatePunchAngles( pPlayer ); return pProjectile; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::UpdatePunchAngles( CTFPlayer *pPlayer ) { // Update the player's punch angle. QAngle angle = pPlayer->GetPunchAngle(); float flPunchAngle = m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flPunchAngle; if ( flPunchAngle > 0 ) { angle.x -= SharedRandomInt( "ShotgunPunchAngle", ( flPunchAngle - 1 ), ( flPunchAngle + 1 ) ); pPlayer->SetPunchAngle( angle ); } } //----------------------------------------------------------------------------- // Purpose: Fire a bullet! //----------------------------------------------------------------------------- void CTFWeaponBaseGun::FireBullet( CTFPlayer *pPlayer ) { PlayWeaponShootSound(); FX_FireBullets( pPlayer->entindex(), pPlayer->Weapon_ShootPosition(), pPlayer->EyeAngles() + pPlayer->GetPunchAngle(), GetWeaponID(), m_iWeaponMode, CBaseEntity::GetPredictionRandomSeed() & 255, GetWeaponSpread(), GetProjectileDamage(), IsCurrentAttackACrit() ); } class CTraceFilterIgnoreTeammates : public CTraceFilterSimple { public: // It does have a base, but we'll never network anything below here.. DECLARE_CLASS( CTraceFilterIgnoreTeammates, CTraceFilterSimple ); CTraceFilterIgnoreTeammates( const IHandleEntity *passentity, int collisionGroup, int iIgnoreTeam ) : CTraceFilterSimple( passentity, collisionGroup ), m_iIgnoreTeam( iIgnoreTeam ) { } virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask ) { CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity ); if ( pEntity->IsPlayer() && pEntity->GetTeamNumber() == m_iIgnoreTeam ) { return false; } return true; } int m_iIgnoreTeam; }; //----------------------------------------------------------------------------- // Purpose: Return the origin & angles for a projectile fired from the player's gun //----------------------------------------------------------------------------- void CTFWeaponBaseGun::GetProjectileFireSetup( CTFPlayer *pPlayer, Vector vecOffset, Vector *vecSrc, QAngle *angForward, bool bHitTeammates /* = true */ ) { Vector vecForward, vecRight, vecUp; AngleVectors( pPlayer->EyeAngles(), &vecForward, &vecRight, &vecUp ); Vector vecShootPos = pPlayer->Weapon_ShootPosition(); // Estimate end point Vector endPos = vecShootPos + vecForward * 2000; // Trace forward and find what's in front of us, and aim at that trace_t tr; if ( bHitTeammates ) { CTraceFilterSimple filter( pPlayer, COLLISION_GROUP_NONE ); UTIL_TraceLine( vecShootPos, endPos, MASK_SOLID, &filter, &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: Fire a rocket //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FireRocket( CTFPlayer *pPlayer ) { PlayWeaponShootSound(); // Server only - create the rocket. #ifdef GAME_DLL Vector vecSrc; QAngle angForward; Vector vecOffset( 23.5f, 12.0f, -3.0f ); if ( pPlayer->GetFlags() & FL_DUCKING ) { vecOffset.z = 8.0f; } GetProjectileFireSetup( pPlayer, vecOffset, &vecSrc, &angForward, false ); CTFProjectile_Rocket *pProjectile = CTFProjectile_Rocket::Create( vecSrc, angForward, pPlayer, pPlayer ); if ( pProjectile ) { pProjectile->SetCritical( IsCurrentAttackACrit() ); pProjectile->SetDamage( GetProjectileDamage() ); } return pProjectile; #endif return NULL; } //----------------------------------------------------------------------------- // Purpose: Fire a projectile nail //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FireNail( CTFPlayer *pPlayer, int iSpecificNail ) { PlayWeaponShootSound(); Vector vecSrc; QAngle angForward; GetProjectileFireSetup( pPlayer, Vector(16,6,-8), &vecSrc, &angForward ); // Add some spread float flSpread = 1.5; angForward.x += RandomFloat( -flSpread, flSpread ); angForward.y += RandomFloat( -flSpread, flSpread ); CTFBaseProjectile *pProjectile = NULL; switch( iSpecificNail ) { case TF_PROJECTILE_SYRINGE: pProjectile = CTFProjectile_Syringe::Create( vecSrc, angForward, pPlayer, pPlayer, IsCurrentAttackACrit() ); break; default: Assert(0); } if ( pProjectile ) { pProjectile->SetWeaponID( GetWeaponID() ); pProjectile->SetCritical( IsCurrentAttackACrit() ); #ifdef GAME_DLL pProjectile->SetDamage( GetProjectileDamage() ); #endif } return pProjectile; } //----------------------------------------------------------------------------- // Purpose: Fire a pipe bomb //----------------------------------------------------------------------------- CBaseEntity *CTFWeaponBaseGun::FirePipeBomb( CTFPlayer *pPlayer, bool bRemoteDetonate ) { PlayWeaponShootSound(); #ifdef GAME_DLL Vector vecForward, vecRight, vecUp; AngleVectors( pPlayer->EyeAngles(), &vecForward, &vecRight, &vecUp ); // Create grenades here!! Vector vecSrc = pPlayer->Weapon_ShootPosition(); vecSrc += vecForward * 16.0f + vecRight * 8.0f + vecUp * -6.0f; Vector vecVelocity = ( vecForward * GetProjectileSpeed() ) + ( vecUp * 200.0f ) + ( random->RandomFloat( -10.0f, 10.0f ) * vecRight ) + ( random->RandomFloat( -10.0f, 10.0f ) * vecUp ); CTFGrenadePipebombProjectile *pProjectile = CTFGrenadePipebombProjectile::Create( vecSrc, pPlayer->EyeAngles(), vecVelocity, AngularImpulse( 600, random->RandomInt( -1200, 1200 ), 0 ), pPlayer, GetTFWpnData(), bRemoteDetonate ); if ( pProjectile ) { pProjectile->SetCritical( IsCurrentAttackACrit() ); } return pProjectile; #endif return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::PlayWeaponShootSound( void ) { if ( IsCurrentAttackACrit() ) { WeaponSound( BURST ); } else { WeaponSound( SINGLE ); } } //----------------------------------------------------------------------------- // Purpose: Accessor for damage, so sniper etc can modify damage //----------------------------------------------------------------------------- float CTFWeaponBaseGun::GetProjectileSpeed( void ) { // placeholder for now // grenade launcher and pipebomb launcher hook this to set variable pipebomb speed return 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CTFWeaponBaseGun::GetWeaponSpread( void ) { return m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_flSpread; } //----------------------------------------------------------------------------- // Purpose: Accessor for damage, so sniper etc can modify damage //----------------------------------------------------------------------------- float CTFWeaponBaseGun::GetProjectileDamage( void ) { return (float)m_pWeaponInfo->GetWeaponData( m_iWeaponMode ).m_nDamage; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CTFWeaponBaseGun::Holster( CBaseCombatWeapon *pSwitchingTo ) { // Server specific. #if !defined( CLIENT_DLL ) // Make sure to zoom out before we holster the weapon. ZoomOut(); SetContextThink( NULL, 0, ZOOM_CONTEXT ); #endif return BaseClass::Holster( pSwitchingTo ); } //----------------------------------------------------------------------------- // Purpose: // NOTE: Should this be put into fire gun //----------------------------------------------------------------------------- void CTFWeaponBaseGun::DoFireEffects() { CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; // Muzzle flash on weapon. bool bMuzzleFlash = true; if ( pPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) { CTFWeaponBase *pWeapon = pPlayer->GetActiveTFWeapon(); if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_MINIGUN ) { bMuzzleFlash = false; } } if ( bMuzzleFlash ) { pPlayer->DoMuzzleFlash(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::ToggleZoom( void ) { // Toggle the zoom. CBasePlayer *pPlayer = GetPlayerOwner(); if ( pPlayer ) { if( pPlayer->GetFOV() >= 75 ) { ZoomIn(); } else { ZoomOut(); } } // Get the zoom animation time. m_flNextSecondaryAttack = gpGlobals->curtime + 1.2; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::ZoomIn( void ) { // The the owning player. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; // Set the weapon zoom. // TODO: The weapon fov should be gotten from the script file. pPlayer->SetFOV( pPlayer, TF_WEAPON_ZOOM_FOV, 0.1f ); pPlayer->m_Shared.AddCond( TF_COND_ZOOMED ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::ZoomOut( void ) { // The the owning player. CTFPlayer *pPlayer = ToTFPlayer( GetPlayerOwner() ); if ( !pPlayer ) return; if ( pPlayer->m_Shared.InCond( TF_COND_ZOOMED ) ) { // Set the FOV to 0 set the default FOV. pPlayer->SetFOV( pPlayer, 0, 0.1f ); pPlayer->m_Shared.RemoveCond( TF_COND_ZOOMED ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CTFWeaponBaseGun::ZoomOutIn( void ) { //Zoom out, set think to zoom back in. ZoomOut(); SetContextThink( &CTFWeaponBaseGun::ZoomIn, gpGlobals->curtime + ZOOM_REZOOM_TIME, ZOOM_CONTEXT ); }