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.
904 lines
21 KiB
904 lines
21 KiB
#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(); |
|
}
|
|
|