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.
1117 lines
28 KiB
1117 lines
28 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
// Author: Michael S. Booth (mike@turtlerockstudios.com), 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; |
|
} |
|
|
|
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 ) |
|
{ |
|
CBaseEntity *attacker = info.GetInflictor(); |
|
|
|
// getting hurt makes us alert |
|
BecomeAlert(); |
|
StopWaiting(); |
|
|
|
// if we were attacked by a teammate, rebuke |
|
if (attacker->IsPlayer()) |
|
{ |
|
CCSPlayer *player = static_cast<CCSPlayer *>( attacker ); |
|
|
|
if (InSameTeam( player ) && !player->IsBot()) |
|
GetChatter()->FriendlyFire(); |
|
} |
|
|
|
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.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 |
|
{ |
|
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 (InSameTeam( player )) |
|
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 ); |
|
} |
|
|
|
|
|
//-------------------------------------------------------------------------------------------------------------- |
|
/** |
|
* 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 const 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", |
|
}; |
|
|
|
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 const 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 const 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 ); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------------------------------------------------------------
|
|
|