diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/cstrike/bot/cs_bot.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/cstrike/bot/cs_bot.cpp')
| -rw-r--r-- | game/server/cstrike/bot/cs_bot.cpp | 1117 |
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 ); + } +} + +//-------------------------------------------------------------------------------------------------------------- |