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/shared/cstrike/bot/bot_hide.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/shared/cstrike/bot/bot_hide.cpp')
| -rw-r--r-- | game/shared/cstrike/bot/bot_hide.cpp | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/game/shared/cstrike/bot/bot_hide.cpp b/game/shared/cstrike/bot/bot_hide.cpp new file mode 100644 index 0000000..9a2b02c --- /dev/null +++ b/game/shared/cstrike/bot/bot_hide.cpp @@ -0,0 +1,490 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// bot_hide.cpp +// Mechanisms for using Hiding Spots in the Navigation Mesh +// Author: Michael Booth, 2003-2004 + +#include "cbase.h" +#include "bot.h" +#include "cs_nav_pathfind.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//-------------------------------------------------------------------------------------------------------------- +/** + * If a player is at the given spot, return true + */ +bool IsSpotOccupied( CBaseEntity *me, const Vector &pos ) +{ + const float closeRange = 75.0f; // 50 + + // is there a player in this spot + float range; + CBasePlayer *player = UTIL_GetClosestPlayer( pos, &range ); + + if (player != me) + { + if (player && range < closeRange) + return true; + } + + // is there is a hostage in this spot + // BOTPORT: Implement hostage manager + /* + if (g_pHostages) + { + CHostage *hostage = g_pHostages->GetClosestHostage( *pos, &range ); + if (hostage && hostage != me && range < closeRange) + return true; + } + */ + + return false; +} + +//-------------------------------------------------------------------------------------------------------------- +class CollectHidingSpotsFunctor +{ +public: + CollectHidingSpotsFunctor( CBaseEntity *me, const Vector &origin, float range, int flags, Place place = UNDEFINED_PLACE ) : m_origin( origin ) + { + m_me = me; + m_count = 0; + m_range = range; + m_flags = (unsigned char)flags; + m_place = place; + m_totalWeight = 0; + } + + enum { MAX_SPOTS = 256 }; + + bool operator() ( CNavArea *area ) + { + // if a place is specified, only consider hiding spots from areas in that place + if (m_place != UNDEFINED_PLACE && area->GetPlace() != m_place) + return true; + + // collect all the hiding spots in this area + const HidingSpotVector *pSpots = area->GetHidingSpots(); + + FOR_EACH_VEC( (*pSpots), it ) + { + const HidingSpot *spot = (*pSpots)[ it ]; + + // if we've filled up, stop searching + if (m_count == MAX_SPOTS) + { + return false; + } + + // make sure hiding spot is in range + if (m_range > 0.0f) + { + if ((spot->GetPosition() - m_origin).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; + } + + if (spot->GetArea() && (spot->GetArea()->GetAttributes() & NAV_MESH_DONT_HIDE)) + { + // the area has been marked as DONT_HIDE since the last analysis, so let's ignore it + continue; + } + + // only collect hiding spots with matching flags + if (m_flags & spot->GetFlags()) + { + m_hidingSpot[ m_count ] = &spot->GetPosition(); + m_hidingSpotWeight[ m_count ] = m_totalWeight; + + // if it's an 'avoid' area, give it a low weight + if ( spot->GetArea() && ( spot->GetArea()->GetAttributes() & NAV_MESH_AVOID ) ) + { + m_totalWeight += 1; + } + else + { + m_totalWeight += 2; + } + + ++m_count; + } + } + + return (m_count < MAX_SPOTS); + } + + /** + * Remove the spot at index "i" + */ + void RemoveSpot( int i ) + { + if (m_count == 0) + return; + + for( int j=i+1; j<m_count; ++j ) + m_hidingSpot[j-1] = m_hidingSpot[j]; + + --m_count; + } + + + int GetRandomHidingSpot( void ) + { + int weight = RandomInt( 0, m_totalWeight-1 ); + for ( int i=0; i<m_count-1; ++i ) + { + // if the next spot's starting weight is over the target weight, this spot is the one + if ( m_hidingSpotWeight[i+1] >= weight ) + { + return i; + } + } + + // if we didn't find any, it's the last one + return m_count - 1; + } + + CBaseEntity *m_me; + const Vector &m_origin; + float m_range; + + const Vector *m_hidingSpot[ MAX_SPOTS ]; + int m_hidingSpotWeight[ MAX_SPOTS ]; + int m_totalWeight; + int m_count; + + unsigned char m_flags; + + Place m_place; +}; + +/** + * Do a breadth-first search to find a nearby hiding spot and return it. + * Don't pick a hiding spot that a Player is currently occupying. + * @todo Clean up this mess + */ +const Vector *FindNearbyHidingSpot( CBaseEntity *me, const Vector &pos, float maxRange, bool isSniper, bool useNearest ) +{ + CNavArea *startArea = TheNavMesh->GetNearestNavArea( pos ); + if (startArea == NULL) + return NULL; + + // collect set of nearby hiding spots + if (isSniper) + { + CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::IDEAL_SNIPER_SPOT ); + SearchSurroundingAreas( startArea, pos, collector, maxRange ); + + if (collector.m_count) + { + int which = collector.GetRandomHidingSpot(); + return collector.m_hidingSpot[ which ]; + } + else + { + // no ideal sniping spots, look for "good" sniping spots + CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::GOOD_SNIPER_SPOT ); + SearchSurroundingAreas( startArea, pos, collector, maxRange ); + + if (collector.m_count) + { + int which = collector.GetRandomHidingSpot(); + return collector.m_hidingSpot[ which ]; + } + + // no sniping spots at all.. fall through and pick a normal hiding spot + } + } + + // collect hiding spots with decent "cover" + CollectHidingSpotsFunctor collector( me, pos, maxRange, HidingSpot::IN_COVER ); + SearchSurroundingAreas( startArea, pos, collector, maxRange ); + + if (collector.m_count == 0) + { + // no hiding spots at all - if we're not a sniper, try to find a sniper spot to use instead + if (!isSniper) + { + return FindNearbyHidingSpot( me, pos, maxRange, true, useNearest ); + } + + return NULL; + } + + if (useNearest) + { + // return closest hiding spot + const Vector *closest = NULL; + float closeRangeSq = 9999999999.9f; + for( int i=0; i<collector.m_count; ++i ) + { + float rangeSq = (*collector.m_hidingSpot[i] - pos).LengthSqr(); + if (rangeSq < closeRangeSq) + { + closeRangeSq = rangeSq; + closest = collector.m_hidingSpot[i]; + } + } + + return closest; + } + + // select a hiding spot at random + int which = collector.GetRandomHidingSpot(); + return collector.m_hidingSpot[ which ]; +} + + +//-------------------------------------------------------------------------------------------------------------- +/** + * Select a random hiding spot among the nav areas that are tagged with the given place + */ +const Vector *FindRandomHidingSpot( CBaseEntity *me, Place place, bool isSniper ) +{ + // collect set of nearby hiding spots + if (isSniper) + { + CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::IDEAL_SNIPER_SPOT, place ); + TheNavMesh->ForAllAreas( collector ); + + if (collector.m_count) + { + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; + } + else + { + // no ideal sniping spots, look for "good" sniping spots + CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::GOOD_SNIPER_SPOT, place ); + TheNavMesh->ForAllAreas( collector ); + + if (collector.m_count) + { + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; + } + + // no sniping spots at all.. fall through and pick a normal hiding spot + } + } + + // collect hiding spots with decent "cover" + CollectHidingSpotsFunctor collector( me, me->GetAbsOrigin(), -1.0f, HidingSpot::IN_COVER, place ); + TheNavMesh->ForAllAreas( collector ); + + if (collector.m_count == 0) + return NULL; + + // select a hiding spot at random + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; +} + + +//-------------------------------------------------------------------------------------------------------------------- +/** + * Select a nearby retreat spot. + * Don't pick a hiding spot that a Player is currently occupying. + * If "avoidTeam" is nonzero, avoid getting close to members of that team. + */ +const Vector *FindNearbyRetreatSpot( CBaseEntity *me, const Vector &start, float maxRange, int avoidTeam ) +{ + CNavArea *startArea = TheNavMesh->GetNearestNavArea( start ); + if (startArea == NULL) + return NULL; + + // collect hiding spots with decent "cover" + CollectHidingSpotsFunctor collector( me, start, maxRange, HidingSpot::IN_COVER ); + SearchSurroundingAreas( startArea, start, collector, maxRange ); + + if (collector.m_count == 0) + return NULL; + + // find the closest unoccupied hiding spot that crosses the least lines of fire and has the best cover + for( int i=0; i<collector.m_count; ++i ) + { + // check if we would have to cross a line of fire to reach this hiding spot + if (IsCrossingLineOfFire( start, *collector.m_hidingSpot[i], me )) + { + collector.RemoveSpot( i ); + + // back up a step, so iteration won't skip a spot + --i; + + continue; + } + + // check if there is someone on the avoidTeam near this hiding spot + if (avoidTeam) + { + float range; + if (UTIL_GetClosestPlayer( *collector.m_hidingSpot[i], avoidTeam, &range )) + { + const float dangerRange = 150.0f; + if (range < dangerRange) + { + // there is an avoidable player too near this spot - remove it + collector.RemoveSpot( i ); + + // back up a step, so iteration won't skip a spot + --i; + + continue; + } + } + } + } + + if (collector.m_count <= 0) + return NULL; + + // all remaining spots are ok - pick one at random + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; +} + + +//-------------------------------------------------------------------------------------------------------------------- +/** + * Functor to collect all hiding spots in range that we can reach before the enemy arrives. + * NOTE: This only works for the initial rush. + */ +class CollectArriveFirstSpotsFunctor +{ +public: + CollectArriveFirstSpotsFunctor( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float range, int flags ) : m_searchOrigin( searchOrigin ) + { + m_me = me; + m_count = 0; + m_range = range; + m_flags = (unsigned char)flags; + m_enemyArriveTime = enemyArriveTime; + } + + 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 ]; + + // make sure hiding spot is in range + if (m_range > 0.0f) + { + if ((spot->GetPosition() - m_searchOrigin).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; + } + + // only collect hiding spots with matching flags + if (!(m_flags & spot->GetFlags())) + { + continue; + } + + // only collect this hiding spot if we can reach it before the enemy arrives + // NOTE: This assumes the area is fairly small and the difference of moving to the corner vs the center is small + const float settleTime = 1.0f; + if (spot->GetArea()->GetEarliestOccupyTime( m_me->GetTeamNumber() ) + settleTime < m_enemyArriveTime) + { + m_hidingSpot[ m_count++ ] = spot; + } + } + + // if we've filled up, stop searching + if (m_count == MAX_SPOTS) + return false; + + return true; + } + + CBaseEntity *m_me; + const Vector &m_searchOrigin; + + float m_range; + float m_enemyArriveTime; + unsigned char m_flags; + + const HidingSpot *m_hidingSpot[ MAX_SPOTS ]; + int m_count; +}; + + +/** + * Select a hiding spot that we can reach before the enemy arrives. + * NOTE: This only works for the initial rush. + */ +const HidingSpot *FindInitialEncounterSpot( CBaseEntity *me, const Vector &searchOrigin, float enemyArriveTime, float maxRange, bool isSniper ) +{ + CNavArea *startArea = TheNavMesh->GetNearestNavArea( searchOrigin ); + if (startArea == NULL) + return NULL; + + // collect set of nearby hiding spots + if (isSniper) + { + CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::IDEAL_SNIPER_SPOT ); + SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange ); + + if (collector.m_count) + { + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; + } + else + { + // no ideal sniping spots, look for "good" sniping spots + CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::GOOD_SNIPER_SPOT ); + SearchSurroundingAreas( startArea, searchOrigin, collector, maxRange ); + + if (collector.m_count) + { + int which = RandomInt( 0, collector.m_count-1 ); + return collector.m_hidingSpot[ which ]; + } + + // no sniping spots at all.. fall through and pick a normal hiding spot + } + } + + // collect hiding spots with decent "cover" + CollectArriveFirstSpotsFunctor collector( me, searchOrigin, enemyArriveTime, maxRange, HidingSpot::IN_COVER | HidingSpot::EXPOSED ); + SearchSurroundingAreas( startArea, searchOrigin, 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_hidingSpot[ which ]; +} + |