You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1457 lines
37 KiB
1457 lines
37 KiB
//========= Copyright © 1996-2005, 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"
|
|
#include "../../../shared/cstrike15/cs_gamerules.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_targetSpot - 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
|
|
*/
|
|
void CCSBot::PickNewAimSpot()
|
|
{
|
|
// aim at enemy, if he's still alive
|
|
if (m_enemy != NULL && m_enemy->IsAlive())
|
|
{
|
|
Vector enemyOrigin = GetCentroid( m_enemy );
|
|
|
|
if ( IsEnemyVisible() )
|
|
{
|
|
//
|
|
// Enemy is visible - determine which part of him to shoot at
|
|
//
|
|
const float sharpshooter = 0.8f;
|
|
VisiblePartType aimAtPart;
|
|
|
|
if (IsUsingMachinegun())
|
|
{
|
|
// spray the big machinegun at the enemy's gut
|
|
aimAtPart = GUT;
|
|
}
|
|
else if (IsUsing( WEAPON_AWP ) || IsUsingShotgun())
|
|
{
|
|
// these weapons are best aimed at the chest
|
|
aimAtPart = GUT;
|
|
}
|
|
else if (GetProfile()->GetSkill() > 0.5f && IsActiveWeaponRecoilHigh() )
|
|
{
|
|
// sprayin' and prayin' - aim at the gut since we're not going to be accurate
|
|
aimAtPart = GUT;
|
|
}
|
|
else if (GetProfile()->GetSkill() < sharpshooter)
|
|
{
|
|
// low skill bots don't go for headshots
|
|
aimAtPart = GUT;
|
|
}
|
|
else
|
|
{
|
|
// high skill - aim for the head
|
|
aimAtPart = HEAD;
|
|
}
|
|
|
|
if (IsEnemyPartVisible( aimAtPart ))
|
|
{
|
|
m_targetSpot = GetPartPosition( GetBotEnemy(), aimAtPart );
|
|
}
|
|
else
|
|
{
|
|
// desired part is blocked - aim at whatever part is visible
|
|
if (IsEnemyPartVisible( GUT ))
|
|
{
|
|
m_targetSpot = GetPartPosition( GetBotEnemy(), GUT );
|
|
}
|
|
else if (IsEnemyPartVisible( HEAD ))
|
|
{
|
|
m_targetSpot = GetPartPosition( GetBotEnemy(), HEAD );
|
|
}
|
|
else if (IsEnemyPartVisible( LEFT_SIDE ))
|
|
{
|
|
m_targetSpot = GetPartPosition( GetBotEnemy(), LEFT_SIDE );
|
|
}
|
|
else if (IsEnemyPartVisible( RIGHT_SIDE ))
|
|
{
|
|
m_targetSpot = GetPartPosition( GetBotEnemy(), RIGHT_SIDE );
|
|
}
|
|
else // FEET
|
|
{
|
|
m_targetSpot = GetPartPosition( GetBotEnemy(), FEET );
|
|
}
|
|
}
|
|
|
|
// temp test
|
|
m_targetSpot = GetPartPosition( GetBotEnemy(), GUT );
|
|
|
|
m_targetSpotVelocity = m_enemy->GetAbsVelocity();
|
|
m_targetSpotTime = gpGlobals->curtime;
|
|
}
|
|
else
|
|
{
|
|
// aim where we last saw enemy - but bend the ray so we don't point directly into walls
|
|
// if we put this back, make sure you only bend the ray ONCE and keep the bent spot - don't continually recompute
|
|
//BendLineOfSight( m_eyePosition, m_lastEnemyPosition, &m_aimSpot );
|
|
m_targetSpot = m_lastEnemyPosition;
|
|
m_targetSpotVelocity.Zero();
|
|
}
|
|
|
|
// decay old aim focus angle
|
|
m_aimFocus *= expf(logf(GetProfile()->GetAimFocusDecay()) * m_aimFocusInterval);
|
|
|
|
// calculate current aim focus maxima
|
|
Vector toTarget = m_targetSpot - EyePositionConst();
|
|
QAngle aimGoal;
|
|
VectorAngles( toTarget, aimGoal );
|
|
QAngle viewAngles = EyeAngles();
|
|
float deltaYaw = AngleDistance(viewAngles.y, aimGoal.y);
|
|
float deltaPitch = AngleDistance(viewAngles.x, aimGoal.x);
|
|
float fAngleOffset = sqrtf(deltaYaw * deltaYaw + deltaPitch * deltaPitch);
|
|
float fNewMaxFocus = MIN(60.0f, fAngleOffset) * GetProfile()->GetAimFocusOffsetScale();
|
|
m_aimFocus = MAX(m_aimFocus, fNewMaxFocus);
|
|
|
|
float fTheta = RandomFloat(0.0f, 2.0f * M_PI);
|
|
float fRadius = RandomFloat(0.0f, m_aimFocus);
|
|
|
|
m_aimError[YAW] = fRadius * cosf(fTheta);
|
|
m_aimError[PITCH] = fRadius * sinf(fTheta);
|
|
}
|
|
|
|
// define time when aim offset will automatically be updated
|
|
m_aimFocusInterval = GetProfile()->GetAimFocusInterval();
|
|
m_aimFocusInterval *= RandomFloat(0.8f, 1.2f); // add some randomness
|
|
m_aimFocusNextUpdate = gpGlobals->curtime + m_aimFocusInterval;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Wiggle aim error based on GetProfile()->GetSkill()
|
|
*/
|
|
void CCSBot::UpdateAimPrediction( void )
|
|
{
|
|
float fTimeSinceAimSpot = gpGlobals->curtime - m_targetSpotTime;
|
|
m_targetSpotPredicted = m_targetSpot + fTimeSinceAimSpot * m_targetSpotVelocity;
|
|
|
|
Vector toTarget = m_targetSpotPredicted - EyePositionConst();
|
|
|
|
VectorAngles( toTarget, m_aimGoal );
|
|
if ( m_aimGoal[PITCH] > 180.0f )
|
|
m_aimGoal[PITCH] -= 360.f;
|
|
|
|
m_aimGoal += m_aimError;
|
|
|
|
// adjust aim angle for recoil
|
|
float fSkill = GetProfile()->GetSkill(); // 0.0 .. 1.0
|
|
float fPunchAngleCorrectionFactor = 1.0f + (1.f - fSkill) * .8f * SlowNoise( 6.f );
|
|
const QAngle &punchAngles = GetAimPunchAngle();
|
|
m_aimGoal -= punchAngles * fPunchAngleCorrectionFactor;
|
|
m_aimGoal[PITCH] = clamp(m_aimGoal[PITCH], -89.0f, +89.0f);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* 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::DoesActiveWeaponHaveRemoveableSilencer( void ) const
|
|
{
|
|
CWeaponCSBase *weapon = GetActiveCSWeapon();
|
|
|
|
if (weapon == NULL)
|
|
return false;
|
|
|
|
if ( 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->GetWeaponType();
|
|
|
|
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 ( CSGameRules()->IsPlayingOffline() && CSGameRules()->GetCustomBotDifficulty() == CUSTOM_BOT_DIFFICULTY_DUMB )
|
|
{
|
|
// For Offline games: Bots in dumb mode should not toss grenades
|
|
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 ) || weapon->IsA( WEAPON_KNIFE_GG )))
|
|
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 ) ||
|
|
weapon->IsA( WEAPON_MOLOTOV ) ||
|
|
weapon->IsA( WEAPON_INCGRENADE ) ||
|
|
weapon->IsA( WEAPON_DECOY ) ||
|
|
weapon->IsA( WEAPON_TAGRENADE ) )
|
|
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 ); // wait up to two seconds for teammates to clear out of the way
|
|
|
|
Vector bend = target;
|
|
BendLineOfSight( target, GetLastKnownEnemyPosition(), &bend );
|
|
|
|
const float angleTolerance = 3.0f;
|
|
SetLookAt( "GrenadeThrowBend", bend, PRIORITY_UNINTERRUPTABLE, 4.0f, false, angleTolerance );
|
|
|
|
if ( !CSGameRules()->IsPlayingCooperativeGametype() )
|
|
Wait( RandomFloat( 2.0f, 4.0f ) );
|
|
|
|
if (cv_bot_debug.GetBool() && IsLocalPlayerWatchingMe())
|
|
{
|
|
NDebugOverlay::Cross3D( bend, 25.0f, 125, 255, 125, true, 300.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;
|
|
float flGrenadeTossDelay = CSGameRules()->IsPlayingCooperativeGametype() ? 0.01f : 1.0f;
|
|
m_tossGrenadeTimer.Start( flGrenadeTossDelay );
|
|
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;
|
|
|
|
if (!DoesActiveWeaponHaveRemoveableSilencer())
|
|
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->GetCSWeaponID() != myGun->GetCSWeaponID() )
|
|
{
|
|
// 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->GetCSWeaponID() )
|
|
break;
|
|
|
|
if ( prefID == droppedGun->GetCSWeaponID() )
|
|
{
|
|
// the gun on the ground is better than the one we have - drop our gun
|
|
DropWeaponSlot( WEAPON_SLOT_RIFLE );
|
|
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 ( !IsOtherEnemy( player->entindex() ) )
|
|
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);
|
|
}
|
|
|