//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "weapon_alyxgun.h" #include "npcevent.h" #include "ai_basenpc.h" #include "globalstate.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" IMPLEMENT_SERVERCLASS_ST(CWeaponAlyxGun, DT_WeaponAlyxGun) END_SEND_TABLE() LINK_ENTITY_TO_CLASS( weapon_alyxgun, CWeaponAlyxGun ); PRECACHE_WEAPON_REGISTER(weapon_alyxgun); BEGIN_DATADESC( CWeaponAlyxGun ) END_DATADESC() acttable_t CWeaponAlyxGun::m_acttable[] = { { ACT_IDLE, ACT_IDLE_PISTOL, true }, { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_PISTOL, true }, { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_PISTOL, true }, { ACT_RELOAD, ACT_RELOAD_PISTOL, true }, { ACT_WALK_AIM, ACT_WALK_AIM_PISTOL, true }, { ACT_RUN_AIM, ACT_RUN_AIM_PISTOL, true }, { ACT_COVER_LOW, ACT_COVER_PISTOL_LOW, true }, { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_PISTOL_LOW, true }, { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_PISTOL,true }, { ACT_RELOAD_LOW, ACT_RELOAD_PISTOL_LOW, true }, { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_PISTOL_LOW, true }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_PISTOL, true }, // Readiness activities (not aiming) { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL, false },//never aims { ACT_IDLE_STIMULATED, ACT_IDLE_STIMULATED, false }, { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, { ACT_WALK_RELAXED, ACT_WALK, false },//never aims { ACT_WALK_STIMULATED, ACT_WALK_STIMULATED, false }, { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, { ACT_RUN_RELAXED, ACT_RUN, false },//never aims { ACT_RUN_STIMULATED, ACT_RUN_STIMULATED, false }, { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, // Readiness activities (aiming) { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL, false },//never aims { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_PISTOL, false }, { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, { ACT_WALK_AIM_RELAXED, ACT_WALK, false },//never aims { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims { ACT_RUN_AIM_RELAXED, ACT_RUN, false },//never aims { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims //End readiness activities // Crouch activities { ACT_CROUCHIDLE_STIMULATED, ACT_CROUCHIDLE_STIMULATED, false }, { ACT_CROUCHIDLE_AIM_STIMULATED,ACT_RANGE_AIM_PISTOL_LOW, false },//always aims { ACT_CROUCHIDLE_AGITATED, ACT_RANGE_AIM_PISTOL_LOW, false },//always aims // Readiness translations { ACT_READINESS_RELAXED_TO_STIMULATED, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED, false }, { ACT_READINESS_RELAXED_TO_STIMULATED_WALK, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK, false }, { ACT_READINESS_AGITATED_TO_STIMULATED, ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED, false }, { ACT_READINESS_STIMULATED_TO_RELAXED, ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED, false }, // { ACT_ARM, ACT_ARM_PISTOL, true }, // { ACT_DISARM, ACT_DISARM_PISTOL, true }, }; IMPLEMENT_ACTTABLE(CWeaponAlyxGun); #define TOOCLOSETIMER_OFF 0.0f #define ALYX_TOOCLOSETIMER 1.0f // Time an enemy must be tooclose before Alyx is allowed to shoot it. //========================================================= CWeaponAlyxGun::CWeaponAlyxGun( ) { m_fMinRange1 = 1; m_fMaxRange1 = 5000; m_flTooCloseTimer = TOOCLOSETIMER_OFF; #ifdef HL2_EPISODIC m_fMinRange1 = 60; m_fMaxRange1 = 2048; #endif//HL2_EPISODIC } CWeaponAlyxGun::~CWeaponAlyxGun( ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponAlyxGun::Precache( void ) { BaseClass::Precache(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CWeaponAlyxGun::Equip( CBaseCombatCharacter *pOwner ) { BaseClass::Equip( pOwner ); } //----------------------------------------------------------------------------- // Purpose: Try to encourage Alyx not to use her weapon at point blank range, // but don't prevent her from defending herself if cornered. // Input : flDot - // flDist - // Output : int //----------------------------------------------------------------------------- int CWeaponAlyxGun::WeaponRangeAttack1Condition( float flDot, float flDist ) { #ifdef HL2_EPISODIC if( flDist < m_fMinRange1 ) { // If Alyx is not able to fire because an enemy is too close, start a timer. // If the condition persists, allow her to ignore it and defend herself. The idea // is to stop Alyx being content to fire point blank at enemies if she's able to move // away, without making her defenseless if she's not able to move. float flTime; if( m_flTooCloseTimer == TOOCLOSETIMER_OFF ) { m_flTooCloseTimer = gpGlobals->curtime; } flTime = gpGlobals->curtime - m_flTooCloseTimer; if( flTime > ALYX_TOOCLOSETIMER ) { // Fake the range to allow Alyx to shoot. flDist = m_fMinRange1 + 1.0f; } } else { m_flTooCloseTimer = TOOCLOSETIMER_OFF; } int nBaseCondition = BaseClass::WeaponRangeAttack1Condition( flDot, flDist ); // While in a vehicle, we extend our aiming cone (this relies on COND_NOT_FACING_ATTACK // TODO: This needs to be rolled in at the animation level if ( GetOwner()->IsInAVehicle() ) { Vector vecRoughDirection = ( GetOwner()->GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter() ); Vector vecRight; GetVectors( NULL, &vecRight, NULL ); bool bRightSide = ( DotProduct( vecRoughDirection, vecRight ) > 0.0f ); float flTargetDot = ( bRightSide ) ? -0.7f : 0.0f; if ( nBaseCondition == COND_NOT_FACING_ATTACK && flDot >= flTargetDot ) { nBaseCondition = COND_CAN_RANGE_ATTACK1; } } return nBaseCondition; #else return BaseClass::WeaponRangeAttack1Condition( flDot, flDist ); #endif//HL2_EPISODIC } //----------------------------------------------------------------------------- // Purpose: // Input : flDot - // flDist - // Output : int //----------------------------------------------------------------------------- int CWeaponAlyxGun::WeaponRangeAttack2Condition( float flDot, float flDist ) { return COND_NONE; } //----------------------------------------------------------------------------- // Purpose: // Input : *pOperator - //----------------------------------------------------------------------------- void CWeaponAlyxGun::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, bool bUseWeaponAngles ) { Vector vecShootOrigin, vecShootDir; CAI_BaseNPC *npc = pOperator->MyNPCPointer(); ASSERT( npc != NULL ); if ( bUseWeaponAngles ) { QAngle angShootDir; GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); AngleVectors( angShootDir, &vecShootDir ); } else { vecShootOrigin = pOperator->Weapon_ShootPosition(); vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); } WeaponSound( SINGLE_NPC ); if( hl2_episodic.GetBool() ) { pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 1 ); } else { pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2 ); } pOperator->DoMuzzleFlash(); if( hl2_episodic.GetBool() ) { // Never fire Alyx's last bullet just in case there's an emergency // and she needs to be able to shoot without reloading. if( m_iClip1 > 1 ) { m_iClip1 = m_iClip1 - 1; } } else { m_iClip1 = m_iClip1 - 1; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponAlyxGun::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) { // Ensure we have enough rounds in the clip m_iClip1++; // HACK: We need the gun to fire its muzzle flash if ( bSecondary == false ) { SetActivity( ACT_RANGE_ATTACK_PISTOL, 0.0f ); } FireNPCPrimaryAttack( pOperator, true ); } //----------------------------------------------------------------------------- void CWeaponAlyxGun::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) { switch( pEvent->event ) { case EVENT_WEAPON_PISTOL_FIRE: { FireNPCPrimaryAttack( pOperator, false ); break; } default: BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); break; } } //----------------------------------------------------------------------------- // Purpose: Whether or not Alyx is in the injured mode //----------------------------------------------------------------------------- bool IsAlyxInInjuredMode( void ) { if ( hl2_episodic.GetBool() == false ) return false; return ( GlobalEntity_GetState("ep2_alyx_injured") == GLOBAL_ON ); } //----------------------------------------------------------------------------- const Vector& CWeaponAlyxGun::GetBulletSpread( void ) { static const Vector cone = VECTOR_CONE_2DEGREES; static const Vector injuredCone = VECTOR_CONE_6DEGREES; if ( IsAlyxInInjuredMode() ) return injuredCone; return cone; } //----------------------------------------------------------------------------- float CWeaponAlyxGun::GetMinRestTime( void ) { if ( IsAlyxInInjuredMode() ) return 1.5f; return BaseClass::GetMinRestTime(); } //----------------------------------------------------------------------------- float CWeaponAlyxGun::GetMaxRestTime( void ) { if ( IsAlyxInInjuredMode() ) return 3.0f; return BaseClass::GetMaxRestTime(); }