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.
491 lines
13 KiB
491 lines
13 KiB
5 years ago
|
//========= 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 ];
|
||
|
}
|
||
|
|