|
|
//========= Copyright � 1996-2005, 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 ) { 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()) { AssertMsg(false, "Check console spew for Hiding Spot off the Nav Mesh errors."); continue; }
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 ]; }
|