mirror of
https://github.com/s1lentq/ReGameDLL_CS.git
synced 2025-04-10 11:40:02 +03:00
Friendly fire fixes Bot Chatter: Bots will react to enemy snipers presence previously unused chatter on both 1.6 and czero but is used on cs source Probably fixed where teammate that use sniperrifles looking at bots that don't use sniperrifles got warned
829 lines
21 KiB
C++
829 lines
21 KiB
C++
/*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at
|
|
* your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* In addition, as a special exception, the author gives permission to
|
|
* link the code of this program with the Half-Life Game Engine ("HL
|
|
* Engine") and Modified Game Libraries ("MODs") developed by Valve,
|
|
* L.L.C ("Valve"). You must obey the GNU General Public License in all
|
|
* respects for all of the code used other than the HL Engine and MODs
|
|
* from Valve. If you modify this file, you may extend this exception
|
|
* to your version of the file, but you are not obligated to do so. If
|
|
* you do not wish to do so, delete this exception statement from your
|
|
* version.
|
|
*
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
// Lightweight maintenance, invoked frequently
|
|
void CCSBot::Upkeep()
|
|
{
|
|
if (TheCSBots()->IsLearningMap() || !IsAlive())
|
|
return;
|
|
|
|
if (m_isRapidFiring)
|
|
TogglePrimaryAttack();
|
|
|
|
// aiming must be smooth - update often
|
|
if (IsAimingAtEnemy())
|
|
{
|
|
UpdateAimOffset();
|
|
|
|
// aim at enemy, if he's still alive
|
|
if (m_enemy.IsValid())
|
|
{
|
|
float feetOffset = pev->origin.z - GetFeetZ();
|
|
|
|
if (IsEnemyVisible())
|
|
{
|
|
if (GetProfile()->GetSkill() > 0.5f)
|
|
{
|
|
const float k = 3.0f;
|
|
m_aimSpot = (m_enemy->pev->velocity - pev->velocity) * g_flBotCommandInterval * k + m_enemy->pev->origin;
|
|
}
|
|
else
|
|
m_aimSpot = m_enemy->pev->origin;
|
|
|
|
bool aimBlocked = false;
|
|
const float sharpshooter = 0.8f;
|
|
|
|
if (IsUsingAWP() || IsUsingShotgun() || IsUsingMachinegun() || GetProfile()->GetSkill() < sharpshooter
|
|
|| (IsActiveWeaponRecoilHigh() && !IsUsingPistol() && !IsUsingSniperRifle()))
|
|
{
|
|
if (IsEnemyPartVisible(CHEST))
|
|
{
|
|
// No headshots, go for the chest.
|
|
aimBlocked = true;
|
|
}
|
|
}
|
|
|
|
if (aimBlocked)
|
|
m_aimSpot.z -= feetOffset * 0.25f;
|
|
|
|
else if (!IsEnemyPartVisible(HEAD))
|
|
{
|
|
if (IsEnemyPartVisible(CHEST))
|
|
{
|
|
m_aimSpot.z -= feetOffset * 0.5f;
|
|
}
|
|
else if (IsEnemyPartVisible(LEFT_SIDE))
|
|
{
|
|
Vector2D to = (m_enemy->pev->origin - pev->origin).Make2D();
|
|
to.NormalizeInPlace();
|
|
|
|
m_aimSpot.x -= to.y * 16.0f;
|
|
m_aimSpot.y += to.x * 16.0f;
|
|
m_aimSpot.z -= feetOffset * 0.5f;
|
|
}
|
|
else if (IsEnemyPartVisible(RIGHT_SIDE))
|
|
{
|
|
Vector2D to = (m_enemy->pev->origin - pev->origin).Make2D();
|
|
to.NormalizeInPlace();
|
|
|
|
m_aimSpot.x += to.y * 16.0f;
|
|
m_aimSpot.y -= to.x * 16.0f;
|
|
m_aimSpot.z -= feetOffset * 0.5f;
|
|
}
|
|
else // FEET
|
|
{
|
|
m_aimSpot.z -= (feetOffset + feetOffset);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_aimSpot = m_lastEnemyPosition;
|
|
}
|
|
|
|
// add in aim error
|
|
m_aimSpot.x += m_aimOffset.x;
|
|
m_aimSpot.y += m_aimOffset.y;
|
|
m_aimSpot.z += m_aimOffset.z;
|
|
|
|
Vector toEnemy = m_aimSpot - pev->origin;
|
|
Vector idealAngle = UTIL_VecToAngles(toEnemy);
|
|
|
|
idealAngle.x = 360.0 - idealAngle.x;
|
|
SetLookAngles(idealAngle.y, idealAngle.x);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_lookAtSpotClearIfClose)
|
|
{
|
|
// dont look at spots just in front of our face - it causes erratic view rotation
|
|
const float tooCloseRange = 100.0f;
|
|
if ((m_lookAtSpot - pev->origin).IsLengthLessThan(tooCloseRange))
|
|
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
|
|
}
|
|
|
|
switch (m_lookAtSpotState)
|
|
{
|
|
case NOT_LOOKING_AT_SPOT:
|
|
{
|
|
// look ahead
|
|
SetLookAngles(m_lookAheadAngle, 0);
|
|
break;
|
|
}
|
|
case LOOK_TOWARDS_SPOT:
|
|
{
|
|
UpdateLookAt();
|
|
|
|
if (IsLookingAtPosition(&m_lookAtSpot, m_lookAtSpotAngleTolerance))
|
|
{
|
|
m_lookAtSpotState = LOOK_AT_SPOT;
|
|
m_lookAtSpotTimestamp = gpGlobals->time;
|
|
}
|
|
break;
|
|
}
|
|
case LOOK_AT_SPOT:
|
|
{
|
|
UpdateLookAt();
|
|
|
|
if (m_lookAtSpotDuration >= 0.0f && gpGlobals->time - m_lookAtSpotTimestamp > m_lookAtSpotDuration)
|
|
{
|
|
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
|
|
m_lookAtSpotDuration = 0.0f;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
float driftAmplitude = 2.0f;
|
|
|
|
// have view "drift" very slowly, so view looks "alive"
|
|
if (IsUsingSniperRifle() && IsUsingScope())
|
|
{
|
|
driftAmplitude = 0.5f;
|
|
}
|
|
|
|
m_lookYaw += driftAmplitude * BotCOS(33.0f * gpGlobals->time);
|
|
m_lookPitch += driftAmplitude * BotSIN(13.0f * gpGlobals->time);
|
|
}
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
// Don't update view angles at frozen state
|
|
if (!(pev->flags & FL_FROZEN))
|
|
#endif
|
|
{
|
|
// view angles can change quickly
|
|
UpdateLookAngles();
|
|
}
|
|
}
|
|
|
|
// Heavyweight processing, invoked less often
|
|
void CCSBot::Update()
|
|
{
|
|
if (TheCSBots()->IsAnalysisRequested() && m_processMode == PROCESS_NORMAL)
|
|
{
|
|
TheCSBots()->AckAnalysisRequest();
|
|
StartAnalyzeAlphaProcess();
|
|
}
|
|
|
|
switch (m_processMode)
|
|
{
|
|
case PROCESS_LEARN: UpdateLearnProcess(); return;
|
|
case PROCESS_ANALYZE_ALPHA: UpdateAnalyzeAlphaProcess(); return;
|
|
case PROCESS_ANALYZE_BETA: UpdateAnalyzeBetaProcess(); return;
|
|
case PROCESS_SAVE: UpdateSaveProcess(); return;
|
|
}
|
|
|
|
// update our radio chatter
|
|
// need to allow bots to finish their chatter even if they are dead
|
|
GetChatter()->Update();
|
|
|
|
if (m_voiceFeedbackEndTimestamp != 0.0f
|
|
&& (m_voiceFeedbackEndTimestamp <= gpGlobals->time || gpGlobals->time < m_voiceFeedbackStartTimestamp))
|
|
{
|
|
EndVoiceFeedback(NO_FORCE);
|
|
}
|
|
|
|
// check if we are dead
|
|
if (!IsAlive())
|
|
{
|
|
// remember that we died
|
|
m_diedLastRound = true;
|
|
BotDeathThink();
|
|
return;
|
|
}
|
|
|
|
// show line of fire
|
|
if ((cv_bot_traceview.value == 100.0 && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 101.0)
|
|
{
|
|
UTIL_MakeVectors(pev->punchangle + pev->v_angle);
|
|
|
|
if (!IsFriendInLineOfFire())
|
|
{
|
|
Vector vecAiming = gpGlobals->v_forward;
|
|
Vector vecSrc = GetGunPosition();
|
|
|
|
if (m_iTeam == TERRORIST)
|
|
UTIL_DrawBeamPoints(vecSrc, vecSrc + 2000.0f * vecAiming, 1, 255, 0, 0);
|
|
else
|
|
UTIL_DrawBeamPoints(vecSrc, vecSrc + 2000.0f * vecAiming, 1, 0, 50, 255);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Debug beam rendering
|
|
//
|
|
|
|
// show approach points
|
|
if ((cv_bot_traceview.value == 2.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 3.0f)
|
|
DrawApproachPoints();
|
|
|
|
// show encounter spot data
|
|
if ((cv_bot_traceview.value == 4.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 5.0f)
|
|
{
|
|
if (m_spotEncounter)
|
|
{
|
|
UTIL_DrawBeamPoints(m_spotEncounter->path.from, m_spotEncounter->path.to, 3, 0, 0, 255);
|
|
|
|
Vector dir = m_spotEncounter->path.to - m_spotEncounter->path.from;
|
|
float length = dir.NormalizeInPlace();
|
|
|
|
for (auto& order : m_spotEncounter->spotList) {
|
|
UTIL_DrawBeamPoints(m_spotEncounter->path.from + order.t * length * dir, *order.spot->GetPosition(), 3, 0, 255, 255);
|
|
}
|
|
}
|
|
}
|
|
|
|
// show path navigation data
|
|
if (cv_bot_traceview.value == 1.0f && IsLocalPlayerWatchingMe())
|
|
{
|
|
Vector from = GetEyePosition();
|
|
const float size = 50.0f;
|
|
|
|
Vector arrow(size * float(Q_cos(m_lookAheadAngle * M_PI / 180.0f)), size * float(Q_sin(m_lookAheadAngle * M_PI / 180.0f)), 0.0f);
|
|
UTIL_DrawBeamPoints(from, from + arrow, 1, 0, 255, 255);
|
|
}
|
|
|
|
if (cv_bot_stop.value != 0.0f)
|
|
return;
|
|
|
|
// check if we are stuck
|
|
StuckCheck();
|
|
|
|
// if our current 'noise' was heard a long time ago, forget it
|
|
const float rememberNoiseDuration = 20.0f;
|
|
if (m_noiseTimestamp > 0.0f && gpGlobals->time - m_noiseTimestamp > rememberNoiseDuration)
|
|
{
|
|
ForgetNoise();
|
|
}
|
|
|
|
// where are we
|
|
if (!m_currentArea || !m_currentArea->Contains(&pev->origin))
|
|
{
|
|
m_currentArea = TheNavAreaGrid.GetNavArea(&pev->origin);
|
|
}
|
|
|
|
// track the last known area we were in
|
|
if (m_currentArea && m_currentArea != m_lastKnownArea)
|
|
{
|
|
m_lastKnownArea = m_currentArea;
|
|
// assume that we "clear" an area of enemies when we enter it
|
|
m_currentArea->SetClearedTimestamp(m_iTeam - 1);
|
|
}
|
|
|
|
// update approach points
|
|
const float recomputeApproachPointTolerance = 50.0f;
|
|
if ((m_approachPointViewPosition - pev->origin).IsLengthGreaterThan(recomputeApproachPointTolerance))
|
|
{
|
|
ComputeApproachPoints();
|
|
m_approachPointViewPosition = pev->origin;
|
|
}
|
|
|
|
if (cv_bot_show_nav.value > 0.0f && m_lastKnownArea)
|
|
{
|
|
m_lastKnownArea->DrawConnectedAreas();
|
|
}
|
|
|
|
// if we're blind, retreat!
|
|
if (IsBlind())
|
|
{
|
|
if (!IsAtHidingSpot())
|
|
{
|
|
switch (m_blindMoveDir)
|
|
{
|
|
case FORWARD: MoveForward(); break;
|
|
case RIGHT: StrafeRight(); break;
|
|
case BACKWARD: MoveBackward(); break;
|
|
case LEFT: StrafeLeft(); break;
|
|
default: Crouch(); break;
|
|
}
|
|
}
|
|
|
|
if (m_blindFire)
|
|
{
|
|
PrimaryAttack();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Enemy acquisition and attack initiation
|
|
// take a snapshot and update our reaction time queue
|
|
UpdateReactionQueue();
|
|
|
|
// "threat" may be the same as our current enemy
|
|
CBasePlayer* threat = GetRecognizedEnemy();
|
|
if (threat)
|
|
{
|
|
// adjust our personal "safe" time
|
|
AdjustSafeTime();
|
|
|
|
if (IsUsingGrenade())
|
|
{
|
|
ThrowGrenade(&threat->pev->origin);
|
|
}
|
|
else
|
|
{
|
|
// Decide if we should attack
|
|
bool doAttack = false;
|
|
switch (GetDisposition())
|
|
{
|
|
case IGNORE_ENEMIES:
|
|
{
|
|
// never attack
|
|
doAttack = false;
|
|
break;
|
|
}
|
|
case SELF_DEFENSE:
|
|
{
|
|
// attack if fired on
|
|
doAttack = IsPlayerLookingAtMe(threat);
|
|
|
|
// attack if enemy very close
|
|
if (!doAttack)
|
|
{
|
|
const float selfDefenseRange = 750.0f;
|
|
doAttack = (pev->origin - threat->pev->origin).IsLengthLessThan(selfDefenseRange);
|
|
}
|
|
break;
|
|
}
|
|
case ENGAGE_AND_INVESTIGATE:
|
|
case OPPORTUNITY_FIRE:
|
|
{
|
|
// normal combat range
|
|
doAttack = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (doAttack)
|
|
{
|
|
if (!GetEnemy() || threat != GetEnemy() || !IsAttacking())
|
|
{
|
|
if (IsUsingKnife() && IsHiding())
|
|
{
|
|
// if hiding with a knife, wait until threat is close
|
|
const float knifeAttackRange = 250.0f;
|
|
if ((pev->origin - threat->pev->origin).IsLengthLessThan(knifeAttackRange))
|
|
{
|
|
Attack(threat);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Attack(threat);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// dont attack, but keep track of nearby enemies
|
|
SetEnemy(threat);
|
|
m_isEnemyVisible = true;
|
|
}
|
|
}
|
|
|
|
// if we aren't attacking but we are being attacked, retaliate
|
|
if (GetDisposition() != IGNORE_ENEMIES && !IsAttacking())
|
|
{
|
|
const float recentAttackDuration = 1.0f;
|
|
if (GetTimeSinceAttacked() < recentAttackDuration)
|
|
{
|
|
// we may not be attacking our attacker, but at least we're not just taking it
|
|
// (since m_attacker isn't reaction-time delayed, we can't directly use it)
|
|
Attack(threat);
|
|
PrintIfWatched("Ouch! Retaliating!\n");
|
|
}
|
|
}
|
|
|
|
TheCSBots()->SetLastSeenEnemyTimestamp();
|
|
}
|
|
|
|
// Validate existing enemy, if any
|
|
if (m_enemy.IsValid())
|
|
{
|
|
if (IsAwareOfEnemyDeath())
|
|
{
|
|
// we have noticed that our enemy has died
|
|
m_enemy = nullptr;
|
|
m_isEnemyVisible = false;
|
|
}
|
|
else
|
|
{
|
|
// check LOS to current enemy (chest & head), in case he's dead (GetNearestEnemy() only returns live players)
|
|
// note we're not checking FOV - once we've acquired an enemy (which does check FOV), assume we know roughly where he is
|
|
if (IsVisible(m_enemy, false, &m_visibleEnemyParts))
|
|
{
|
|
m_isEnemyVisible = true;
|
|
m_lastSawEnemyTimestamp = gpGlobals->time;
|
|
m_lastEnemyPosition = m_enemy->pev->origin;
|
|
}
|
|
else
|
|
{
|
|
m_isEnemyVisible = false;
|
|
}
|
|
|
|
// check if enemy died
|
|
if (m_enemy->IsAlive())
|
|
{
|
|
m_enemyDeathTimestamp = 0.0f;
|
|
m_isLastEnemyDead = false;
|
|
}
|
|
else if (m_enemyDeathTimestamp == 0.0f)
|
|
{
|
|
// note time of death (to allow bots to overshoot for a time)
|
|
m_enemyDeathTimestamp = gpGlobals->time;
|
|
m_isLastEnemyDead = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_isEnemyVisible = false;
|
|
}
|
|
|
|
// if we have seen an enemy recently, keep an eye on him if we can
|
|
const float seenRecentTime = 3.0f;
|
|
if (m_enemy.IsValid() && GetTimeSinceLastSawEnemy() < seenRecentTime)
|
|
{
|
|
AimAtEnemy();
|
|
}
|
|
else
|
|
{
|
|
StopAiming();
|
|
}
|
|
|
|
// Hack to fire while retreating
|
|
// TODO: Encapsulate aiming and firing on enemies separately from current task
|
|
if (GetDisposition() == IGNORE_ENEMIES)
|
|
{
|
|
FireWeaponAtEnemy();
|
|
}
|
|
|
|
if (IsEndOfSafeTime() && IsUsingGrenade() && (IsWellPastSafe() || !IsUsingHEGrenade()) && !m_isWaitingToTossGrenade)
|
|
{
|
|
Vector target;
|
|
if (FindGrenadeTossPathTarget(&target))
|
|
{
|
|
ThrowGrenade(&target);
|
|
}
|
|
}
|
|
|
|
if (IsUsingGrenade())
|
|
{
|
|
bool doToss = (m_isWaitingToTossGrenade && (m_tossGrenadeTimer.IsElapsed() || m_lookAtSpotState == LOOK_AT_SPOT));
|
|
|
|
if (doToss)
|
|
{
|
|
ClearPrimaryAttack();
|
|
m_isWaitingToTossGrenade = false;
|
|
}
|
|
else
|
|
{
|
|
PrimaryAttack();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_isWaitingToTossGrenade = false;
|
|
}
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
// bots with low skill cannot switch weapons underwater
|
|
if (GetProfile()->GetSkill() > 0.4f && pev->waterlevel == 3 && !IsActiveWeaponCanShootUnderwater())
|
|
{
|
|
EquipBestWeapon(MUST_EQUIP);
|
|
}
|
|
#endif
|
|
|
|
if (IsHunting() && IsWellPastSafe() && IsUsingGrenade())
|
|
{
|
|
EquipBestWeapon(MUST_EQUIP);
|
|
}
|
|
|
|
// check if our weapon is totally out of ammo
|
|
// or if we no longer feel "safe", equip our weapon
|
|
if (!IsSafe() && !IsUsingGrenade() && IsActiveWeaponOutOfAmmo())
|
|
{
|
|
EquipBestWeapon();
|
|
}
|
|
|
|
// TODO: This doesn't work if we are restricted to just knives and sniper rifles because we cant use the rifle at close range
|
|
if (!IsSafe() && !IsUsingGrenade() && IsUsingKnife() && !IsEscapingFromBomb())
|
|
{
|
|
EquipBestWeapon();
|
|
}
|
|
|
|
// if we haven't seen an enemy in awhile, and we switched to our pistol during combat,
|
|
// switch back to our primary weapon (if it still has ammo left)
|
|
const float safeRearmTime = 5.0f;
|
|
if (!IsActiveWeaponReloading() && IsUsingPistol() && !IsPrimaryWeaponEmpty() && GetTimeSinceLastSawEnemy() > safeRearmTime)
|
|
{
|
|
EquipBestWeapon();
|
|
}
|
|
|
|
// reload our weapon if we must
|
|
ReloadCheck();
|
|
|
|
// equip silencer
|
|
SilencerCheck();
|
|
|
|
// listen to the radio
|
|
RespondToRadioCommands();
|
|
|
|
// make way
|
|
const float avoidTime = 0.33f;
|
|
if (gpGlobals->time - m_avoidTimestamp < avoidTime && m_avoid)
|
|
{
|
|
StrafeAwayFromPosition(&m_avoid->pev->origin);
|
|
}
|
|
else
|
|
{
|
|
m_avoid = nullptr;
|
|
}
|
|
|
|
if (m_isJumpCrouching)
|
|
{
|
|
const float duration = 0.75f;
|
|
const float crouchDelayTime = 0.05f;
|
|
const float standUpTime = 0.6f;
|
|
float elapsed = gpGlobals->time - m_jumpCrouchTimestamp;
|
|
|
|
if (elapsed > crouchDelayTime && elapsed < standUpTime)
|
|
Crouch();
|
|
|
|
if (elapsed >= standUpTime)
|
|
StandUp();
|
|
|
|
if (elapsed > duration)
|
|
m_isJumpCrouching = false;
|
|
}
|
|
|
|
// if we're using a sniper rifle and are no longer attacking, stop looking thru scope
|
|
if (!IsAtHidingSpot() && !IsAttacking() && IsUsingSniperRifle() && IsUsingScope())
|
|
{
|
|
SecondaryAttack();
|
|
}
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
if (!IsBlind())
|
|
{
|
|
#endif
|
|
// check encounter spots
|
|
UpdatePeripheralVision();
|
|
|
|
// Update gamestate
|
|
if (m_bomber)
|
|
{
|
|
GetChatter()->SpottedBomber(GetBomber());
|
|
}
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
// watch for snipers
|
|
if (CanSeeSniper() && !HasSeenSniperRecently())
|
|
{
|
|
GetChatter()->SpottedSniper();
|
|
|
|
const float sniperRecentInterval = 20.0f;
|
|
m_sawEnemySniperTimer.Start(sniperRecentInterval);
|
|
}
|
|
#endif
|
|
|
|
if (CanSeeLooseBomb())
|
|
{
|
|
GetChatter()->SpottedLooseBomb(TheCSBots()->GetLooseBomb());
|
|
}
|
|
#ifdef REGAMEDLL_ADD
|
|
}
|
|
#endif
|
|
// Scenario interrupts
|
|
switch (TheCSBots()->GetScenario())
|
|
{
|
|
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
|
|
{
|
|
// flee if the bomb is ready to blow and we aren't defusing it or attacking and we know where the bomb is
|
|
// (aggressive players wait until its almost too late)
|
|
float gonnaBlowTime = 8.0f - (2.0f * GetProfile()->GetAggression());
|
|
|
|
// if we have a defuse kit, can wait longer
|
|
if (m_bHasDefuser)
|
|
gonnaBlowTime *= 0.66f;
|
|
|
|
if (!IsEscapingFromBomb() // we aren't already escaping the bomb
|
|
&& TheCSBots()->IsBombPlanted() // is the bomb planted
|
|
&& GetGameState()->IsPlantedBombLocationKnown() // we know where the bomb is
|
|
&& TheCSBots()->GetBombTimeLeft() < gonnaBlowTime // is the bomb about to explode
|
|
&& !IsDefusingBomb() // we aren't defusing the bomb
|
|
&& !IsAttacking()) // we aren't in the midst of a firefight
|
|
{
|
|
EscapeFromBomb();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
|
|
{
|
|
if (m_iTeam == CT)
|
|
{
|
|
UpdateHostageEscortCount();
|
|
}
|
|
else
|
|
{
|
|
// Terrorists have imperfect information on status of hostages
|
|
CSGameState::ValidateStatusType status = GetGameState()->ValidateHostagePositions();
|
|
|
|
if (status & CSGameState::HOSTAGES_ALL_GONE)
|
|
{
|
|
GetChatter()->HostagesTaken();
|
|
Idle();
|
|
}
|
|
else if (status & CSGameState::HOSTAGE_GONE)
|
|
{
|
|
GetGameState()->HostageWasTaken();
|
|
Idle();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Follow nearby humans if our co-op is high and we have nothing else to do
|
|
// If we were just following someone, don't auto-follow again for a short while to
|
|
// give us a chance to do something else.
|
|
const float earliestAutoFollowTime = 5.0f;
|
|
const float minAutoFollowTeamwork = 0.4f;
|
|
if (TheCSBots()->GetElapsedRoundTime() > earliestAutoFollowTime
|
|
&& GetProfile()->GetTeamwork() > minAutoFollowTeamwork
|
|
&& CanAutoFollow()
|
|
&& !IsBusy()
|
|
&& !IsFollowing()
|
|
&& !GetGameState()->IsAtPlantedBombsite())
|
|
{
|
|
// chance of following is proportional to teamwork attribute
|
|
if (GetProfile()->GetTeamwork() > RANDOM_FLOAT(0.0f, 1.0f))
|
|
{
|
|
CBasePlayer *pLeader = GetClosestVisibleHumanFriend();
|
|
if (pLeader && pLeader->IsAutoFollowAllowed())
|
|
{
|
|
// count how many bots are already following this player
|
|
const float maxFollowCount = 2;
|
|
if (GetBotFollowCount(pLeader) < maxFollowCount)
|
|
{
|
|
const float autoFollowRange = 300.0f;
|
|
if ((pLeader->pev->origin - pev->origin).IsLengthLessThan(autoFollowRange))
|
|
{
|
|
CNavArea *leaderArea = TheNavAreaGrid.GetNavArea(&pLeader->pev->origin);
|
|
if (leaderArea)
|
|
{
|
|
PathCost cost(this, FASTEST_ROUTE);
|
|
float travelRange = NavAreaTravelDistance(GetLastKnownArea(), leaderArea, cost);
|
|
if (/*travelRange >= 0.0f &&*/ travelRange < autoFollowRange)
|
|
{
|
|
// follow this human
|
|
Follow(pLeader);
|
|
PrintIfWatched("Auto-Following %s\n", STRING(pLeader->pev->netname));
|
|
|
|
if (CSGameRules()->IsCareer())
|
|
{
|
|
GetChatter()->Say("FollowingCommander", 10.0f);
|
|
}
|
|
else
|
|
{
|
|
GetChatter()->Say("FollowingSir", 10.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we decided not to follow, don't re-check for a duration
|
|
m_allowAutoFollowTime = gpGlobals->time + 15.0f + (1.0f - GetProfile()->GetTeamwork()) * 30.0f;
|
|
}
|
|
}
|
|
|
|
if (IsFollowing())
|
|
{
|
|
// if we are following someone, make sure they are still alive
|
|
CBaseEntity *pLeader = m_leader;
|
|
if (!pLeader || !pLeader->IsAlive())
|
|
{
|
|
StopFollowing();
|
|
}
|
|
|
|
// decide whether to continue following them
|
|
const float highTeamwork = 0.85f;
|
|
if (GetProfile()->GetTeamwork() < highTeamwork)
|
|
{
|
|
float minFollowDuration = 15.0f;
|
|
if (GetFollowDuration() > minFollowDuration + 40.0f * GetProfile()->GetTeamwork())
|
|
{
|
|
// we are bored of following our leader
|
|
StopFollowing();
|
|
PrintIfWatched("Stopping following - bored\n");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GetMorale() < NEUTRAL && IsSafe() && GetSafeTimeRemaining() < 2.0f && IsHunting())
|
|
{
|
|
if (GetMorale() * -40.0 > RANDOM_FLOAT(0.0f, 100.0f))
|
|
{
|
|
if (TheCSBots()->IsOnOffense(this) || !TheCSBots()->IsDefenseRushing())
|
|
{
|
|
SetDisposition(OPPORTUNITY_FIRE);
|
|
Hide(m_lastKnownArea, RANDOM_FLOAT(3.0f, 15.0f));
|
|
GetChatter()->Say("WaitingHere");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Execute state machine
|
|
if (m_isAttacking)
|
|
{
|
|
m_attackState.OnUpdate(this);
|
|
}
|
|
else
|
|
{
|
|
m_state->OnUpdate(this);
|
|
}
|
|
|
|
if (m_isWaitingToTossGrenade)
|
|
{
|
|
ResetStuckMonitor();
|
|
ClearMovement();
|
|
}
|
|
|
|
// don't move while reloading unless we see an enemy
|
|
if (IsReloading() && !m_isEnemyVisible)
|
|
{
|
|
ResetStuckMonitor();
|
|
ClearMovement();
|
|
}
|
|
|
|
// if we get too far ahead of the hostages we are escorting, wait for them
|
|
if (!IsAttacking() && m_inhibitWaitingForHostageTimer.IsElapsed())
|
|
{
|
|
const float waitForHostageRange = 500.0f;
|
|
if (GetTask() == RESCUE_HOSTAGES && GetRangeToFarthestEscortedHostage() > waitForHostageRange)
|
|
{
|
|
if (!m_isWaitingForHostage)
|
|
{
|
|
// just started waiting
|
|
m_isWaitingForHostage = true;
|
|
m_waitForHostageTimer.Start(10.0f);
|
|
}
|
|
else
|
|
{
|
|
// we've been waiting
|
|
if (m_waitForHostageTimer.IsElapsed())
|
|
{
|
|
// give up waiting for awhile
|
|
m_isWaitingForHostage = false;
|
|
m_inhibitWaitingForHostageTimer.Start(3.0f);
|
|
}
|
|
else
|
|
{
|
|
// keep waiting
|
|
ResetStuckMonitor();
|
|
ClearMovement();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remember our prior safe time status
|
|
m_wasSafe = IsSafe();
|
|
}
|