summaryrefslogtreecommitdiff
path: root/game/server/cstrike/bot/cs_bot_weapon.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/cstrike/bot/cs_bot_weapon.cpp')
-rw-r--r--game/server/cstrike/bot/cs_bot_weapon.cpp1363
1 files changed, 1363 insertions, 0 deletions
diff --git a/game/server/cstrike/bot/cs_bot_weapon.cpp b/game/server/cstrike/bot/cs_bot_weapon.cpp
new file mode 100644
index 0000000..4c737f5
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot_weapon.cpp
@@ -0,0 +1,1363 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_bot.h"
+#include "basecsgrenade_projectile.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Fire our active weapon towards our current enemy
+ * NOTE: Aiming our weapon is handled in RunBotUpkeep()
+ */
+void CCSBot::FireWeaponAtEnemy( void )
+{
+ if (cv_bot_dont_shoot.GetBool())
+ {
+ return;
+ }
+
+ CBasePlayer *enemy = GetBotEnemy();
+ if (enemy == NULL)
+ {
+ return;
+ }
+
+ Vector myOrigin = GetCentroid( this );
+
+ if (IsUsingSniperRifle())
+ {
+ // if we're using a sniper rifle, don't fire until we are standing still, are zoomed in, and not rapidly moving our view
+ if (!IsNotMoving() || IsWaitingForZoom() || !HasViewBeenSteady( GetProfile()->GetReactionTime() ) )
+ {
+ return;
+ }
+ }
+
+ if (gpGlobals->curtime > m_fireWeaponTimestamp &&
+ GetTimeSinceAcquiredCurrentEnemy() >= GetProfile()->GetAttackDelay() &&
+ !IsSurprised())
+ {
+ if (!(IsRecognizedEnemyProtectedByShield() && IsPlayerFacingMe( enemy )) && // don't shoot at enemies behind shields
+ !IsReloading() &&
+ !IsActiveWeaponClipEmpty() &&
+ //gpGlobals->curtime > m_reacquireTimestamp &&
+ IsEnemyVisible())
+ {
+ // we have a clear shot - pull trigger if we are aiming at enemy
+ Vector toAimSpot = m_aimSpot - EyePosition();
+ float rangeToEnemy = toAimSpot.NormalizeInPlace();
+
+ if ( IsUsingSniperRifle() )
+ {
+ // check our accuracy versus our target distance
+ float fProjectedSpread = rangeToEnemy * GetActiveCSWeapon()->GetInaccuracy();
+ float fRequiredSpread = IsUsing( WEAPON_AWP ) ? 50.0f : 25.0f; // AWP will kill with any hit
+ if ( fProjectedSpread > fRequiredSpread )
+ return;
+ }
+
+ // get actual view direction vector
+ Vector aimDir = GetViewVector();
+
+ float onTarget = DotProduct( toAimSpot, aimDir );
+
+ // aim more precisely with a sniper rifle
+ // because rifles' bullets spray, don't have to be very precise
+ const float halfSize = (IsUsingSniperRifle()) ? HalfHumanWidth : 2.0f * HalfHumanWidth;
+
+ // aiming tolerance depends on how close the target is - closer targets subtend larger angles
+ float aimTolerance = (float)cos( atan( halfSize / rangeToEnemy ) );
+
+ if (onTarget > aimTolerance)
+ {
+ bool doAttack;
+
+ // if friendly fire is on, don't fire if a teammate is blocking our line of fire
+ if (TheCSBots()->AllowFriendlyFireDamage())
+ {
+ if (IsFriendInLineOfFire())
+ doAttack = false;
+ else
+ doAttack = true;
+ }
+ else
+ {
+ // fire freely
+ doAttack = true;
+ }
+
+ if (doAttack)
+ {
+ // if we are using a knife, only swing it if we're close
+ if (IsUsingKnife())
+ {
+ const float knifeRange = 75.0f; // 50
+ if (rangeToEnemy < knifeRange)
+ {
+ // since we've given ourselves away - run!
+ ForceRun( 5.0f );
+
+ // if our prey is facing away, backstab him!
+ if (!IsPlayerFacingMe( enemy ))
+ {
+ SecondaryAttack();
+ }
+ else
+ {
+ // randomly choose primary and secondary attacks with knife
+ const float knifeStabChance = 33.3f;
+ if (RandomFloat( 0, 100 ) < knifeStabChance)
+ SecondaryAttack();
+ else
+ PrimaryAttack();
+ }
+ }
+ }
+ else
+ {
+ PrimaryAttack();
+ }
+ }
+
+ if (IsUsingPistol())
+ {
+ // high-skill bots fire their pistols quickly at close range
+ const float closePistolRange = 360.0f;
+ if (GetProfile()->GetSkill() > 0.75f && rangeToEnemy < closePistolRange)
+ {
+ // fire as fast as possible
+ m_fireWeaponTimestamp = 0.0f;
+ }
+ else
+ {
+ // fire somewhat quickly
+ m_fireWeaponTimestamp = RandomFloat( 0.15f, 0.4f );
+ }
+ }
+ else // not using a pistol
+ {
+ const float sprayRange = 400.0f;
+ if (GetProfile()->GetSkill() < 0.5f || rangeToEnemy < sprayRange || IsUsingMachinegun())
+ {
+ // spray 'n pray if enemy is close, or we're not that good, or we're using the big machinegun
+ m_fireWeaponTimestamp = 0.0f;
+ }
+ else
+ {
+ const float distantTargetRange = 800.0f;
+ if (!IsUsingSniperRifle() && rangeToEnemy > distantTargetRange)
+ {
+ // if very far away, fire slowly for better accuracy
+ m_fireWeaponTimestamp = RandomFloat( 0.3f, 0.7f );
+ }
+ else
+ {
+ // fire short bursts for accuracy
+ m_fireWeaponTimestamp = RandomFloat( 0.15f, 0.25f ); // 0.15, 0.5
+ }
+ }
+ }
+
+ // subtract system latency
+ m_fireWeaponTimestamp -= g_BotUpdateInterval;
+
+ m_fireWeaponTimestamp += gpGlobals->curtime;
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Set the current aim offset using given accuracy (1.0 = perfect aim, 0.0f = terrible aim)
+ */
+void CCSBot::SetAimOffset( float accuracy )
+{
+ // if our accuracy is less than perfect, it will improve as we "focus in" while not rotating our view
+ if (accuracy < 1.0f)
+ {
+ // if we moved our view, reset our "focus" mechanism
+ if (IsViewMoving( 100.0f ))
+ m_aimSpreadTimestamp = gpGlobals->curtime;
+
+ // focusTime is the time it takes for a bot to "focus in" for very good aim, from 2 to 5 seconds
+ const float focusTime = MAX( 5.0f * (1.0f - accuracy), 2.0f );
+ float focusInterval = gpGlobals->curtime - m_aimSpreadTimestamp;
+
+ float focusAccuracy = focusInterval / focusTime;
+
+ // limit how much "focus" will help
+ const float maxFocusAccuracy = 0.75f;
+ if (focusAccuracy > maxFocusAccuracy)
+ focusAccuracy = maxFocusAccuracy;
+
+ accuracy = MAX( accuracy, focusAccuracy );
+ }
+
+ //PrintIfWatched( "Accuracy = %4.3f\n", accuracy );
+
+ // aim error increases with distance, such that actual crosshair error stays about the same
+ float range = (m_lastEnemyPosition - EyePosition()).Length();
+ float maxOffset = (GetFOV()/GetDefaultFOV()) * 0.05f * range; // 0.1
+ float error = maxOffset * (1.0f - accuracy);
+
+ m_aimOffsetGoal.x = RandomFloat( -error, error );
+ m_aimOffsetGoal.y = RandomFloat( -error, error );
+ m_aimOffsetGoal.z = RandomFloat( -error, error );
+
+ // define time when aim offset will automatically be updated
+ m_aimOffsetTimestamp = gpGlobals->curtime + RandomFloat( 0.25f, 1.0f ); // 0.25, 1.5f
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Wiggle aim error based on GetProfile()->GetSkill()
+ */
+void CCSBot::UpdateAimOffset( void )
+{
+ if (gpGlobals->curtime >= m_aimOffsetTimestamp)
+ {
+ SetAimOffset( GetProfile()->GetSkill() );
+ }
+
+ // move current offset towards goal offset
+ Vector d = m_aimOffsetGoal - m_aimOffset;
+ const float stiffness = 0.1f;
+ m_aimOffset.x += stiffness * d.x;
+ m_aimOffset.y += stiffness * d.y;
+ m_aimOffset.z += stiffness * d.z;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Change our zoom level to be appropriate for the given range.
+ * Return true if the zoom level changed.
+ */
+bool CCSBot::AdjustZoom( float range )
+{
+ bool adjustZoom = false;
+
+ if (IsUsingSniperRifle())
+ {
+ const float sniperZoomRange = 150.0f; // NOTE: This must be less than sniperMinRange in AttackState
+ const float sniperFarZoomRange = 1500.0f;
+
+ // if range is too close, don't zoom
+ if (range <= sniperZoomRange)
+ {
+ // zoom out
+ if (GetZoomLevel() != NO_ZOOM)
+ {
+ adjustZoom = true;
+ }
+ }
+ else if (range < sniperFarZoomRange)
+ {
+ // maintain low zoom
+ if (GetZoomLevel() != LOW_ZOOM)
+ {
+ adjustZoom = true;
+ }
+ }
+ else
+ {
+ // maintain high zoom
+ if (GetZoomLevel() != HIGH_ZOOM)
+ {
+ adjustZoom = true;
+ }
+ }
+ }
+ else
+ {
+ // zoom out
+ if (GetZoomLevel() != NO_ZOOM)
+ {
+ adjustZoom = true;
+ }
+ }
+
+ if (adjustZoom)
+ {
+ SecondaryAttack();
+
+ // pause after zoom to allow "eyes" to refocus
+// m_zoomTimer.Start( 0.25f + (1.0f - GetProfile()->GetSkill()) );
+ m_zoomTimer.Start( 0.25f );
+ }
+
+ return adjustZoom;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if using the specific weapon
+ */
+bool CCSBot::IsUsing( CSWeaponID weaponID ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon == NULL)
+ return false;
+
+ if (weapon->IsA( weaponID ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if we are using a weapon with a removable silencer
+ */
+bool CCSBot::DoesActiveWeaponHaveSilencer( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon == NULL)
+ return false;
+
+ if (weapon->IsA( WEAPON_M4A1 ) || weapon->IsA( WEAPON_USP ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are using a sniper rifle
+ */
+bool CCSBot::IsUsingSniperRifle( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon && IsSniperRifle( weapon ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we have a sniper rifle in our inventory
+ */
+bool CCSBot::IsSniper( void ) const
+{
+ CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
+
+ if (weapon && IsSniperRifle( weapon ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are actively sniping (moving to sniper spot or settled in)
+ */
+bool CCSBot::IsSniping( void ) const
+{
+ if (GetTask() == MOVE_TO_SNIPER_SPOT || GetTask() == SNIPING)
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are using a shotgun
+ */
+bool CCSBot::IsUsingShotgun( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon == NULL)
+ return false;
+
+ return weapon->IsKindOf(WEAPONTYPE_SHOTGUN);
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if using the big 'ol machinegun
+ */
+bool CCSBot::IsUsingMachinegun( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon && weapon->IsA( WEAPON_M249 ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if primary weapon doesn't exist or is totally out of ammo
+ */
+bool CCSBot::IsPrimaryWeaponEmpty( void ) const
+{
+ CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
+
+ if (weapon == NULL)
+ return true;
+
+ // check if gun has any ammo left
+ if (weapon->HasAnyAmmo())
+ return false;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if pistol doesn't exist or is totally out of ammo
+ */
+bool CCSBot::IsPistolEmpty( void ) const
+{
+ CWeaponCSBase *weapon = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) );
+
+ if (weapon == NULL)
+ return true;
+
+ // check if gun has any ammo left
+ if (weapon->HasAnyAmmo())
+ return false;
+
+ return true;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Equip the given item
+ */
+bool CCSBot::DoEquip( CWeaponCSBase *weapon )
+{
+ if (weapon == NULL)
+ return false;
+
+ // check if weapon has any ammo left
+ if (!weapon->HasAnyAmmo())
+ return false;
+
+ // equip it
+ SelectItem( weapon->GetClassname() );
+ m_equipTimer.Start();
+
+ return true;
+}
+
+
+// throttle how often equipping is allowed
+const float minEquipInterval = 5.0f;
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Equip the best weapon we are carrying that has ammo
+ */
+void CCSBot::EquipBestWeapon( bool mustEquip )
+{
+ // throttle how often equipping is allowed
+ if (!mustEquip && m_equipTimer.GetElapsedTime() < minEquipInterval)
+ return;
+
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheBots );
+
+ CWeaponCSBase *primary = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
+ if (primary)
+ {
+ CSWeaponType weaponClass = primary->GetCSWpnData().m_WeaponType;
+
+ if ((ctrl->AllowShotguns() && weaponClass == WEAPONTYPE_SHOTGUN) ||
+ (ctrl->AllowMachineGuns() && weaponClass == WEAPONTYPE_MACHINEGUN) ||
+ (ctrl->AllowRifles() && weaponClass == WEAPONTYPE_RIFLE) ||
+ (ctrl->AllowShotguns() && weaponClass == WEAPONTYPE_SHOTGUN) ||
+ (ctrl->AllowSnipers() && weaponClass == WEAPONTYPE_SNIPER_RIFLE) ||
+ (ctrl->AllowSubMachineGuns() && weaponClass == WEAPONTYPE_SUBMACHINEGUN))
+ {
+ if (DoEquip( primary ))
+ return;
+ }
+ }
+
+ if (ctrl->AllowPistols())
+ {
+ if (DoEquip( static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) ) ))
+ return;
+ }
+
+ // always have a knife
+ EquipKnife();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Equip our pistol
+ */
+void CCSBot::EquipPistol( void )
+{
+ // throttle how often equipping is allowed
+ if (m_equipTimer.GetElapsedTime() < minEquipInterval)
+ return;
+
+ if (TheCSBots()->AllowPistols() && !IsUsingPistol())
+ {
+ CWeaponCSBase *pistol = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_PISTOL ) );
+ DoEquip( pistol );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Equip the knife
+ */
+void CCSBot::EquipKnife( void )
+{
+ if (!IsUsingKnife())
+ {
+ SelectItem( "weapon_knife" );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we have a grenade in our inventory
+ */
+bool CCSBot::HasGrenade( void ) const
+{
+ CWeaponCSBase *grenade = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_GRENADES ) );
+ return (grenade) ? true : false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Equip a grenade, return false if we cant
+ */
+bool CCSBot::EquipGrenade( bool noSmoke )
+{
+ // snipers don't use grenades
+ if (IsSniper())
+ return false;
+
+ if (IsUsingGrenade())
+ return true;
+
+ if (HasGrenade())
+ {
+ CWeaponCSBase *grenade = static_cast<CWeaponCSBase *>( Weapon_GetSlot( WEAPON_SLOT_GRENADES ) );
+
+ if (noSmoke && grenade->IsA( WEAPON_SMOKEGRENADE ))
+ return false;
+
+ SelectItem( grenade->GetClassname() );
+
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if we have knife equipped
+ */
+bool CCSBot::IsUsingKnife( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon && weapon->IsA( WEAPON_KNIFE ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if we have pistol equipped
+ */
+bool CCSBot::IsUsingPistol( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (weapon && weapon->IsPistol())
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if we have a grenade equipped
+ */
+bool CCSBot::IsUsingGrenade( void ) const
+{
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+
+ if (!weapon)
+ return false;
+
+ if (weapon->IsA( WEAPON_FLASHBANG ) ||
+ weapon->IsA( WEAPON_SMOKEGRENADE ) ||
+ weapon->IsA( WEAPON_HEGRENADE ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Begin the process of throwing the grenade
+ */
+void CCSBot::ThrowGrenade( const Vector &target )
+{
+ if (IsUsingGrenade() && m_grenadeTossState == NOT_THROWING && !IsOnLadder())
+ {
+ m_grenadeTossState = START_THROW;
+ m_tossGrenadeTimer.Start( 2.0f );
+
+ const float angleTolerance = 3.0f;
+ SetLookAt( "GrenadeThrow", target, PRIORITY_UNINTERRUPTABLE, 4.0f, false, angleTolerance );
+
+ Wait( RandomFloat( 2.0f, 4.0f ) );
+
+ if (cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
+ {
+ NDebugOverlay::Cross3D( target, 25.0f, 255, 125, 0, true, 3.0f );
+ }
+
+ PrintIfWatched( "%3.2f: Grenade: START_THROW\n", gpGlobals->curtime );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Returns true if our weapon can attack
+ */
+bool CCSBot::CanActiveWeaponFire( void ) const
+{
+ return ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Find spot to throw grenade ahead of us and "around the corner" along our path
+ */
+bool CCSBot::FindGrenadeTossPathTarget( Vector *pos )
+{
+ if (!HasPath())
+ return false;
+
+ // find farthest point we can see on the path
+ int i;
+ for( i=m_pathIndex; i<m_pathLength; ++i )
+ {
+ if (!FVisible( m_path[i].pos + Vector( 0, 0, HalfHumanHeight ) ))
+ break;
+ }
+
+ if (i == m_pathIndex)
+ return false;
+
+ // find exact spot where we lose sight
+ Vector dir = m_path[i].pos - m_path[i-1].pos;
+ float length = dir.NormalizeInPlace();
+
+ const float inc = 25.0f;
+ Vector p;
+ Vector visibleSpot = m_path[i-1].pos;
+ for( float t = 0.0f; t<length; t += inc )
+ {
+ p = m_path[i-1].pos + t * dir;
+ p.z += HalfHumanHeight;
+ if (!FVisible( p ))
+ break;
+
+ visibleSpot = p;
+ }
+
+ // massage the location a bit
+ visibleSpot.z += 10.0f;
+
+ const float bufferRange = 50.0f;
+
+ trace_t result;
+ Vector check;
+
+ // check +X
+ check = visibleSpot + Vector( 999.9f, 0, 0 );
+ UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction < 1.0f)
+ {
+ float range = result.endpos.x - visibleSpot.x;
+ if (range < bufferRange)
+ {
+ visibleSpot.x = result.endpos.x - bufferRange;
+ }
+ }
+
+ // check -X
+ check = visibleSpot + Vector( -999.9f, 0, 0 );
+ UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction < 1.0f)
+ {
+ float range = visibleSpot.x - result.endpos.x;
+ if (range < bufferRange)
+ {
+ visibleSpot.x = result.endpos.x + bufferRange;
+ }
+ }
+
+ // check +Y
+ check = visibleSpot + Vector( 0, 999.9f, 0 );
+ UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction < 1.0f)
+ {
+ float range = result.endpos.y - visibleSpot.y;
+ if (range < bufferRange)
+ {
+ visibleSpot.y = result.endpos.y - bufferRange;
+ }
+ }
+
+ // check -Y
+ check = visibleSpot + Vector( 0, -999.9f, 0 );
+ UTIL_TraceLine( visibleSpot, check, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ if (result.fraction < 1.0f)
+ {
+ float range = visibleSpot.y - result.endpos.y;
+ if (range < bufferRange)
+ {
+ visibleSpot.y = result.endpos.y + bufferRange;
+ }
+ }
+
+ *pos = visibleSpot;
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Look for grenade throw targets and throw the grenade
+ */
+void CCSBot::LookForGrenadeTargets( void )
+{
+ if (!IsUsingGrenade() || IsThrowingGrenade())
+ {
+ return;
+ }
+
+ const CNavArea *tossArea = GetInitialEncounterArea();
+ if (tossArea == NULL)
+ {
+ return;
+ }
+
+ int enemyTeam = OtherTeam( GetTeamNumber() );
+
+ // check if we should put our grenade away
+ if (tossArea->GetEarliestOccupyTime( enemyTeam ) > gpGlobals->curtime)
+ {
+ EquipBestWeapon( MUST_EQUIP );
+ return;
+ }
+
+ // throw grenades at initial encounter area
+ Vector tossTarget = Vector( 0, 0, 0 );
+ if (!tossArea->IsVisible( EyePosition(), &tossTarget ))
+ {
+ return;
+ }
+
+
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+ if (weapon && weapon->IsA( WEAPON_SMOKEGRENADE ))
+ {
+ // don't worry so much about smokes
+ ThrowGrenade( tossTarget );
+ PrintIfWatched( "Throwing smoke grenade!" );
+ SetInitialEncounterArea( NULL );
+ return;
+ }
+ else // explosive and flashbang grenades
+ {
+ // initial encounter area is visible, wait to throw until timing is right
+
+ const float leadTime = 1.5f;
+ float enemyTime = tossArea->GetEarliestOccupyTime( enemyTeam );
+ if (enemyTime - TheCSBots()->GetElapsedRoundTime() > leadTime)
+ {
+ // don't throw yet
+ return;
+ }
+
+
+ Vector to = tossTarget - EyePosition();
+ float range = to.Length();
+
+ const float slope = 0.2f; // 0.25f;
+ float tossHeight = slope * range;
+
+ trace_t result;
+ CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
+
+ const float heightInc = tossHeight / 10.0f;
+ Vector target;
+ float safeSpace = tossHeight / 2.0f;
+
+ // Build a box to sweep along the ray when looking for obstacles
+ const Vector& eyePosition = EyePosition();
+ Vector mins = VEC_HULL_MIN;
+ Vector maxs = VEC_HULL_MAX;
+ mins.z = 0;
+ maxs.z = heightInc;
+
+
+ // find low and high bounds of toss window
+ float low = 0.0f;
+ float high = tossHeight + safeSpace;
+ bool gotLow = false;
+ float lastH = 0.0f;
+ for( float h = 0.0f; h < 3.0f * tossHeight; h += heightInc )
+ {
+ target = tossTarget + Vector( 0, 0, h );
+
+ // make sure toss line is clear
+
+ QAngle angles( 0, 0, 0 );
+ Ray_t ray;
+ ray.Init( eyePosition, target, mins, maxs );
+ enginetrace->TraceRay( ray, MASK_VISIBLE_AND_NPCS | CONTENTS_GRATE, &traceFilter, &result );
+ if (result.fraction == 1.0f)
+ {
+ //NDebugOverlay::SweptBox( eyePosition, target, mins, maxs, angles, 0, 0, 255, 40, 10.0f );
+
+ // line is clear
+ if (!gotLow)
+ {
+ low = h;
+ gotLow = true;
+ }
+ }
+ else
+ {
+ //NDebugOverlay::SweptBox( eyePosition, target, mins, maxs, angles, 255, 0, 0, 5, 10.0f );
+
+ // line is blocked
+ if (gotLow)
+ {
+ high = lastH;
+ break;
+ }
+ }
+
+ lastH = h;
+ }
+
+ if (gotLow)
+ {
+ // throw grenade into toss window
+ if (tossHeight < low)
+ {
+ if (low + safeSpace > high)
+ {
+ // narrow window
+ tossHeight = (high + low)/2.0f;
+ }
+ else
+ {
+ tossHeight = low + safeSpace;
+ }
+ }
+ else if (tossHeight > high - safeSpace)
+ {
+ if (high - safeSpace < low)
+ {
+ // narrow window
+ tossHeight = (high + low)/2.0f;
+ }
+ else
+ {
+ tossHeight = high - safeSpace;
+ }
+ }
+
+ ThrowGrenade( tossTarget + Vector( 0, 0, tossHeight ) );
+ SetInitialEncounterArea( NULL );
+ return;
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+class FOVClearOfFriends
+{
+public:
+ FOVClearOfFriends( CCSBot *me )
+ {
+ m_me = me;
+ }
+
+ bool operator() ( CBasePlayer *player )
+ {
+ if (player == m_me || !player->IsAlive())
+ return true;
+
+ if (m_me->InSameTeam( player ))
+ {
+ Vector to = player->EyePosition() - m_me->EyePosition();
+ to.NormalizeInPlace();
+
+ Vector forward;
+ m_me->EyeVectors( &forward );
+
+ if (DotProduct( to, forward ) > 0.95f)
+ {
+ if (m_me->IsVisible( (CCSPlayer *)player ))
+ {
+ // we see a friend in our FOV
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ CCSBot *m_me;
+};
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Process the grenade throw state machine
+ */
+void CCSBot::UpdateGrenadeThrow( void )
+{
+ switch( m_grenadeTossState )
+ {
+ case START_THROW:
+ {
+ if (m_tossGrenadeTimer.IsElapsed())
+ {
+ // something prevented the throw - give up
+ EquipBestWeapon( MUST_EQUIP );
+ ClearLookAt();
+ m_grenadeTossState = NOT_THROWING;
+ PrintIfWatched( "%3.2f: Grenade: THROW FAILED\n", gpGlobals->curtime );
+ return;
+ }
+
+ if (m_lookAtSpotState == LOOK_AT_SPOT)
+ {
+ // don't throw if there are friends ahead of us
+ FOVClearOfFriends fovClear( this );
+ if (ForEachPlayer( fovClear ))
+ {
+ m_grenadeTossState = FINISH_THROW;
+ m_tossGrenadeTimer.Start( 1.0f );
+ PrintIfWatched( "%3.2f: Grenade: FINISH_THROW\n", gpGlobals->curtime );
+ }
+ else
+ {
+ PrintIfWatched( "%3.2f: Grenade: Friend is in the way...\n", gpGlobals->curtime );
+ }
+ }
+
+ // hold in the trigger and be ready to throw
+ PrimaryAttack();
+
+ break;
+ }
+
+ case FINISH_THROW:
+ {
+ // throw the grenade and hold our aiming line for a moment
+ if (m_tossGrenadeTimer.IsElapsed())
+ {
+ ClearLookAt();
+
+ m_grenadeTossState = NOT_THROWING;
+ PrintIfWatched( "%3.2f: Grenade: THROW COMPLETE\n", gpGlobals->curtime );
+ }
+ break;
+ }
+
+ default:
+ {
+ if (IsUsingGrenade())
+ {
+ // pull the pin
+ PrimaryAttack();
+ }
+ break;
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+class GrenadeResponse
+{
+public:
+ GrenadeResponse( CCSBot *me )
+ {
+ m_me = me;
+ }
+
+ bool operator() ( ActiveGrenade *ag ) const
+ {
+ const float retreatRange = 300.0f;
+ const float hideTime = 1.0f;
+
+ // do we see this grenade
+ if (m_me->IsVisible( ag->GetPosition(), CHECK_FOV, (CBaseEntity *)ag->GetEntity() ))
+ {
+ // we see it
+ if (ag->IsSmoke())
+ {
+ // ignore smokes
+ return true;
+ }
+
+ Vector velDir = ag->GetEntity()->GetAbsVelocity();
+ float grenadeSpeed = velDir.NormalizeInPlace();
+ const float atRestSpeed = 50.0f;
+
+ const float aboutToBlow = 0.5f;
+ if (ag->IsFlashbang() && ag->GetEntity()->m_flDetonateTime - gpGlobals->curtime < aboutToBlow)
+ {
+ // turn away from flashbangs about to explode
+ QAngle eyeAngles = m_me->EyeAngles();
+
+ float yaw = RandomFloat( 100.0f, 135.0f );
+ eyeAngles.y += (RandomFloat( -1.0f, 1.0f ) < 0.0f) ? (-yaw) : yaw;
+
+ Vector forward;
+ AngleVectors( eyeAngles, &forward );
+
+ Vector away = m_me->EyePosition() - 1000.0f * forward;
+ const float duration = 2.0f;
+
+ m_me->ClearLookAt();
+ m_me->SetLookAt( "Avoid Flashbang", away, PRIORITY_UNINTERRUPTABLE, duration );
+
+ m_me->StopAiming();
+
+ return false;
+ }
+
+
+ // flee from grenades if close by or thrown towards us
+ const float throwDangerRange = 750.0f;
+ const float nearDangerRange = 300.0f;
+ Vector to = ag->GetPosition() - m_me->GetAbsOrigin();
+ float range = to.NormalizeInPlace();
+ if (range > throwDangerRange)
+ {
+ return true;
+ }
+
+ if (grenadeSpeed > atRestSpeed)
+ {
+ // grenade is moving
+ if (DotProduct( to, velDir ) >= -0.5f)
+ {
+ // going away from us
+ return true;
+ }
+
+ m_me->PrintIfWatched( "Retreating from a grenade thrown towards me!\n" );
+ }
+ else if (range < nearDangerRange)
+ {
+ // grenade has come to rest near us
+ m_me->PrintIfWatched( "Retreating from a grenade that landed near me!\n" );
+ }
+
+ // retreat!
+ m_me->TryToRetreat( retreatRange, hideTime );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ CCSBot *m_me;
+};
+
+/**
+ * React to enemy grenades we see
+ */
+void CCSBot::AvoidEnemyGrenades( void )
+{
+ // low skill bots dont avoid grenades
+ if (GetProfile()->GetSkill() < 0.5)
+ {
+ return;
+ }
+
+ if (IsAvoidingGrenade())
+ {
+ // already avoiding one
+ return;
+ }
+
+ // low skill bots don't avoid grenades
+ if (GetProfile()->GetSkill() < 0.6f)
+ {
+ return;
+ }
+
+ GrenadeResponse respond( this );
+ if (TheBots->ForEachGrenade( respond ) == false)
+ {
+ const float avoidTime = 4.0f;
+ m_isAvoidingGrenade.Start( avoidTime );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Reload our weapon if we must
+ */
+void CCSBot::ReloadCheck( void )
+{
+ const float safeReloadWaitTime = 3.0f;
+ const float reloadAmmoRatio = 0.6f;
+
+ // don't bother to reload if there are no enemies left
+ if (GetEnemiesRemaining() == 0)
+ return;
+
+ if (IsDefusingBomb() || IsReloading())
+ return;
+
+ if (IsActiveWeaponClipEmpty())
+ {
+ // high-skill players switch to pistol instead of reloading during combat
+ if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
+ {
+ if (!GetActiveCSWeapon()->IsPistol() && !IsPistolEmpty())
+ {
+ // switch to pistol instead of reloading
+ EquipPistol();
+ return;
+ }
+ }
+ }
+ else if (GetTimeSinceLastSawEnemy() > safeReloadWaitTime && GetActiveWeaponAmmoRatio() <= reloadAmmoRatio)
+ {
+ // high-skill players use all their ammo and switch to pistol instead of reloading during combat
+ if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
+ return;
+ }
+ else
+ {
+ // do not need to reload
+ return;
+ }
+
+ // don't reload the AWP until it is totally out of ammo
+ if (IsUsing( WEAPON_AWP ) && !IsActiveWeaponClipEmpty())
+ return;
+
+ Reload();
+
+ // move to cover to reload if there are enemies nearby
+ if (GetNearbyEnemyCount())
+ {
+ // avoid enemies while reloading (above 0.75 skill always hide to reload)
+ const float hideChance = 25.0f + 100.0f * GetProfile()->GetSkill();
+
+ if (!IsHiding() && RandomFloat( 0, 100 ) < hideChance)
+ {
+ const float safeTime = 5.0f;
+ if (GetTimeSinceLastSawEnemy() < safeTime)
+ {
+ PrintIfWatched( "Retreating to a safe spot to reload!\n" );
+ const Vector *spot = FindNearbyRetreatSpot( this, 1000.0f );
+ if (spot)
+ {
+ // ignore enemies for a second to give us time to hide
+ // reaching our hiding spot clears our disposition
+ IgnoreEnemies( 10.0f );
+
+ Run();
+ StandUp();
+ Hide( *spot, 0.0f );
+ }
+ }
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Silence/unsilence our weapon if we must
+ */
+void CCSBot::SilencerCheck( void )
+{
+ const float safeSilencerWaitTime = 3.5f; // longer than reload check because reloading should take precedence
+
+ if (IsDefusingBomb() || IsReloading() || IsAttacking())
+ return;
+
+ // M4A1 and USP are the only weapons with removable silencers
+ if (!DoesActiveWeaponHaveSilencer())
+ return;
+
+ if (GetTimeSinceLastSawEnemy() < safeSilencerWaitTime)
+ return;
+
+ // don't touch the silencer if there are enemies nearby
+ if (GetNearbyEnemyCount() == 0)
+ {
+ CWeaponCSBase *weapon = GetActiveCSWeapon();
+ if (weapon == NULL)
+ return;
+
+ bool isSilencerOn = weapon->IsSilenced();
+
+ if ( weapon->m_flNextSecondaryAttack >= gpGlobals->curtime )
+ return;
+
+ // equip silencer if we want to and we don't have a shield.
+ if ( isSilencerOn != (GetProfile()->PrefersSilencer() || GetProfile()->GetSkill() > 0.7f) && !HasShield() )
+ {
+ PrintIfWatched( "%s silencer!\n", (isSilencerOn) ? "Unequipping" : "Equipping" );
+ weapon->SecondaryAttack();
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when in contact with a CBaseCombatWeapon
+ */
+bool CCSBot::BumpWeapon( CBaseCombatWeapon *pWeapon )
+{
+ CWeaponCSBase *droppedGun = dynamic_cast< CWeaponCSBase* >( pWeapon );
+
+ // right now we only care about primary weapons on the ground
+ if ( droppedGun && droppedGun->GetSlot() == WEAPON_SLOT_RIFLE )
+ {
+ CWeaponCSBase *myGun = dynamic_cast< CWeaponCSBase* >( Weapon_GetSlot( WEAPON_SLOT_RIFLE ) );
+
+ // if the gun on the ground is the same one we have, dont bother
+ if ( myGun && droppedGun->GetWeaponID() != myGun->GetWeaponID() )
+ {
+ // if we don't have a weapon preference, give up
+ if ( GetProfile()->HasPrimaryPreference() )
+ {
+ // don't change weapons if we've seen enemies recently
+ const float safeTime = 2.5f;
+ if ( GetTimeSinceLastSawEnemy() >= safeTime )
+ {
+ // we have a primary weapon - drop it if the one on the ground is better
+ for( int i = 0; i < GetProfile()->GetWeaponPreferenceCount(); ++i )
+ {
+ CSWeaponID prefID = GetProfile()->GetWeaponPreference( i );
+
+ if (!IsPrimaryWeapon( prefID ))
+ continue;
+
+ // if the gun we are using is more desirable, give up
+ if ( prefID == myGun->GetWeaponID() )
+ break;
+
+ if ( prefID == droppedGun->GetWeaponID() )
+ {
+ // the gun on the ground is better than the one we have - drop our gun
+ DropRifle();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return BaseClass::BumpWeapon( droppedGun );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if a friend is in our weapon's way
+ * @todo Check more rays for safety.
+ */
+bool CCSBot::IsFriendInLineOfFire( void )
+{
+ // compute the unit vector along our view
+ Vector aimDir = GetViewVector();
+
+ // trace the bullet's path
+ trace_t result;
+ UTIL_TraceLine( EyePosition(), EyePosition() + 10000.0f * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ if (result.DidHitNonWorldEntity())
+ {
+ CBaseEntity *victim = result.m_pEnt;
+
+ if (victim && victim->IsPlayer() && victim->IsAlive())
+ {
+ CBasePlayer *player = static_cast<CBasePlayer *>( victim );
+
+ if (player->InSameTeam( this ))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return line-of-sight distance to obstacle along weapon fire ray
+ * @todo Re-use this computation with IsFriendInLineOfFire()
+ */
+float CCSBot::ComputeWeaponSightRange( void )
+{
+ // compute the unit vector along our view
+ Vector aimDir = GetViewVector();
+
+ // trace the bullet's path
+ trace_t result;
+ UTIL_TraceLine( EyePosition(), EyePosition() + 10000.0f * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+
+ return (EyePosition() - result.endpos).Length();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if the given player just fired their weapon
+ */
+bool CCSBot::DidPlayerJustFireWeapon( const CCSPlayer *player ) const
+{
+ // if this player has just fired his weapon, we notice him
+ CWeaponCSBase *weapon = player->GetActiveCSWeapon();
+ return (weapon && !weapon->IsSilenced() && weapon->m_flNextPrimaryAttack > gpGlobals->curtime);
+}
+