summaryrefslogtreecommitdiff
path: root/game/server/cstrike/bot/cs_bot.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/cstrike/bot/cs_bot.cpp')
-rw-r--r--game/server/cstrike/bot/cs_bot.cpp1117
1 files changed, 1117 insertions, 0 deletions
diff --git a/game/server/cstrike/bot/cs_bot.cpp b/game/server/cstrike/bot/cs_bot.cpp
new file mode 100644
index 0000000..d3d8235
--- /dev/null
+++ b/game/server/cstrike/bot/cs_bot.cpp
@@ -0,0 +1,1117 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+#include "cs_simple_hostage.h"
+#include "cs_gamerules.h"
+#include "func_breakablesurf.h"
+#include "obstacle_pushaway.h"
+
+#include "cs_bot.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+LINK_ENTITY_TO_CLASS( cs_bot, CCSBot );
+
+BEGIN_DATADESC( CCSBot )
+
+END_DATADESC()
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the number of bots following the given player
+ */
+int GetBotFollowCount( CCSPlayer *leader )
+{
+ int count = 0;
+
+ for( int i=1; i <= gpGlobals->maxClients; ++i )
+ {
+ CBaseEntity *entity = UTIL_PlayerByIndex( i );
+
+ if (entity == NULL)
+ continue;
+
+ CBasePlayer *player = static_cast<CBasePlayer *>( entity );
+
+ if (!player->IsBot())
+ continue;
+
+ if (!player->IsAlive())
+ continue;
+
+ CCSBot *bot = dynamic_cast<CCSBot *>( player );
+ if (bot && bot->GetFollowLeader() == leader)
+ ++count;
+ }
+
+ return count;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Change movement speed to walking
+ */
+void CCSBot::Walk( void )
+{
+ if (m_mustRunTimer.IsElapsed())
+ {
+ BaseClass::Walk();
+ }
+ else
+ {
+ // must run
+ Run();
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if jump was started.
+ * This is extended from the base jump to disallow jumping when in a crouch area.
+ */
+bool CCSBot::Jump( bool mustJump )
+{
+ // prevent jumping if we're crouched, unless we're in a crouchjump area - jump wins
+ bool inCrouchJumpArea = (m_lastKnownArea &&
+ (m_lastKnownArea->GetAttributes() & NAV_MESH_CROUCH) &&
+ (m_lastKnownArea->GetAttributes() & NAV_MESH_JUMP));
+
+ if ( !IsUsingLadder() && IsDucked() && !inCrouchJumpArea )
+ {
+ return false;
+ }
+
+ return BaseClass::Jump( mustJump );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when injured by something
+ * NOTE: We dont want to directly call Attack() here, or the bots will have super-human reaction times when injured
+ */
+int CCSBot::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ CBaseEntity *attacker = info.GetInflictor();
+
+ // getting hurt makes us alert
+ BecomeAlert();
+ StopWaiting();
+
+ // if we were attacked by a teammate, rebuke
+ if (attacker->IsPlayer())
+ {
+ CCSPlayer *player = static_cast<CCSPlayer *>( attacker );
+
+ if (InSameTeam( player ) && !player->IsBot())
+ GetChatter()->FriendlyFire();
+ }
+
+ if (attacker->IsPlayer() && IsEnemy( attacker ))
+ {
+ // Track previous attacker so we don't try to panic multiple times for a shotgun blast
+ CCSPlayer *lastAttacker = m_attacker;
+ float lastAttackedTimestamp = m_attackedTimestamp;
+
+ // keep track of our last attacker
+ m_attacker = reinterpret_cast<CCSPlayer *>( attacker );
+ m_attackedTimestamp = gpGlobals->curtime;
+
+ // no longer safe
+ AdjustSafeTime();
+
+ if ( !IsSurprised() && (m_attacker != lastAttacker || m_attackedTimestamp != lastAttackedTimestamp) )
+ {
+ CCSPlayer *enemy = static_cast<CCSPlayer *>( attacker );
+
+ // being hurt by an enemy we can't see causes panic
+ if (!IsVisible( enemy, CHECK_FOV ))
+ {
+ // if not attacking anything, look around to try to find attacker
+ if (!IsAttacking())
+ {
+ Panic();
+ }
+ else // we are attacking
+ {
+ if (!IsEnemyVisible())
+ {
+ // can't see our current enemy, panic to acquire new attacker
+ Panic();
+ }
+ }
+ }
+ }
+ }
+
+ // extend
+ return BaseClass::OnTakeDamage( info );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked when killed
+ */
+void CCSBot::Event_Killed( const CTakeDamageInfo &info )
+{
+// PrintIfWatched( "Killed( attacker = %s )\n", STRING(pevAttacker->netname) );
+
+ GetChatter()->OnDeath();
+
+ // increase the danger where we died
+ const float deathDanger = 1.0f;
+ const float deathDangerRadius = 500.0f;
+ TheNavMesh->IncreaseDangerNearby( GetTeamNumber(), deathDanger, m_lastKnownArea, GetAbsOrigin(), deathDangerRadius );
+
+ // end voice feedback
+ m_voiceEndTimestamp = 0.0f;
+
+ // extend
+ BaseClass::Event_Killed( info );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if line segment intersects rectagular volume
+ */
+#define HI_X 0x01
+#define LO_X 0x02
+#define HI_Y 0x04
+#define LO_Y 0x08
+#define HI_Z 0x10
+#define LO_Z 0x20
+
+inline bool IsIntersectingBox( const Vector& start, const Vector& end, const Vector& boxMin, const Vector& boxMax )
+{
+ unsigned char startFlags = 0;
+ unsigned char endFlags = 0;
+
+ // classify start point
+ if (start.x < boxMin.x)
+ startFlags |= LO_X;
+ if (start.x > boxMax.x)
+ startFlags |= HI_X;
+
+ if (start.y < boxMin.y)
+ startFlags |= LO_Y;
+ if (start.y > boxMax.y)
+ startFlags |= HI_Y;
+
+ if (start.z < boxMin.z)
+ startFlags |= LO_Z;
+ if (start.z > boxMax.z)
+ startFlags |= HI_Z;
+
+ // classify end point
+ if (end.x < boxMin.x)
+ endFlags |= LO_X;
+ if (end.x > boxMax.x)
+ endFlags |= HI_X;
+
+ if (end.y < boxMin.y)
+ endFlags |= LO_Y;
+ if (end.y > boxMax.y)
+ endFlags |= HI_Y;
+
+ if (end.z < boxMin.z)
+ endFlags |= LO_Z;
+ if (end.z > boxMax.z)
+ endFlags |= HI_Z;
+
+ // trivial reject
+ if (startFlags & endFlags)
+ return false;
+
+ /// @todo Do exact line/box intersection check
+
+ return true;
+}
+
+
+extern void UTIL_DrawBox( Extent *extent, int lifetime, int red, int green, int blue );
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * When bot is touched by another entity.
+ */
+void CCSBot::Touch( CBaseEntity *other )
+{
+ // EXTEND
+ BaseClass::Touch( other );
+
+ // if we have touched a higher-priority player, make way
+ /// @todo Need to account for reaction time, etc.
+ if (other->IsPlayer())
+ {
+ // if we are defusing a bomb, don't move
+ if (IsDefusingBomb())
+ return;
+
+ // if we are on a ladder, don't move
+ if (IsUsingLadder())
+ return;
+
+ CCSPlayer *player = static_cast<CCSPlayer *>( other );
+
+ // get priority of other player
+ unsigned int otherPri = TheCSBots()->GetPlayerPriority( player );
+
+ // get our priority
+ unsigned int myPri = TheCSBots()->GetPlayerPriority( this );
+
+ // if our priority is better, don't budge
+ if (myPri < otherPri)
+ return;
+
+ // they are higher priority - make way, unless we're already making way for someone more important
+ if (m_avoid != NULL)
+ {
+ unsigned int avoidPri = TheCSBots()->GetPlayerPriority( static_cast<CBasePlayer *>( static_cast<CBaseEntity *>( m_avoid ) ) );
+ if (avoidPri < otherPri)
+ {
+ // ignore 'other' because we're already avoiding someone better
+ return;
+ }
+ }
+
+ m_avoid = other;
+ m_avoidTimestamp = gpGlobals->curtime;
+ }
+
+ // Check for breakables we're actually touching
+ // If we're not stuck or crouched, we don't care
+ if ( !m_isStuck && !IsCrouching() && !IsOnLadder() )
+ return;
+
+ // See if it's breakable
+ if ( IsBreakableEntity( other ) )
+ {
+ // it's breakable - try to shoot it.
+ SetLookAt( "Breakable", other->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are busy doing something important
+ */
+bool CCSBot::IsBusy( void ) const
+{
+ if (IsAttacking() ||
+ IsBuying() ||
+ IsDefusingBomb() ||
+ GetTask() == PLANT_BOMB ||
+ GetTask() == RESCUE_HOSTAGES ||
+ IsSniping())
+ {
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+void CCSBot::BotDeathThink( void )
+{
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Try to join the given team
+ */
+void CCSBot::TryToJoinTeam( int team )
+{
+ m_desiredTeam = team;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Assign given player as our current enemy to attack
+ */
+void CCSBot::SetBotEnemy( CCSPlayer *enemy )
+{
+ if (m_enemy != enemy)
+ {
+ m_enemy = enemy;
+ m_currentEnemyAcquireTimestamp = gpGlobals->curtime;
+
+ PrintIfWatched( "SetBotEnemy: %s\n", (enemy) ? enemy->GetPlayerName() : "(NULL)" );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * If we are not on the navigation mesh (m_currentArea == NULL),
+ * move towards last known area.
+ * Return false if off mesh.
+ */
+bool CCSBot::StayOnNavMesh( void )
+{
+ if (m_currentArea == NULL)
+ {
+ // move back onto the area map
+
+ // if we have no lastKnownArea, we probably started off
+ // of the nav mesh - find the closest nav area and use it
+ CNavArea *goalArea;
+ if (!m_currentArea && !m_lastKnownArea)
+ {
+ goalArea = TheNavMesh->GetNearestNavArea( GetCentroid( this ) );
+ PrintIfWatched( "Started off the nav mesh - moving to closest nav area...\n" );
+ }
+ else
+ {
+ goalArea = m_lastKnownArea;
+ PrintIfWatched( "Getting out of NULL area...\n" );
+ }
+
+ if (goalArea)
+ {
+ Vector pos;
+ goalArea->GetClosestPointOnArea( GetCentroid( this ), &pos );
+
+ // move point into area
+ Vector to = pos - GetCentroid( this );
+ to.NormalizeInPlace();
+
+ const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size
+ pos = pos + (stepInDist * to);
+
+ MoveTowardsPosition( pos );
+ }
+
+ // if we're stuck, try to get un-stuck
+ // do stuck movements last, so they override normal movement
+ if (m_isStuck)
+ Wiggle();
+
+ return false;
+ }
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we will do scenario-related tasks
+ */
+bool CCSBot::IsDoingScenario( void ) const
+{
+ // if we are deferring to humans, and there is a live human on our team, don't do the scenario
+ if (cv_bot_defer_to_human.GetBool())
+ {
+ if (UTIL_HumansOnTeam( GetTeamNumber(), IS_ALIVE ))
+ return false;
+ }
+
+ return true;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we noticed the bomb on the ground or on the radar (for T's only)
+ */
+bool CCSBot::NoticeLooseBomb( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
+ return false;
+
+ CBaseEntity *bomb = ctrl->GetLooseBomb();
+
+ if (bomb)
+ {
+ // T's can always see bomb on their radar
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if can see the bomb lying on the ground
+ */
+bool CCSBot::CanSeeLooseBomb( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
+ return false;
+
+ CBaseEntity *bomb = ctrl->GetLooseBomb();
+
+ if (bomb)
+ {
+ if (IsVisible( bomb->GetAbsOrigin(), CHECK_FOV ))
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if can see the planted bomb
+ */
+bool CCSBot::CanSeePlantedBomb( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
+ return false;
+
+ if (!GetGameState()->IsBombPlanted())
+ return false;
+
+ const Vector *bombPos = GetGameState()->GetBombPosition();
+
+ if (bombPos && IsVisible( *bombPos, CHECK_FOV ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return last enemy that hurt us
+ */
+CCSPlayer *CCSBot::GetAttacker( void ) const
+{
+ if (m_attacker && m_attacker->IsAlive())
+ return m_attacker;
+
+ return NULL;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Immediately jump off of our ladder, if we're on one
+ */
+void CCSBot::GetOffLadder( void )
+{
+ if (IsUsingLadder())
+ {
+ Jump( MUST_JUMP );
+ DestroyPath();
+ }
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return time when given spot was last checked
+ */
+float CCSBot::GetHidingSpotCheckTimestamp( HidingSpot *spot ) const
+{
+ for( int i=0; i<m_checkedHidingSpotCount; ++i )
+ if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
+ return m_checkedHidingSpot[i].timestamp;
+
+ return -999999.9f;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Set the timestamp of the given spot to now.
+ * If the spot is not in the set, overwrite the least recently checked spot.
+ */
+void CCSBot::SetHidingSpotCheckTimestamp( HidingSpot *spot )
+{
+ int leastRecent = 0;
+ float leastRecentTime = gpGlobals->curtime + 1.0f;
+
+ for( int i=0; i<m_checkedHidingSpotCount; ++i )
+ {
+ // if spot is in the set, just update its timestamp
+ if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
+ {
+ m_checkedHidingSpot[i].timestamp = gpGlobals->curtime;
+ return;
+ }
+
+ // keep track of least recent spot
+ if (m_checkedHidingSpot[i].timestamp < leastRecentTime)
+ {
+ leastRecentTime = m_checkedHidingSpot[i].timestamp;
+ leastRecent = i;
+ }
+ }
+
+ // if there is room for more spots, append this one
+ if (m_checkedHidingSpotCount < MAX_CHECKED_SPOTS)
+ {
+ m_checkedHidingSpot[ m_checkedHidingSpotCount ].spot = spot;
+ m_checkedHidingSpot[ m_checkedHidingSpotCount ].timestamp = gpGlobals->curtime;
+ ++m_checkedHidingSpotCount;
+ }
+ else
+ {
+ // replace the least recent spot
+ m_checkedHidingSpot[ leastRecent ].spot = spot;
+ m_checkedHidingSpot[ leastRecent ].timestamp = gpGlobals->curtime;
+ }
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Periodic check of hostage count in case we lost some
+ */
+void CCSBot::UpdateHostageEscortCount( void )
+{
+ const float updateInterval = 1.0f;
+ if (m_hostageEscortCount == 0 || gpGlobals->curtime - m_hostageEscortCountTimestamp < updateInterval)
+ return;
+
+ m_hostageEscortCountTimestamp = gpGlobals->curtime;
+
+ // recount the hostages in case we lost some
+ m_hostageEscortCount = 0;
+
+ for( int i=0; i<g_Hostages.Count(); ++i )
+ {
+ CHostage *hostage = g_Hostages[i];
+
+ // skip dead or rescued hostages
+ if ( !hostage->IsValid() || !hostage->IsAlive() )
+ continue;
+
+ // check if hostage has targeted us, and is following
+ if ( hostage->IsFollowing( this ) )
+ ++m_hostageEscortCount;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are outnumbered by enemies
+ */
+bool CCSBot::IsOutnumbered( void ) const
+{
+ return (GetNearbyFriendCount() < GetNearbyEnemyCount()-1) ? true : false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return number of enemies we are outnumbered by
+ */
+int CCSBot::OutnumberedCount( void ) const
+{
+ if (IsOutnumbered())
+ return (GetNearbyEnemyCount()-1) - GetNearbyFriendCount();
+
+ return 0;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the closest "important" enemy for the given scenario (bomb carrier, VIP, hostage escorter)
+ */
+CCSPlayer *CCSBot::GetImportantEnemy( bool checkVisibility ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+ CCSPlayer *nearEnemy = NULL;
+ float nearDist = 999999999.9f;
+
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CBaseEntity *entity = UTIL_PlayerByIndex( i );
+
+ if (entity == NULL)
+ continue;
+
+// if (FNullEnt( entity->pev ))
+// continue;
+
+// if (FStrEq( STRING( entity->pev->netname ), "" ))
+// continue;
+
+ // is it a player?
+ if (!entity->IsPlayer())
+ continue;
+
+ CCSPlayer *player = static_cast<CCSPlayer *>( entity );
+
+ // is it alive?
+ if (!player->IsAlive())
+ continue;
+
+ // skip friends
+ if (InSameTeam( player ))
+ continue;
+
+ // is it "important"
+ if (!ctrl->IsImportantPlayer( player ))
+ continue;
+
+ // is it closest?
+ Vector d = GetAbsOrigin() - player->GetAbsOrigin();
+ float distSq = d.x*d.x + d.y*d.y + d.z*d.z;
+ if (distSq < nearDist)
+ {
+ if (checkVisibility && !IsVisible( player, CHECK_FOV ))
+ continue;
+
+ nearEnemy = player;
+ nearDist = distSq;
+ }
+ }
+
+ return nearEnemy;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Sets our current disposition
+ */
+void CCSBot::SetDisposition( DispositionType disposition )
+{
+ m_disposition = disposition;
+
+ if (m_disposition != IGNORE_ENEMIES)
+ m_ignoreEnemiesTimer.Invalidate();
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return our current disposition
+ */
+CCSBot::DispositionType CCSBot::GetDisposition( void ) const
+{
+ if (!m_ignoreEnemiesTimer.IsElapsed())
+ return IGNORE_ENEMIES;
+
+ return m_disposition;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Ignore enemies for a short durationy
+ */
+void CCSBot::IgnoreEnemies( float duration )
+{
+ m_ignoreEnemiesTimer.Start( duration );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Increase morale one step
+ */
+void CCSBot::IncreaseMorale( void )
+{
+ if (m_morale < EXCELLENT)
+ m_morale = static_cast<MoraleType>( m_morale + 1 );
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Decrease morale one step
+ */
+void CCSBot::DecreaseMorale( void )
+{
+ if (m_morale > TERRIBLE)
+ m_morale = static_cast<MoraleType>( m_morale - 1 );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are acting like a rogue (not listening to teammates, not doing scenario goals)
+ * @todo Account for morale
+ */
+bool CCSBot::IsRogue( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+ if (!ctrl->AllowRogues())
+ return false;
+
+ // periodically re-evaluate our rogue status
+ if (m_rogueTimer.IsElapsed())
+ {
+ m_rogueTimer.Start( RandomFloat( 10.0f, 30.0f ) );
+
+ // our chance of going rogue is inversely proportional to our teamwork attribute
+ const float rogueChance = 100.0f * (1.0f - GetProfile()->GetTeamwork());
+
+ m_isRogue = (RandomFloat( 0, 100 ) < rogueChance);
+ }
+
+ return m_isRogue;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we are in a hurry
+ */
+bool CCSBot::IsHurrying( void ) const
+{
+ if (!m_hurryTimer.IsElapsed())
+ return true;
+
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ // if the bomb has been planted, we are in a hurry, CT or T (they could be defusing it!)
+ if (ctrl->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB && ctrl->IsBombPlanted())
+ return true;
+
+ // if we are a T and hostages are being rescued, we are in a hurry
+ if (ctrl->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES &&
+ GetTeamNumber() == TEAM_TERRORIST &&
+ GetGameState()->AreAllHostagesBeingRescued())
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if it is the early, "safe", part of the round
+ */
+bool CCSBot::IsSafe( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ if (ctrl->GetElapsedRoundTime() < m_safeTime)
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if it is well past the early, "safe", part of the round
+ */
+bool CCSBot::IsWellPastSafe( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ if (ctrl->GetElapsedRoundTime() > 2.0f * m_safeTime)
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we were in the safe time last update, but not now
+ */
+bool CCSBot::IsEndOfSafeTime( void ) const
+{
+ return m_wasSafe && !IsSafe();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return the amount of "safe time" we have left
+ */
+float CCSBot::GetSafeTimeRemaining( void ) const
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ return m_safeTime - ctrl->GetElapsedRoundTime();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Called when enemy seen to adjust safe time for this round
+ */
+void CCSBot::AdjustSafeTime( void )
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ // if we spotted an enemy sooner than we thought possible, adjust our notion of "safe" time
+ if (ctrl->GetElapsedRoundTime() < m_safeTime)
+ {
+ // since right now is not safe, adjust safe time to be a few seconds ago
+ m_safeTime = ctrl->GetElapsedRoundTime() - 2.0f;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if we haven't seen an enemy for "a long time"
+ */
+bool CCSBot::HasNotSeenEnemyForLongTime( void ) const
+{
+ const float longTime = 30.0f;
+ return (GetTimeSinceLastSawEnemy() > longTime);
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Pick a random zone and hide near it
+ */
+bool CCSBot::GuardRandomZone( float range )
+{
+ CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
+
+ const CCSBotManager::Zone *zone = ctrl->GetRandomZone();
+ if (zone)
+ {
+ CNavArea *rescueArea = ctrl->GetRandomAreaInZone( zone );
+ if (rescueArea)
+ {
+ Hide( rescueArea, -1.0f, range );
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+
+//--------------------------------------------------------------------------------------------------------------
+class CollectRetreatSpotsFunctor
+{
+public:
+ CollectRetreatSpotsFunctor( CCSBot *me, float range )
+ {
+ m_me = me;
+ m_count = 0;
+ m_range = range;
+ }
+
+ enum { MAX_SPOTS = 256 };
+
+ bool operator() ( CNavArea *area )
+ {
+ // collect all the hiding spots in this area
+ const HidingSpotVector *pSpots = area->GetHidingSpots();
+
+ FOR_EACH_VEC( (*pSpots), it )
+ {
+ const HidingSpot *spot = (*pSpots)[ it ];
+
+ if (m_count >= MAX_SPOTS)
+ break;
+
+ // make sure hiding spot is in range
+ if (m_range > 0.0f)
+ if ((spot->GetPosition() - GetCentroid( m_me )).IsLengthGreaterThan( m_range ))
+ continue;
+
+ // if a Player is using this hiding spot, don't consider it
+ if (IsSpotOccupied( m_me, spot->GetPosition() ))
+ {
+ // player is in hiding spot
+ /// @todo Check if player is moving or sitting still
+ continue;
+ }
+
+ // don't select spot if an enemy can see it
+ if (UTIL_IsVisibleToTeam( spot->GetPosition() + Vector( 0, 0, HalfHumanHeight ), OtherTeam( m_me->GetTeamNumber() ) ))
+ continue;
+
+ // don't select spot if it is closest to an enemy
+ CBasePlayer *owner = UTIL_GetClosestPlayer( spot->GetPosition() );
+ if (owner && !m_me->InSameTeam( owner ))
+ continue;
+
+ m_spot[ m_count++ ] = &spot->GetPosition();
+ }
+
+ // if we've filled up, stop searching
+ if (m_count == MAX_SPOTS)
+ return false;
+
+ return true;
+ }
+
+ CCSBot *m_me;
+ float m_range;
+
+ const Vector *m_spot[ MAX_SPOTS ];
+ int m_count;
+};
+
+
+/**
+ * Do a breadth-first search to find a good retreat spot.
+ * Don't pick a spot that a Player is currently occupying.
+ */
+const Vector *FindNearbyRetreatSpot( CCSBot *me, float maxRange )
+{
+ CNavArea *area = me->GetLastKnownArea();
+ if (area == NULL)
+ return NULL;
+
+ // collect spots that enemies cannot see
+ CollectRetreatSpotsFunctor collector( me, maxRange );
+ SearchSurroundingAreas( area, GetCentroid( me ), collector, maxRange );
+
+ if (collector.m_count == 0)
+ return NULL;
+
+ // select a hiding spot at random
+ int which = RandomInt( 0, collector.m_count-1 );
+ return collector.m_spot[ which ];
+}
+
+//--------------------------------------------------------------------------------------------------------------
+class FarthestHostage
+{
+public:
+ FarthestHostage( const CCSBot *me )
+ {
+ m_me = me;
+ m_farRange = -1.0f;
+ }
+
+ bool operator() ( CHostage *hostage )
+ {
+ if (hostage->IsFollowing( m_me ))
+ {
+ float range = (hostage->GetAbsOrigin() - m_me->GetAbsOrigin()).Length();
+ if (range > m_farRange)
+ {
+ m_farRange = range;
+ }
+ }
+
+ return true;
+ }
+
+ const CCSBot *m_me;
+ float m_farRange;
+};
+
+/**
+ * Return euclidean distance to farthest escorted hostage.
+ * Return -1 if no hostage is following us.
+ */
+float CCSBot::GetRangeToFarthestEscortedHostage( void ) const
+{
+ FarthestHostage away( this );
+
+ ForEachHostage( away );
+
+ return away.m_farRange;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return string describing current task
+ * NOTE: This MUST be kept in sync with the CCSBot::TaskType enum
+ */
+const char *CCSBot::GetTaskName( void ) const
+{
+ static const char *name[ NUM_TASKS ] =
+ {
+ "SEEK_AND_DESTROY",
+ "PLANT_BOMB",
+ "FIND_TICKING_BOMB",
+ "DEFUSE_BOMB",
+ "GUARD_TICKING_BOMB",
+ "GUARD_BOMB_DEFUSER",
+ "GUARD_LOOSE_BOMB",
+ "GUARD_BOMB_ZONE",
+ "GUARD_INITIAL_ENCOUNTER",
+ "ESCAPE_FROM_BOMB",
+ "HOLD_POSITION",
+ "FOLLOW",
+ "VIP_ESCAPE",
+ "GUARD_VIP_ESCAPE_ZONE",
+ "COLLECT_HOSTAGES",
+ "RESCUE_HOSTAGES",
+ "GUARD_HOSTAGES",
+ "GUARD_HOSTAGE_RESCUE_ZONE",
+ "MOVE_TO_LAST_KNOWN_ENEMY_POSITION",
+ "MOVE_TO_SNIPER_SPOT",
+ "SNIPING",
+ };
+
+ return name[ (int)GetTask() ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return string describing current disposition
+ * NOTE: This MUST be kept in sync with the CCSBot::DispositionType enum
+ */
+const char *CCSBot::GetDispositionName( void ) const
+{
+ static const char *name[ NUM_DISPOSITIONS ] =
+ {
+ "ENGAGE_AND_INVESTIGATE",
+ "OPPORTUNITY_FIRE",
+ "SELF_DEFENSE",
+ "IGNORE_ENEMIES"
+ };
+
+ return name[ (int)GetDisposition() ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return string describing current morale
+ * NOTE: This MUST be kept in sync with the CCSBot::MoraleType enum
+ */
+const char *CCSBot::GetMoraleName( void ) const
+{
+ static const char *name[ EXCELLENT - TERRIBLE + 1 ] =
+ {
+ "TERRIBLE",
+ "BAD",
+ "NEGATIVE",
+ "NEUTRAL",
+ "POSITIVE",
+ "GOOD",
+ "EXCELLENT"
+ };
+
+ return name[ (int)GetMorale() + 3 ];
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Fill in a CUserCmd with our data
+ */
+void CCSBot::BuildUserCmd( CUserCmd& cmd, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse )
+{
+ Q_memset( &cmd, 0, sizeof( cmd ) );
+ if ( !RunMimicCommand( cmd ) )
+ {
+ // Don't walk when ducked - it's painfully slow
+ if ( m_Local.m_bDucked || m_Local.m_bDucking )
+ {
+ buttons &= ~IN_SPEED;
+ }
+
+ cmd.command_number = gpGlobals->tickcount;
+ cmd.forwardmove = forwardmove;
+ cmd.sidemove = sidemove;
+ cmd.upmove = upmove;
+ cmd.buttons = buttons;
+ cmd.impulse = impulse;
+
+ VectorCopy( viewangles, cmd.viewangles );
+ cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------