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.
753 lines
18 KiB
753 lines
18 KiB
8 years ago
|
#include "bot_common.h"
|
||
|
|
||
|
// Lightweight maintenance, invoked frequently
|
||
|
|
||
|
void CCSBot::Upkeep()
|
||
|
{
|
||
|
CCSBotManager *ctrl = TheCSBots();
|
||
|
|
||
|
if (ctrl->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 != NULL)
|
||
|
{
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
// view angles can change quickly
|
||
|
UpdateLookAngles();
|
||
|
}
|
||
|
|
||
|
// Heavyweight processing, invoked less often
|
||
|
|
||
|
void CCSBot::Update()
|
||
|
{
|
||
|
CCSBotManager *ctrl = TheCSBots();
|
||
|
|
||
|
if (ctrl->IsAnalysisRequested() && m_processMode == PROCESS_NORMAL)
|
||
|
{
|
||
|
ctrl->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.0f && IsLocalPlayerWatchingMe()) || cv_bot_traceview.value == 101.0f)
|
||
|
{
|
||
|
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, 224, 0);
|
||
|
//else
|
||
|
// UTIL_DrawBeamPoints(vecSrc, vecSrc + 2000.0f * vecAiming, 1, 0, 50, 255);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Debug beam rendering
|
||
|
if( ( cv_bot_traceview.value == 2.0f && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.value == 3.0f )
|
||
|
DrawApproachPoints();
|
||
|
|
||
|
if( ( cv_bot_traceview.value == 4.0f && IsLocalPlayerWatchingMe() ) || cv_bot_traceview.value == 5.0f )
|
||
|
{
|
||
|
// ...
|
||
|
}
|
||
|
|
||
|
if (cv_bot_traceview.value == 1.0f && IsLocalPlayerWatchingMe())
|
||
|
{
|
||
|
// ...
|
||
|
}
|
||
|
|
||
|
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 != NULL && 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 != NULL)
|
||
|
{
|
||
|
m_lastKnownArea->DrawConnectedAreas();
|
||
|
}
|
||
|
|
||
|
#if 0
|
||
|
// 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;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// 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 != NULL)
|
||
|
{
|
||
|
// adjust our personal "safe" time
|
||
|
AdjustSafeTime();
|
||
|
|
||
|
// Decide if we should attack
|
||
|
bool doAttack = false;
|
||
|
if (!IsUsingGrenade())
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
ThrowGrenade(&threat->pev->origin);
|
||
|
|
||
|
// if we aren't attacking but we are being attacked, retaliate
|
||
|
if (!doAttack && !IsAttacking() && GetDisposition() != IGNORE_ENEMIES)
|
||
|
{
|
||
|
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)
|
||
|
doAttack = true;
|
||
|
PrintIfWatched("Ouch! Retaliating!\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (doAttack)
|
||
|
{
|
||
|
if (GetEnemy() == NULL || !IsAttacking() || threat != GetEnemy())
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
ctrl->SetLastSeenEnemyTimestamp();
|
||
|
}
|
||
|
|
||
|
// Validate existing enemy, if any
|
||
|
if (m_enemy != NULL)
|
||
|
{
|
||
|
if (IsAwareOfEnemyDeath())
|
||
|
{
|
||
|
// we have noticed that our enemy has died
|
||
|
m_enemy = NULL;
|
||
|
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 != NULL && 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;
|
||
|
}
|
||
|
|
||
|
|
||
|
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 != NULL)
|
||
|
{
|
||
|
StrafeAwayFromPosition(&m_avoid->pev->origin);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_avoid = NULL;
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
// check encounter spots
|
||
|
UpdatePeripheralVision();
|
||
|
|
||
|
// Update gamestate
|
||
|
if (m_bomber != NULL)
|
||
|
GetChatter()->SpottedBomber(GetBomber());
|
||
|
|
||
|
if (CanSeeLooseBomb())
|
||
|
GetChatter()->SpottedLooseBomb(ctrl->GetLooseBomb());
|
||
|
|
||
|
// Scenario interrupts
|
||
|
switch (ctrl->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
|
||
|
&& ctrl->IsBombPlanted() // is the bomb planted
|
||
|
&& GetGameState()->IsPlantedBombLocationKnown() // we know where the bomb is
|
||
|
&& ctrl->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 0
|
||
|
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;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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 (ctrl->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 *leader = GetClosestVisibleHumanFriend();
|
||
|
#if 0
|
||
|
if (leader != NULL && leader->IsAutoFollowAllowed())
|
||
|
{
|
||
|
// count how many bots are already following this player
|
||
|
const float maxFollowCount = 2;
|
||
|
if (GetBotFollowCount(leader) < maxFollowCount)
|
||
|
{
|
||
|
const float autoFollowRange = 300.0f;
|
||
|
if ((leader->pev->origin - pev->origin).IsLengthLessThan(autoFollowRange))
|
||
|
{
|
||
|
CNavArea *leaderArea = TheNavAreaGrid.GetNavArea(&leader->pev->origin);
|
||
|
if (leaderArea != NULL)
|
||
|
{
|
||
|
PathCost cost(this, FASTEST_ROUTE);
|
||
|
float travelRange = NavAreaTravelDistance(GetLastKnownArea(), leaderArea, cost);
|
||
|
if (/*travelRange >= 0.0f &&*/ travelRange < autoFollowRange)
|
||
|
{
|
||
|
// follow this human
|
||
|
Follow(leader);
|
||
|
PrintIfWatched("Auto-Following %s\n", STRING(leader->pev->netname));
|
||
|
|
||
|
if (g_pGameRules->IsCareer())
|
||
|
{
|
||
|
GetChatter()->Say("FollowingCommander", 10.0f);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetChatter()->Say("FollowingSir", 10.0f);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
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 *leader = m_leader;
|
||
|
if (leader == NULL || !leader->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 (ctrl->IsOnOffense(this) || !ctrl->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();
|
||
|
}
|
||
|
#if 0
|
||
|
// don't move while reloading unless we see an enemy
|
||
|
if (IsReloading() && !m_isEnemyVisible)
|
||
|
{
|
||
|
ResetStuckMonitor();
|
||
|
ClearMovement();
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// 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();
|
||
|
}
|