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.
1230 lines
31 KiB
1230 lines
31 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
// Author: Michael S. Booth ([email protected]), 2003
|
|
|
|
#include "cbase.h"
|
|
#include "cs_simple_hostage.h"
|
|
#include "cs_gamerules.h"
|
|
#include "func_breakablesurf.h"
|
|
#include "obstacle_pushaway.h"
|
|
|
|
#include "cs_bot.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
LINK_ENTITY_TO_CLASS( cs_bot, CCSBot );
|
|
|
|
BEGIN_DATADESC( CCSBot )
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return the number of bots following the given player
|
|
*/
|
|
int GetBotFollowCount( CCSPlayer *leader )
|
|
{
|
|
int count = 0;
|
|
|
|
for( int i=1; i <= gpGlobals->maxClients; ++i )
|
|
{
|
|
CBaseEntity *entity = UTIL_PlayerByIndex( i );
|
|
|
|
if (entity == NULL)
|
|
continue;
|
|
|
|
CBasePlayer *player = static_cast<CBasePlayer *>( entity );
|
|
|
|
if (!player->IsBot())
|
|
continue;
|
|
|
|
if (!player->IsAlive())
|
|
continue;
|
|
|
|
CCSBot *bot = dynamic_cast<CCSBot *>( player );
|
|
if (bot && bot->GetFollowLeader() == leader)
|
|
++count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Change movement speed to walking
|
|
*/
|
|
void CCSBot::Walk( void )
|
|
{
|
|
if (m_mustRunTimer.IsElapsed())
|
|
{
|
|
BaseClass::Walk();
|
|
}
|
|
else
|
|
{
|
|
// must run
|
|
Run();
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if jump was started.
|
|
* This is extended from the base jump to disallow jumping when in a crouch area.
|
|
*/
|
|
bool CCSBot::Jump( bool mustJump )
|
|
{
|
|
// prevent jumping if we're crouched, unless we're in a crouchjump area - jump wins
|
|
bool inCrouchJumpArea = (m_lastKnownArea &&
|
|
(m_lastKnownArea->GetAttributes() & NAV_MESH_CROUCH) &&
|
|
(m_lastKnownArea->GetAttributes() & NAV_MESH_JUMP));
|
|
|
|
if ( !IsUsingLadder() && IsDucked() && !inCrouchJumpArea )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( CSGameRules()->IsPlayingCoopMission() && !mustJump )
|
|
return false;
|
|
|
|
return BaseClass::Jump( mustJump );
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Invoked when injured by something
|
|
* NOTE: We dont want to directly call Attack() here, or the bots will have super-human reaction times when injured
|
|
*/
|
|
int CCSBot::OnTakeDamage( const CTakeDamageInfo &info )
|
|
{
|
|
m_bIsSleeping = false;
|
|
|
|
CBaseEntity *attacker = info.GetInflictor();
|
|
|
|
// getting hurt makes us alert
|
|
BecomeAlert();
|
|
StopWaiting();
|
|
|
|
if ( info.GetDamageType() == DMG_BURN )
|
|
{
|
|
m_burnedByFlamesTimer.Start();
|
|
}
|
|
|
|
// if we were attacked by a teammate, rebuke
|
|
if (attacker->IsPlayer())
|
|
{
|
|
CCSPlayer *player = static_cast<CCSPlayer *>( attacker );
|
|
|
|
if ( IsOtherSameTeam( player->GetTeamNumber() ) && !IsOtherEnemy( player ) && !player->IsBot() )
|
|
{
|
|
// Response rules specifically needs to know if this is bullet or knife damage, so no need to do a fully general solution at this time
|
|
const char *pDmgType = "OTHER";
|
|
if ( info.GetDamageType() & DMG_BULLET )
|
|
{
|
|
pDmgType = "DMG_BULLET";
|
|
}
|
|
else if ( info.GetDamageType() & DMG_SLASH )
|
|
{
|
|
pDmgType = "DMG_SLASH";
|
|
}
|
|
GetChatter()->FriendlyFire( pDmgType );
|
|
}
|
|
}
|
|
|
|
if (attacker->IsPlayer() && IsEnemy( attacker ))
|
|
{
|
|
// Track previous attacker so we don't try to panic multiple times for a shotgun blast
|
|
CCSPlayer *lastAttacker = m_attacker;
|
|
float lastAttackedTimestamp = m_attackedTimestamp;
|
|
|
|
// keep track of our last attacker
|
|
m_attacker = reinterpret_cast<CCSPlayer *>( attacker );
|
|
m_attackedTimestamp = gpGlobals->curtime;
|
|
|
|
// no longer safe
|
|
AdjustSafeTime();
|
|
|
|
if ( !IsSurprised() && (m_attacker != lastAttacker || m_attackedTimestamp != lastAttackedTimestamp) )
|
|
{
|
|
CCSPlayer *enemy = static_cast<CCSPlayer *>( attacker );
|
|
|
|
// being hurt by an enemy we can't see causes panic
|
|
if (!IsVisible( enemy, CHECK_FOV ))
|
|
{
|
|
// if not attacking anything, look around to try to find attacker
|
|
if (!IsAttacking())
|
|
{
|
|
Panic();
|
|
}
|
|
else // we are attacking
|
|
{
|
|
if (!IsEnemyVisible())
|
|
{
|
|
// can't see our current enemy, panic to acquire new attacker
|
|
Panic();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// extend
|
|
return BaseClass::OnTakeDamage( info );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Invoked when killed
|
|
*/
|
|
void CCSBot::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
// PrintIfWatched( "Killed( attacker = %s )\n", STRING(pevAttacker->netname) );
|
|
|
|
GetChatter()->OnDeath();
|
|
|
|
// increase the danger where we died
|
|
const float deathDanger = 1.0f;
|
|
const float deathDangerRadius = 500.0f;
|
|
TheNavMesh->IncreaseDangerNearby( GetTeamNumber(), deathDanger, m_lastKnownArea, GetAbsOrigin(), deathDangerRadius );
|
|
|
|
// end voice feedback
|
|
m_voiceEndTimestamp = 0.0f;
|
|
|
|
// extend
|
|
BaseClass::Event_Killed( info );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if line segment intersects rectagular volume
|
|
*/
|
|
#define HI_X 0x01
|
|
#define LO_X 0x02
|
|
#define HI_Y 0x04
|
|
#define LO_Y 0x08
|
|
#define HI_Z 0x10
|
|
#define LO_Z 0x20
|
|
|
|
inline bool IsIntersectingBox( const Vector& start, const Vector& end, const Vector& boxMin, const Vector& boxMax )
|
|
{
|
|
unsigned char startFlags = 0;
|
|
unsigned char endFlags = 0;
|
|
|
|
// classify start point
|
|
if (start.x < boxMin.x)
|
|
startFlags |= LO_X;
|
|
if (start.x > boxMax.x)
|
|
startFlags |= HI_X;
|
|
|
|
if (start.y < boxMin.y)
|
|
startFlags |= LO_Y;
|
|
if (start.y > boxMax.y)
|
|
startFlags |= HI_Y;
|
|
|
|
if (start.z < boxMin.z)
|
|
startFlags |= LO_Z;
|
|
if (start.z > boxMax.z)
|
|
startFlags |= HI_Z;
|
|
|
|
// classify end point
|
|
if (end.x < boxMin.x)
|
|
endFlags |= LO_X;
|
|
if (end.x > boxMax.x)
|
|
endFlags |= HI_X;
|
|
|
|
if (end.y < boxMin.y)
|
|
endFlags |= LO_Y;
|
|
if (end.y > boxMax.y)
|
|
endFlags |= HI_Y;
|
|
|
|
if (end.z < boxMin.z)
|
|
endFlags |= LO_Z;
|
|
if (end.z > boxMax.z)
|
|
endFlags |= HI_Z;
|
|
|
|
// trivial reject
|
|
if (startFlags & endFlags)
|
|
return false;
|
|
|
|
/// @todo Do exact line/box intersection check
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
extern void UTIL_DrawBox( Extent *extent, int lifetime, int red, int green, int blue );
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* When bot is touched by another entity.
|
|
*/
|
|
void CCSBot::Touch( CBaseEntity *other )
|
|
{
|
|
// EXTEND
|
|
BaseClass::Touch( other );
|
|
|
|
// if we have touched a higher-priority player, make way
|
|
/// @todo Need to account for reaction time, etc.
|
|
if (other->IsPlayer())
|
|
{
|
|
// if we are defusing a bomb, don't move
|
|
if (IsDefusingBomb())
|
|
return;
|
|
|
|
// if we are on a ladder, don't move
|
|
if (IsUsingLadder())
|
|
return;
|
|
|
|
CCSPlayer *player = static_cast<CCSPlayer *>( other );
|
|
|
|
// get priority of other player
|
|
unsigned int otherPri = TheCSBots()->GetPlayerPriority( player );
|
|
|
|
// get our priority
|
|
unsigned int myPri = TheCSBots()->GetPlayerPriority( this );
|
|
|
|
// if our priority is better, don't budge
|
|
if (myPri < otherPri)
|
|
return;
|
|
|
|
// they are higher priority - make way, unless we're already making way for someone more important
|
|
if (m_avoid != NULL)
|
|
{
|
|
unsigned int avoidPri = TheCSBots()->GetPlayerPriority( static_cast<CBasePlayer *>( static_cast<CBaseEntity *>( m_avoid ) ) );
|
|
if (avoidPri < otherPri)
|
|
{
|
|
// ignore 'other' because we're already avoiding someone better
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_avoid = other;
|
|
m_avoidTimestamp = gpGlobals->curtime;
|
|
}
|
|
|
|
// Check for breakables we're actually touching
|
|
// If we're not stuck or crouched, we don't care
|
|
if ( !m_isStuck && !IsCrouching() && !IsOnLadder() )
|
|
return;
|
|
|
|
// See if it's breakable
|
|
if ( IsBreakableEntity( other ) )
|
|
{
|
|
// it's breakable - try to shoot it.
|
|
SetLookAt( "Breakable", other->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if we are busy doing something important
|
|
*/
|
|
bool CCSBot::IsBusy( void ) const
|
|
{
|
|
if (IsAttacking() ||
|
|
IsBuying() ||
|
|
IsDefusingBomb() ||
|
|
GetTask() == PLANT_BOMB ||
|
|
GetTask() == RESCUE_HOSTAGES ||
|
|
IsSniping())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
void CCSBot::BotDeathThink( void )
|
|
{
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Try to join the given team
|
|
*/
|
|
void CCSBot::TryToJoinTeam( int team )
|
|
{
|
|
m_desiredTeam = team;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Assign given player as our current enemy to attack
|
|
*/
|
|
void CCSBot::SetBotEnemy( CCSPlayer *enemy )
|
|
{
|
|
if (m_enemy != enemy)
|
|
{
|
|
m_enemy = enemy;
|
|
m_currentEnemyAcquireTimestamp = gpGlobals->curtime;
|
|
|
|
PrintIfWatched( "SetBotEnemy: %s\n", (enemy) ? enemy->GetPlayerName() : "(NULL)" );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* If we are not on the navigation mesh (m_currentArea == NULL),
|
|
* move towards last known area.
|
|
* Return false if off mesh.
|
|
*/
|
|
bool CCSBot::StayOnNavMesh( void )
|
|
{
|
|
if (m_currentArea == NULL)
|
|
{
|
|
// move back onto the area map
|
|
|
|
// if we have no lastKnownArea, we probably started off
|
|
// of the nav mesh - find the closest nav area and use it
|
|
CNavArea *goalArea;
|
|
if (!m_currentArea && !m_lastKnownArea)
|
|
{
|
|
goalArea = TheNavMesh->GetNearestNavArea( GetCentroid( this ) );
|
|
PrintIfWatched( "Started off the nav mesh - moving to closest nav area...\n" );
|
|
}
|
|
else
|
|
{
|
|
goalArea = m_lastKnownArea;
|
|
PrintIfWatched( "Getting out of NULL area...\n" );
|
|
}
|
|
|
|
if (goalArea)
|
|
{
|
|
Vector pos;
|
|
goalArea->GetClosestPointOnArea( GetCentroid( this ), &pos );
|
|
|
|
// move point into area
|
|
Vector to = pos - GetCentroid( this );
|
|
to.NormalizeInPlace();
|
|
|
|
const float stepInDist = 5.0f; // how far to "step into" an area - must be less than min area size
|
|
pos = pos + (stepInDist * to);
|
|
|
|
MoveTowardsPosition( pos );
|
|
}
|
|
|
|
// if we're stuck, try to get un-stuck
|
|
// do stuck movements last, so they override normal movement
|
|
if (m_isStuck)
|
|
Wiggle();
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if we will do scenario-related tasks
|
|
*/
|
|
bool CCSBot::IsDoingScenario( void ) const
|
|
{
|
|
// if we are deferring to humans, and there is a live human on our team, don't do the scenario
|
|
if (cv_bot_defer_to_human_goals.GetBool())
|
|
{
|
|
if (UTIL_HumansOnTeam( GetTeamNumber(), IS_ALIVE ))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if we noticed the bomb on the ground or on the radar (for T's only)
|
|
*/
|
|
bool CCSBot::NoticeLooseBomb( void ) const
|
|
{
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
|
|
if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
|
|
return false;
|
|
|
|
CBaseEntity *bomb = ctrl->GetLooseBomb();
|
|
|
|
if (bomb)
|
|
{
|
|
// T's can always see bomb on their radar
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if can see the bomb lying on the ground
|
|
*/
|
|
bool CCSBot::CanSeeLooseBomb( void ) const
|
|
{
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
|
|
if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
|
|
return false;
|
|
|
|
CBaseEntity *bomb = ctrl->GetLooseBomb();
|
|
|
|
if (bomb)
|
|
{
|
|
if (IsVisible( bomb->GetAbsOrigin(), CHECK_FOV ))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if can see the planted bomb
|
|
*/
|
|
bool CCSBot::CanSeePlantedBomb( void ) const
|
|
{
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
|
|
if (ctrl->GetScenario() != CCSBotManager::SCENARIO_DEFUSE_BOMB)
|
|
return false;
|
|
|
|
if (!GetGameState()->IsBombPlanted())
|
|
return false;
|
|
|
|
const Vector *bombPos = GetGameState()->GetBombPosition();
|
|
|
|
if (bombPos && IsVisible( *bombPos, CHECK_FOV ))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return last enemy that hurt us
|
|
*/
|
|
CCSPlayer *CCSBot::GetAttacker( void ) const
|
|
{
|
|
if (m_attacker && m_attacker->IsAlive())
|
|
return m_attacker;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Immediately jump off of our ladder, if we're on one
|
|
*/
|
|
void CCSBot::GetOffLadder( void )
|
|
{
|
|
if (IsUsingLadder())
|
|
{
|
|
Jump( MUST_JUMP );
|
|
DestroyPath();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return time when given spot was last checked
|
|
*/
|
|
float CCSBot::GetHidingSpotCheckTimestamp( HidingSpot *spot ) const
|
|
{
|
|
for( int i=0; i<m_checkedHidingSpotCount; ++i )
|
|
if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
|
|
return m_checkedHidingSpot[i].timestamp;
|
|
|
|
return -999999.9f;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Set the timestamp of the given spot to now.
|
|
* If the spot is not in the set, overwrite the least recently checked spot.
|
|
*/
|
|
void CCSBot::SetHidingSpotCheckTimestamp( HidingSpot *spot )
|
|
{
|
|
int leastRecent = 0;
|
|
float leastRecentTime = gpGlobals->curtime + 1.0f;
|
|
|
|
for( int i=0; i<m_checkedHidingSpotCount; ++i )
|
|
{
|
|
// if spot is in the set, just update its timestamp
|
|
if (m_checkedHidingSpot[i].spot->GetID() == spot->GetID())
|
|
{
|
|
m_checkedHidingSpot[i].timestamp = gpGlobals->curtime;
|
|
return;
|
|
}
|
|
|
|
// keep track of least recent spot
|
|
if (m_checkedHidingSpot[i].timestamp < leastRecentTime)
|
|
{
|
|
leastRecentTime = m_checkedHidingSpot[i].timestamp;
|
|
leastRecent = i;
|
|
}
|
|
}
|
|
|
|
// if there is room for more spots, append this one
|
|
if (m_checkedHidingSpotCount < MAX_CHECKED_SPOTS)
|
|
{
|
|
m_checkedHidingSpot[ m_checkedHidingSpotCount ].spot = spot;
|
|
m_checkedHidingSpot[ m_checkedHidingSpotCount ].timestamp = gpGlobals->curtime;
|
|
++m_checkedHidingSpotCount;
|
|
}
|
|
else
|
|
{
|
|
// replace the least recent spot
|
|
m_checkedHidingSpot[ leastRecent ].spot = spot;
|
|
m_checkedHidingSpot[ leastRecent ].timestamp = gpGlobals->curtime;
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Periodic check of hostage count in case we lost some
|
|
*/
|
|
void CCSBot::UpdateHostageEscortCount( void )
|
|
{
|
|
const float updateInterval = 1.0f;
|
|
if (m_hostageEscortCount == 0 || gpGlobals->curtime - m_hostageEscortCountTimestamp < updateInterval)
|
|
return;
|
|
|
|
m_hostageEscortCountTimestamp = gpGlobals->curtime;
|
|
|
|
// recount the hostages in case we lost some
|
|
m_hostageEscortCount = 0;
|
|
|
|
for( int i=0; i<g_Hostages.Count(); ++i )
|
|
{
|
|
CHostage *hostage = g_Hostages[i];
|
|
|
|
// skip dead or rescued hostages
|
|
if ( !hostage->IsValid() || !hostage->IsAlive() )
|
|
continue;
|
|
|
|
// check if hostage has targeted us, and is following
|
|
if ( hostage->IsFollowing( this ) )
|
|
++m_hostageEscortCount;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if we are outnumbered by enemies
|
|
*/
|
|
bool CCSBot::IsOutnumbered( void ) const
|
|
{
|
|
// Play panic lines more often in coop
|
|
if ( CSGameRules()->IsPlayingCoopMission() )
|
|
return GetNearbyFriendCount() <= GetNearbyEnemyCount();
|
|
|
|
return (GetNearbyFriendCount() < GetNearbyEnemyCount()-1) ? true : false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return number of enemies we are outnumbered by
|
|
*/
|
|
int CCSBot::OutnumberedCount( void ) const
|
|
{
|
|
if (IsOutnumbered())
|
|
return (GetNearbyEnemyCount()-1) - GetNearbyFriendCount();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return the closest "important" enemy for the given scenario (bomb carrier, VIP, hostage escorter)
|
|
*/
|
|
CCSPlayer *CCSBot::GetImportantEnemy( bool checkVisibility ) const
|
|
{
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
CCSPlayer *nearEnemy = NULL;
|
|
float nearDist = 999999999.9f;
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
|
|
{
|
|
CBaseEntity *entity = UTIL_PlayerByIndex( i );
|
|
|
|
if (entity == NULL)
|
|
continue;
|
|
|
|
// if (FNullEnt( entity->pev ))
|
|
// continue;
|
|
|
|
// if (FStrEq( STRING( entity->pev->netname ), "" ))
|
|
// continue;
|
|
|
|
// is it a player?
|
|
if (!entity->IsPlayer())
|
|
continue;
|
|
|
|
CCSPlayer *player = static_cast<CCSPlayer *>( entity );
|
|
|
|
// is it alive?
|
|
if (!player->IsAlive())
|
|
continue;
|
|
|
|
// skip friends
|
|
if ( player->IsOtherSameTeam( GetTeamNumber() ) && !player->IsOtherEnemy( entindex() ) )
|
|
continue;
|
|
|
|
// is it "important"
|
|
if (!ctrl->IsImportantPlayer( player ))
|
|
continue;
|
|
|
|
// is it closest?
|
|
Vector d = GetAbsOrigin() - player->GetAbsOrigin();
|
|
float distSq = d.x*d.x + d.y*d.y + d.z*d.z;
|
|
if (distSq < nearDist)
|
|
{
|
|
if (checkVisibility && !IsVisible( player, CHECK_FOV ))
|
|
continue;
|
|
|
|
nearEnemy = player;
|
|
nearDist = distSq;
|
|
}
|
|
}
|
|
|
|
return nearEnemy;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Sets our current disposition
|
|
*/
|
|
void CCSBot::SetDisposition( DispositionType disposition )
|
|
{
|
|
m_disposition = disposition;
|
|
|
|
if (m_disposition != IGNORE_ENEMIES)
|
|
m_ignoreEnemiesTimer.Invalidate();
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return our current disposition
|
|
*/
|
|
CCSBot::DispositionType CCSBot::GetDisposition( void ) const
|
|
{
|
|
if (!m_ignoreEnemiesTimer.IsElapsed())
|
|
return IGNORE_ENEMIES;
|
|
|
|
return m_disposition;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Ignore enemies for a short durationy
|
|
*/
|
|
void CCSBot::IgnoreEnemies( float duration )
|
|
{
|
|
m_ignoreEnemiesTimer.Start( duration );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Increase morale one step
|
|
*/
|
|
void CCSBot::IncreaseMorale( void )
|
|
{
|
|
if (m_morale < EXCELLENT)
|
|
m_morale = static_cast<MoraleType>( m_morale + 1 );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Decrease morale one step
|
|
*/
|
|
void CCSBot::DecreaseMorale( void )
|
|
{
|
|
if (m_morale > TERRIBLE)
|
|
m_morale = static_cast<MoraleType>( m_morale - 1 );
|
|
}
|
|
|
|
|
|
float CCSBot::GetNoiseInvestigateChance( void ) const
|
|
{
|
|
if ( CSGameRules()->IsPlayingCoopMission() )
|
|
return 100.0f;
|
|
|
|
// chance of investigating is inversely proportional to distance
|
|
const float maxNoiseDist = 3000.0f;
|
|
float chance = 100.0f * ( 1.0f - ( GetNoiseRange() / maxNoiseDist ) );
|
|
|
|
// modify chance by number of friends remaining
|
|
// if we have lots of friends, presumably one of them is closer and will check it out
|
|
if ( GetFriendsRemaining() >= 3 )
|
|
{
|
|
float friendFactor = 5.0f * GetFriendsRemaining();
|
|
if ( friendFactor > 50.0f )
|
|
friendFactor = 50.0f;
|
|
|
|
chance -= friendFactor;
|
|
}
|
|
return chance;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if we are acting like a rogue (not listening to teammates, not doing scenario goals)
|
|
* @todo Account for morale
|
|
*/
|
|
bool CCSBot::IsRogue( void ) const
|
|
{
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
if (!ctrl->AllowRogues())
|
|
return false;
|
|
|
|
// periodically re-evaluate our rogue status
|
|
if (m_rogueTimer.IsElapsed())
|
|
{
|
|
m_rogueTimer.Start( RandomFloat( 10.0f, 30.0f ) );
|
|
|
|
// our chance of going rogue is inversely proportional to our teamwork attribute
|
|
const float rogueChance = 100.0f * (1.0f - GetProfile()->GetTeamwork());
|
|
|
|
m_isRogue = (RandomFloat( 0, 100 ) < rogueChance);
|
|
}
|
|
|
|
return m_isRogue;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if we are in a hurry
|
|
*/
|
|
bool CCSBot::IsHurrying( void ) const
|
|
{
|
|
if (!m_hurryTimer.IsElapsed())
|
|
return true;
|
|
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
|
|
// if the bomb has been planted, we are in a hurry, CT or T (they could be defusing it!)
|
|
if (ctrl->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB && ctrl->IsBombPlanted())
|
|
return true;
|
|
|
|
// if we are a T and hostages are being rescued, we are in a hurry
|
|
if (ctrl->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES &&
|
|
GetTeamNumber() == TEAM_TERRORIST &&
|
|
GetGameState()->AreAllHostagesBeingRescued())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if it is the early, "safe", part of the round
|
|
*/
|
|
bool CCSBot::IsSafe( void ) const
|
|
{
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
|
|
if (ctrl->GetElapsedRoundTime() < m_safeTime)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if it is well past the early, "safe", part of the round
|
|
*/
|
|
bool CCSBot::IsWellPastSafe( void ) const
|
|
{
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
|
|
if (ctrl->GetElapsedRoundTime() > 2.0f * m_safeTime)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if we were in the safe time last update, but not now
|
|
*/
|
|
bool CCSBot::IsEndOfSafeTime( void ) const
|
|
{
|
|
return m_wasSafe && !IsSafe();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return the amount of "safe time" we have left
|
|
*/
|
|
float CCSBot::GetSafeTimeRemaining( void ) const
|
|
{
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
|
|
return m_safeTime - ctrl->GetElapsedRoundTime();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Called when enemy seen to adjust safe time for this round
|
|
*/
|
|
void CCSBot::AdjustSafeTime( void )
|
|
{
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
|
|
// if we spotted an enemy sooner than we thought possible, adjust our notion of "safe" time
|
|
if (ctrl->GetElapsedRoundTime() < m_safeTime)
|
|
{
|
|
// since right now is not safe, adjust safe time to be a few seconds ago
|
|
m_safeTime = ctrl->GetElapsedRoundTime() - 2.0f;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return true if we haven't seen an enemy for "a long time"
|
|
*/
|
|
bool CCSBot::HasNotSeenEnemyForLongTime( void ) const
|
|
{
|
|
const float longTime = 30.0f;
|
|
return (GetTimeSinceLastSawEnemy() > longTime);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Pick a random zone and hide near it
|
|
*/
|
|
bool CCSBot::GuardRandomZone( float range )
|
|
{
|
|
CCSBotManager *ctrl = static_cast<CCSBotManager *>( TheCSBots() );
|
|
|
|
const CCSBotManager::Zone *zone = ctrl->GetRandomZone();
|
|
if (zone)
|
|
{
|
|
CNavArea *rescueArea = ctrl->GetRandomAreaInZone( zone );
|
|
if (rescueArea)
|
|
{
|
|
Hide( rescueArea, -1.0f, range );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
class CollectRetreatSpotsFunctor
|
|
{
|
|
public:
|
|
CollectRetreatSpotsFunctor( CCSBot *me, float range )
|
|
{
|
|
m_me = me;
|
|
m_count = 0;
|
|
m_range = range;
|
|
}
|
|
|
|
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 ];
|
|
|
|
if (m_count >= MAX_SPOTS)
|
|
break;
|
|
|
|
// make sure hiding spot is in range
|
|
if (m_range > 0.0f)
|
|
if ((spot->GetPosition() - GetCentroid( m_me )).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;
|
|
}
|
|
|
|
// don't select spot if an enemy can see it
|
|
if (UTIL_IsVisibleToTeam( spot->GetPosition() + Vector( 0, 0, HalfHumanHeight ), OtherTeam( m_me->GetTeamNumber() ) ))
|
|
continue;
|
|
|
|
// don't select spot if it is closest to an enemy
|
|
CBasePlayer *owner = UTIL_GetClosestPlayer( spot->GetPosition() );
|
|
if (owner && !m_me->InSameTeam( owner ))
|
|
continue;
|
|
|
|
m_spot[ m_count++ ] = &spot->GetPosition();
|
|
}
|
|
|
|
// if we've filled up, stop searching
|
|
if (m_count == MAX_SPOTS)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
CCSBot *m_me;
|
|
float m_range;
|
|
|
|
const Vector *m_spot[ MAX_SPOTS ];
|
|
int m_count;
|
|
};
|
|
|
|
|
|
/**
|
|
* Do a breadth-first search to find a good retreat spot.
|
|
* Don't pick a spot that a Player is currently occupying.
|
|
*/
|
|
const Vector *FindNearbyRetreatSpot( CCSBot *me, float maxRange )
|
|
{
|
|
CNavArea *area = me->GetLastKnownArea();
|
|
if (area == NULL)
|
|
return NULL;
|
|
|
|
// collect spots that enemies cannot see
|
|
CollectRetreatSpotsFunctor collector( me, maxRange );
|
|
SearchSurroundingAreas( area, GetCentroid( me ), 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_spot[ which ];
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
class FarthestHostage
|
|
{
|
|
public:
|
|
FarthestHostage( const CCSBot *me )
|
|
{
|
|
m_me = me;
|
|
m_farRange = -1.0f;
|
|
}
|
|
|
|
bool operator() ( CHostage *hostage )
|
|
{
|
|
if (hostage->IsFollowing( m_me ))
|
|
{
|
|
float range = (hostage->GetAbsOrigin() - m_me->GetAbsOrigin()).Length();
|
|
if (range > m_farRange)
|
|
{
|
|
m_farRange = range;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const CCSBot *m_me;
|
|
float m_farRange;
|
|
};
|
|
|
|
/**
|
|
* Return euclidean distance to farthest escorted hostage.
|
|
* Return -1 if no hostage is following us.
|
|
*/
|
|
float CCSBot::GetRangeToFarthestEscortedHostage( void ) const
|
|
{
|
|
FarthestHostage away( this );
|
|
|
|
ForEachHostage( away );
|
|
|
|
return away.m_farRange;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return string describing current task
|
|
* NOTE: This MUST be kept in sync with the CCSBot::TaskType enum
|
|
*/
|
|
const char *CCSBot::GetTaskName( void ) const
|
|
{
|
|
static char *name[ NUM_TASKS ] =
|
|
{
|
|
"SEEK_AND_DESTROY",
|
|
"PLANT_BOMB",
|
|
"FIND_TICKING_BOMB",
|
|
"DEFUSE_BOMB",
|
|
"GUARD_TICKING_BOMB",
|
|
"GUARD_BOMB_DEFUSER",
|
|
"GUARD_LOOSE_BOMB",
|
|
"GUARD_BOMB_ZONE",
|
|
"GUARD_INITIAL_ENCOUNTER",
|
|
"ESCAPE_FROM_BOMB",
|
|
"HOLD_POSITION",
|
|
"FOLLOW",
|
|
"VIP_ESCAPE",
|
|
"GUARD_VIP_ESCAPE_ZONE",
|
|
"COLLECT_HOSTAGES",
|
|
"RESCUE_HOSTAGES",
|
|
"GUARD_HOSTAGES",
|
|
"GUARD_HOSTAGE_RESCUE_ZONE",
|
|
"MOVE_TO_LAST_KNOWN_ENEMY_POSITION",
|
|
"MOVE_TO_SNIPER_SPOT",
|
|
"SNIPING",
|
|
"ESCAPE_FROM_FLAMES",
|
|
};
|
|
|
|
return name[ (int)GetTask() ];
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return string describing current disposition
|
|
* NOTE: This MUST be kept in sync with the CCSBot::DispositionType enum
|
|
*/
|
|
const char *CCSBot::GetDispositionName( void ) const
|
|
{
|
|
static char *name[ NUM_DISPOSITIONS ] =
|
|
{
|
|
"ENGAGE_AND_INVESTIGATE",
|
|
"OPPORTUNITY_FIRE",
|
|
"SELF_DEFENSE",
|
|
"IGNORE_ENEMIES"
|
|
};
|
|
|
|
return name[ (int)GetDisposition() ];
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Return string describing current morale
|
|
* NOTE: This MUST be kept in sync with the CCSBot::MoraleType enum
|
|
*/
|
|
const char *CCSBot::GetMoraleName( void ) const
|
|
{
|
|
static char *name[ EXCELLENT - TERRIBLE + 1 ] =
|
|
{
|
|
"TERRIBLE",
|
|
"BAD",
|
|
"NEGATIVE",
|
|
"NEUTRAL",
|
|
"POSITIVE",
|
|
"GOOD",
|
|
"EXCELLENT"
|
|
};
|
|
|
|
return name[ (int)GetMorale() + 3 ];
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
/**
|
|
* Fill in a CUserCmd with our data
|
|
*/
|
|
void CCSBot::BuildUserCmd( CUserCmd& cmd, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse )
|
|
{
|
|
Q_memset( &cmd, 0, sizeof( cmd ) );
|
|
if ( !RunMimicCommand( cmd ) )
|
|
{
|
|
// Don't walk when ducked - it's painfully slow
|
|
if ( m_Local.m_bDucked || m_Local.m_bDucking )
|
|
{
|
|
buttons &= ~IN_SPEED;
|
|
}
|
|
|
|
cmd.command_number = gpGlobals->tickcount;
|
|
cmd.forwardmove = forwardmove;
|
|
cmd.sidemove = sidemove;
|
|
cmd.upmove = upmove;
|
|
cmd.buttons = buttons;
|
|
cmd.impulse = impulse;
|
|
|
|
VectorCopy( viewangles, cmd.viewangles );
|
|
cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Returns a value in the -1 .. +1 range based on adding some cosines together. Cheap and sloppy.
|
|
float CCSBot::SlowNoise( float fTau ) const
|
|
{
|
|
int iUniqueOffset = HashInt(entindex()) & 0xFF;
|
|
float t = (float)iUniqueOffset;
|
|
|
|
t = (t + gpGlobals->curtime / fTau) * M_PI * 2.0f;
|
|
|
|
return 0.25f * ( cosf( fTau ) + cosf( fTau * 29.f / 47.f ) + cosf( fTau * 59.f / 137.f ) + cosf( fTau * 151.f / 499.f ) );
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
// Some game types allow players to pass through each other, this method pushes them apart
|
|
void CCSBot::AvoidPlayers( CUserCmd *pCmd )
|
|
{
|
|
Vector forward, right;
|
|
EyeVectors( &forward, &right );
|
|
|
|
CUtlVector< CCSPlayer * > playerVector;
|
|
CollectPlayers( &playerVector, GetTeamNumber(), COLLECT_ONLY_LIVING_PLAYERS );
|
|
|
|
Vector avoidVector = vec3_origin;
|
|
|
|
float tooClose = 2.0f * HalfHumanWidth;
|
|
|
|
for( int i=0; i<playerVector.Count(); ++i )
|
|
{
|
|
CCSPlayer *them = playerVector[i];
|
|
|
|
if ( entindex() == them->entindex() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Vector between = GetAbsOrigin() - them->GetAbsOrigin();
|
|
if ( between.IsLengthLessThan( tooClose ) )
|
|
{
|
|
float range = between.NormalizeInPlace();
|
|
|
|
avoidVector += ( 1.0f - ( range / tooClose ) ) * between;
|
|
}
|
|
}
|
|
|
|
if ( avoidVector.IsZero() )
|
|
{
|
|
// m_Shared.SetSeparation( false );
|
|
// m_Shared.SetSeparationVelocity( vec3_origin );
|
|
return;
|
|
}
|
|
|
|
avoidVector.NormalizeInPlace();
|
|
|
|
// m_Shared.SetSeparation( true );
|
|
|
|
const float maxSpeed = 50.0f;
|
|
// m_Shared.SetSeparationVelocity( avoidVector * maxSpeed );
|
|
|
|
float ahead = maxSpeed * DotProduct( forward, avoidVector );
|
|
float side = maxSpeed * DotProduct( right, avoidVector );
|
|
|
|
pCmd->forwardmove += ahead;
|
|
pCmd->sidemove += side;
|
|
}
|