Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.

768 lines
19 KiB

5 years ago
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Encapsulation of the current scenario/game state. Allows each bot imperfect knowledge.
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "KeyValues.h"
#include "cs_bot.h"
#include "cs_gamestate.h"
#include "cs_simple_hostage.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
CSGameState::CSGameState( CCSBot *owner )
{
m_owner = owner;
m_isRoundOver = false;
m_bombState = MOVING;
m_lastSawBomber.Invalidate();
m_lastSawLooseBomb.Invalidate();
m_isPlantedBombPosKnown = false;
m_plantedBombsite = UNKNOWN;
m_bombsiteCount = 0;
m_bombsiteSearchIndex = 0;
for( int i=0; i<MAX_HOSTAGES; ++i )
{
m_hostage[i].hostage = NULL;
m_hostage[i].isValid = false;
m_hostage[i].isAlive = false;
m_hostage[i].isFree = true;
m_hostage[i].knownPos = Vector( 0, 0, 0 );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset at round start
*/
void CSGameState::Reset( void )
{
m_isRoundOver = false;
// bomb -----------------------------------------------------------------------
m_bombState = MOVING;
m_lastSawBomber.Invalidate();
m_lastSawLooseBomb.Invalidate();
m_isPlantedBombPosKnown = false;
m_plantedBombsite = UNKNOWN;
m_bombsiteCount = TheCSBots()->GetZoneCount();
int i;
for( i=0; i<m_bombsiteCount; ++i )
{
m_isBombsiteClear[i] = false;
m_bombsiteSearchOrder[i] = i;
}
// shuffle the bombsite search order
// allows T's to plant at random site, and TEAM_CT's to search in a random order
// NOTE: VS6 std::random_shuffle() doesn't work well with an array of two elements (most maps)
for( i=0; i < m_bombsiteCount; ++i )
{
int swap = m_bombsiteSearchOrder[i];
int rnd = RandomInt( i, m_bombsiteCount-1 );
m_bombsiteSearchOrder[i] = m_bombsiteSearchOrder[ rnd ];
m_bombsiteSearchOrder[ rnd ] = swap;
}
m_bombsiteSearchIndex = 0;
// hostage ---------------------------------------------------------------------
InitializeHostageInfo();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnHostageRescuedAll( IGameEvent *event )
{
m_allHostagesRescued = true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnRoundEnd( IGameEvent *event )
{
m_isRoundOver = true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnRoundStart( IGameEvent *event )
{
Reset();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnBombPlanted( IGameEvent *event )
{
// change state - the event is announced to everyone
SetBombState( PLANTED );
CBasePlayer *plantingPlayer = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
// Terrorists always know where the bomb is
if (m_owner->GetTeamNumber() == TEAM_TERRORIST && plantingPlayer)
{
UpdatePlantedBomb( plantingPlayer->GetAbsOrigin() );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnBombDefused( IGameEvent *event )
{
// change state - the event is announced to everyone
SetBombState( DEFUSED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Update game state based on events we have received
*/
void CSGameState::OnBombExploded( IGameEvent *event )
{
// change state - the event is announced to everyone
SetBombState( EXPLODED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* True if round has been won or lost (but not yet reset)
*/
bool CSGameState::IsRoundOver( void ) const
{
return m_isRoundOver;
}
//--------------------------------------------------------------------------------------------------------------
void CSGameState::SetBombState( BombState state )
{
// if state changed, reset "last seen" timestamps
if (m_bombState != state)
{
m_bombState = state;
}
}
//--------------------------------------------------------------------------------------------------------------
void CSGameState::UpdateLooseBomb( const Vector &pos )
{
m_looseBombPos = pos;
m_lastSawLooseBomb.Reset();
// we saw the loose bomb, update our state
SetBombState( LOOSE );
}
//--------------------------------------------------------------------------------------------------------------
float CSGameState::TimeSinceLastSawLooseBomb( void ) const
{
return m_lastSawLooseBomb.GetElapsedTime();
}
//--------------------------------------------------------------------------------------------------------------
bool CSGameState::IsLooseBombLocationKnown( void ) const
{
if (m_bombState != LOOSE)
return false;
return (m_lastSawLooseBomb.HasStarted()) ? true : false;
}
//--------------------------------------------------------------------------------------------------------------
void CSGameState::UpdateBomber( const Vector &pos )
{
m_bomberPos = pos;
m_lastSawBomber.Reset();
// we saw the bomber, update our state
SetBombState( MOVING );
}
//--------------------------------------------------------------------------------------------------------------
float CSGameState::TimeSinceLastSawBomber( void ) const
{
return m_lastSawBomber.GetElapsedTime();
}
//--------------------------------------------------------------------------------------------------------------
bool CSGameState::IsPlantedBombLocationKnown( void ) const
{
if (m_bombState != PLANTED)
return false;
return m_isPlantedBombPosKnown;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the zone index of the planted bombsite, or UNKNOWN
*/
int CSGameState::GetPlantedBombsite( void ) const
{
if (m_bombState != PLANTED)
return UNKNOWN;
return m_plantedBombsite;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are currently in the bombsite where the bomb is planted
*/
bool CSGameState::IsAtPlantedBombsite( void ) const
{
if (m_bombState != PLANTED)
return false;
Vector myOrigin = GetCentroid( m_owner );
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( myOrigin );
if (zone)
{
return (m_plantedBombsite == zone->m_index);
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the zone index of the next bombsite to search
*/
int CSGameState::GetNextBombsiteToSearch( void )
{
if (m_bombsiteCount <= 0)
return 0;
int i;
// return next non-cleared bombsite index
for( i=m_bombsiteSearchIndex; i<m_bombsiteCount; ++i )
{
int z = m_bombsiteSearchOrder[i];
if (!m_isBombsiteClear[z])
{
m_bombsiteSearchIndex = i;
return z;
}
}
// all the bombsites are clear, someone must have been mistaken - start search over
for( i=0; i<m_bombsiteCount; ++i )
m_isBombsiteClear[i] = false;
m_bombsiteSearchIndex = 0;
return GetNextBombsiteToSearch();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Returns position of bomb in its various states (moving, loose, planted),
* or NULL if we don't know where the bomb is
*/
const Vector *CSGameState::GetBombPosition( void ) const
{
switch( m_bombState )
{
case MOVING:
{
if (!m_lastSawBomber.HasStarted())
return NULL;
return &m_bomberPos;
}
case LOOSE:
{
if (IsLooseBombLocationKnown())
return &m_looseBombPos;
return NULL;
}
case PLANTED:
{
if (IsPlantedBombLocationKnown())
return &m_plantedBombPos;
return NULL;
}
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------------
/**
* We see the planted bomb at 'pos'
*/
void CSGameState::UpdatePlantedBomb( const Vector &pos )
{
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( pos );
if (zone == NULL)
{
CONSOLE_ECHO( "ERROR: Bomb planted outside of a zone!\n" );
m_plantedBombsite = UNKNOWN;
}
else
{
m_plantedBombsite = zone->m_index;
}
m_plantedBombPos = pos;
m_isPlantedBombPosKnown = true;
SetBombState( PLANTED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Someone told us where the bomb is planted
*/
void CSGameState::MarkBombsiteAsPlanted( int zoneIndex )
{
m_plantedBombsite = zoneIndex;
SetBombState( PLANTED );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Someone told us a bombsite is clear
*/
void CSGameState::ClearBombsite( int zoneIndex )
{
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
m_isBombsiteClear[ zoneIndex ] = true;
}
//--------------------------------------------------------------------------------------------------------------
bool CSGameState::IsBombsiteClear( int zoneIndex ) const
{
if (zoneIndex >= 0 && zoneIndex < m_bombsiteCount)
return m_isBombsiteClear[ zoneIndex ];
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Initialize our knowledge of the number and location of hostages
*/
void CSGameState::InitializeHostageInfo( void )
{
m_hostageCount = 0;
m_allHostagesRescued = false;
m_haveSomeHostagesBeenTaken = false;
for( int i=0; i<g_Hostages.Count(); ++i )
{
m_hostage[ m_hostageCount ].hostage = g_Hostages[i];
m_hostage[ m_hostageCount ].knownPos = g_Hostages[i]->GetAbsOrigin();
m_hostage[ m_hostageCount ].isValid = true;
m_hostage[ m_hostageCount ].isAlive = true;
m_hostage[ m_hostageCount ].isFree = true;
++m_hostageCount;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the closest free and live hostage
* If we are a CT this information is perfect.
* Otherwise, this is based on our individual memory of the game state.
* If NULL is returned, we don't think there are any hostages left, or we dont know where they are.
* NOTE: a T can remember a hostage who has died. knowPos will be filled in, but NULL will be
* returned, since CHostages get deleted when they die.
*/
CHostage *CSGameState::GetNearestFreeHostage( Vector *knowPos ) const
{
if (m_owner == NULL)
return NULL;
CNavArea *startArea = m_owner->GetLastKnownArea();
if (startArea == NULL)
return NULL;
CHostage *close = NULL;
Vector closePos( 0, 0, 0 );
float closeDistance = 9999999999.9f;
for( int i=0; i<m_hostageCount; ++i )
{
CHostage *hostage = m_hostage[i].hostage;
Vector hostagePos;
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// we know exactly where the hostages are, and if they are alive
if (!m_hostage[i].hostage || !m_hostage[i].hostage->IsValid())
continue;
if (m_hostage[i].hostage->IsFollowingSomeone())
continue;
hostagePos = m_hostage[i].hostage->GetAbsOrigin();
}
else
{
// use our memory of where we think the hostages are
if (m_hostage[i].isValid == false)
continue;
hostagePos = m_hostage[i].knownPos;
}
CNavArea *hostageArea = TheNavMesh->GetNearestNavArea( hostagePos );
if (hostageArea)
{
ShortestPathCost cost;
float travelDistance = NavAreaTravelDistance( startArea, hostageArea, cost );
if (travelDistance >= 0.0f && travelDistance < closeDistance)
{
closeDistance = travelDistance;
closePos = hostagePos;
close = hostage;
}
}
}
// return where we think the hostage is
if (knowPos && close)
*knowPos = closePos;
return close;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the location of a "free" hostage, or NULL if we dont know of any
*/
const Vector *CSGameState::GetRandomFreeHostagePosition( void ) const
{
if (m_owner == NULL)
return NULL;
static Vector freePos[ MAX_HOSTAGES ];
int freeCount = 0;
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// we know exactly where the hostages are, and if they are alive
if (!info->hostage || !info->hostage->IsAlive())
continue;
// escorted hostages are not "free"
if (info->hostage->IsFollowingSomeone())
continue;
freePos[ freeCount++ ] = info->hostage->GetAbsOrigin();
}
else
{
// use our memory of where we think the hostages are
if (info->isValid == false)
continue;
freePos[ freeCount++ ] = info->knownPos;
}
}
if (freeCount)
{
return &freePos[ RandomInt( 0, freeCount-1 ) ];
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------------
/**
* If we can see any of the positions where we think a hostage is, validate it
* Return status of any changes (a hostage died or was moved)
*/
unsigned char CSGameState::ValidateHostagePositions( void )
{
// limit how often we validate
if (!m_validateInterval.IsElapsed())
return NO_CHANGE;
const float validateInterval = 0.5f;
m_validateInterval.Start( validateInterval );
// check the status of hostages
unsigned char status = NO_CHANGE;
int i;
int startValidCount = 0;
for( i=0; i<m_hostageCount; ++i )
if (m_hostage[i].isValid)
++startValidCount;
for( i=0; i<m_hostageCount; ++i )
{
HostageInfo *info = &m_hostage[i];
if (!info->hostage )
continue;
// if we can see a hostage, update our knowledge of it
Vector pos = info->hostage->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight );
if (m_owner->IsVisible( pos, CHECK_FOV ))
{
if (info->hostage->IsAlive())
{
// live hostage
// if hostage is being escorted by a CT, we don't "see" it, we see the CT
if (info->hostage->IsFollowingSomeone())
{
info->isValid = false;
}
else
{
info->knownPos = info->hostage->GetAbsOrigin();
info->isValid = true;
}
}
else
{
// dead hostage
// if we thought it was alive, this is news to us
if (info->isAlive)
status |= HOSTAGE_DIED;
info->isAlive = false;
info->isValid = false;
}
continue;
}
// if we dont know where this hostage is, nothing to validate
if (!info->isValid)
continue;
// can't directly see this hostage
// check line of sight to where we think this hostage is, to see if we noticed that is has moved
pos = info->knownPos + Vector( 0, 0, HalfHumanHeight );
if (m_owner->IsVisible( pos, CHECK_FOV ))
{
// we can see where we thought the hostage was - verify it is still there and alive
if (!info->hostage->IsValid())
{
// since we have line of sight to an invalid hostage, it must be dead
// discovered that hostage has been killed
status |= HOSTAGE_DIED;
info->isAlive = false;
info->isValid = false;
continue;
}
if (info->hostage->IsFollowingSomeone())
{
// discovered the hostage has been taken
status |= HOSTAGE_GONE;
info->isValid = false;
continue;
}
const float tolerance = 50.0f;
if ((info->hostage->GetAbsOrigin() - info->knownPos).IsLengthGreaterThan( tolerance ))
{
// discovered that hostage has been moved
status |= HOSTAGE_GONE;
info->isValid = false;
continue;
}
}
}
int endValidCount = 0;
for( i=0; i<m_hostageCount; ++i )
if (m_hostage[i].isValid)
++endValidCount;
if (endValidCount == 0 && startValidCount > 0)
{
// we discovered all the hostages are gone
status &= ~HOSTAGE_GONE;
status |= HOSTAGES_ALL_GONE;
}
return status;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return the nearest visible free hostage
* Since we can actually see any hostage we return, we know its actual position
*/
CHostage *CSGameState::GetNearestVisibleFreeHostage( void ) const
{
CHostage *close = NULL;
float closeRangeSq = 999999999.9f;
float rangeSq;
Vector pos;
Vector myOrigin = GetCentroid( m_owner );
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if ( !info->hostage )
continue;
// if the hostage is dead or rescued, its not free
if (!info->hostage->IsAlive())
continue;
// if this hostage is following someone, its not free
if (info->hostage->IsFollowingSomeone())
continue;
/// @todo Use travel distance here
pos = info->hostage->GetAbsOrigin();
rangeSq = (pos - myOrigin).LengthSqr();
if (rangeSq < closeRangeSq)
{
if (!m_owner->IsVisible( pos ))
continue;
close = info->hostage;
closeRangeSq = rangeSq;
}
}
return close;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if there are no free hostages
*/
bool CSGameState::AreAllHostagesBeingRescued( void ) const
{
// if the hostages have all been rescued, they are not being rescued any longer
if (m_allHostagesRescued)
return false;
bool isAllDead = true;
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// CT's have perfect knowledge via their radar
if (info->hostage && info->hostage->IsValid())
{
if (!info->hostage->IsFollowingSomeone())
return false;
isAllDead = false;
}
}
else
{
if (info->isValid && info->isAlive)
return false;
if (info->isAlive)
isAllDead = false;
}
}
// if all of the remaining hostages are dead, they arent being rescued
if (isAllDead)
return false;
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* All hostages have been rescued or are dead
*/
bool CSGameState::AreAllHostagesGone( void ) const
{
if (m_allHostagesRescued)
return true;
// do we know that all the hostages are dead
for( int i=0; i<m_hostageCount; ++i )
{
const HostageInfo *info = &m_hostage[i];
if (m_owner->GetTeamNumber() == TEAM_CT)
{
// CT's have perfect knowledge via their radar
if (info->hostage && info->hostage->IsAlive())
return false;
}
else
{
if (info->isValid && info->isAlive)
return false;
}
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Someone told us all the hostages are gone
*/
void CSGameState::AllHostagesGone( void )
{
for( int i=0; i<m_hostageCount; ++i )
m_hostage[i].isValid = false;
}