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.
693 lines
17 KiB
693 lines
17 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003 |
|
|
|
#include "cbase.h" |
|
#include "cs_bot.h" |
|
#include "cs_nav_path.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* This method is the ONLY legal way to change a bot's current state |
|
*/ |
|
void CCSBot::SetState( BotState *state ) |
|
{ |
|
PrintIfWatched( "%s: SetState: %s -> %s\n", GetPlayerName(), (m_state) ? m_state->GetName() : "NULL", state->GetName() ); |
|
|
|
/* |
|
if ( IsDefusingBomb() ) |
|
{ |
|
const Vector *bombPos = GetGameState()->GetBombPosition(); |
|
if ( bombPos != NULL ) |
|
{ |
|
if ( TheCSBots()->GetBombDefuser() == this ) |
|
{ |
|
if ( TheCSBots()->IsBombPlanted() ) |
|
{ |
|
Msg( "Bot %s is switching from defusing the bomb to %s\n", |
|
GetPlayerName(), state->GetName() ); |
|
} |
|
} |
|
} |
|
} |
|
*/ |
|
|
|
// if we changed state from within the special Attack state, we are no longer attacking |
|
if (m_isAttacking) |
|
StopAttacking(); |
|
|
|
if (m_state) |
|
m_state->OnExit( this ); |
|
|
|
state->OnEnter( this ); |
|
|
|
m_state = state; |
|
m_stateTimestamp = gpGlobals->curtime; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
void CCSBot::Idle( void ) |
|
{ |
|
SetTask( SEEK_AND_DESTROY ); |
|
SetState( &m_idleState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
void CCSBot::EscapeFromBomb( void ) |
|
{ |
|
SetTask( ESCAPE_FROM_BOMB ); |
|
SetState( &m_escapeFromBombState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
void CCSBot::Follow( CCSPlayer *player ) |
|
{ |
|
if (player == NULL) |
|
return; |
|
|
|
// note when we began following |
|
if (!m_isFollowing || m_leader != player) |
|
m_followTimestamp = gpGlobals->curtime; |
|
|
|
m_isFollowing = true; |
|
m_leader = player; |
|
|
|
SetTask( FOLLOW ); |
|
m_followState.SetLeader( player ); |
|
SetState( &m_followState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Continue following our leader after finishing what we were doing |
|
*/ |
|
void CCSBot::ContinueFollowing( void ) |
|
{ |
|
SetTask( FOLLOW ); |
|
|
|
m_followState.SetLeader( m_leader ); |
|
|
|
SetState( &m_followState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Stop following |
|
*/ |
|
void CCSBot::StopFollowing( void ) |
|
{ |
|
m_isFollowing = false; |
|
m_leader = NULL; |
|
m_allowAutoFollowTime = gpGlobals->curtime + 10.0f; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Begin process of rescuing hostages |
|
*/ |
|
void CCSBot::RescueHostages( void ) |
|
{ |
|
SetTask( RESCUE_HOSTAGES ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Use the entity |
|
*/ |
|
void CCSBot::UseEntity( CBaseEntity *entity ) |
|
{ |
|
m_useEntityState.SetEntity( entity ); |
|
SetState( &m_useEntityState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Open the door. |
|
* This assumes the bot is directly in front of the door with no obstructions. |
|
* NOTE: This state is special, like Attack, in that it suspends the current behavior and returns to it when done. |
|
*/ |
|
void CCSBot::OpenDoor( CBaseEntity *door ) |
|
{ |
|
m_openDoorState.SetDoor( door ); |
|
m_isOpeningDoor = true; |
|
m_openDoorState.OnEnter( this ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* DEPRECATED: Use TryToHide() instead. |
|
* Move to a hiding place. |
|
* If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first. |
|
*/ |
|
void CCSBot::Hide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition ) |
|
{ |
|
DestroyPath(); |
|
|
|
CNavArea *source; |
|
Vector sourcePos; |
|
if (searchFromArea) |
|
{ |
|
source = searchFromArea; |
|
sourcePos = searchFromArea->GetCenter(); |
|
} |
|
else |
|
{ |
|
source = m_lastKnownArea; |
|
sourcePos = GetCentroid( this ); |
|
} |
|
|
|
if (source == NULL) |
|
{ |
|
PrintIfWatched( "Hide from area is NULL.\n" ); |
|
Idle(); |
|
return; |
|
} |
|
|
|
m_hideState.SetSearchArea( source ); |
|
m_hideState.SetSearchRange( hideRange ); |
|
m_hideState.SetDuration( duration ); |
|
m_hideState.SetHoldPosition( holdPosition ); |
|
|
|
// search around source area for a good hiding spot |
|
Vector useSpot; |
|
|
|
const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper() ); |
|
if (pos == NULL) |
|
{ |
|
PrintIfWatched( "No available hiding spots.\n" ); |
|
// hide at our current position |
|
useSpot = GetCentroid( this ); |
|
} |
|
else |
|
{ |
|
useSpot = *pos; |
|
} |
|
|
|
m_hideState.SetHidingSpot( useSpot ); |
|
|
|
// build a path to our new hiding spot |
|
if (ComputePath( useSpot, FASTEST_ROUTE ) == false) |
|
{ |
|
PrintIfWatched( "Can't pathfind to hiding spot\n" ); |
|
Idle(); |
|
return; |
|
} |
|
|
|
SetState( &m_hideState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Move to the given hiding place |
|
*/ |
|
void CCSBot::Hide( const Vector &hidingSpot, float duration, bool holdPosition ) |
|
{ |
|
CNavArea *hideArea = TheNavMesh->GetNearestNavArea( hidingSpot ); |
|
if (hideArea == NULL) |
|
{ |
|
PrintIfWatched( "Hiding spot off nav mesh\n" ); |
|
Idle(); |
|
return; |
|
} |
|
|
|
DestroyPath(); |
|
|
|
m_hideState.SetSearchArea( hideArea ); |
|
m_hideState.SetSearchRange( 750.0f ); |
|
m_hideState.SetDuration( duration ); |
|
m_hideState.SetHoldPosition( holdPosition ); |
|
m_hideState.SetHidingSpot( hidingSpot ); |
|
|
|
// build a path to our new hiding spot |
|
if (ComputePath( hidingSpot, FASTEST_ROUTE ) == false) |
|
{ |
|
PrintIfWatched( "Can't pathfind to hiding spot\n" ); |
|
Idle(); |
|
return; |
|
} |
|
|
|
SetState( &m_hideState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Try to hide nearby. Return true if hiding, false if can't hide here. |
|
* If 'searchFromArea' is non-NULL, hiding spots are looked for from that area first. |
|
*/ |
|
bool CCSBot::TryToHide( CNavArea *searchFromArea, float duration, float hideRange, bool holdPosition, bool useNearest ) |
|
{ |
|
CNavArea *source; |
|
Vector sourcePos; |
|
if (searchFromArea) |
|
{ |
|
source = searchFromArea; |
|
sourcePos = searchFromArea->GetCenter(); |
|
} |
|
else |
|
{ |
|
source = m_lastKnownArea; |
|
sourcePos = GetCentroid( this ); |
|
} |
|
|
|
if (source == NULL) |
|
{ |
|
PrintIfWatched( "Hide from area is NULL.\n" ); |
|
return false; |
|
} |
|
|
|
m_hideState.SetSearchArea( source ); |
|
m_hideState.SetSearchRange( hideRange ); |
|
m_hideState.SetDuration( duration ); |
|
m_hideState.SetHoldPosition( holdPosition ); |
|
|
|
// search around source area for a good hiding spot |
|
const Vector *pos = FindNearbyHidingSpot( this, sourcePos, hideRange, IsSniper(), useNearest ); |
|
if (pos == NULL) |
|
{ |
|
PrintIfWatched( "No available hiding spots.\n" ); |
|
return false; |
|
} |
|
|
|
m_hideState.SetHidingSpot( *pos ); |
|
|
|
// build a path to our new hiding spot |
|
if (ComputePath( *pos, FASTEST_ROUTE ) == false) |
|
{ |
|
PrintIfWatched( "Can't pathfind to hiding spot\n" ); |
|
return false; |
|
} |
|
|
|
SetState( &m_hideState ); |
|
return true; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Retreat to a nearby hiding spot, away from enemies |
|
*/ |
|
bool CCSBot::TryToRetreat( float maxRange, float duration ) |
|
{ |
|
const Vector *spot = FindNearbyRetreatSpot( this, maxRange ); |
|
if (spot) |
|
{ |
|
// ignore enemies for a second to give us time to hide |
|
// reaching our hiding spot clears our disposition |
|
IgnoreEnemies( 10.0f ); |
|
|
|
if (duration < 0.0f) |
|
{ |
|
duration = RandomFloat( 3.0f, 15.0f ); |
|
} |
|
|
|
StandUp(); |
|
Run(); |
|
Hide( *spot, duration ); |
|
|
|
PrintIfWatched( "Retreating to a safe spot!\n" ); |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
void CCSBot::Hunt( void ) |
|
{ |
|
SetState( &m_huntState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Attack our the given victim |
|
* NOTE: Attacking does not change our task. |
|
*/ |
|
void CCSBot::Attack( CCSPlayer *victim ) |
|
{ |
|
if (victim == NULL) |
|
return; |
|
|
|
// zombies never attack |
|
if (cv_bot_zombie.GetBool()) |
|
return; |
|
|
|
// cannot attack if we are reloading |
|
if (IsReloading()) |
|
return; |
|
|
|
// change enemy |
|
SetBotEnemy( victim ); |
|
|
|
// |
|
// Do not "re-enter" the attack state if we are already attacking |
|
// |
|
if (IsAttacking()) |
|
return; |
|
|
|
// if we're holding a grenade, throw it at the victim |
|
if (IsUsingGrenade()) |
|
{ |
|
// throw towards their feet |
|
ThrowGrenade( victim->GetAbsOrigin() ); |
|
return; |
|
} |
|
|
|
|
|
// if we are currently hiding, increase our chances of crouching and holding position |
|
if (IsAtHidingSpot()) |
|
m_attackState.SetCrouchAndHold( (RandomFloat( 0.0f, 100.0f ) < 60.0f) ? true : false ); |
|
else |
|
m_attackState.SetCrouchAndHold( false ); |
|
|
|
//SetState( &m_attackState ); |
|
//PrintIfWatched( "ATTACK BEGIN (reaction time = %g (+ update time), surprise time = %g, attack delay = %g)\n", |
|
// GetProfile()->GetReactionTime(), m_surpriseDelay, GetProfile()->GetAttackDelay() ); |
|
m_isAttacking = true; |
|
m_attackState.OnEnter( this ); |
|
|
|
|
|
Vector victimOrigin = GetCentroid( victim ); |
|
|
|
// cheat a bit and give the bot the initial location of its victim |
|
m_lastEnemyPosition = victimOrigin; |
|
m_lastSawEnemyTimestamp = gpGlobals->curtime; |
|
m_aimSpreadTimestamp = gpGlobals->curtime; |
|
|
|
// compute the angle difference between where are looking, and where we need to look |
|
Vector toEnemy = victimOrigin - GetCentroid( this ); |
|
|
|
QAngle idealAngle; |
|
VectorAngles( toEnemy, idealAngle ); |
|
|
|
float deltaYaw = (float)fabs(m_lookYaw - idealAngle.y); |
|
|
|
while( deltaYaw > 180.0f ) |
|
deltaYaw -= 360.0f; |
|
|
|
if (deltaYaw < 0.0f) |
|
deltaYaw = -deltaYaw; |
|
|
|
// immediately aim at enemy - accuracy penalty depending on how far we must turn to aim |
|
// accuracy is halved if we have to turn 180 degrees |
|
float turn = deltaYaw / 180.0f; |
|
float accuracy = GetProfile()->GetSkill() / (1.0f + turn); |
|
|
|
SetAimOffset( accuracy ); |
|
|
|
// define time when aim offset will automatically be updated |
|
// longer time the more we had to turn (surprise) |
|
m_aimOffsetTimestamp = gpGlobals->curtime + RandomFloat( 0.25f + turn, 1.5f ); |
|
|
|
// forget any look at targets we have |
|
ClearLookAt(); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Exit the Attack state |
|
*/ |
|
void CCSBot::StopAttacking( void ) |
|
{ |
|
PrintIfWatched( "ATTACK END\n" ); |
|
m_attackState.OnExit( this ); |
|
m_isAttacking = false; |
|
|
|
// if we are following someone, go to the Idle state after the attack to decide whether we still want to follow |
|
if (IsFollowing()) |
|
{ |
|
Idle(); |
|
} |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
bool CCSBot::IsAttacking( void ) const |
|
{ |
|
return m_isAttacking; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Return true if we are escaping from the bomb |
|
*/ |
|
bool CCSBot::IsEscapingFromBomb( void ) const |
|
{ |
|
if (m_state == static_cast<const BotState *>( &m_escapeFromBombState )) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Return true if we are defusing the bomb |
|
*/ |
|
bool CCSBot::IsDefusingBomb( void ) const |
|
{ |
|
if (m_state == static_cast<const BotState *>( &m_defuseBombState )) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Return true if we are hiding |
|
*/ |
|
bool CCSBot::IsHiding( void ) const |
|
{ |
|
if (m_state == static_cast<const BotState *>( &m_hideState )) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Return true if we are hiding and at our hiding spot |
|
*/ |
|
bool CCSBot::IsAtHidingSpot( void ) const |
|
{ |
|
if (!IsHiding()) |
|
return false; |
|
|
|
return m_hideState.IsAtSpot(); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Return number of seconds we have been at our current hiding spot |
|
*/ |
|
float CCSBot::GetHidingTime( void ) const |
|
{ |
|
if (IsHiding()) |
|
{ |
|
return m_hideState.GetHideTime(); |
|
} |
|
|
|
return 0.0f; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Return true if we are huting |
|
*/ |
|
bool CCSBot::IsHunting( void ) const |
|
{ |
|
if (m_state == static_cast<const BotState *>( &m_huntState )) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Return true if we are in the MoveTo state |
|
*/ |
|
bool CCSBot::IsMovingTo( void ) const |
|
{ |
|
if (m_state == static_cast<const BotState *>( &m_moveToState )) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Return true if we are buying |
|
*/ |
|
bool CCSBot::IsBuying( void ) const |
|
{ |
|
if (m_state == static_cast<const BotState *>( &m_buyState )) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
bool CCSBot::IsInvestigatingNoise( void ) const |
|
{ |
|
if (m_state == static_cast<const BotState *>( &m_investigateNoiseState )) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Move to potentially distant position |
|
*/ |
|
void CCSBot::MoveTo( const Vector &pos, RouteType route ) |
|
{ |
|
m_moveToState.SetGoalPosition( pos ); |
|
m_moveToState.SetRouteType( route ); |
|
SetState( &m_moveToState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
void CCSBot::PlantBomb( void ) |
|
{ |
|
SetState( &m_plantBombState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Bomb has been dropped - go get it |
|
*/ |
|
void CCSBot::FetchBomb( void ) |
|
{ |
|
SetState( &m_fetchBombState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
void CCSBot::DefuseBomb( void ) |
|
{ |
|
SetState( &m_defuseBombState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Investigate recent enemy noise |
|
*/ |
|
void CCSBot::InvestigateNoise( void ) |
|
{ |
|
SetState( &m_investigateNoiseState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
void CCSBot::Buy( void ) |
|
{ |
|
SetState( &m_buyState ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* Move to a hiding spot and wait for initial encounter with enemy team. |
|
* Return false if can't do this behavior (ie: no hiding spots available). |
|
*/ |
|
bool CCSBot::MoveToInitialEncounter( void ) |
|
{ |
|
int myTeam = GetTeamNumber(); |
|
int enemyTeam = OtherTeam( myTeam ); |
|
|
|
// build a path to an enemy spawn point |
|
CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( enemyTeam ); |
|
|
|
if (enemySpawn == NULL) |
|
{ |
|
PrintIfWatched( "MoveToInitialEncounter: No enemy spawn points?\n" ); |
|
return false; |
|
} |
|
|
|
// build a path from us to the enemy spawn |
|
CCSNavPath path; |
|
PathCost cost( this, FASTEST_ROUTE ); |
|
path.Compute( WorldSpaceCenter(), enemySpawn->GetAbsOrigin(), cost ); |
|
|
|
if (!path.IsValid()) |
|
{ |
|
PrintIfWatched( "MoveToInitialEncounter: Pathfind failed.\n" ); |
|
return false; |
|
} |
|
|
|
// find battlefront area where teams will first meet along this path |
|
int i; |
|
for( i=0; i<path.GetSegmentCount(); ++i ) |
|
{ |
|
if (path[i]->area->GetEarliestOccupyTime( myTeam ) > path[i]->area->GetEarliestOccupyTime( enemyTeam )) |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
if (i == path.GetSegmentCount()) |
|
{ |
|
PrintIfWatched( "MoveToInitialEncounter: Can't find battlefront!\n" ); |
|
return false; |
|
} |
|
|
|
/// @todo Remove this evil side-effect |
|
SetInitialEncounterArea( path[i]->area ); |
|
|
|
// find a hiding spot on our side of the battlefront that has LOS to it |
|
const float maxRange = 1500.0f; |
|
const HidingSpot *spot = FindInitialEncounterSpot( this, path[i]->area->GetCenter(), path[i]->area->GetEarliestOccupyTime( enemyTeam ), maxRange, IsSniper() ); |
|
|
|
if (spot == NULL) |
|
{ |
|
PrintIfWatched( "MoveToInitialEncounter: Can't find a hiding spot\n" ); |
|
return false; |
|
} |
|
|
|
float timeToWait = path[i]->area->GetEarliestOccupyTime( enemyTeam ) - spot->GetArea()->GetEarliestOccupyTime( myTeam ); |
|
float minWaitTime = 4.0f * GetProfile()->GetAggression() + 3.0f; |
|
if (timeToWait < minWaitTime) |
|
{ |
|
timeToWait = minWaitTime; |
|
} |
|
|
|
Hide( spot->GetPosition(), timeToWait ); |
|
|
|
return true; |
|
} |
|
|
|
|