|
|
//========= 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; }
|