//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "NPCevent.h" #include "basecombatcharacter.h" #include "AI_BaseNPC.h" #include "player.h" #include "game.h" #include "in_buttons.h" #include "grenade_ar2.h" #include "AI_Memory.h" #include "soundent.h" #include "rumble_shared.h" #include "gamestats.h" #include "prop_combine_ball.h" #include "weapon_grenadelauncher.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //extern ConVar sk_plr_dmg_grenadelauncher; ConVar sk_plr_dmg_grenadelauncher( "sk_plr_dmg_grenadelauncher", "0", (FCVAR_REPLICATED),"Grenade launcher damage ConVar" ); ConVar sk_npc_dmg_grenadelauncher( "sk_npc_dmg_grenadelauncher", "0", (FCVAR_REPLICATED),"Grenade launcher damage ConVar" ); //#define AR2FUNC 0 IMPLEMENT_SERVERCLASS_ST(CWeaponGrenadelauncher, DT_WeaponGrenadelauncher) END_SEND_TABLE() LINK_ENTITY_TO_CLASS( weapon_grenadelauncher, CWeaponGrenadelauncher ); PRECACHE_WEAPON_REGISTER(weapon_grenadelauncher); BEGIN_DATADESC( CWeaponGrenadelauncher ) DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ), DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ), END_DATADESC() acttable_t CWeaponGrenadelauncher::m_acttable[] = { { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SHOTGUN, false }, { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SHOTGUN, false }, { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SHOTGUN, false }, { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SHOTGUN, false }, { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN, false }, { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SHOTGUN, false }, { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SHOTGUN, false }, //{ ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SHOTGUN, false }, { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, true }, { ACT_RELOAD, ACT_RELOAD_SMG1, true }, { ACT_IDLE, ACT_IDLE_SMG1, true }, { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SMG1, true }, { ACT_WALK, ACT_WALK_RIFLE, true }, { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, // Readiness activities (not aiming) { ACT_IDLE_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims { ACT_IDLE_STIMULATED, ACT_IDLE_SMG1_STIMULATED, false }, { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims { ACT_WALK_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims { ACT_WALK_STIMULATED, ACT_WALK_RIFLE_STIMULATED, false }, { ACT_WALK_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims { ACT_RUN_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims { ACT_RUN_STIMULATED, ACT_RUN_RIFLE_STIMULATED, false }, { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims // Readiness activities (aiming) { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_RIFLE_STIMULATED, false }, { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims { ACT_WALK_AIM_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_RIFLE_STIMULATED, false }, { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims { ACT_RUN_AIM_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_RIFLE_STIMULATED, false }, { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims //End readiness activities { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, { ACT_RUN, ACT_RUN_RIFLE, true }, { ACT_RUN_AIM, ACT_RUN_AIM_RIFLE, true }, { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SMG1, true }, { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SMG1_LOW, true }, { ACT_COVER_LOW, ACT_COVER_SMG1_LOW, false }, { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG1_LOW, false }, { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, { ACT_RANGE_ATTACK2, ACT_RANGE_ATTACK_AR2_GRENADE, true }, }; IMPLEMENT_ACTTABLE(CWeaponGrenadelauncher); //========================================================= CWeaponGrenadelauncher::CWeaponGrenadelauncher( ) { m_fMinRange1 = 0;// No minimum range. m_fMaxRange1 = 1400; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponGrenadelauncher::Precache( void ) { UTIL_PrecacheOther("grenade_ar2"); BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: Give this weapon longer range when wielded by an ally NPC. //----------------------------------------------------------------------------- void CWeaponGrenadelauncher::Equip( CBaseCombatCharacter *pOwner ) { if( pOwner->Classify() == CLASS_PLAYER_ALLY ) { m_fMaxRange1 = 3000; } else { m_fMaxRange1 = 1400; } BaseClass::Equip( pOwner ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponGrenadelauncher::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ) { // FIXME: use the returned number of bullets to account for >10hz firerate WeaponSoundRealtime( SINGLE_NPC ); CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); pOperator->FireBullets(1, vecShootOrigin, vecShootDir, pOperator->GetAttackSpread(this), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), 0 ); pOperator->DoMuzzleFlash(); m_iClip1 = m_iClip1 - 1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponGrenadelauncher::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) { // Ensure we have enough rounds in the clip m_iClip1++; Vector vecShootOrigin, vecShootDir; QAngle angShootDir; GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); AngleVectors( angShootDir, &vecShootDir ); FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CWeaponGrenadelauncher::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ) { switch( pEvent->event ) { /* case EVENT_WEAPON_SMG1: { Vector vecShootOrigin, vecShootDir; QAngle angDiscard; // Support old style attachment point firing if ((pEvent->options == NULL) || (pEvent->options[0] == '\0') || (!pOperator->GetAttachment(pEvent->options, vecShootOrigin, angDiscard))) { vecShootOrigin = pOperator->Weapon_ShootPosition(); } CAI_BaseNPC *npc = pOperator->MyNPCPointer(); ASSERT( npc != NULL ); vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); } break;*/ //case EVENT_WEAPON_AR2_ALTFIRE: case EVENT_WEAPON_SMG1: { /*int m_RandomShoot; m_RandomShoot = random->RandomInt( 1, 7 ); if ( m_RandomShoot == 1 ) {*/ CAI_BaseNPC *npc = pOperator->MyNPCPointer(); Vector vecShootOrigin, vecShootDir; vecShootOrigin = pOperator->Weapon_ShootPosition(); //vecShootDir = npc->GetShootEnemyDir( vecShootOrigin ); //Checks if it can fire the grenade WeaponRangeAttack2Condition(); Vector vecThrow = m_vecTossVelocity; //If on the rare case the vector is 0 0 0, cancel for avoid launching the grenade without speed //This should be on WeaponRangeAttack2Condition(), but for some unknown reason return CASE_NONE //doesn't stop the launch if (vecThrow == Vector(0, 0, 0)) { break; } CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create("grenade_ar2", vecShootOrigin, vec3_angle, npc); pGrenade->SetAbsVelocity( vecThrow ); pGrenade->SetLocalAngularVelocity(RandomAngle(-400, 400)); //tumble in air pGrenade->SetMoveType(MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE); pGrenade->SetThrower(GetOwner()); pGrenade->SetGravity(0.5); // lower gravity since grenade is aerodynamic and engine doesn't know it. pGrenade->SetDamage(sk_npc_dmg_grenadelauncher.GetFloat()); if (g_pGameRules->IsSkillLevel(SKILL_HARD)) { m_flNextGrenadeCheck = gpGlobals->curtime + RandomFloat(2, 3); } else { m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown. } m_iClip1--; //} } break; default: BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); break; } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CWeaponGrenadelauncher::Reload( void ) { //DisableIronsights(); bool fRet; //float fCacheTime = m_flNextSecondaryAttack; //float fCacheTime = m_flNextPrimaryAttack; fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); if ( fRet ) { // Undo whatever the reload process has done to our secondary // attack timer. We allow you to interrupt reloading to fire // a grenade. //m_flNextSecondaryAttack = GetOwner()->m_flNextAttack = fCacheTime; WeaponSound( RELOAD ); } return fRet; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- //void CWeaponGrenadelauncher::SecondaryAttack( void ) void CWeaponGrenadelauncher::PrimaryAttack( void ) { //lk // If my clip is empty (and I use clips) start reload /*if ( UsesClipsForAmmo1() && !m_iClip1 ) { Reload(); return; }*/ //lk debug : reload when ammo is 0 // Only the player fires this way so we can cast CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); if ( pPlayer == NULL ) return; if (IsNearWall() || GetOwnerIsRunning()) { return; } //Must have ammo /*if (( m_iPrimaryAmmoType <= 0 ) || ( pPlayer->GetWaterLevel() == 3 ) ) { SendWeaponAnim( ACT_VM_DRYFIRE ); BaseClass::WeaponSound( EMPTY ); //m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f; m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; return; } if( m_bInReload ) m_bInReload = false; // MUST call sound before removing a round from the clip of a CMachineGun BaseClass::WeaponSound( SINGLE );*/ pPlayer->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAGS_NONE ); Vector vecSrc = pPlayer->Weapon_ShootPosition(); Vector vecThrow; // Don't autoaim on grenade tosses AngleVectors( pPlayer->EyeAngles() + pPlayer->GetPunchAngle(), &vecThrow ); VectorScale( vecThrow, 1000.0f, vecThrow ); //Create the grenade QAngle angles; VectorAngles( vecThrow, angles ); CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create( "grenade_ar2", vecSrc, angles, pPlayer ); pGrenade->SetAbsVelocity( vecThrow ); pGrenade->SetLocalAngularVelocity( RandomAngle( -400, 400 ) ); pGrenade->SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); pGrenade->SetThrower( GetOwner() ); pGrenade->SetDamage( sk_plr_dmg_grenadelauncher.GetFloat() /*120*/ ); BaseClass::PrimaryAttack(); /*SendWeaponAnim(GetPrimaryAttackActivity()); //ACT_VM_SECONDARYATTACK CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1000, 0.2, GetOwner(), SOUNDENT_CHANNEL_WEAPON ); // player "shoot" animation pPlayer->SetAnimation( PLAYER_ATTACK1 ); // Decrease ammo pPlayer->RemoveAmmo( 1, m_iPrimaryAmmoType ); //pPlayer->RemoveAmmo( 1, m_iSecondaryAmmoType ); // Can shoot again immediately //m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f; // Can blow up after a short delay (so have time to release mouse button) //m_flNextSecondaryAttack = gpGlobals->curtime + 1.0f; m_flNextPrimaryAttack = gpGlobals->curtime + 1.0f; // Register a muzzleflash for the AI. pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 ); //m_iSecondaryAttacks++; m_iPrimaryAttacks++; // lk debug : add warn. if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) { // HEV suit - indicate out of ammo condition pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); } // lk debug : add warn. gamestats->Event_WeaponFired( pPlayer, false, GetClassname() );*/ } #define COMBINE_MIN_GRENADE_CLEAR_DIST 256 /*void CWeaponGrenadelauncher::SecondaryAttack( void ) { DevMsg("Grenade launcher alt attack not available.\n", FCVAR_ARCHIVE); m_flNextSecondaryAttack = gpGlobals->curtime + 1.0f; }*/ //----------------------------------------------------------------------------- // Purpose: // Input : flDot - // flDist - // Output : int //----------------------------------------------------------------------------- int CWeaponGrenadelauncher::WeaponRangeAttack2Condition() { CAI_BaseNPC *npcOwner = GetOwner()->MyNPCPointer(); //return COND_NONE; /* // -------------------------------------------------------- // Assume things haven't changed too much since last time // -------------------------------------------------------- if (gpGlobals->curtime < m_flNextGrenadeCheck ) return m_lastGrenadeCondition; */ // ----------------------- // If moving, don't check. // ----------------------- if ( npcOwner->IsMoving()) return COND_NONE; CBaseEntity *pEnemy = npcOwner->GetEnemy(); if (!pEnemy) return (COND_NONE); Vector vecEnemyLKP = npcOwner->GetEnemyLKP(); if ( !( pEnemy->GetFlags() & FL_ONGROUND ) && pEnemy->GetWaterLevel() == 0 && vecEnemyLKP.z > (GetAbsOrigin().z + WorldAlignMaxs().z) ) { //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to // be grenaded. // don't throw grenades at anything that isn't on the ground! return (COND_NONE); } // -------------------------------------- // Get target vector // -------------------------------------- Vector vecTarget; if (random->RandomInt(0,1)) { // magically know where they are vecTarget = pEnemy->WorldSpaceCenter(); } else { // toss it to where you last saw them vecTarget = vecEnemyLKP; } // vecTarget = m_vecEnemyLKP + (pEnemy->BodyTarget( GetLocalOrigin() ) - pEnemy->GetLocalOrigin()); // estimate position // vecTarget = vecTarget + pEnemy->m_vecVelocity * 2; if ( ( vecTarget - npcOwner->GetLocalOrigin() ).Length2D() <= COMBINE_MIN_GRENADE_CLEAR_DIST ) { // crap, I don't want to blow myself up m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. return (COND_NONE); } // --------------------------------------------------------------------- // Are any friendlies near the intended grenade impact area? // --------------------------------------------------------------------- CBaseEntity *pTarget = NULL; while ( ( pTarget = gEntList.FindEntityInSphere( pTarget, vecTarget, COMBINE_MIN_GRENADE_CLEAR_DIST ) ) != NULL ) { //Check to see if the default relationship is hatred, and if so intensify that if ( npcOwner->IRelationType( pTarget ) == D_LI ) { // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. return (COND_WEAPON_BLOCKED_BY_FRIEND); } } // --------------------------------------------------------------------- // Check that throw is legal and clear // --------------------------------------------------------------------- // FIXME: speed is based on difficulty... Vector vecToss = VecCheckThrow( this, npcOwner->GetLocalOrigin() + Vector(0,0,60), vecTarget, 600.0, 0.5 ); if (vecToss != vec3_origin) { m_vecTossVelocity = vecToss; // don't check again for a while. // JAY: HL1 keeps checking - test? //m_flNextGrenadeCheck = gpGlobals->curtime; m_flNextGrenadeCheck = gpGlobals->curtime + 0.3; // 1/3 second. return (COND_CAN_RANGE_ATTACK2); } else { // don't check again for a while. m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. return (COND_WEAPON_SIGHT_OCCLUDED); } } //----------------------------------------------------------------------------- const WeaponProficiencyInfo_t *CWeaponGrenadelauncher::GetProficiencyValues() { static WeaponProficiencyInfo_t proficiencyTable[] = { { 7.0, 0.75 }, { 5.00, 0.75 }, { 10.0/3.0, 0.75 }, { 5.0/3.0, 0.75 }, { 1.00, 1.0 }, }; COMPILE_TIME_ASSERT( ARRAYSIZE(proficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1); return proficiencyTable; }