|
|
|
#include "bot_common.h"
|
|
|
|
|
|
|
|
// Fire our active weapon towards our current enemy
|
|
|
|
// NOTE: Aiming our weapon is handled in RunBotUpkeep()
|
|
|
|
|
|
|
|
void CCSBot::FireWeaponAtEnemy()
|
|
|
|
{
|
|
|
|
CBasePlayer *enemy = GetEnemy();
|
|
|
|
if (enemy == NULL)
|
|
|
|
{
|
|
|
|
StopRapidFire();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (gpGlobals->time > m_fireWeaponTimestamp && GetTimeSinceAcquiredCurrentEnemy() >= GetProfile()->GetAttackDelay() && GetTimeSinceAcquiredCurrentEnemy() >= GetSurpriseDelay())
|
|
|
|
{
|
|
|
|
ClearSurpriseDelay();
|
|
|
|
|
|
|
|
if (!(IsRecognizedEnemyProtectedByShield() && IsPlayerFacingMe(enemy)) // dont shoot at enemies behind shields
|
|
|
|
&& !IsActiveWeaponReloading()
|
|
|
|
&& !IsActiveWeaponClipEmpty()
|
|
|
|
&& IsEnemyVisible())
|
|
|
|
{
|
|
|
|
// we have a clear shot - pull trigger if we are aiming at enemy
|
|
|
|
Vector2D toAimSpot = (m_aimSpot - pev->origin).Make2D();
|
|
|
|
float rangeToEnemy = toAimSpot.NormalizeInPlace();
|
|
|
|
|
|
|
|
const float halfPI = (M_PI / 180.0f);
|
|
|
|
float yaw = pev->v_angle[ YAW ] * halfPI;
|
|
|
|
|
|
|
|
Vector2D dir(cos(yaw), sin(yaw));
|
|
|
|
float onTarget = DotProduct(toAimSpot, dir);
|
|
|
|
|
|
|
|
// aim more precisely with a sniper rifle
|
|
|
|
// because rifles' bullets spray, dont 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 = 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.0f
|
|
|
|
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 (RANDOM_FLOAT(0, 100) < knifeStabChance)
|
|
|
|
SecondaryAttack();
|
|
|
|
else
|
|
|
|
PrimaryAttack();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
PrimaryAttack();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsUsingPistol())
|
|
|
|
{
|
|
|
|
// high-skill bots fire their pistols quickly at close range
|
|
|
|
const float closePistolRange = 999999.9f;
|
|
|
|
if (GetProfile()->GetSkill() > 0.75f && rangeToEnemy < closePistolRange)
|
|
|
|
{
|
|
|
|
StartRapidFire();
|
|
|
|
|
|
|
|
// fire as fast as possible
|
|
|
|
m_fireWeaponTimestamp = 0.0f;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// fire somewhat quickly
|
|
|
|
m_fireWeaponTimestamp = RANDOM_FLOAT(0.15f, 0.4f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// not using a pistol
|
|
|
|
else
|
|
|
|
{
|
|
|
|
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 = RANDOM_FLOAT(0.3f, 0.7f);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// fire short bursts for accuracy
|
|
|
|
m_fireWeaponTimestamp = RANDOM_FLOAT(0.15f, 0.5f); // 0.15f, 0.25f
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// subtract system latency
|
|
|
|
m_fireWeaponTimestamp -= g_flBotFullThinkInterval;
|
|
|
|
m_fireWeaponTimestamp += gpGlobals->time;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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->time;
|
|
|
|
}
|
|
|
|
|
|
|
|
// focusTime is the time it takes for a bot to "focus in" for very good aim, from 2 to 5 seconds
|
|
|
|
const float focusTime = Q_max(5.0f * (1.0f - accuracy), 2.0f);
|
|
|
|
|
|
|
|
float focusInterval = gpGlobals->time - m_aimSpreadTimestamp;
|
|
|
|
float focusAccuracy = focusInterval / focusTime;
|
|
|
|
|
|
|
|
// limit how much "focus" will help
|
|
|
|
const float maxFocusAccuracy = 0.75f;
|
|
|
|
|
|
|
|
if (focusAccuracy > maxFocusAccuracy)
|
|
|
|
focusAccuracy = maxFocusAccuracy;
|
|
|
|
|
|
|
|
accuracy = Q_max(accuracy, focusAccuracy);
|
|
|
|
}
|
|
|
|
|
|
|
|
PrintIfWatched("Accuracy = %4.3f\n", accuracy);
|
|
|
|
|
|
|
|
float range = (m_lastEnemyPosition - pev->origin).Length();
|
|
|
|
const float maxOffset = range * 0.1;
|
|
|
|
float error = maxOffset * (1 - accuracy);
|
|
|
|
|
|
|
|
m_aimOffsetGoal[0] = RANDOM_FLOAT(-error, error);
|
|
|
|
m_aimOffsetGoal[1] = RANDOM_FLOAT(-error, error);
|
|
|
|
m_aimOffsetGoal[2] = RANDOM_FLOAT(-error, error);
|
|
|
|
|
|
|
|
// define time when aim offset will automatically be updated
|
|
|
|
m_aimOffsetTimestamp = gpGlobals->time + RANDOM_FLOAT(0.25, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wiggle aim error based on GetProfile()->GetSkill()
|
|
|
|
|
|
|
|
void CCSBot::UpdateAimOffset()
|
|
|
|
{
|
|
|
|
if (gpGlobals->time >= 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())
|
|
|
|
{
|
|
|
|
// NOTE: This must be less than sniperMinRange in AttackState
|
|
|
|
const float sniperZoomRange = 300.0f; //150.0f
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
return adjustZoom;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true if the given weapon is a sniper rifle
|
|
|
|
|
|
|
|
bool isSniperRifle(CBasePlayerItem *item)
|
|
|
|
{
|
|
|
|
switch (item->m_iId)
|
|
|
|
{
|
|
|
|
case WEAPON_CROSSBOW:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CCSBot::IsUsingAWP() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = GetActiveWeapon();
|
|
|
|
|
|
|
|
if (weapon != NULL && weapon->m_iId == WEAPON_CROSSBOW)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if we are using a weapon with a removable silencer
|
|
|
|
|
|
|
|
bool CCSBot::DoesActiveWeaponHaveSilencer() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = GetActiveWeapon();
|
|
|
|
|
|
|
|
if (weapon == NULL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
//if (weapon->m_iId == WEAPON_M4A1 || weapon->m_iId == WEAPON_USP)
|
|
|
|
// return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true if we are using a sniper rifle
|
|
|
|
|
|
|
|
bool CCSBot::IsUsingSniperRifle() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = GetActiveWeapon();
|
|
|
|
|
|
|
|
if (weapon != NULL && isSniperRifle(weapon))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true if we have a sniper rifle in our inventory
|
|
|
|
|
|
|
|
bool CCSBot::IsSniper() const
|
|
|
|
{
|
|
|
|
for (int i = 0; i < MAX_ITEM_TYPES; ++i)
|
|
|
|
{
|
|
|
|
CBasePlayerItem *item = m_rgpPlayerItems[i];
|
|
|
|
|
|
|
|
while (item != NULL)
|
|
|
|
{
|
|
|
|
if (isSniperRifle(item))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
item = item->m_pNext;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true if we are actively sniping (moving to sniper spot or settled in)
|
|
|
|
|
|
|
|
bool CCSBot::IsSniping() const
|
|
|
|
{
|
|
|
|
if (GetTask() == MOVE_TO_SNIPER_SPOT || GetTask() == SNIPING)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true if we are using a shotgun
|
|
|
|
|
|
|
|
bool CCSBot::IsUsingShotgun() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = GetActiveWeapon();
|
|
|
|
|
|
|
|
if (weapon == NULL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (weapon->m_iId == WEAPON_SHOTGUN)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if using the big 'ol machinegun
|
|
|
|
|
|
|
|
bool CCSBot::IsUsingMachinegun() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = GetActiveWeapon();
|
|
|
|
|
|
|
|
if (weapon != NULL && weapon->m_iId == WEAPON_MP5)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true if primary weapon doesn't exist or is totally out of ammo
|
|
|
|
|
|
|
|
bool CCSBot::IsPrimaryWeaponEmpty() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 2 ]);
|
|
|
|
|
|
|
|
if (weapon == NULL)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// check if gun has any ammo left
|
|
|
|
if (HasAnyAmmo(weapon))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true if pistol doesn't exist or is totally out of ammo
|
|
|
|
|
|
|
|
bool CCSBot::IsPistolEmpty() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 1 ]);
|
|
|
|
|
|
|
|
if (weapon == NULL)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// check if gun has any ammo left
|
|
|
|
if (HasAnyAmmo(weapon))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Equip the given item
|
|
|
|
|
|
|
|
bool CCSBot::DoEquip(CBasePlayerWeapon *gun)
|
|
|
|
{
|
|
|
|
if (gun == NULL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// check if weapon has any ammo left
|
|
|
|
if (!HasAnyAmmo(gun))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// equip it
|
|
|
|
SelectItem(STRING(gun->pev->classname));
|
|
|
|
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 = TheCSBots();
|
|
|
|
CBasePlayerWeapon *primary = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 2 ]);
|
|
|
|
#if 0
|
|
|
|
if (primary != NULL)
|
|
|
|
{
|
|
|
|
WeaponClassType weaponClass = WeaponIDToWeaponClass(primary->m_iId);
|
|
|
|
|
|
|
|
if ((ctrl->AllowShotguns() && weaponClass == WEAPONCLASS_SHOTGUN)
|
|
|
|
|| (ctrl->AllowMachineGuns() && weaponClass == WEAPONCLASS_MACHINEGUN)
|
|
|
|
|| (ctrl->AllowRifles() && weaponClass == WEAPONCLASS_RIFLE)
|
|
|
|
|| (ctrl->AllowSnipers() && weaponClass == WEAPONCLASS_SNIPERRIFLE)
|
|
|
|
|| (ctrl->AllowSubMachineGuns() && weaponClass == WEAPONCLASS_SUBMACHINEGUN)
|
|
|
|
|| (ctrl->AllowTacticalShield() && primary->m_iId == WEAPON_SHIELDGUN))
|
|
|
|
{
|
|
|
|
if (DoEquip(primary))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctrl->AllowPistols())
|
|
|
|
{
|
|
|
|
if (DoEquip(static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 1 ])))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
// always have a knife
|
|
|
|
EquipKnife();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Equip our pistol
|
|
|
|
|
|
|
|
void CCSBot::EquipPistol()
|
|
|
|
{
|
|
|
|
// throttle how often equipping is allowed
|
|
|
|
if (m_equipTimer.GetElapsedTime() < minEquipInterval)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (TheCSBots()->AllowPistols() && !IsUsingPistol())
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *pistol = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 1 ]);
|
|
|
|
DoEquip(pistol);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Equip the knife
|
|
|
|
|
|
|
|
void CCSBot::EquipKnife()
|
|
|
|
{
|
|
|
|
if (!IsUsingKnife())
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *knife = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 0 ]);
|
|
|
|
if (knife != NULL)
|
|
|
|
{
|
|
|
|
SelectItem(STRING(knife->pev->classname));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true if we have a grenade in our inventory
|
|
|
|
|
|
|
|
bool CCSBot::HasGrenade() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *grenade = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 4 ]);
|
|
|
|
return grenade != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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())
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *grenade = static_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 4 ]);
|
|
|
|
|
|
|
|
if (grenade != NULL)
|
|
|
|
{
|
|
|
|
//if (noSmoke && grenade->m_iId == WEAPON_SMOKEGRENADE)
|
|
|
|
//return false;
|
|
|
|
|
|
|
|
SelectItem(STRING(grenade->pev->classname));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if we have knife equipped
|
|
|
|
|
|
|
|
bool CCSBot::IsUsingKnife() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = GetActiveWeapon();
|
|
|
|
|
|
|
|
if (weapon != NULL && weapon->m_iId == WEAPON_CROWBAR)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if we have pistol equipped
|
|
|
|
|
|
|
|
bool CCSBot::IsUsingPistol() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = GetActiveWeapon();
|
|
|
|
|
|
|
|
//if (weapon != NULL && weapon->IsPistol())
|
|
|
|
// return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if we have a grenade equipped
|
|
|
|
|
|
|
|
bool CCSBot::IsUsingGrenade() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = GetActiveWeapon();
|
|
|
|
|
|
|
|
if (weapon == NULL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (weapon->m_iId == WEAPON_HANDGRENADE )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CCSBot::IsUsingHEGrenade() const
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *weapon = GetActiveWeapon();
|
|
|
|
|
|
|
|
if (weapon != NULL && weapon->m_iId == WEAPON_HANDGRENADE)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Begin the process of throwing the grenade
|
|
|
|
|
|
|
|
void CCSBot::ThrowGrenade(const Vector *target)
|
|
|
|
{
|
|
|
|
if (IsUsingGrenade() && !m_isWaitingToTossGrenade)
|
|
|
|
{
|
|
|
|
const float angleTolerance = 1.0f;
|
|
|
|
|
|
|
|
SetLookAt("GrenadeThrow", target, PRIORITY_UNINTERRUPTABLE, 3.0f, false, angleTolerance);
|
|
|
|
|
|
|
|
m_isWaitingToTossGrenade = true;
|
|
|
|
m_tossGrenadeTimer.Start(3.0f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
TraceResult result;
|
|
|
|
Vector check;
|
|
|
|
|
|
|
|
// check +X
|
|
|
|
check = visibleSpot + Vector(999.9f, 0, 0);
|
|
|
|
UTIL_TraceLine(visibleSpot, check, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
|
|
|
|
|
|
|
|
if (result.flFraction < 1.0f)
|
|
|
|
{
|
|
|
|
float range = result.vecEndPos.x - visibleSpot.x;
|
|
|
|
if (range < bufferRange)
|
|
|
|
{
|
|
|
|
visibleSpot.x = result.vecEndPos.x - bufferRange;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check -X
|
|
|
|
check = visibleSpot + Vector(-999.9f, 0, 0);
|
|
|
|
UTIL_TraceLine(visibleSpot, check, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
|
|
|
|
|
|
|
|
if (result.flFraction < 1.0f)
|
|
|
|
{
|
|
|
|
float range = visibleSpot.x - result.vecEndPos.x;
|
|
|
|
if (range < bufferRange)
|
|
|
|
{
|
|
|
|
visibleSpot.x = result.vecEndPos.x + bufferRange;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check +Y
|
|
|
|
check = visibleSpot + Vector(0, 999.9f, 0);
|
|
|
|
UTIL_TraceLine(visibleSpot, check, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
|
|
|
|
|
|
|
|
if (result.flFraction < 1.0f)
|
|
|
|
{
|
|
|
|
float range = result.vecEndPos.y - visibleSpot.y;
|
|
|
|
if (range < bufferRange)
|
|
|
|
{
|
|
|
|
visibleSpot.y = result.vecEndPos.y - bufferRange;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check -Y
|
|
|
|
check = visibleSpot + Vector(0, -999.9f, 0);
|
|
|
|
UTIL_TraceLine(visibleSpot, check, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
|
|
|
|
|
|
|
|
if (result.flFraction < 1.0f)
|
|
|
|
{
|
|
|
|
float range = visibleSpot.y - result.vecEndPos.y;
|
|
|
|
if (range < bufferRange)
|
|
|
|
{
|
|
|
|
visibleSpot.y = result.vecEndPos.y + bufferRange;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*pos = visibleSpot;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reload our weapon if we must
|
|
|
|
|
|
|
|
void CCSBot::ReloadCheck()
|
|
|
|
{
|
|
|
|
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 0
|
|
|
|
if (IsDefusingBomb() || IsActiveWeaponReloading())
|
|
|
|
return;
|
|
|
|
#else
|
|
|
|
if (IsActiveWeaponReloading())
|
|
|
|
return;
|
|
|
|
#endif
|
|
|
|
if (IsActiveWeaponClipEmpty())
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
// high-skill players switch to pistol instead of reloading during combat
|
|
|
|
if (GetProfile()->GetSkill() > 0.5f && IsAttacking())
|
|
|
|
{
|
|
|
|
if (!GetActiveWeapon()->IsPistol() && !IsPistolEmpty())
|
|
|
|
{
|
|
|
|
// switch to pistol instead of reloading
|
|
|
|
EquipPistol();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
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 (IsUsingAWP() && !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() && RANDOM_FLOAT(0.0f, 100.0f) < 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 != NULL)
|
|
|
|
{
|
|
|
|
// 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()
|
|
|
|
{
|
|
|
|
// longer than reload check because reloading should take precedence
|
|
|
|
const float safeSilencerWaitTime = 3.5f;
|
|
|
|
|
|
|
|
if (IsActiveWeaponReloading() || 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)
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *myGun = GetActiveWeapon();
|
|
|
|
if (myGun == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
//bool isSilencerOn = (myGun->m_iWeaponState & (WPNSTATE_M4A1_SILENCED | WPNSTATE_USP_SILENCED)) != 0;
|
|
|
|
|
|
|
|
if (myGun->m_flNextSecondaryAttack >= gpGlobals->time)
|
|
|
|
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");
|
|
|
|
myGun->SecondaryAttack();
|
|
|
|
}*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invoked when in contact with a CWeaponBox
|
|
|
|
|
|
|
|
void CCSBot::OnTouchingWeapon(CWeaponBox *box)
|
|
|
|
{
|
|
|
|
CBasePlayerItem *droppedGun = dynamic_cast<CBasePlayerItem *>(box->m_rgpPlayerItems[ 2 ]);
|
|
|
|
|
|
|
|
// right now we only care about primary weapons on the ground
|
|
|
|
if (droppedGun != NULL)
|
|
|
|
{
|
|
|
|
CBasePlayerWeapon *myGun = dynamic_cast<CBasePlayerWeapon *>(m_rgpPlayerItems[ 2 ]);
|
|
|
|
|
|
|
|
// if the gun on the ground is the same one we have, dont bother
|
|
|
|
if (myGun != NULL && droppedGun->m_iId != myGun->m_iId)
|
|
|
|
{
|
|
|
|
// 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)
|
|
|
|
{
|
|
|
|
int prefID = GetProfile()->GetWeaponPreference(i);
|
|
|
|
|
|
|
|
//if (!IsPrimaryWeapon(prefID))
|
|
|
|
// continue;
|
|
|
|
|
|
|
|
// if the gun we are using is more desirable, give up
|
|
|
|
if (prefID == myGun->m_iId)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (prefID == droppedGun->m_iId)
|
|
|
|
{
|
|
|
|
// the gun on the ground is better than the one we have - drop our gun
|
|
|
|
//DropPrimary(this);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return true if a friend is in our weapon's way
|
|
|
|
// TODO: Check more rays for safety.
|
|
|
|
|
|
|
|
bool CCSBot::IsFriendInLineOfFire()
|
|
|
|
{
|
|
|
|
UTIL_MakeVectors(pev->punchangle + pev->v_angle);
|
|
|
|
|
|
|
|
// compute the unit vector along our view
|
|
|
|
Vector aimDir = gpGlobals->v_forward;
|
|
|
|
Vector target = GetGunPosition();
|
|
|
|
|
|
|
|
// trace the bullet's path
|
|
|
|
TraceResult result;
|
|
|
|
UTIL_TraceLine(GetGunPosition(), target + 10000.0f * aimDir, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
|
|
|
|
|
|
|
|
if (result.pHit != NULL)
|
|
|
|
{
|
|
|
|
CBaseEntity *victim = CBaseEntity::Instance(result.pHit);
|
|
|
|
|
|
|
|
if (victim != NULL && victim->IsPlayer() && victim->IsAlive())
|
|
|
|
{
|
|
|
|
CBasePlayer *player = static_cast<CBasePlayer *>(victim);
|
|
|
|
|
|
|
|
// if (player->m_iTeam == m_iTeam)
|
|
|
|
// 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()
|
|
|
|
{
|
|
|
|
UTIL_MakeVectors(pev->punchangle + pev->v_angle);
|
|
|
|
|
|
|
|
// compute the unit vector along our view
|
|
|
|
Vector aimDir = gpGlobals->v_forward;
|
|
|
|
Vector target = GetGunPosition();
|
|
|
|
|
|
|
|
// trace the bullet's path
|
|
|
|
TraceResult result;
|
|
|
|
UTIL_TraceLine(GetGunPosition(), target + 10000.0f * aimDir, dont_ignore_monsters, ignore_glass, ENT(pev), &result);
|
|
|
|
|
|
|
|
return (GetGunPosition() - result.vecEndPos).Length();
|
|
|
|
}
|