This commit is contained in:
nephacks
2025-06-04 03:22:50 +02:00
parent f234f23848
commit f12416cffd
14243 changed files with 6446499 additions and 26 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,676 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Bot radio chatter system
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef CS_BOT_CHATTER_H
#define CS_BOT_CHATTER_H
#pragma warning( disable : 4786 ) // long STL names get truncated in browse info.
#include "nav_mesh.h"
#include "cs_gamestate.h"
class CCSBot;
class BotChatterInterface;
#define MAX_PLACES_PER_MAP 64
typedef unsigned int PlaceCriteria;
typedef unsigned int CountCriteria;
#define UNDEFINED_COUNT 0xFFFF
#define COUNT_CURRENT_ENEMIES 0xFF // use the number of enemies we see right when we speak
#define COUNT_MANY 4 // equal to or greater than this is "many"
#define UNDEFINED_SUBJECT (-1)
/// @todo Make Place a class with member fuctions for this
bool GetRandomSpotAtPlace( Place place, Vector *pPos );
//----------------------------------------------------------------------------------------------------
/**
* A meme is a unit information that bots use to
* transmit information to each other via the radio
*/
class BotMeme
{
public:
void Transmit( CCSBot *sender ) const; ///< transmit meme to other bots
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const = 0; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotHelpMeme : public BotMeme
{
public:
BotHelpMeme( Place place = UNDEFINED_PLACE )
{
m_place = place;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
Place m_place; ///< where the help is needed
};
//----------------------------------------------------------------------------------------------------
class BotBombsiteStatusMeme : public BotMeme
{
public:
enum StatusType { CLEAR, PLANTED };
BotBombsiteStatusMeme( int zoneIndex, StatusType status )
{
m_zoneIndex = zoneIndex;
m_status = status;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
int m_zoneIndex; ///< the bombsite
StatusType m_status; ///< whether it is cleared or the bomb is there (planted)
};
//----------------------------------------------------------------------------------------------------
class BotBombStatusMeme : public BotMeme
{
public:
BotBombStatusMeme( CSGameState::BombState state, const Vector &pos )
{
m_state = state;
m_pos = pos;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
CSGameState::BombState m_state;
Vector m_pos;
};
//----------------------------------------------------------------------------------------------------
class BotFollowMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotDefendHereMeme : public BotMeme
{
public:
BotDefendHereMeme( const Vector &pos )
{
m_pos = pos;
}
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
private:
Vector m_pos;
};
//----------------------------------------------------------------------------------------------------
class BotWhereBombMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotRequestReportMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotAllHostagesGoneMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotHostageBeingTakenMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotHeardNoiseMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
class BotWarnSniperMeme : public BotMeme
{
public:
virtual void Interpret( CCSBot *sender, CCSBot *receiver ) const; ///< cause the given bot to act on this meme
};
//----------------------------------------------------------------------------------------------------
enum BotStatementType
{
REPORT_VISIBLE_ENEMIES,
REPORT_ENEMY_ACTION,
REPORT_MY_CURRENT_TASK,
REPORT_MY_INTENTION,
REPORT_CRITICAL_EVENT,
REPORT_REQUEST_HELP,
REPORT_REQUEST_INFORMATION,
REPORT_ROUND_END,
REPORT_MY_PLAN,
REPORT_INFORMATION,
REPORT_EMOTE,
REPORT_ACKNOWLEDGE, ///< affirmative or negative
REPORT_ENEMIES_REMAINING,
REPORT_FRIENDLY_FIRE,
REPORT_KILLED_FRIEND,
REPORT_ENEMY_LOST,
NUM_BOT_STATEMENT_TYPES
};
//----------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------
/**
* BotSpeakables are the smallest unit of bot chatter.
* They represent a specific wav file of a phrase, and the criteria for which it is useful
*/
class BotSpeakable
{
public:
BotSpeakable();
~BotSpeakable();
char *m_phrase;
float m_duration;
PlaceCriteria m_place;
CountCriteria m_count;
};
typedef CUtlVector< BotSpeakable * > BotSpeakableVector;
typedef CUtlVector< BotSpeakableVector * > BotVoiceBankVector;
//----------------------------------------------------------------------------------------------------
/**
* The BotPhrase class is a collection of Speakables associated with a name, ID, and criteria
*/
class BotPhrase
{
public:
char *GetSpeakable( int bankIndex, float *duration = NULL ) const; ///< return a random speakable and its duration in seconds that meets the current criteria
// NOTE: Criteria must be set just before the GetSpeakable() call, since they are shared among all bots
void ClearCriteria( void ) const;
void SetPlaceCriteria( PlaceCriteria place ) const; ///< all returned phrases must have this place criteria
void SetCountCriteria( CountCriteria count ) const; ///< all returned phrases must have this count criteria
void SetCriteriaSet( AI_CriteriaSet &criteria ) const;
const char *GetName( void ) const { return m_name; }
const unsigned int GetPlace( void ) const { return m_place; }
RadioType GetRadioEquivalent( void ) const { return m_radioEvent; } ///< return equivalent "standard radio" event
bool IsImportant( void ) const { return m_isImportant; } ///< return true if this phrase is part of an important statement
bool IsPlace( void ) const { return m_isPlace; }
void Randomize( void ); ///< randomly shuffle the speakable order
PlaceCriteria GetPlaceCriteria( void ) const { return m_placeCriteria; }
CountCriteria GetCountCriteria( void ) const { return m_countCriteria; }
AI_CriteriaSet &GetCriteriaSet( void ) const { return m_contexts; }
private:
friend class BotPhraseManager;
BotPhrase( bool isPlace );
~BotPhrase();
char *m_name;
Place m_place;
bool m_isPlace; ///< true if this is a Place phrase
RadioType m_radioEvent; ///< equivalent radio event
bool m_isImportant; ///< mission-critical statement
mutable BotVoiceBankVector m_voiceBank; ///< array of voice banks (arrays of speakables)
CUtlVector< int > m_count; ///< number of speakables
mutable CUtlVector< int > m_index; ///< index of next speakable to return
int m_numVoiceBanks; ///< number of voice banks that have been initialized
void InitVoiceBank( int bankIndex ); ///< sets up the vector of voice banks for the first bankIndex voice banks
mutable PlaceCriteria m_placeCriteria;
mutable CountCriteria m_countCriteria;
mutable AI_CriteriaSet m_contexts;
};
typedef CUtlVector<BotPhrase *> BotPhraseList;
inline void BotPhrase::ClearCriteria( void ) const
{
m_placeCriteria = ANY_PLACE;
m_countCriteria = UNDEFINED_COUNT;
}
inline void BotPhrase::SetPlaceCriteria( PlaceCriteria place ) const
{
m_placeCriteria = place;
}
inline void BotPhrase::SetCountCriteria( CountCriteria count ) const
{
m_countCriteria = count;
}
inline void BotPhrase::SetCriteriaSet( AI_CriteriaSet &criteria ) const
{
m_contexts.Reset();
m_contexts.Merge( &criteria );
}
enum BotChatterOutputType
{
BOT_CHATTER_RADIO,
BOT_CHATTER_VOICE
};
typedef CUtlVector<BotChatterOutputType> BotOutputList;
//----------------------------------------------------------------------------------------------------
/**
* The BotPhraseManager is a singleton that provides an interface to all BotPhrase collections
*/
class BotPhraseManager
{
public:
BotPhraseManager( void );
~BotPhraseManager();
bool Initialize( const char *filename, int bankIndex ); ///< initialize phrase system from database file for a specific voice bank (0 is the default voice bank)
void OnRoundRestart( void ); ///< invoked when round resets
void OnMapChange( void ); ///< invoked when map changes
void Reset( void );
const BotPhrase *GetPhrase( const char *name ) const; ///< given a name, return the associated phrase collection
const BotPhrase *GetPainPhrase( void ) const { return m_painPhrase; } ///< optimization, replaces a static pointer to the phrase
const BotPhrase *GetAgreeWithPlanPhrase( void ) const { return m_agreeWithPlanPhrase; } ///< optimization, replaces a static pointer to the phrase
const BotPhrase *GetPlace( const char *name ) const; ///< given a name, return the associated Place phrase collection
const BotPhrase *GetPlace( unsigned int id ) const; ///< given an id, return the associated Place phrase collection
const BotPhraseList *GetPlaceList( void ) const { return &m_placeList; }
float GetPlaceStatementInterval( Place where ) const; ///< return time last statement of given type was emitted by a teammate for the given place
void ResetPlaceStatementInterval( Place where ); ///< set time of last statement of given type was emitted by a teammate for the given place
BotChatterOutputType GetOutputType( int voiceBank ) const;
private:
BotPhraseList m_list; ///< master list of all phrase collections
BotPhraseList m_placeList; ///< master list of all Place phrases
BotOutputList m_output;
const BotPhrase *m_painPhrase;
const BotPhrase *m_agreeWithPlanPhrase;
struct PlaceTimeInfo
{
Place placeID;
IntervalTimer timer;
};
mutable PlaceTimeInfo m_placeStatementHistory[ MAX_PLACES_PER_MAP ];
mutable int m_placeCount;
int FindPlaceIndex( Place where ) const;
};
inline int BotPhraseManager::FindPlaceIndex( Place where ) const
{
for( int i=0; i<m_placeCount; ++i )
if (m_placeStatementHistory[i].placeID == where)
return i;
// no such place - allocate it
if (m_placeCount < MAX_PLACES_PER_MAP)
{
m_placeStatementHistory[ m_placeCount ].placeID = where;
m_placeStatementHistory[ m_placeCount ].timer.Invalidate();
++m_placeCount;
return m_placeCount-1;
}
// place directory is full
return -1;
}
/**
* Return time last statement of given type was emitted by a teammate for the given place
*/
inline float BotPhraseManager::GetPlaceStatementInterval( Place place ) const
{
int index = FindPlaceIndex( place );
if (index < 0)
return 999999.9f;
if (index >= m_placeCount)
return 999999.9f;
return m_placeStatementHistory[ index ].timer.GetElapsedTime();
}
/**
* Set time of last statement of given type was emitted by a teammate for the given place
*/
inline void BotPhraseManager::ResetPlaceStatementInterval( Place place )
{
int index = FindPlaceIndex( place );
if (index < 0)
return;
if (index >= m_placeCount)
return;
// update entry
m_placeStatementHistory[ index ].timer.Reset();
}
extern BotPhraseManager *TheBotPhrases;
//----------------------------------------------------------------------------------------------------
/**
* Statements are meaningful collections of phrases
*/
class BotStatement
{
public:
BotStatement( BotChatterInterface *chatter, BotStatementType type, float expireDuration );
~BotStatement();
BotChatterInterface *GetChatter( void ) const { return m_chatter; }
CCSBot *GetOwner( void ) const;
BotStatementType GetType( void ) const { return m_type; } ///< return the type of statement this is
bool IsImportant( void ) const; ///< return true if this statement is "important" and not personality chatter
bool HasSubject( void ) const { return (m_subject == UNDEFINED_SUBJECT) ? false : true; }
void SetSubject( int playerID ) { m_subject = playerID; } ///< who this statement is about
int GetSubject( void ) const { return m_subject; } ///< who this statement is about
bool HasPlace( void ) const { return (GetPlace()) ? true : false; }
Place GetPlace( void ) const; ///< if this statement refers to a specific place, return that place
void SetPlace( Place where ) { m_place = where; } ///< explicitly set place
bool HasCount( void ) const; ///< return true if this statement has an associated count
bool IsRedundant( const BotStatement *say ) const; ///< return true if this statement is the same as the given one
bool IsObsolete( void ) const; ///< return true if this statement is no longer appropriate to say
void Convert( const BotStatement *say ); ///< possibly change what were going to say base on what teammate is saying
void AppendPhrase( const BotPhrase *phrase );
void SetStartTime( float timestamp ) { m_startTime = timestamp; } ///< define the earliest time this statement can be spoken
float GetStartTime( void ) const { return m_startTime; }
enum ConditionType
{
IS_IN_COMBAT,
RADIO_SILENCE,
ENEMIES_REMAINING,
NUM_CONDITIONS
};
void AddCondition( ConditionType condition ); ///< conditions must be true for the statement to be spoken
bool IsValid( void ) const; ///< verify all attached conditions
enum ContextType
{
CURRENT_ENEMY_COUNT,
REMAINING_ENEMY_COUNT,
SHORT_DELAY,
LONG_DELAY,
ACCUMULATE_ENEMIES_DELAY
};
void AppendPhrase( ContextType contextPhrase ); ///< special phrases that depend on the context
bool Update( void ); ///< emit statement over time, return false if statement is done
bool IsSpeaking( void ) const { return m_isSpeaking; } ///< return true if this statement is currently being spoken
float GetTimestamp( void ) const { return m_timestamp; } ///< get time statement was created (but not necessarily started talking)
void AttachMeme( BotMeme *meme ); ///< attach a meme to this statement, to be transmitted to other friendly bots when spoken
private:
friend class BotChatterInterface;
BotChatterInterface *m_chatter; ///< the chatter system this statement is part of
BotStatement *m_next, *m_prev; ///< linked list hooks
BotStatementType m_type; ///< what kind of statement this is
int m_subject; ///< who this subject is about
Place m_place; ///< explicit place - note some phrases have implicit places as well
BotMeme *m_meme; ///< a statement can only have a single meme for now
float m_timestamp; ///< time when message was created
float m_startTime; ///< the earliest time this statement can be spoken
float m_expireTime; ///< time when this statement is no longer valid
float m_speakTimestamp; ///< time when message began being spoken
bool m_isSpeaking; ///< true if this statement is current being spoken
float m_nextTime; ///< time for next phrase to begin
enum { MAX_BOT_PHRASES = 4 };
struct
{
bool isPhrase;
union
{
const BotPhrase *phrase;
ContextType context;
};
}
m_statement[ MAX_BOT_PHRASES ];
enum { MAX_BOT_CONDITIONS = 4 };
ConditionType m_condition[ MAX_BOT_CONDITIONS ]; ///< conditions that must be true for the statement to be said
int m_conditionCount;
int m_index; ///< m_index refers to the phrase currently being spoken, or -1 if we havent started yet
int m_count;
};
//----------------------------------------------------------------------------------------------------
/**
* This class defines the interface to the bot radio chatter system
*/
class BotChatterInterface
{
public:
BotChatterInterface( CCSBot *me );
virtual ~BotChatterInterface();
void Reset( void ); ///< reset to initial state
void Update( void ); ///< process ongoing chatter
/// invoked when event occurs in the game (some events have NULL entities)
void OnDeath( void ); ///< invoked when we die
enum VerbosityType
{
NORMAL, ///< full chatter
MINIMAL, ///< only scenario-critical events
RADIO, ///< use the standard radio instead
OFF ///< no chatter at all
};
VerbosityType GetVerbosity( void ) const; ///< return our current level of verbosity
CCSBot *GetOwner( void ) const { return m_me; }
bool IsTalking( void ) const; ///< return true if we are currently talking
float GetRadioSilenceDuration( void ); ///< return time since any teammate said anything
void ResetRadioSilenceDuration( void );
enum { MUST_ADD = 1 };
void AddStatement( BotStatement *statement, bool mustAdd = false ); ///< register a statement for speaking
void RemoveStatement( BotStatement *statement ); ///< remove a statement
BotStatement *GetActiveStatement( void ); ///< returns the statement that is being spoken, or is next to be spoken if no-one is speaking now
BotStatement *GetStatement( void ) const; ///< returns our current statement, or NULL if we aren't speaking
int GetPitch( void ) const { return m_pitch; }
//-- things the bots can say ---------------------------------------------------------------------
void Say( const char *phraseName, float lifetime = 3.0f, float delay = 0.0f );
void AnnouncePlan( const char *phraseName, Place where );
void Affirmative( void );
void Negative( void );
virtual void EnemySpotted( void ); ///< report enemy sightings
virtual void KilledMyEnemy( int victimID );
virtual void EnemiesRemaining( void );
void SpottedSniper( void );
void FriendSpottedSniper( void );
void Clear( Place where );
void ReportIn( void ); ///< ask for current situation
void ReportingIn( void ); ///< report current situation
virtual bool NeedBackup( void );
void PinnedDown( void );
void Scared( void );
void HeardNoise( const Vector &pos );
void FriendHeardNoise( void );
void TheyPickedUpTheBomb( void );
void GoingToPlantTheBomb( Place where );
void BombsiteClear( int zoneIndex );
void FoundPlantedBomb( int zoneIndex );
void PlantingTheBomb( Place where );
void SpottedBomber( CBasePlayer *bomber );
void SpottedLooseBomb( CBaseEntity *bomb );
void GuardingLooseBomb( CBaseEntity *bomb );
void RequestBombLocation( void );
#define IS_PLAN true
void GuardingHostages( Place where, bool isPlan = false );
void GuardingHostageEscapeZone( bool isPlan = false );
void HostagesBeingTaken( void );
void HostagesTaken( void );
void TalkingToHostages( void );
void EscortingHostages( void );
void HostageDown( void );
void GuardingBombsite( Place where );
virtual void CelebrateWin( void );
void Encourage( const char *phraseName, float repeatInterval = 10.0f, float lifetime = 3.0f ); ///< "encourage" the player to do the scenario
void KilledFriend( void );
void FriendlyFire( const char *pDmgType );
void DoPhoenixHeavyWakeTaunt( void );
bool SeesAtLeastOneEnemy( void ) const { return m_seeAtLeastOneEnemy; }
private:
BotStatement *m_statementList; ///< list of all active/pending messages for this bot
void ReportEnemies( void ); ///< track nearby enemy count and generate enemy activity statements
bool ShouldSpeak( void ) const; ///< return true if we speaking makes sense now
CCSBot *m_me; ///< the bot this chatter is for
bool m_seeAtLeastOneEnemy;
float m_timeWhenSawFirstEnemy;
bool m_reportedEnemies;
bool m_requestedBombLocation; ///< true if we already asked where the bomb has been planted
int m_pitch;
static IntervalTimer m_radioSilenceInterval[ 2 ]; ///< one timer for each team
IntervalTimer m_needBackupInterval;
IntervalTimer m_spottedBomberInterval;
IntervalTimer m_scaredInterval;
IntervalTimer m_planInterval;
CountdownTimer m_spottedLooseBombTimer;
CountdownTimer m_heardNoiseTimer;
CountdownTimer m_escortingHostageTimer;
CountdownTimer m_warnSniperTimer;
static CountdownTimer m_encourageTimer; ///< timer to know when we can "encourage" the human player again - shared by all bots
CountdownTimer m_heavyTauntTimer;
};
inline BotChatterInterface::VerbosityType BotChatterInterface::GetVerbosity( void ) const
{
const char *string = cv_bot_chatter.GetString();
if (string == NULL)
return NORMAL;
if (string[0] == 'm' || string[0] == 'M')
return MINIMAL;
if (string[0] == 'r' || string[0] == 'R')
return RADIO;
if (string[0] == 'o' || string[0] == 'O')
return OFF;
return NORMAL;
}
inline bool BotChatterInterface::IsTalking( void ) const
{
if (m_statementList)
return m_statementList->IsSpeaking();
return false;
}
inline BotStatement *BotChatterInterface::GetStatement( void ) const
{
return m_statementList;
}
inline void BotChatterInterface::Say( const char *phraseName, float lifetime, float delay )
{
BotStatement *say = new BotStatement( this, REPORT_MY_INTENTION, lifetime );
say->AppendPhrase( TheBotPhrases->GetPhrase( phraseName ) );
if (delay > 0.0f)
say->SetStartTime( gpGlobals->curtime + delay );
AddStatement( say );
}
// In player vs bot game modes, have the bots chatter about what they're doing to the players
class BotChatterCoop : public BotChatterInterface
{
typedef BotChatterInterface BaseClass;
public:
BotChatterCoop( CCSBot *me );
virtual void KilledMyEnemy( int nVictimID ) OVERRIDE;
virtual void EnemiesRemaining( void ) OVERRIDE;
virtual void CelebrateWin( void ) OVERRIDE;
virtual void EnemySpotted( void ) OVERRIDE;
};
#endif // CS_BOT_CHATTER_H

View File

@@ -0,0 +1,82 @@
//============== Copyright Valve Corporation, All rights reserved. ============//
//
/// Custom chatter rules for bots when playing in cooperative modes
//
//=============================================================================//
#include "cbase.h"
#include "cs_player.h"
#include "bot_util.h"
#include "cs_bot.h"
#include "cs_bot_chatter.h"
#include "cs_team.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
BotChatterCoop::BotChatterCoop( CCSBot *me ) :
BotChatterInterface( me )
{
}
//---------------------------------------------------------------------------------------------------------------
void BotChatterCoop::KilledMyEnemy( int victimID )
{
if ( GetGlobalCSTeam(TEAM_CT)->GetAliveMembers() == 0 )
{
CelebrateWin();
}
else
{
BotStatement *say = new BotStatement( this, REPORT_ENEMY_ACTION, 3.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "KilledMyEnemy" ) );
say->SetSubject( victimID );
AddStatement( say );
}
}
void BotChatterCoop::EnemiesRemaining( void )
{
if ( GetGlobalCSTeam(TEAM_CT)->GetAliveMembers() == 0 )
{
CelebrateWin();
}
else
{
BotStatement *say = new BotStatement( this, REPORT_ENEMIES_REMAINING, 5.0f );
say->AppendPhrase( BotStatement::REMAINING_ENEMY_COUNT );
say->SetStartTime( gpGlobals->curtime );
AddStatement( say );
}
}
void BotChatterCoop::EnemySpotted( void )
{
float flChance = RandomFloat();
if( flChance < 0.3 )
{
BaseClass::EnemySpotted();
}
else if ( flChance < 0.7 )
{
BotStatement *say = new BotStatement( this, REPORT_EMOTE, 3.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "GoGoGo" ) );
AddStatement( say );
}
else
{
BotStatement *say = new BotStatement( this, REPORT_EMOTE, 3.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "Cheer" ) );
AddStatement( say );
}
}
void BotChatterCoop::CelebrateWin( void )
{
BotStatement *say = new BotStatement( this, REPORT_EMOTE, 15.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "WonRound" ) );
AddStatement( say );
}

View File

@@ -0,0 +1,480 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "keyvalues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Checks if the bot can hear the event
*/
void CCSBot::OnAudibleEvent( IGameEvent *event, CBasePlayer *player, float range, PriorityType priority, bool isHostile, bool isFootstep, const Vector *actualOrigin )
{
/// @todo Listen to non-player sounds
if (player == NULL)
return;
// don't pay attention to noise that friends make (unless it is a decoy)
if ( !IsEnemy( player ) )
{
if ( !event || !FStrEq( event->GetName(), "decoy_firing" ) )
return;
}
Vector playerOrigin = GetCentroid( player );
Vector myOrigin = GetCentroid( this );
// If the event occurs far from the triggering player, it may override the origin
if ( actualOrigin )
{
playerOrigin = *actualOrigin;
}
// check if noise is close enough for us to hear
const Vector *newNoisePosition = &playerOrigin;
float newNoiseDist = (myOrigin - *newNoisePosition).Length();
if (newNoiseDist < range)
{
// we heard the sound
if ((IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() == 3) || cv_bot_debug.GetInt() == 4)
{
PrintIfWatched( "Heard noise (%s from %s, pri %s, time %3.1f)\n",
(FStrEq( "weapon_fire", event ? event->GetName() : "<no event>" )) ? "Weapon fire " : "",
(player) ? player->GetPlayerName() : "NULL",
(priority == PRIORITY_HIGH) ? "HIGH" : ((priority == PRIORITY_MEDIUM) ? "MEDIUM" : "LOW"),
gpGlobals->curtime );
}
// should we pay attention to it
// if noise timestamp is zero, there is no prior noise
if (m_noiseTimestamp > 0.0f)
{
// only overwrite recent sound if we are louder (closer), or more important - if old noise was long ago, its faded
const float shortTermMemoryTime = 3.0f;
if (gpGlobals->curtime - m_noiseTimestamp < shortTermMemoryTime)
{
// prior noise is more important - ignore new one
if (priority < m_noisePriority)
return;
float oldNoiseDist = (myOrigin - m_noisePosition).Length();
if (newNoiseDist >= oldNoiseDist)
return;
}
}
// find the area in which the noise occured
/// @todo Better handle when noise occurs off the nav mesh
/// @todo Make sure noise area is not through a wall or ceiling from source of noise
/// @todo Change GetNavTravelTime to better deal with NULL destination areas
CNavArea *noiseArea = TheNavMesh->GetNearestNavArea( *newNoisePosition );
if (noiseArea == NULL)
{
PrintIfWatched( " *** Noise occurred off the nav mesh - ignoring!\n" );
return;
}
m_noiseArea = noiseArea;
// remember noise priority
m_noisePriority = priority;
// randomize noise position in the area a bit - hearing isn't very accurate
// the closer the noise is, the more accurate our placement
/// @todo Make sure not to pick a position on the opposite side of ourselves.
const float maxErrorRadius = 400.0f;
const float maxHearingRange = 2000.0f;
float errorRadius = maxErrorRadius * newNoiseDist/maxHearingRange;
m_noisePosition.x = newNoisePosition->x + RandomFloat( -errorRadius, errorRadius );
m_noisePosition.y = newNoisePosition->y + RandomFloat( -errorRadius, errorRadius );
// note the *travel distance* to the noise
// EDIT: use straight line distance for now; the A* calc is really expensive
m_noiseTravelDistance = EyePosition().DistTo( player->EyePosition() );
// make sure noise position remains in the same area
m_noiseArea->GetClosestPointOnArea( m_noisePosition, &m_noisePosition );
// note when we heard the noise
m_noiseTimestamp = gpGlobals->curtime;
// if we hear a nearby enemy, become alert
const float nearbyNoiseRange = 1000.0f;
if (m_noiseTravelDistance < nearbyNoiseRange && m_noiseTravelDistance > 0.0f)
{
BecomeAlert();
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHEGrenadeDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 99999.0f, PRIORITY_HIGH, true ); // hegrenade_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnFlashbangDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // flashbang_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnSmokeGrenadeDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1000.0f, PRIORITY_LOW, true ); // smokegrenade_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnMolotovDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 99999.0f, PRIORITY_HIGH, true ); // molotov_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnDecoyDetonate( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 99999.0f, PRIORITY_HIGH, true ); // decoy_detonate
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnDecoyFiring( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *thrower = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( thrower == this )
return;
Vector decoySpot( event->GetInt( "x" ), event->GetInt( "y" ), event->GetInt( "z" ) );
OnAudibleEvent( event, thrower, 99999.0f, PRIORITY_HIGH, true, false, &decoySpot );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnGrenadeBounce( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 500.0f, PRIORITY_LOW, true ); // grenade_bounce
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBulletImpact( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// Construct an origin for the sound, since it can be far from the originating player
Vector actualOrigin;
actualOrigin.x = event->GetFloat( "x", 0.0f );
actualOrigin.y = event->GetFloat( "y", 0.0f );
actualOrigin.z = event->GetFloat( "z", 0.0f );
/// @todo Ignoring bullet impact events for now - we dont want bots to look directly at them!
//OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true, false, &actualOrigin ); // bullet_impact
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBreakProp( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_prop
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBreakBreakable( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, true ); // break_glass
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnDoorMoving( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_MEDIUM, false ); // door_moving
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHostageFollows( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// player_follows needs a player
if (player == NULL)
return;
// don't pay attention to noise that friends make
if (!IsEnemy( player ))
return;
Vector playerOrigin = GetCentroid( player );
Vector myOrigin = GetCentroid( this );
const float range = 1200.0f;
// this is here so T's not only act on the noise, but look at it, too
if (GetTeamNumber() == TEAM_TERRORIST)
{
// make sure we can hear the noise
if ((playerOrigin - myOrigin).IsLengthGreaterThan( range ))
return;
// tell our teammates that the hostages are being taken
GetChatter()->HostagesBeingTaken();
// only move if we hear them being rescued and can't see any hostages
if (GetGameState()->GetNearestVisibleFreeHostage() == NULL)
{
// since we are guarding the hostages, presumably we know where they are
// if we're close enough to "hear" this event, either go to where the event occured,
// or head for an escape zone to head them off
if (GetTask() != CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
//const float headOffChance = 33.3f;
if (true) // || RandomFloat( 0, 100 ) < headOffChance)
{
// head them off at a rescue zone
if (GuardRandomZone())
{
SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
SetDisposition( CCSBot::OPPORTUNITY_FIRE );
PrintIfWatched( "Trying to beat them to an escape zone!\n" );
}
}
else
{
SetTask( SEEK_AND_DESTROY );
StandUp();
Run();
MoveTo( playerOrigin, FASTEST_ROUTE );
}
}
}
}
else
{
// CT's don't care about this noise
return;
}
OnAudibleEvent( event, player, range, PRIORITY_MEDIUM, false ); // hostage_follows
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnRoundEnd( IGameEvent *event )
{
// Morale adjustments happen even for dead players
int winner = event->GetInt( "winner" );
switch ( winner )
{
case WINNER_TER:
if (GetTeamNumber() == TEAM_CT)
{
DecreaseMorale();
}
else
{
IncreaseMorale();
}
break;
case WINNER_CT:
if (GetTeamNumber() == TEAM_CT)
{
IncreaseMorale();
}
else
{
DecreaseMorale();
}
break;
default:
break;
}
m_gameState.OnRoundEnd( event );
if ( !IsAlive() )
return;
if ( event->GetInt( "winner" ) == WINNER_TER )
{
if (GetTeamNumber() == TEAM_TERRORIST)
GetChatter()->CelebrateWin();
}
else if ( event->GetInt( "winner" ) == WINNER_CT )
{
if (GetTeamNumber() == TEAM_CT)
GetChatter()->CelebrateWin();
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnRoundStart( IGameEvent *event )
{
m_gameState.OnRoundStart( event );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnHostageRescuedAll( IGameEvent *event )
{
m_gameState.OnHostageRescuedAll( event );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnNavBlocked( IGameEvent *event )
{
if ( event->GetBool( "blocked" ) )
{
unsigned int areaID = event->GetInt( "area" );
if ( areaID )
{
// An area was blocked off. Reset our path if it has this area on it.
for( int i=0; i<m_pathLength; ++i )
{
const ConnectInfo *info = &m_path[ i ];
if ( info->area && info->area->GetID() == areaID )
{
DestroyPath();
return;
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Invoked when bot enters a nav area
*/
void CCSBot::OnEnteredNavArea( CNavArea *newArea )
{
SNPROF("OnEnteredNavArea");
// assume that we "clear" an area of enemies when we enter it
newArea->SetClearedTimestamp( GetTeamNumber()-1 );
// if we just entered a 'stop' area, set the flag
if ( newArea->GetAttributes() & NAV_MESH_STOP )
{
m_isStopping = true;
}
/// @todo Flag these areas as spawn areas during load
if (IsAtEnemySpawn())
{
m_hasVisitedEnemySpawn = true;
}
}

View File

@@ -0,0 +1,165 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "keyvalues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombPickedUp( IGameEvent *event )
{
if ( !IsAlive() )
return;
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
// In guardian mode, terrorists always know who the bomber is
if ( CSGameRules()->IsPlayingCoopGuardian() && GetTeamNumber() == TEAM_TERRORIST )
{
GetGameState()->UpdateBomber( player->GetAbsOrigin() );
}
// don't react to our own events
if ( player == this )
return;
if (GetTeamNumber() == TEAM_CT && player)
{
// check if we're close enough to hear it
const float bombPickupHearRangeSq = 1000.0f * 1000.0f;
Vector myOrigin = GetCentroid( this );
if ((myOrigin - player->GetAbsOrigin()).LengthSqr() < bombPickupHearRangeSq)
{
GetChatter()->TheyPickedUpTheBomb();
GetGameState()->UpdateBomber( player->GetAbsOrigin() );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombPlanted( IGameEvent *event )
{
m_gameState.OnBombPlanted( event );
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// if we're a TEAM_CT, forget what we're doing and go after the bomb
if (GetTeamNumber() == TEAM_CT)
{
Idle();
}
// if we are following someone, stop following
if (IsFollowing())
{
StopFollowing();
Idle();
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombBeep( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
CBaseEntity *entity = UTIL_EntityByIndex( event->GetInt( "entindex" ) );
Vector myOrigin = GetCentroid( this );
// if we don't know where the bomb is, but heard it beep, we've discovered it
if (GetGameState()->IsPlantedBombLocationKnown() == false && entity)
{
// check if we're close enough to hear it
const float bombBeepHearRangeSq = 1500.0f * 1500.0f;
if ((myOrigin - entity->GetAbsOrigin()).LengthSqr() < bombBeepHearRangeSq)
{
// radio the news to our team
if (GetTeamNumber() == TEAM_CT && GetGameState()->GetPlantedBombsite() == CSGameState::UNKNOWN)
{
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( entity->GetAbsOrigin() );
if (zone)
GetChatter()->FoundPlantedBomb( zone->m_index );
}
// remember where the bomb is
GetGameState()->UpdatePlantedBomb( entity->GetAbsOrigin() );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombDefuseBegin( IGameEvent *event )
{
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombDefused( IGameEvent *event )
{
m_gameState.OnBombDefused( event );
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
if (GetTeamNumber() == TEAM_CT)
{
if (TheCSBots()->GetBombTimeLeft() < 2.0f)
GetChatter()->Say( "BarelyDefused" );
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombDefuseAbort( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
PrintIfWatched( "BOMB DEFUSE ABORTED\n" );
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnBombExploded( IGameEvent *event )
{
m_gameState.OnBombExploded( event );
}

View File

@@ -0,0 +1,238 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "keyvalues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerDeath( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
Vector playerOrigin = (player) ? GetCentroid( player ) : Vector( 0, 0, 0 );
CBasePlayer *other = UTIL_PlayerByUserId( event->GetInt( "attacker" ) );
CBasePlayer *victim = player;
CBasePlayer *killer = (other && other->IsPlayer()) ? static_cast<CBasePlayer *>( other ) : NULL;
// if the human player died in the single player game, tell the team
if (CSGameRules()->IsCareer() && !victim->IsBot() && victim->GetTeamNumber() == GetTeamNumber())
{
GetChatter()->Say( "CommanderDown", 20.0f );
}
// keep track of the last player we killed
if (killer == this)
{
m_lastVictimID = victim->entindex();
}
// react to teammate death
if (victim->GetTeamNumber() == GetTeamNumber())
{
// note time of death
m_friendDeathTimestamp = gpGlobals->curtime;
// chastise friendly fire from humans
if (killer && !killer->IsBot() && killer->GetTeamNumber() == GetTeamNumber() && killer != this)
{
GetChatter()->KilledFriend();
}
if ( CSGameRules()->IsPlayingCoopMission() )
{
if ( victim )
{
Vector vDelta = GetAbsOrigin() - victim->GetAbsOrigin();
if ( vDelta.Length() < 320 )
m_bIsSleeping = false;
}
}
if (IsAttacking())
{
if ( !CSGameRules()->IsPlayingCoopMission() && GetTimeSinceLastSawEnemy() > 0.4f)
{
PrintIfWatched( "Rethinking my attack due to teammate death\n" );
// allow us to sneak past windows, doors, etc
IgnoreEnemies( 1.0f );
// move to last known position of enemy - this could cause us to flank if
// the danger has changed due to our teammate's recent death
SetTask( MOVE_TO_LAST_KNOWN_ENEMY_POSITION, GetBotEnemy() );
MoveTo( GetLastKnownEnemyPosition() );
return;
}
}
else // not attacking
{
//
// If we just saw a nearby friend die, and we haven't yet acquired an enemy
// automatically acquire our dead friend's killer
//
if (GetDisposition() == ENGAGE_AND_INVESTIGATE || GetDisposition() == OPPORTUNITY_FIRE)
{
CBasePlayer *other = UTIL_PlayerByUserId( event->GetInt( "attacker" ) );
// check that attacker is an enemy (for friendly fire, etc)
if (other && other->IsPlayer())
{
CCSPlayer *killer = static_cast<CCSPlayer *>( other );
if (killer->GetTeamNumber() != GetTeamNumber())
{
// check if we saw our friend die - dont check FOV - assume we're aware of our surroundings in combat
// snipers stay put
if (!IsSniper() && IsVisible( playerOrigin ))
{
// people are dying - we should hurry
Hurry( RandomFloat( 10.0f, 15.0f ) );
// if we're hiding with only our knife, be a little more cautious
const float knifeAmbushChance = 50.0f;
if (!IsHiding() || !IsUsingKnife() || RandomFloat( 0, 100 ) < knifeAmbushChance)
{
PrintIfWatched( "Attacking our friend's killer!\n" );
Attack( killer );
return;
}
}
// if friend was far away and we haven't seen an enemy in awhile, go to where our friend was killed
const float longHidingTime = 20.0f;
if (IsHunting() || IsInvestigatingNoise() || (IsHiding() && GetTask() != FOLLOW && GetHidingTime() > longHidingTime))
{
const float someTime = 10.0f;
const float farAway = 750.0f;
if (GetTimeSinceLastSawEnemy() > someTime && (playerOrigin - GetAbsOrigin()).IsLengthGreaterThan( farAway ))
{
PrintIfWatched( "Checking out where our friend was killed\n" );
MoveTo( playerOrigin, FASTEST_ROUTE );
return;
}
}
}
}
}
}
}
else // an enemy was killed
{
// forget our current noise - it may have come from the now dead enemy
ForgetNoise();
if (killer && killer->GetTeamNumber() == GetTeamNumber())
{
// only chatter about enemy kills if we see them occur, and they were the last one we see
if (GetNearbyEnemyCount() <= 1)
{
// report if number of enemies left is few and we killed the last one we saw locally
GetChatter()->EnemiesRemaining();
Vector victimOrigin = GetCentroid( victim );
if (IsVisible( victimOrigin, CHECK_FOV ))
{
// congratulate teammates on their kills
if (killer && killer != this)
{
float delay = RandomFloat( 2.0f, 3.0f );
if (killer->IsBot())
{
if (RandomFloat( 0.0f, 100.0f ) < 40.0f)
GetChatter()->Say( "NiceShot", 3.0f, delay );
}
else
{
// humans get the honorific
if (CSGameRules()->IsCareer())
GetChatter()->Say( "NiceShotCommander", 3.0f, delay );
else
GetChatter()->Say( "NiceShotSir", 3.0f, delay );
}
}
}
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerRadio( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CCSPlayer *player = ToCSPlayer( UTIL_PlayerByUserId( event->GetInt( "userid" ) ) );
if ( player == this )
return;
//
// Process radio events from our team
//
if (player && player->GetTeamNumber() == GetTeamNumber() )
{
/// @todo Distinguish between radio commands and responses
RadioType radioEvent = (RadioType)event->GetInt( "slot" );
if (radioEvent != RADIO_INVALID && radioEvent != RADIO_AFFIRMATIVE && radioEvent != RADIO_NEGATIVE && radioEvent != RADIO_REPORTING_IN
&& radioEvent != RADIO_CHEER && radioEvent != RADIO_THANKS && radioEvent != RADIO_COMPLIMENT)
{
m_lastRadioCommand = radioEvent;
m_lastRadioRecievedTimestamp = gpGlobals->curtime;
m_radioSubject = player;
m_radioPosition = GetCentroid( player );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerFallDamage( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // player_falldamage
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnPlayerFootstep( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false, IS_FOOTSTEP ); // player_footstep
}

View File

@@ -0,0 +1,156 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "keyvalues.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponFire( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// for knife fighting - if our victim is attacking or reloading, rush him
/// @todo Propagate events into active state
if (GetBotEnemy() == player && IsUsingKnife())
{
ForceRun( 5.0f );
}
const float ShortRange = 1000.0f;
const float NormalRange = 2000.0f;
float range;
/// @todo Check weapon type (knives are pretty quiet)
/// @todo Use actual volume, account for silencers, etc.
// [mlowrance] use the weapon as posted in the event message
int iWeaponID = -1;
const char *weaponName = event->GetString( "weapon" );
if ( weaponName )
{
iWeaponID = AliasToWeaponID( weaponName );
}
if ( iWeaponID == -1 )
return;
switch( iWeaponID )
{
// silent "firing"
case WEAPON_HEGRENADE:
case WEAPON_SMOKEGRENADE:
case WEAPON_FLASHBANG:
case WEAPON_INCGRENADE:
case WEAPON_MOLOTOV:
case WEAPON_DECOY:
case WEAPON_TAGRENADE:
case WEAPON_C4:
return;
// quiet
case WEAPON_KNIFE:
case WEAPON_KNIFE_GG:
range = ShortRange;
break;
// loud
case WEAPON_AWP:
range = 99999.0f;
break;
// normal
default:
if ( event->GetBool( "silenced" ) )
{
range = ShortRange;
}
else
{
range = NormalRange;
}
break;
}
OnAudibleEvent( event, player, range, PRIORITY_HIGH, true ); // weapon_fire
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponFireOnEmpty( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// for knife fighting - if our victim is attacking or reloading, rush him
/// @todo Propagate events into active state
if (GetBotEnemy() == player && IsUsingKnife())
{
ForceRun( 5.0f );
}
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_fire_on_empty
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponReload( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
// for knife fighting - if our victim is attacking or reloading, rush him
/// @todo Propagate events into active state
if (GetBotEnemy() == player && IsUsingKnife())
{
ForceRun( 5.0f );
}
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_reload
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::OnWeaponZoom( IGameEvent *event )
{
if ( !IsAlive() )
return;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player == this )
return;
OnAudibleEvent( event, player, 1100.0f, PRIORITY_LOW, false ); // weapon_zoom
}

View File

@@ -0,0 +1,545 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "cs_shareddefs.h"
#include "mathlib/mathlib.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#pragma warning( disable : 4355 ) // warning 'this' used in base member initializer list - we're using it safely
ConVar mp_coopmission_bot_difficulty_offset(
"mp_coopmission_bot_difficulty_offset",
"0",
FCVAR_REPLICATED | FCVAR_RELEASE,
"The difficulty offset modifier for bots during coop missions." );
//--------------------------------------------------------------------------------------------------------------
static void PrefixChanged( IConVar *c, const char *oldPrefix, float flOldValue )
{
if ( TheCSBots() && TheCSBots()->IsServerActive() )
{
for( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );
if ( !player )
continue;
if ( !player->IsBot() || !IsEntityValid( player ) )
continue;
CCSBot *bot = dynamic_cast< CCSBot * >( player );
if ( !bot )
continue;
// set the bot's name
char botName[MAX_PLAYER_NAME_LENGTH];
UTIL_ConstructBotNetName( botName, MAX_PLAYER_NAME_LENGTH, bot->GetProfile() );
engine->SetFakeClientConVarValue( bot->edict(), "name", botName );
}
}
}
ConVar cv_bot_traceview( "bot_traceview", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_stop( "bot_stop", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, immediately stops all bot processing." );
ConVar cv_bot_show_nav( "bot_show_nav", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_walk( "bot_walk", "0", FCVAR_REPLICATED, "If nonzero, bots can only walk, not run." );
ConVar cv_bot_difficulty( "bot_difficulty", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "Defines the skill of bots joining the game. Values are: 0=easy, 1=normal, 2=hard, 3=expert." );
ConVar cv_bot_debug( "bot_debug", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_debug_target( "bot_debug_target", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "For internal testing purposes." );
ConVar cv_bot_quota( "bot_quota", "10", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines the total number of bots in the game." );
ConVar cv_bot_quota_mode( "bot_quota_mode", "normal", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is bot_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is bot_quota." );
ConVar cv_bot_prefix( "bot_prefix", "", FCVAR_REPLICATED, "This string is prefixed to the name of all bots that join the game.\n<difficulty> will be replaced with the bot's difficulty.\n<weaponclass> will be replaced with the bot's desired weapon class.\n<skill> will be replaced with a 0-100 representation of the bot's skill.", PrefixChanged );
ConVar cv_bot_allow_rogues( "bot_allow_rogues", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may occasionally go 'rogue'. Rogue bots do not obey radio commands, nor pursue scenario goals." );
ConVar cv_bot_allow_pistols( "bot_allow_pistols", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use pistols." );
ConVar cv_bot_allow_shotguns( "bot_allow_shotguns", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use shotguns." );
ConVar cv_bot_allow_sub_machine_guns( "bot_allow_sub_machine_guns", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use sub-machine guns." );
ConVar cv_bot_allow_rifles( "bot_allow_rifles", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use rifles." );
ConVar cv_bot_allow_machine_guns( "bot_allow_machine_guns", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use the machine gun." );
ConVar cv_bot_allow_grenades( "bot_allow_grenades", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use grenades." );
ConVar cv_bot_allow_snipers( "bot_allow_snipers", "1", FCVAR_RELEASE|FCVAR_REPLICATED, "If nonzero, bots may use sniper rifles." );
#ifdef CS_SHIELD_ENABLED
ConVar cv_bot_allow_shield( "bot_allow_shield", "1", FCVAR_REPLICATED );
#endif // CS_SHIELD_ENABLED
ConVar cv_bot_join_team( "bot_join_team", "any", FCVAR_REPLICATED | FCVAR_RELEASE, "Determines the team bots will join into. Allowed values: 'any', 'T', or 'CT'." );
ConVar cv_bot_join_after_player( "bot_join_after_player", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "If nonzero, bots wait until a player joins before entering the game." );
ConVar cv_bot_auto_vacate( "bot_auto_vacate", "1", FCVAR_REPLICATED, "If nonzero, bots will automatically leave to make room for human players." );
ConVar cv_bot_zombie( "bot_zombie", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "If nonzero, bots will stay in idle mode and not attack." );
ConVar cv_bot_defer_to_human_goals( "bot_defer_to_human_goals", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "If nonzero and there is a human on the team, the bots will not do the scenario tasks." );
ConVar cv_bot_defer_to_human_items( "bot_defer_to_human_items", "1", FCVAR_REPLICATED | FCVAR_RELEASE, "If nonzero and there is a human on the team, the bots will not get scenario items." );
ConVar cv_bot_chatter( "bot_chatter", "normal", FCVAR_REPLICATED | FCVAR_RELEASE, "Control how bots talk. Allowed values: 'off', 'radio', 'minimal', or 'normal'." );
ConVar cv_bot_profile_db( "bot_profile_db", "BotProfile.db", FCVAR_REPLICATED, "The filename from which bot profiles will be read." );
ConVar cv_bot_dont_shoot( "bot_dont_shoot", "0", FCVAR_REPLICATED | FCVAR_RELEASE | FCVAR_CHEAT, "If nonzero, bots will not fire weapons (for debugging)." );
ConVar cv_bot_eco_limit( "bot_eco_limit", "2000", FCVAR_REPLICATED, "If nonzero, bots will not buy if their money falls below this amount." );
ConVar cv_bot_auto_follow( "bot_auto_follow", "0", FCVAR_REPLICATED, "If nonzero, bots with high co-op may automatically follow a nearby human player." );
ConVar cv_bot_flipout( "bot_flipout", "0", FCVAR_REPLICATED, "If nonzero, bots use no CPU for AI. Instead, they run around randomly." );
#if CS_CONTROLLABLE_BOTS_ENABLED
ConVar cv_bot_controllable( "bot_controllable", "1", FCVAR_REPLICATED, "Determines whether bots can be controlled by players" );
#endif
extern void FinishClientPutInServer( CCSPlayer *pPlayer );
//--------------------------------------------------------------------------------------------------------------
// Engine callback for custom server commands
void Bot_ServerCommand( void )
{
}
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor
*/
CCSBot::CCSBot( void ) :
m_gameState( this ),
m_hasJoined( false ),
m_pLocalProfile( NULL )
{
if ( CSGameRules()->IsPlayingCoopMission() )
{
m_pChatter = new BotChatterCoop( this );
}
else
{
m_pChatter = new BotChatterInterface( this );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Destructor
*/
CCSBot::~CCSBot()
{
if ( m_pLocalProfile )
{
delete m_pLocalProfile;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Prepare bot for action
*/
bool CCSBot::Initialize( const BotProfile *profile, int team )
{
int preserved_voice_pitch = -1;
char preserved_name[256];
preserved_name[0] = 0;
AssertMsg( profile != NULL, "You cannot pass in null for a bot profile." );
if ( NULL == profile ) return false;
if ( m_pLocalProfile )
{
// Preserve the voice pitch and name from the existing profile
preserved_voice_pitch = m_pLocalProfile->GetVoicePitch();
if ( CSGameRules() && CSGameRules()->IsPlayingCooperativeGametype() )
{
// change names everytime in coop
Q_snprintf( preserved_name, 256, "%s", profile->GetName() );
}
else
Q_snprintf( preserved_name, 256, "%s", m_pLocalProfile->GetName() );
delete m_pLocalProfile;
}
m_pLocalProfile = new BotProfile;
if ( m_pLocalProfile )
{
// Copy the profile into the local profile
m_pLocalProfile->Clone( profile );
// Restore the name
if ( Q_strlen( preserved_name ) > 0 )
{
m_pLocalProfile->SetName( preserved_name );
}
// Restore the voice pitch
if ( preserved_voice_pitch > -1 )
{
m_pLocalProfile->SetVoicePitch( preserved_voice_pitch );
}
}
// extend
BaseClass::Initialize( m_pLocalProfile, team );
// CS bot initialization
m_diedLastRound = false;
if ( CSGameRules()->IsPlayingGunGameTRBomb() )
{
// in demolition, start the CT's off with terrible morale, so they try to camp the bombsite
if ( team == TEAM_CT )
{
m_morale = TERRIBLE;
}
else
{
m_morale = POSITIVE; // starting a new round makes everyone a little happy
}
}
else
{
m_morale = POSITIVE; // starting a new round makes everyone a little happy
}
m_combatRange = RandomFloat( 325.0f, 425.0f );
// set initial safe time guess for this map
m_safeTime = 15.0f + 5.0f * GetProfile()->GetAggression( );
m_name[0] = '\000';
ResetValues();
m_desiredTeam = team;
if (GetTeamNumber() == 0)
{
HandleCommand_JoinTeam( m_desiredTeam );
// join class is already called within JoinTeam
//HandleCommand_JoinClass();
}
return true;
}
void CCSBot::CoopInitialize( void )
{
if ( CSGameRules() && CSGameRules()->IsPlayingCoopMission() && GetTeamNumber() == TEAM_TERRORIST )
{
// bots start asleep when they spawn and wake up when players come close to them
SpawnPointCoopEnemy *pEnemySpawnSpot = GetLastCoopSpawnPoint();
if ( pEnemySpawnSpot )
{
SetAbsAngles( pEnemySpawnSpot->GetAbsAngles() );
int nDifficulty = pEnemySpawnSpot->GetBotDifficulty();
int nOffset = mp_coopmission_bot_difficulty_offset.GetInt();
nDifficulty = nDifficulty + nOffset;
BotDifficultyType botDifficultyType = BOT_EASY;
if ( nDifficulty >= 6 )
botDifficultyType = BOT_EXPERT;
else if ( nDifficulty >= 3 )
botDifficultyType = BOT_HARD;
else if ( nDifficulty >= 1 )
botDifficultyType = BOT_NORMAL;
bool bIsAgressive = pEnemySpawnSpot->IsBotAgressive();
char profileName[128];
Q_snprintf( profileName, sizeof( profileName ), "Level_%d%s", ( int )nDifficulty, bIsAgressive ? "_Agressive" : "" );
const BotProfile* pNewProfileData = TheBotProfiles->GetProfileMatchingTemplate( profileName, TEAM_TERRORIST, botDifficultyType, CSGameRules()->GetMatchDevice(), true );
if ( pNewProfileData )
{
char szHeavy[64] = {};
int nArmor = pEnemySpawnSpot->GetArmorToSpawnWith();
if ( nArmor == 1 )
Q_snprintf( szHeavy, sizeof( szHeavy ), "%s", "" );
else if ( nArmor == 2 )
Q_snprintf( szHeavy, sizeof( szHeavy ), "%s", "Heavy " );
char szName[128] = {};
char szDifficulty[128] = {};
if ( nDifficulty >= 6 )
Q_snprintf( szDifficulty, sizeof( szDifficulty ), "%s", "Elite " );
else if ( nDifficulty >= 4 )
Q_snprintf( szDifficulty, sizeof( szDifficulty ), "%s", "Expert " );
else
Q_snprintf( szDifficulty, sizeof( szDifficulty ), "%s", "" );
Q_snprintf( szName, sizeof( szName ), "%s%sPhoenix", szHeavy, szDifficulty );
Initialize( pNewProfileData, GetTeamNumber() );
SetPlayerName( szName );
// have to inform the engine that the bot name has been updated
engine->SetFakeClientConVarValue( edict(), "name", szName );
}
// sleeping needs to be set after the initialize because we reset values in the init
m_bIsSleeping = pEnemySpawnSpot->ShouldStartAsleep();
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset internal data to initial state
*/
void CCSBot::ResetValues( void )
{
m_pChatter->Reset();
m_gameState.Reset();
m_avoid = NULL;
m_avoidTimestamp = 0.0f;
m_hurryTimer.Invalidate();
m_alertTimer.Invalidate();
m_sneakTimer.Invalidate();
m_noiseBendTimer.Invalidate();
m_bendNoisePositionValid = false;
m_isStuck = false;
m_stuckTimestamp = 0.0f;
m_wiggleTimer.Invalidate();
m_stuckJumpTimer.Invalidate();
m_nextCleanupCheckTimestamp = 0.0f;
m_pathLength = 0;
m_pathIndex = 0;
m_areaEnteredTimestamp = 0.0f;
m_currentArea = NULL;
m_lastKnownArea = NULL;
m_isStopping = false;
m_avoidFriendTimer.Invalidate();
m_isFriendInTheWay = false;
m_isWaitingBehindFriend = false;
m_isAvoidingGrenade.Invalidate();
StopPanicking();
m_disposition = ENGAGE_AND_INVESTIGATE;
m_enemy = NULL;
m_grenadeTossState = NOT_THROWING;
m_initialEncounterArea = NULL;
m_wasSafe = true;
m_nearbyEnemyCount = 0;
m_enemyPlace = 0;
m_nearbyFriendCount = 0;
m_closestVisibleFriend = NULL;
m_closestVisibleHumanFriend = NULL;
for( int w=0; w<MAX_PLAYERS; ++w )
{
m_watchInfo[w].timestamp = 0.0f;
m_watchInfo[w].isEnemy = false;
m_playerTravelDistance[ w ] = -1.0f;
}
// randomly offset each bot's timer to spread computation out
m_updateTravelDistanceTimer.Start( RandomFloat( 0.0f, 0.9f ) );
m_travelDistancePhase = 0;
m_isEnemyVisible = false;
m_visibleEnemyParts = NONE;
m_lastSawEnemyTimestamp = -999.9f;
m_firstSawEnemyTimestamp = 0.0f;
m_currentEnemyAcquireTimestamp = 0.0f;
m_isLastEnemyDead = true;
m_attacker = NULL;
m_attackedTimestamp = 0.0f;
m_enemyDeathTimestamp = 0.0f;
m_friendDeathTimestamp = 0.0f;
m_lastVictimID = 0;
m_isAimingAtEnemy = false;
m_fireWeaponTimestamp = 0.0f;
m_equipTimer.Invalidate();
m_zoomTimer.Invalidate();
m_isFollowing = false;
m_leader = NULL;
m_followTimestamp = 0.0f;
m_allowAutoFollowTime = 0.0f;
m_enemyQueueIndex = 0;
m_enemyQueueCount = 0;
m_enemyQueueAttendIndex = 0;
m_bomber = NULL;
m_bIsSleeping = false;
m_isEnemySniperVisible = false;
m_sawEnemySniperTimer.Invalidate();
m_lookAroundStateTimestamp = 0.0f;
m_inhibitLookAroundTimestamp = 0.0f;
m_lookPitch = 0.0f;
m_lookPitchVel = 0.0f;
m_lookYaw = 0.0f;
m_lookYawVel = 0.0f;
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
m_targetSpot.Zero();
m_targetSpotVelocity.Zero();
m_targetSpotPredicted.Zero();
m_aimError.Init();
m_aimGoal.Init();
m_targetSpotTime = 0.0f;
m_aimFocus = 0.0f;
m_aimFocusInterval = 0.0f;
m_aimFocusNextUpdate = 0.0f;
for( int p=0; p<MAX_PLAYERS; ++p )
{
m_partInfo[p].m_validFrame = 0;
}
m_spotEncounter = NULL;
m_spotCheckTimestamp = 0.0f;
m_peripheralTimestamp = 0.0f;
m_avgVelIndex = 0;
m_avgVelCount = 0;
m_lastOrigin = GetCentroid( this );
m_lastRadioCommand = RADIO_INVALID;
m_lastRadioRecievedTimestamp = 0.0f;
m_lastRadioSentTimestamp = 0.0f;
m_radioSubject = NULL;
m_voiceEndTimestamp = 0.0f;
m_hostageEscortCount = 0;
m_hostageEscortCountTimestamp = 0.0f;
m_noisePosition = Vector( 0, 0, 0 );
m_noiseTimestamp = 0.0f;
m_stateTimestamp = 0.0f;
m_task = SEEK_AND_DESTROY;
m_taskEntity = NULL;
m_approachPointCount = 0;
m_approachPointViewPosition.x = 99999999999.9f;
m_approachPointViewPosition.y = 0.0f;
m_approachPointViewPosition.z = 0.0f;
m_checkedHidingSpotCount = 0;
StandUp();
Run();
m_mustRunTimer.Invalidate();
m_waitTimer.Invalidate();
m_pathLadder = NULL;
m_repathTimer.Invalidate();
m_huntState.ClearHuntArea();
m_hasVisitedEnemySpawn = false;
m_stillTimer.Invalidate();
// adjust morale - if we died, our morale decreased,
// but if we live, no adjustement (round win/loss also adjusts morale)
if (m_diedLastRound)
DecreaseMorale();
m_diedLastRound = false;
// IsRogue() randomly changes this
m_isRogue = false;
m_surpriseTimer.Invalidate();
// even though these are EHANDLEs, they need to be NULL-ed
m_goalEntity = NULL;
m_avoid = NULL;
m_enemy = NULL;
for ( int i=0; i<MAX_ENEMY_QUEUE; ++i )
{
m_enemyQueue[i].player = NULL;
m_enemyQueue[i].isReloading = false;
m_enemyQueue[i].isProtectedByShield = false;
}
m_burnedByFlamesTimer.Invalidate();
#ifdef OPT_VIS_CSGO
V_memset( m_bVis, 0, sizeof(m_bVis) );
V_memset( m_aVisParts, 0, sizeof(m_aVisParts) );
#endif
// start in idle state
m_isOpeningDoor = false;
StopAttacking();
Idle();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Called when bot is placed in map, and when bots are reset after a round ends.
* NOTE: For some reason, this can be called twice when a bot is added.
*/
void CCSBot::Spawn( void )
{
if ( CSGameRules() && CSGameRules()->IsPlayingCoopMission() && GetTeamNumber() == TEAM_TERRORIST )
SetLastCoopSpawnPoint( NULL );
// do the normal player spawn process
BaseClass::Spawn();
ResetValues();
Buy();
if ( CSGameRules()->IsPlayingCoopMission() )
{
if ( GetTeamNumber() == TEAM_CT )
{
SetDisposition( CCSBot::SELF_DEFENSE );
for ( int i = 1; i <= MAX_PLAYERS; i++ )
{
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
if ( pPlayer && !pPlayer->IsBot() && pPlayer->GetTeamNumber() == TEAM_CT )
{
//m_isFollowing = true;
//m_leader = pPlayer;
//SetTask( CCSBot::FOLLOW );
Follow( pPlayer );
m_lastRadioCommand = RADIO_FOLLOW_ME;
m_lastRadioRecievedTimestamp = gpGlobals->curtime;
m_radioSubject = pPlayer;
m_radioPosition = GetCentroid( pPlayer );
break;
}
}
}
else if ( GetTeamNumber() == TEAM_TERRORIST )
{
CoopInitialize();
}
}
// set the bot name here (after we've had a chance to initialize a new profile
V_strcpy_safe( m_name, GetPlayerName() );
}

View File

@@ -0,0 +1,217 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
bool CCSBot::IsNoiseHeard( void ) const
{
if (m_noiseTimestamp <= 0.0f)
return false;
// primitive reaction time simulation - cannot "hear" noise until reaction time has elapsed
if (gpGlobals->curtime - m_noiseTimestamp >= GetProfile()->GetReactionTime())
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Listen for enemy noises, and determine if we should react to them.
* Returns true if heard a noise and should move to investigate.
*/
bool CCSBot::HeardInterestingNoise( void )
{
if (IsBlind())
return false;
// don't investigate noises during safe time
if (!IsWellPastSafe())
return false;
// if our disposition is not to investigate, dont investigate
if (GetDisposition() != ENGAGE_AND_INVESTIGATE)
return false;
// listen for enemy noises
if (IsNoiseHeard())
{
// if we are hiding, only react to noises very nearby, depending on how aggressive we are
if (IsAtHidingSpot() && GetNoiseRange() > 100.0f + 400.0f * GetProfile()->GetAggression())
return false;
float chance = GetNoiseInvestigateChance();
if (RandomFloat( 0.0f, 100.0f ) <= chance)
{
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we hear nearby threatening enemy gunfire within given range
* -1 == infinite range
*/
bool CCSBot::CanHearNearbyEnemyGunfire( float range ) const
{
Vector myOrigin = GetCentroid( this );
// only attend to noise if it just happened
if (gpGlobals->curtime - m_noiseTimestamp > 0.5f)
return false;
// gunfire is high priority
if (m_noisePriority < PRIORITY_HIGH)
return false;
// check noise range
if (range > 0.0f && (myOrigin - m_noisePosition).IsLengthGreaterThan( range ))
return false;
// if we dont have line of sight, it's not threatening (cant get shot)
if (!CanSeeNoisePosition())
return false;
if (IsAttacking() && m_enemy != NULL && GetTimeSinceLastSawEnemy() < 1.0f)
{
// gunfire is only threatening if it is closer than our current enemy
float gunfireDistSq = (m_noisePosition - myOrigin).LengthSqr();
float enemyDistSq = (GetCentroid( m_enemy ) - myOrigin).LengthSqr();
const float muchCloserSq = 100.0f * 100.0f;
if (gunfireDistSq > enemyDistSq - muchCloserSq)
return false;
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we directly see where we think the noise came from
* NOTE: Dont check FOV, since this is used to determine if we should turn our head to look at the noise
* NOTE: Dont use IsVisible(), because smoke shouldnt cause us to not look toward noises
*/
bool CCSBot::CanSeeNoisePosition( void ) const
{
trace_t result;
CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
UTIL_TraceLine( EyePositionConst(), m_noisePosition + Vector( 0, 0, HalfHumanHeight ), MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
if (result.fraction == 1.0f)
{
// we can see the source of the noise
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we decided to look towards the most recent noise source
* Assumes m_noisePosition is valid.
*/
bool CCSBot::UpdateLookAtNoise( void )
{
// make sure a noise exists
if (!IsNoiseHeard())
{
return false;
}
Vector spot;
// if we have clear line of sight to noise position, look directly at it
if (CanSeeNoisePosition())
{
/// @todo adjust noise Z to keep consistent with current height while fighting
spot = m_noisePosition + Vector( 0, 0, HalfHumanHeight );
// since we can see the noise spot, forget about it
ForgetNoise();
}
else
{
// line of sight is blocked, bend it
// the bending algorithm is very expensive, throttle how often it is done
if (m_noiseBendTimer.IsElapsed())
{
const float noiseBendLOSInterval = RandomFloat( 0.2f, 0.3f );
m_noiseBendTimer.Start( noiseBendLOSInterval );
// line of sight is blocked, bend it
if (BendLineOfSight( EyePosition(), m_noisePosition, &spot ) == false)
{
m_bendNoisePositionValid = false;
return false;
}
m_bentNoisePosition = spot;
m_bendNoisePositionValid = true;
}
else if (m_bendNoisePositionValid)
{
// use result of prior bend computation
spot = m_bentNoisePosition;
}
else
{
// prior bend failed
return false;
}
}
// it's always important to look at enemy noises, because they come from ... enemies!
PriorityType pri = PRIORITY_HIGH;
// look longer if we're hiding
if (IsAtHidingSpot())
{
// if there is only one enemy left, look for a long time
if (GetEnemiesRemaining() == 1)
{
SetLookAt( "Noise", spot, pri, RandomFloat( 5.0f, 15.0f ), true );
}
else
{
SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
}
}
else
{
const float closeRange = 500.0f;
if (GetNoiseRange() < closeRange)
{
// look at nearby enemy noises for a longer time
SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
}
else
{
SetLookAt( "Noise", spot, pri, RandomFloat( 1.0f, 2.0f ), true );
}
}
return true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,452 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef CS_CONTROL_H
#define CS_CONTROL_H
#include "bot_manager.h"
#include "nav_area.h"
#include "bot_util.h"
#include "bot_profile.h"
#include "cs_shareddefs.h"
#include "cs_player.h"
extern ConVar mp_friendlyfire;
extern ConVar throttle_expensive_ai;
class CBasePlayerWeapon;
/**
* Given one team, return the other
*/
inline int OtherTeam( int team )
{
return (team == TEAM_TERRORIST) ? TEAM_CT : TEAM_TERRORIST;
}
class CCSBotManager;
// accessor for CS-specific bots
inline CCSBotManager *TheCSBots( void )
{
return reinterpret_cast< CCSBotManager * >( TheBots );
}
//--------------------------------------------------------------------------------------------------------------
class BotEventInterface : public IGameEventListener2
{
public:
virtual const char *GetEventName( void ) const = 0;
};
//--------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------
/**
* Macro to set up an OnEventClass() in TheCSBots.
*/
#define DECLARE_BOTMANAGER_EVENT_LISTENER( BotManagerSingleton, EventClass, EventName ) \
public: \
virtual void On##EventClass( IGameEvent *data ); \
private: \
class EventClass##Event : public BotEventInterface \
{ \
bool m_enabled; \
public: \
EventClass##Event( void ) \
{ \
gameeventmanager->AddListener( this, #EventName, true ); \
m_enabled = true; \
} \
~EventClass##Event( void ) \
{ \
if ( m_enabled ) gameeventmanager->RemoveListener( this ); \
} \
virtual const char *GetEventName( void ) const \
{ \
return #EventName; \
} \
void Enable( bool enable ) \
{ \
m_enabled = enable; \
if ( enable ) \
gameeventmanager->AddListener( this, #EventName, true ); \
else \
gameeventmanager->RemoveListener( this ); \
} \
bool IsEnabled( void ) const { return m_enabled; } \
void FireGameEvent( IGameEvent *event ) \
{ \
BotManagerSingleton()->On##EventClass( event ); \
} \
int GetEventDebugID( void ) \
{ \
return EVENT_DEBUG_ID_INIT; \
} \
}; \
EventClass##Event m_##EventClass##Event;
//--------------------------------------------------------------------------------------------------------------
#define DECLARE_CSBOTMANAGER_EVENT_LISTENER( EventClass, EventName ) DECLARE_BOTMANAGER_EVENT_LISTENER( TheCSBots, EventClass, EventName )
//--------------------------------------------------------------------------------------------------------------
/**
* Macro to propogate an event from the bot manager to all bots
*/
// @kutta: changed dynamic_cast -> static_cast; what other bot types can there be?
#define CCSBOTMANAGER_ITERATE_BOTS( Callback, arg1 ) \
{ \
for ( int idx = 1; idx <= gpGlobals->maxClients; ++idx ) \
{ \
CBasePlayer *player = UTIL_PlayerByIndex( idx ); \
if (player == NULL) continue; \
if (!player->IsBot()) continue; \
CCSBot *bot = static_cast< CCSBot * >(player); \
bot->Callback( arg1 ); \
} \
}
//--------------------------------------------------------------------------------------------------------------
//
// The manager for Counter-Strike specific bots
//
class CCSBotManager : public CBotManager
{
public:
CCSBotManager();
virtual CBasePlayer *AllocateBotEntity( void ); ///< factory method to allocate the appropriate entity for the bot
virtual void ClientDisconnect( CBaseEntity *entity );
virtual bool ClientCommand( CBasePlayer *player, const CCommand &args );
virtual void ServerActivate( void );
virtual void ServerDeactivate( void );
virtual bool ServerCommand( const char *cmd );
bool IsServerActive( void ) const { return m_serverActive; }
virtual void RestartRound( void ); ///< (EXTEND) invoked when a new round begins
virtual void StartFrame( void ); ///< (EXTEND) called each frame
virtual unsigned int GetPlayerPriority( CBasePlayer *player ) const; ///< return priority of player (0 = max pri)
virtual bool IsImportantPlayer( CCSPlayer *player ) const; ///< return true if player is important to scenario (VIP, bomb carrier, etc)
void ExtractScenarioData( void ); ///< search the map entities to determine the game scenario and define important zones
// difficulty levels -----------------------------------------------------------------------------------------
static BotDifficultyType GetDifficultyLevel( void )
{
static ConVarRef sv_mmqueue_reservation( "sv_mmqueue_reservation" );
if ( sv_mmqueue_reservation.GetString()[0] == 'Q' && CSGameRules() && !CSGameRules()->IsPlayingCooperativeGametype() )
return BOT_EASY; // Queue matchmaking always subs people with easy bots
if (cv_bot_difficulty.GetFloat() < 0.9f)
return BOT_EASY;
if (cv_bot_difficulty.GetFloat() < 1.9f)
return BOT_NORMAL;
if (cv_bot_difficulty.GetFloat() < 2.9f)
return BOT_HARD;
return BOT_EXPERT;
}
// the supported game scenarios ------------------------------------------------------------------------------
enum GameScenarioType
{
SCENARIO_DEATHMATCH,
SCENARIO_DEFUSE_BOMB,
SCENARIO_RESCUE_HOSTAGES,
SCENARIO_ESCORT_VIP
};
GameScenarioType GetScenario( void ) const { return m_gameScenario; }
// "zones" ---------------------------------------------------------------------------------------------------
// depending on the game mode, these are bomb zones, rescue zones, etc.
enum { MAX_ZONES = 4 }; ///< max # of zones in a map
enum { MAX_ZONE_NAV_AREAS = 16 }; ///< max # of nav areas in a zone
struct Zone
{
CBaseEntity *m_entity; ///< the map entity
CNavArea *m_area[ MAX_ZONE_NAV_AREAS ]; ///< nav areas that overlap this zone
int m_areaCount;
Vector m_center;
bool m_isLegacy; ///< if true, use pev->origin and 256 unit radius as zone
int m_index;
bool m_isBlocked;
Extent m_extent;
};
const Zone *GetZone( int i ) const { return ( ( i >= 0 ) && ( i < m_zoneCount ) ) ? ( &m_zone[i] ) : NULL; }
const Zone *GetZone( const Vector &pos ) const; ///< return the zone that contains the given position
const Zone *GetClosestZone( const Vector &pos ) const; ///< return the closest zone to the given position
const Zone *GetClosestZone( const CBaseEntity *entity ) const; ///< return the closest zone to the given entity
int GetZoneCount( void ) const { return m_zoneCount; }
void CheckForBlockedZones( void );
const Vector *GetRandomPositionInZone( const Zone *zone ) const; ///< return a random position inside the given zone
CNavArea *GetRandomAreaInZone( const Zone *zone ) const; ///< return a random area inside the given zone
/**
* Return the zone closest to the given position, using the given cost heuristic
*/
template< typename CostFunctor >
const Zone *GetClosestZone( CNavArea *startArea, CostFunctor costFunc, float *travelDistance = NULL ) const
{
const Zone *closeZone = NULL;
float closeDist = 99999999.9f;
if (startArea == NULL)
return NULL;
for( int i=0; i<m_zoneCount; ++i )
{
if (m_zone[i].m_areaCount == 0)
continue;
if ( m_zone[i].m_isBlocked )
continue;
// just use the first overlapping nav area as a reasonable approximation
float dist = NavAreaTravelDistance( startArea, m_zone[i].m_area[0], costFunc );
if (dist >= 0.0f && dist < closeDist)
{
closeZone = &m_zone[i];
closeDist = dist;
}
}
if (travelDistance)
*travelDistance = closeDist;
return closeZone;
}
/// pick a zone at random and return it
const Zone *GetRandomZone( void ) const
{
if (m_zoneCount == 0)
return NULL;
int i;
CUtlVector< const Zone * > unblockedZones;
for ( i=0; i<m_zoneCount; ++i )
{
if ( m_zone[i].m_isBlocked )
continue;
unblockedZones.AddToTail( &(m_zone[i]) );
}
if ( unblockedZones.Count() == 0 )
return NULL;
return unblockedZones[ RandomInt( 0, unblockedZones.Count()-1 ) ];
}
/// returns a random spawn point for the given team (no arg means use both team spawnpoints)
CBaseEntity *GetRandomSpawn( int team = TEAM_MAXCOUNT ) const;
bool IsBombPlanted( void ) const { return m_isBombPlanted; } ///< returns true if bomb has been planted
float GetBombPlantTimestamp( void ) const { return m_bombPlantTimestamp; } ///< return time bomb was planted
bool IsTimeToPlantBomb( void ) const; ///< return true if it's ok to try to plant bomb
CCSPlayer *GetBombDefuser( void ) const { return m_bombDefuser; } ///< return the player currently defusing the bomb, or NULL
float GetBombTimeLeft( void ) const; ///< get the time remaining before the planted bomb explodes
CBaseEntity *GetLooseBomb( void ) { return m_looseBomb; } ///< return the bomb if it is loose on the ground
CNavArea *GetLooseBombArea( void ) const { return m_looseBombArea; } ///< return area that bomb is in/near
void SetLooseBomb( CBaseEntity *bomb );
enum ETStrat
{
k_ETStrat_Rush, // Hit the target site asap
k_ETStrat_Slow, // Spend time at initial engagement spots, move in if time is low or if team gets a kill
k_ETStrat_Fake, // One player goes to the site not being hit
k_ETStrat_Count,
};
ETStrat GetTStrat( void ) const { return m_eTStrat; }
int GetTerroristTargetSite( void ) const { return m_iTerroristTargetSite; }
enum ECTStrat
{
k_ECTStrat_GuardRandomSite, // Used for testing
k_ECTStrat_212, // 2 each site, one aggressive mid
k_ECTStrat_StackSite, // 4 on priority site, 1 alternate site
k_ECTStrat_Count,
};
ECTStrat GetCTStrat( void ) const { return m_eCTStrat; }
int GetCTPrioritySite( void ) const { return m_iCTPrioritySite; }
float GetRadioMessageTimestamp( RadioType event, int teamID ) const; ///< return the last time the given radio message was sent for given team
float GetRadioMessageInterval( RadioType event, int teamID ) const; ///< return the interval since the last time this message was sent
void SetRadioMessageTimestamp( RadioType event, int teamID );
void ResetRadioMessageTimestamps( void );
float GetLastSeenEnemyTimestamp( void ) const { return m_lastSeenEnemyTimestamp; } ///< return the last time anyone has seen an enemy
void SetLastSeenEnemyTimestamp( void ) { m_lastSeenEnemyTimestamp = gpGlobals->curtime; }
float GetRoundStartTime( void ) const { return m_roundStartTimestamp; }
float GetElapsedRoundTime( void ) const { return gpGlobals->curtime - m_roundStartTimestamp; } ///< return the elapsed time since the current round began
bool AllowRogues( void ) const { return cv_bot_allow_rogues.GetBool(); }
bool AllowPistols( void ) const { return cv_bot_allow_pistols.GetBool(); }
bool AllowShotguns( void ) const { return cv_bot_allow_shotguns.GetBool(); }
bool AllowSubMachineGuns( void ) const { return cv_bot_allow_sub_machine_guns.GetBool(); }
bool AllowRifles( void ) const { return cv_bot_allow_rifles.GetBool(); }
bool AllowMachineGuns( void ) const { return cv_bot_allow_machine_guns.GetBool(); }
bool AllowGrenades( void ) const { return cv_bot_allow_grenades.GetBool(); }
bool AllowSnipers( void ) const { return cv_bot_allow_snipers.GetBool(); }
#ifdef CS_SHIELD_ENABLED
bool AllowTacticalShield( void ) const { return cv_bot_allow_shield.GetBool(); }
#else
bool AllowTacticalShield( void ) const { return false; }
#endif // CS_SHIELD_ENABLED
bool AllowFriendlyFireDamage( void ) const { return mp_friendlyfire.GetBool(); }
bool IsWeaponUseable( const CWeaponCSBase *weapon ) const; ///< return true if the bot can use this weapon
bool IsDefenseRushing( void ) const { return m_isDefenseRushing; } ///< returns true if defense team has "decided" to rush this round
bool IsOnDefense( const CCSPlayer *player ) const; ///< return true if this player is on "defense"
bool IsOnOffense( const CCSPlayer *player ) const; ///< return true if this player is on "offense"
bool IsRoundOver( void ) const { return m_isRoundOver; } ///< return true if the round has ended
#define FROM_CONSOLE true
bool BotAddCommand( int team, bool isFromConsole = false, const char *profileName = NULL, CSWeaponType weaponType = WEAPONTYPE_UNKNOWN, BotDifficultyType difficulty = NUM_DIFFICULTY_LEVELS ); ///< process the "bot_add" console command
bool BotPlaceCommand( uint nTeamMask = 0xFFFFFFFF ); //Moves a bot at the location under the cursor. For perf and lighting testing.
// Called to mark that an expensive operation has happened this frame, used for budgeting/throttling AI
void OnExpensiveBotOperation() { m_nNumExpensiveOperationsThisFrame ++; }
// Query to see if we have exceeded our per-frame budget for "expensive" AI operations
bool AllowedToDoExpensiveBotOperationThisFrame() { return !throttle_expensive_ai.GetBool() || m_nNumExpensiveOperationsThisFrame < 1; }
void ForceMaintainBotQuota( void ) { MaintainBotQuota(); }
private:
enum SkillType { LOW, AVERAGE, HIGH, RANDOM };
void MaintainBotQuota( void );
static bool m_isMapDataLoaded; ///< true if we've attempted to load map data
bool m_serverActive; ///< true between ServerActivate() and ServerDeactivate()
GameScenarioType m_gameScenario; ///< what kind of game are we playing
Zone m_zone[ MAX_ZONES ];
int m_zoneCount;
bool m_isBombPlanted; ///< true if bomb has been planted
float m_bombPlantTimestamp; ///< time bomb was planted
float m_earliestBombPlantTimestamp; ///< don't allow planting until after this time has elapsed
CHandle<CCSPlayer> m_bombDefuser; ///< the player currently defusing a bomb
EHANDLE m_looseBomb; ///< will be non-NULL if bomb is loose on the ground
CNavArea *m_looseBombArea; ///< area that bomb is is/near
bool m_isRoundOver; ///< true if the round has ended
CountdownTimer m_checkTransientAreasTimer; ///< when elapsed, all transient nav areas should be checked for blockage
float m_radioMsgTimestamp[ RADIO_END - RADIO_START_1 ][ 2 ];
float m_lastSeenEnemyTimestamp;
float m_roundStartTimestamp; ///< the time when the current round began
bool m_isDefenseRushing; ///< whether defensive team is rushing this round or not
int m_nNumExpensiveOperationsThisFrame;
// Cooperative mode vars
ETStrat m_eTStrat; // Current plan for T
int m_iTerroristTargetSite; // Bomb site Ts are planing to plant at
ECTStrat m_eCTStrat; // Current plan for CT
int m_iCTPrioritySite; // Guess at which site will be attacked
// Event Handlers --------------------------------------------------------------------------------------------
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerFootstep, player_footstep )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerRadio, player_radio )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerDeath, player_death )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( PlayerFallDamage, player_falldamage )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombPickedUp, bomb_pickup )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombPlanted, bomb_planted )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombBeep, bomb_beep )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefuseBegin, bomb_begindefuse )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefused, bomb_defused )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombDefuseAbort, bomb_abortdefuse )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BombExploded, bomb_exploded )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundEnd, round_end )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundStart, round_start )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( RoundFreezeEnd, round_freeze_end )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( DoorMoving, door_moving )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BreakProp, break_prop )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BreakBreakable, break_breakable )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HostageFollows, hostage_follows )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HostageRescuedAll, hostage_rescued_all )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponFire, weapon_fire )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponFireOnEmpty, weapon_fire_on_empty )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponReload, weapon_reload )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( WeaponZoom, weapon_zoom )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( BulletImpact, bullet_impact )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( HEGrenadeDetonate, hegrenade_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( FlashbangDetonate, flashbang_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( SmokeGrenadeDetonate, smokegrenade_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( MolotovDetonate, molotov_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( DecoyDetonate, decoy_detonate )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( DecoyFiring, decoy_firing )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( GrenadeBounce, grenade_bounce )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( NavBlocked, nav_blocked )
DECLARE_CSBOTMANAGER_EVENT_LISTENER( ServerShutdown, server_shutdown )
CUtlVector< BotEventInterface * > m_commonEventListeners; // These event listeners fire often, and can be disabled for performance gains when no bots are present.
bool m_eventListenersEnabled;
void EnableEventListeners( bool enable );
};
inline CBasePlayer *CCSBotManager::AllocateBotEntity( void )
{
return static_cast<CBasePlayer *>( CreateEntityByName( "cs_bot" ) );
}
inline bool CCSBotManager::IsTimeToPlantBomb( void ) const
{
return (gpGlobals->curtime >= m_earliestBombPlantTimestamp);
}
inline const CCSBotManager::Zone *CCSBotManager::GetClosestZone( const CBaseEntity *entity ) const
{
if (entity == NULL)
return NULL;
Vector centroid = entity->GetAbsOrigin();
centroid.z += HalfHumanHeight;
return GetClosestZone( centroid );
}
#endif

View File

@@ -0,0 +1,985 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "obstacle_pushaway.h"
#include "fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
const float NearBreakableCheckDist = 20.0f;
const float FarBreakableCheckDist = 300.0f;
#define DEBUG_BREAKABLES 0
#define DEBUG_DOORS 0
//--------------------------------------------------------------------------------------------------------------
#if DEBUG_BREAKABLES
static void DrawOutlinedQuad( const Vector &p1,
const Vector &p2,
const Vector &p3,
const Vector &p4,
int r, int g, int b,
float duration )
{
NDebugOverlay::Triangle( p1, p2, p3, r, g, b, 20, false, duration );
NDebugOverlay::Triangle( p3, p4, p1, r, g, b, 20, false, duration );
NDebugOverlay::Line( p1, p2, r, g, b, false, duration );
NDebugOverlay::Line( p2, p3, r, g, b, false, duration );
NDebugOverlay::Line( p3, p4, r, g, b, false, duration );
NDebugOverlay::Line( p4, p1, r, g, b, false, duration );
}
ConVar bot_debug_breakable_duration( "bot_debug_breakable_duration", "30" );
#endif // DEBUG_BREAKABLES
//--------------------------------------------------------------------------------------------------------------
CBaseEntity * CheckForEntitiesAlongSegment( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, CPushAwayEnumerator *enumerator )
{
CBaseEntity *entity = NULL;
Ray_t ray;
ray.Init( start, end, mins, maxs );
::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, enumerator );
if ( enumerator->m_nAlreadyHit > 0 )
{
entity = enumerator->m_AlreadyHit[0];
}
#if DEBUG_BREAKABLES
if ( entity )
{
DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 255, 0, 0, bot_debug_breakable_duration.GetFloat() );
}
else
{
DrawOutlinedQuad( start + mins, start + maxs, end + maxs, end + mins, 0, 255, 0, 0.1 );
}
#endif // DEBUG_BREAKABLES
return entity;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Look up to 'distance' units ahead on the bot's path for entities. Returns the closest one.
*/
CBaseEntity * CCSBot::FindEntitiesOnPath( float distance, CPushAwayEnumerator *enumerator, bool checkStuck )
{
Vector goal;
int pathIndex = FindPathPoint( distance, &goal, NULL );
bool isDegeneratePath = ( pathIndex == m_pathLength );
if ( isDegeneratePath )
{
goal = m_goalPosition;
}
goal.z += HalfHumanHeight;
Vector mins, maxs;
mins = Vector( 0, 0, -HalfHumanWidth );
maxs = Vector( 0, 0, HalfHumanHeight );
if ( distance <= NearBreakableCheckDist && m_isStuck && checkStuck )
{
mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
}
CBaseEntity *entity = NULL;
if ( isDegeneratePath )
{
entity = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_goalPosition + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
#if DEBUG_BREAKABLES
if ( entity )
{
NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_goalPosition, 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
}
#endif // DEBUG_BREAKABLES
}
else
{
int startIndex = MAX( 0, m_pathIndex );
float distanceLeft = distance;
// HACK: start with an index one lower than normal, so we can trace from the bot's location to the
// start of the path nodes.
for( int i=startIndex-1; i<m_pathLength-1; ++i )
{
Vector start, end;
if ( i == startIndex - 1 )
{
start = GetAbsOrigin();
end = m_path[i+1].pos;
}
else
{
start = m_path[i].pos;
end = m_path[i+1].pos;
if ( m_path[i+1].how == GO_LADDER_UP )
{
// Need two checks. First we'll check along the ladder
start = m_path[i].pos;
end = m_path[i+1].ladder->m_top;
}
else if ( m_path[i].how == GO_LADDER_UP )
{
start = m_path[i].ladder->m_top;
}
else if ( m_path[i+1].how == GO_LADDER_DOWN )
{
// Need two checks. First we'll check along the ladder
start = m_path[i].pos;
end = m_path[i+1].ladder->m_bottom;
}
else if ( m_path[i].how == GO_LADDER_DOWN )
{
start = m_path[i].ladder->m_bottom;
}
}
float segmentLength = (start - end).Length();
if ( distanceLeft - segmentLength < 0 )
{
// scale our segment back so we don't look too far
Vector direction = end - start;
direction.NormalizeInPlace();
end = start + direction * distanceLeft;
}
entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
if ( entity )
{
#if DEBUG_BREAKABLES
NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
#endif // DEBUG_BREAKABLES
break;
}
if ( m_path[i].ladder && !IsOnLadder() && distance > NearBreakableCheckDist ) // don't try to break breakables on the other end of a ladder
break;
distanceLeft -= segmentLength;
if ( distanceLeft < 0 )
break;
if ( i != startIndex - 1 && m_path[i+1].ladder )
{
// Now we'll check from the ladder out to the endpoint
start = ( m_path[i+1].how == GO_LADDER_DOWN ) ? m_path[i+1].ladder->m_bottom : m_path[i+1].ladder->m_top;
end = m_path[i+1].pos;
entity = CheckForEntitiesAlongSegment( start + Vector( 0, 0, HalfHumanHeight ), end + Vector( 0, 0, HalfHumanHeight ), mins, maxs, enumerator );
if ( entity )
{
#if DEBUG_BREAKABLES
NDebugOverlay::HorzArrow( start, end, 4, 0, 255, 0, 255, true, bot_debug_breakable_duration.GetFloat() );
#endif // DEBUG_BREAKABLES
break;
}
}
}
}
if ( entity && !IsVisible( entity->WorldSpaceCenter(), false, entity ) )
return NULL;
return entity;
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::PushawayTouch( CBaseEntity *pOther )
{
#if DEBUG_BREAKABLES
NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 127, 0.1f );
#endif // DEBUG_BREAKABLES
// if we're not stuck or crouched, we don't care
if ( !m_isStuck && !IsCrouching() )
return;
// See if it's breakable
CBaseEntity *props[1];
CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
enumerator.EnumElement( pOther );
if ( enumerator.m_nAlreadyHit == 1 )
{
// it's breakable - try to shoot it.
SetLookAt( "Breakable", pOther->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check for breakable physics props and other breakable entities. We do this here instead of catching them
* in OnTouch() because players don't collide with physics props, so OnTouch() doesn't get called. Also,
* looking ahead like this lets us anticipate when we'll need to break something, and do it before being
* stopped by it.
*/
void CCSBot::BreakablesCheck( void )
{
SNPROF( "BreakablesCheck" );
if ( !TheCSBots()->AllowedToDoExpensiveBotOperationThisFrame() )
return;
#if DEBUG_BREAKABLES
/*
// Debug code to visually mark all breakables near us
{
Ray_t ray;
Vector origin = WorldSpaceCenter();
Vector mins( -400, -400, -400 );
Vector maxs( 400, 400, 400 );
ray.Init( origin, origin, mins, maxs );
CBaseEntity *props[40];
CBotBreakableEnumerator enumerator( props, ARRAYSIZE( props ) );
::partition->EnumerateElementsAlongRay( PARTITION_ENGINE_SOLID_EDICTS, ray, false, &enumerator );
for ( int i=0; i<enumerator.m_nAlreadyHit; ++i )
{
CBaseEntity *prop = props[i];
if ( prop && prop->m_takedamage == DAMAGE_YES )
{
CFmtStr msg;
const char *text = msg.sprintf( "%s, %d health", prop->GetClassname(), prop->m_iHealth );
if ( prop->m_iHealth > 200 )
{
NDebugOverlay::EntityBounds( prop, 255, 0, 0, 10, 0.2f );
prop->EntityText( 0, text, 0.2f, 255, 0, 0, 255 );
}
else
{
NDebugOverlay::EntityBounds( prop, 0, 255, 0, 10, 0.2f );
prop->EntityText( 0, text, 0.2f, 0, 255, 0, 255 );
}
}
}
}
*/
#endif // DEBUG_BREAKABLES
if ( IsAttacking() )
{
// make sure we aren't running into a breakable trying to knife an enemy
if ( IsUsingKnife() && m_enemy != NULL )
{
CBaseEntity *breakables[1];
CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
CBaseEntity *breakable = NULL;
Vector mins = Vector( -HalfHumanWidth, -HalfHumanWidth, -HalfHumanWidth );
Vector maxs = Vector( HalfHumanWidth, HalfHumanWidth, HalfHumanHeight );
breakable = CheckForEntitiesAlongSegment( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), mins, maxs, &enumerator );
if ( breakable )
{
#if DEBUG_BREAKABLES
NDebugOverlay::HorzArrow( WorldSpaceCenter(), m_enemy->WorldSpaceCenter(), 6, 0, 0, 255, 255, true, bot_debug_breakable_duration.GetFloat() );
#endif // DEBUG_BREAKABLES
// look at it (chances are we'll already be looking at it, since it's between us and our enemy)
SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
// break it (again, don't wait: we don't have ammo, since we're using the knife, and we're looking mostly at it anyway)
PrimaryAttack();
}
}
return;
}
if ( !HasPath() )
return;
bool isNear = true;
// Check just in front of us on the path
CBaseEntity *breakables[4];
CBotBreakableEnumerator enumerator( breakables, ARRAYSIZE( breakables ) );
CBaseEntity *breakable = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, true );
// If we don't have an object right in front of us, check a ways out
if ( !breakable )
{
breakable = FindEntitiesOnPath( FarBreakableCheckDist, &enumerator, false );
isNear = false;
}
// Try to shoot a breakable we know about
if ( breakable )
{
// look at it
SetLookAt( "Breakable", breakable->WorldSpaceCenter(), PRIORITY_HIGH, 0.1f, false, 5.0f, true );
}
// break it
if ( IsLookingAtSpot( PRIORITY_HIGH ) && m_lookAtSpotAttack )
{
if ( IsUsingGrenade() || ( !isNear && IsUsingKnife() ) )
{
EquipBestWeapon( MUST_EQUIP );
}
else if ( GetActiveWeapon() && GetActiveWeapon()->m_flNextPrimaryAttack <= gpGlobals->curtime )
{
bool shouldShoot = IsLookingAtPosition( m_lookAtSpot, 10.0f );
if ( !shouldShoot )
{
CBaseEntity *breakables[ 1 ];
CBotBreakableEnumerator LOSbreakable( breakables, ARRAYSIZE( breakables ) );
// compute the unit vector along our view
Vector aimDir = GetViewVector();
// trace the potential bullet's path
trace_t result;
UTIL_TraceLine( EyePosition(), EyePosition() + FarBreakableCheckDist * aimDir, MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
if ( result.DidHitNonWorldEntity() )
{
LOSbreakable.EnumElement( result.m_pEnt );
if ( LOSbreakable.m_nAlreadyHit == 1 && LOSbreakable.m_AlreadyHit[ 0 ] == breakable )
{
shouldShoot = true;
}
}
}
// Shoot out breakable if the LOS is clear, or if we're playing a mode with no friendly fire
shouldShoot = shouldShoot && ( !TheCSBots()->AllowFriendlyFireDamage() || !IsFriendInLineOfFire() );
if ( shouldShoot )
{
PrimaryAttack();
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check for doors that need +use to open.
*/
void CCSBot::DoorCheck( void )
{
SNPROF( "DoorCheck" );
if ( !TheCSBots()->AllowedToDoExpensiveBotOperationThisFrame() )
return;
if ( IsAttacking() && !IsUsingKnife() )
{
// If we're attacking with a gun or nade, don't bother with doors. If we're trying to
// knife someone, we might need to open a door.
m_isOpeningDoor = false;
return;
}
if ( !HasPath() )
return;
// Find any doors that need a +use to open just in front of us along the path.
CBaseEntity *doors[4];
CBotDoorEnumerator enumerator( doors, ARRAYSIZE( doors ) );
CBaseEntity *door = FindEntitiesOnPath( NearBreakableCheckDist, &enumerator, false );
if ( door )
{
if ( !IsLookingAtSpot( PRIORITY_UNINTERRUPTABLE ) )
{
if ( !IsOpeningDoor() )
{
CBasePropDoor *pPropDoor = dynamic_cast<CBasePropDoor*>( door );
if ( pPropDoor && pPropDoor->IsDoorLocked() )
return;
else if ( CBaseDoor *pFuncDoor = dynamic_cast< CBaseDoor * >( door ) )
{
if ( pFuncDoor->m_bLocked )
return;
}
OpenDoor( door );
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset the stuck-checker.
*/
void CCSBot::ResetStuckMonitor( void )
{
if (m_isStuck)
{
if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetBool() && UTIL_GetListenServerHost())
{
CBasePlayer *localPlayer = UTIL_GetListenServerHost();
CSingleUserRecipientFilter filter( localPlayer );
EmitSound( filter, localPlayer->entindex(), "Bot.StuckSound" );
}
}
m_isStuck = false;
m_stuckTimestamp = 0.0f;
m_stuckJumpTimer.Invalidate();
m_avgVelIndex = 0;
m_avgVelCount = 0;
m_nextCleanupCheckTimestamp = 0.0f;
m_areaEnteredTimestamp = gpGlobals->curtime;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Test if we have become stuck
*/
void CCSBot::StuckCheck( void )
{
SNPROF("StuckCheck");
if (m_isStuck)
{
// we are stuck - see if we have moved far enough to be considered unstuck
Vector delta = GetAbsOrigin() - m_stuckSpot;
const float unstuckRange = 75.0f;
if (delta.IsLengthGreaterThan( unstuckRange ))
{
// we are no longer stuck
ResetStuckMonitor();
PrintIfWatched( "UN-STUCK\n" );
}
}
else
{
// check if we are stuck
// compute average velocity over a short period (for stuck check)
Vector vel = GetAbsOrigin() - m_lastOrigin;
// if we are jumping, ignore Z
if (IsJumping())
vel.z = 0.0f;
// cannot be Length2D, or will break ladder movement (they are only Z)
float moveDist = vel.Length();
float deltaT = g_BotUpdateInterval;
m_avgVel[ m_avgVelIndex++ ] = moveDist/deltaT;
if (m_avgVelIndex == MAX_VEL_SAMPLES)
m_avgVelIndex = 0;
if (m_avgVelCount < MAX_VEL_SAMPLES)
{
m_avgVelCount++;
}
else
{
// we have enough samples to know if we're stuck
float avgVel = 0.0f;
for( int t=0; t<m_avgVelCount; ++t )
avgVel += m_avgVel[t];
avgVel /= m_avgVelCount;
// cannot make this velocity too high, or bots will get "stuck" when going down ladders
//(IsUsingLadder()) ? 10.0f : 20.0f;
float stuckVel = ( IsRunning() ) ? 10.0f : 5.0f;
if (avgVel < stuckVel)
{
// we are stuck - note when and where we initially become stuck
m_stuckTimestamp = gpGlobals->curtime;
m_stuckSpot = GetAbsOrigin();
m_stuckJumpTimer.Start( RandomFloat( 0.3f, 0.75f ) ); // 1.0
PrintIfWatched( "STUCK\n" );
if (IsLocalPlayerWatchingMe() && cv_bot_debug.GetInt() > 0 && UTIL_GetListenServerHost() )
{
CBasePlayer *localPlayer = UTIL_GetListenServerHost();
CSingleUserRecipientFilter filter( localPlayer );
EmitSound( filter, localPlayer->entindex(), "Bot.StuckStart" );
}
m_isStuck = true;
}
}
}
// always need to track this
m_lastOrigin = GetAbsOrigin();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check if we need to jump due to height change
*/
bool CCSBot::DiscontinuityJump( float ground, bool onlyJumpDown, bool mustJump )
{
// Don't try to jump if in the air.
if( !(GetFlags() & FL_ONGROUND) )
{
return false;
}
float dz = ground - GetFeetZ();
if (dz > StepHeight && !onlyJumpDown)
{
// dont restrict jump time when going up
if (Jump( MUST_JUMP ))
{
return true;
}
}
else if (!IsUsingLadder() && dz < -JumpHeight)
{
if (Jump( mustJump ))
{
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Find "simple" ground height, treating current nav area as part of the floor
*/
bool CCSBot::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal )
{
if (TheNavMesh->GetSimpleGroundHeight( pos, height, normal ))
{
// our current nav area also serves as a ground polygon
if (m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos ))
*height = MAX( (*height), m_lastKnownArea->GetZ( pos ) );
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Get our current radio chatter place
*/
Place CCSBot::GetPlace( void ) const
{
if (m_lastKnownArea)
return m_lastKnownArea->GetPlace();
return UNDEFINED_PLACE;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move towards position, independant of view angle
*/
void CCSBot::MoveTowardsPosition( const Vector &pos )
{
Vector myOrigin = GetCentroid( this );
//
// Jump up on ledges
// Because we may not be able to get to our goal position and enter the next
// area because our extent collides with a nearby vertical ledge, make sure
// we look far enough ahead to avoid this situation.
// Can't look too far ahead, or bots will try to jump up slopes.
//
// NOTE: We need to do this frequently to catch edges at the right time
// @todo Look ahead *along path* instead of straight line
//
if ( (m_lastKnownArea == NULL || !( m_lastKnownArea->GetAttributes() & ( NAV_MESH_NO_JUMP | NAV_MESH_STAIRS ) ) ) &&
!IsOnLadder())
{
float ground;
Vector aheadRay( pos.x - myOrigin.x, pos.y - myOrigin.y, 0 );
aheadRay.NormalizeInPlace();
// look far ahead to allow us to smoothly jump over gaps, ledges, etc
// only jump if ground is flat at lookahead spot to avoid jumping up slopes
bool jumped = false;
if (IsRunning())
{
const float farLookAheadRange = 80.0f; // 60
Vector normal;
Vector stepAhead = myOrigin + farLookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground, &normal ))
{
if (normal.z > 0.9f)
jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN );
}
}
if (!jumped)
{
// close up jumping
const float lookAheadRange = 30.0f; // cant be less or will miss jumps over low walls
Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
{
jumped = DiscontinuityJump( ground );
}
}
if (!jumped)
{
// about to fall gap-jumping
const float lookAheadRange = 10.0f;
Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
{
jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN, MUST_JUMP );
}
}
}
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - myOrigin.x, pos.y - myOrigin.y );
to.NormalizeInPlace();
// move towards the position independant of our view direction
float toProj = to.x * dir.x + to.y * dir.y;
float latProj = to.x * lat.x + to.y * lat.y;
const float c = 0.25f; // 0.5
if (toProj > c)
MoveForward();
else if (toProj < -c)
MoveBackward();
// if we are avoiding someone via strafing, don't override
if (m_avoid != NULL)
return;
if (latProj >= c)
StrafeLeft();
else if (latProj <= -c)
StrafeRight();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move away from position, independant of view angle
*/
void CCSBot::MoveAwayFromPosition( const Vector &pos )
{
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
to.NormalizeInPlace();
// move away from the position independant of our view direction
float toProj = to.x * dir.x + to.y * dir.y;
float latProj = to.x * lat.x + to.y * lat.y;
const float c = 0.5f;
if (toProj > c)
MoveBackward();
else if (toProj < -c)
MoveForward();
if (latProj >= c)
StrafeRight();
else if (latProj <= -c)
StrafeLeft();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Strafe (sidestep) away from position, independant of view angle
*/
void CCSBot::StrafeAwayFromPosition( const Vector &pos )
{
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
to.NormalizeInPlace();
float latProj = to.x * lat.x + to.y * lat.y;
if (latProj >= 0.0f)
StrafeRight();
else
StrafeLeft();
}
//--------------------------------------------------------------------------------------------------------------
/**
* For getting un-stuck
*/
void CCSBot::Wiggle( void )
{
if (IsCrouching())
{
return;
}
// for wiggling
if (m_wiggleTimer.IsElapsed())
{
m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 );
m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) ); // 0.3, 0.5
}
Vector forward, right;
EyeVectors( &forward, &right );
const float lookAheadRange = (m_lastKnownArea && (m_lastKnownArea->GetAttributes() & NAV_MESH_WALK)) ? 5.0f : 30.0f;
float ground;
switch( m_wiggleDirection )
{
case LEFT:
{
// don't move left if we will fall
Vector pos = GetAbsOrigin() - (lookAheadRange * right);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
StrafeLeft();
}
}
break;
}
case RIGHT:
{
// don't move right if we will fall
Vector pos = GetAbsOrigin() + (lookAheadRange * right);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
StrafeRight();
}
}
break;
}
case FORWARD:
{
// don't move forward if we will fall
Vector pos = GetAbsOrigin() + (lookAheadRange * forward);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
MoveForward();
}
}
break;
}
case BACKWARD:
{
// don't move backward if we will fall
Vector pos = GetAbsOrigin() - (lookAheadRange * forward);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
MoveBackward();
}
}
break;
}
}
if (m_stuckJumpTimer.IsElapsed() && m_lastKnownArea && !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP))
{
if (Jump())
{
m_stuckJumpTimer.Start( RandomFloat( 1.0f, 2.0f ) );
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine approach points from eye position and approach areas of current area
*/
void CCSBot::ComputeApproachPoints( void )
{
m_approachPointCount = 0;
if (m_lastKnownArea == NULL)
{
return;
}
// assume we're crouching for now
Vector eye = GetCentroid( this ); // + pev->view_ofs; // eye position
Vector ap;
float halfWidth;
for( int i=0; i<m_lastKnownArea->GetApproachInfoCount() && m_approachPointCount < MAX_APPROACH_POINTS; ++i )
{
const CCSNavArea::ApproachInfo *info = m_lastKnownArea->GetApproachInfo( i );
if (info->here.area == NULL || info->prev.area == NULL)
{
continue;
}
// compute approach point (approach area is "info->here")
if (info->prevToHereHow <= GO_WEST)
{
info->prev.area->ComputePortal( info->here.area, (NavDirType)info->prevToHereHow, &ap, &halfWidth );
ap.z = info->here.area->GetZ( ap );
}
else
{
// use the area's center as an approach point
ap = info->here.area->GetCenter();
}
// "bend" our line of sight around corners until we can see the approach point
Vector bendPoint;
if (BendLineOfSight( eye, ap + Vector( 0, 0, HalfHumanHeight ), &bendPoint ))
{
// put point on the ground
if (TheNavMesh->GetGroundHeight( bendPoint, &bendPoint.z ) == false)
{
bendPoint.z = ap.z;
}
m_approachPoint[ m_approachPointCount ].m_pos = bendPoint;
m_approachPoint[ m_approachPointCount ].m_area = info->here.area;
++m_approachPointCount;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::DrawApproachPoints( void ) const
{
for( int i=0; i<m_approachPointCount; ++i )
{
if (TheCSBots()->GetElapsedRoundTime() >= m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 255, 0, 255, true, 0.1f );
else
NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 100, 100, 100, true, 0.1f );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Find the approach point that is nearest to our current path, ahead of us
*/
bool CCSBot::FindApproachPointNearestPath( Vector *pos )
{
if (!HasPath())
return false;
// make sure approach points are accurate
ComputeApproachPoints();
if (m_approachPointCount == 0)
return false;
Vector target = Vector( 0, 0, 0 ), close;
float targetRangeSq = 0.0f;
bool found = false;
int start = m_pathIndex;
int end = m_pathLength;
//
// We dont want the strictly closest point, but the farthest approach point
// from us that is near our path
//
const float nearPathSq = 10000.0f; // (100)
for( int i=0; i<m_approachPointCount; ++i )
{
if (FindClosestPointOnPath( m_approachPoint[i].m_pos, start, end, &close ) == false)
continue;
float rangeSq = (m_approachPoint[i].m_pos - close).LengthSqr();
if (rangeSq > nearPathSq)
continue;
if (rangeSq > targetRangeSq)
{
target = close;
targetRangeSq = rangeSq;
found = true;
}
}
if (found)
{
*pos = target + Vector( 0, 0, HalfHumanHeight );
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are at the/an enemy spawn right now
*/
bool CCSBot::IsAtEnemySpawn( void ) const
{
CBaseEntity *spot;
const char *spawnName = (GetTeamNumber() == TEAM_TERRORIST) ? "info_player_counterterrorist" : "info_player_terrorist";
// check if we are at any of the enemy's spawn points
for( spot = gEntList.FindEntityByClassname( NULL, spawnName ); spot; spot = gEntList.FindEntityByClassname( spot, spawnName ) )
{
CNavArea *area = TheNavMesh->GetNearestNavArea( spot->WorldSpaceCenter() );
if (area && GetLastKnownArea() == area)
{
return true;
}
}
return false;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,380 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "usermessages.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern int gmsgBotVoice;
//--------------------------------------------------------------------------------------------------------------
/**
* Returns true if the radio message is an order to do something
* NOTE: "Report in" is not considered a "command" because it doesnt ask the bot to go somewhere, or change its mind
*/
bool CCSBot::IsRadioCommand( RadioType event ) const
{
if (event == RADIO_AFFIRMATIVE ||
event == RADIO_NEGATIVE ||
event == RADIO_ENEMY_SPOTTED ||
event == RADIO_SECTOR_CLEAR ||
event == RADIO_REPORTING_IN ||
event == RADIO_REPORT_IN_TEAM ||
event == RADIO_ENEMY_DOWN ||
event == RADIO_INVALID )
return false;
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Respond to radio commands from HUMAN players
*/
void CCSBot::RespondToRadioCommands( void )
{
// bots use the chatter system to respond to each other
if (m_radioSubject != NULL && m_radioSubject->IsPlayer())
{
CCSPlayer *player = m_radioSubject;
if (player->IsBot())
{
m_lastRadioCommand = RADIO_INVALID;
return;
}
}
if (m_lastRadioCommand == RADIO_INVALID)
return;
// a human player has issued a radio command
GetChatter()->ResetRadioSilenceDuration();
// if we are doing something important, ignore the radio
// unless it is a "report in" request - we can do that while we continue to do other things
/// @todo Create "uninterruptable" flag
if (m_lastRadioCommand != RADIO_REPORT_IN_TEAM)
{
if (IsBusy())
{
// consume command
m_lastRadioCommand = RADIO_INVALID;
return;
}
}
// wait for reaction time before responding
// delay needs to be long enough for the radio message we're responding to to finish
float respondTime = 1.0f + 2.0f * GetProfile()->GetReactionTime();
if (IsRogue())
respondTime += 2.0f;
if (gpGlobals->curtime - m_lastRadioRecievedTimestamp < respondTime)
return;
// rogues won't follow commands, unless already following the player
if (!IsFollowing() && IsRogue())
{
if (IsRadioCommand( m_lastRadioCommand ))
{
GetChatter()->Negative();
}
// consume command
m_lastRadioCommand = RADIO_INVALID;
return;
}
CCSPlayer *player = m_radioSubject;
if (player == NULL)
return;
// respond to command
bool canDo = false;
const float inhibitAutoFollowDuration = 60.0f;
switch( m_lastRadioCommand )
{
case RADIO_REPORT_IN_TEAM:
{
GetChatter()->ReportingIn();
break;
}
case RADIO_FOLLOW_ME:
case RADIO_COVER_ME:
case RADIO_STICK_TOGETHER_TEAM:
case RADIO_REGROUP_TEAM:
{
if (!IsFollowing())
{
Follow( player );
player->AllowAutoFollow();
canDo = true;
}
break;
}
case RADIO_ENEMY_SPOTTED:
case RADIO_NEED_BACKUP:
case RADIO_TAKING_FIRE:
if (!IsFollowing())
{
Follow( player );
GetChatter()->Say( "OnMyWay" );
player->AllowAutoFollow();
canDo = false;
}
break;
case RADIO_TEAM_FALL_BACK:
{
if (TryToRetreat())
canDo = true;
break;
}
case RADIO_HOLD_THIS_POSITION:
{
// find the leader's area
SetTask( HOLD_POSITION );
StopFollowing();
player->InhibitAutoFollow( inhibitAutoFollowDuration );
Hide( TheNavMesh->GetNearestNavArea( m_radioPosition ) );
canDo = true;
break;
}
case RADIO_GO_GO_GO:
case RADIO_STORM_THE_FRONT:
StopFollowing();
Hunt();
canDo = true;
player->InhibitAutoFollow( inhibitAutoFollowDuration );
break;
case RADIO_GET_OUT_OF_THERE:
if (TheCSBots()->IsBombPlanted())
{
EscapeFromBomb();
player->InhibitAutoFollow( inhibitAutoFollowDuration );
canDo = true;
}
break;
case RADIO_SECTOR_CLEAR:
{
// if this is a defusal scenario, and the bomb is planted,
// and a human player cleared a bombsite, check it off our list too
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
if (GetTeamNumber() == TEAM_CT && TheCSBots()->IsBombPlanted())
{
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( player );
if (zone)
{
GetGameState()->ClearBombsite( zone->m_index );
// if we are huting for the planted bomb, re-select bombsite
if (GetTask() == FIND_TICKING_BOMB)
Idle();
canDo = true;
}
}
}
break;
}
default:
// ignore all other radio commands for now
return;
}
if (canDo)
{
// affirmative
GetChatter()->Affirmative();
// if we agreed to follow a new command, put away our grenade
if (IsRadioCommand( m_lastRadioCommand ) && IsUsingGrenade())
{
EquipBestWeapon();
}
}
// consume command
m_lastRadioCommand = RADIO_INVALID;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Decide if we should move to help the player, return true if we will
*/
bool CCSBot::RespondToHelpRequest( CCSPlayer *them, Place place, float maxRange )
{
if (IsRogue())
return false;
// if we're busy, ignore
if (IsBusy())
return false;
Vector themOrigin = GetCentroid( them );
// if we are too far away, ignore
if (maxRange > 0.0f)
{
// compute actual travel distance
PathCost cost(this);
float travelDistance = NavAreaTravelDistance( m_lastKnownArea, TheNavMesh->GetNearestNavArea( themOrigin ), cost );
if (travelDistance < 0.0f)
return false;
if (travelDistance > maxRange)
return false;
}
if (place == UNDEFINED_PLACE)
{
// if we have no "place" identifier, go directly to them
// if we are already there, ignore
float rangeSq = (them->GetAbsOrigin() - GetAbsOrigin()).LengthSqr();
const float close = 750.0f * 750.0f;
if (rangeSq < close)
return true;
MoveTo( themOrigin, FASTEST_ROUTE );
}
else
{
// if we are already there, ignore
if (GetPlace() == place)
return true;
// go to where help is needed
Vector pos;
if ( GetRandomSpotAtPlace( place, &pos ) )
{
MoveTo( pos, FASTEST_ROUTE );
}
else
{
MoveTo( themOrigin, FASTEST_ROUTE );
}
}
// acknowledge
GetChatter()->Say( "OnMyWay" );
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Send a radio message
*/
void CCSBot::SendRadioMessage( RadioType event )
{
// make sure this is a radio event
if (event <= RADIO_START_1 || event >= RADIO_END)
return;
PrintIfWatched( "%3.1f: SendRadioMessage( %s )\n", gpGlobals->curtime, RadioEventName[ event ] );
// note the time the message was sent
TheCSBots()->SetRadioMessageTimestamp( event, GetTeamNumber() );
m_lastRadioSentTimestamp = gpGlobals->curtime;
char slot[2];
slot[1] = '\000';
if (event > RADIO_START_1 && event < RADIO_START_2)
{
HandleMenu_Radio1( event );
}
else if (event > RADIO_START_2 && event < RADIO_START_3)
{
HandleMenu_Radio2( event );
}
else
{
HandleMenu_Radio3( event );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Send voice chatter. Also sends the entindex and duration for voice feedback.
*/
void CCSBot::SpeakAudio( const char *voiceFilename, float duration, int pitch )
{
if( !IsAlive() )
return;
if ( IsObserver() )
return;
CRecipientFilter filter;
ConstructRadioFilter( filter );
CCSUsrMsg_RawAudio msg;
msg.set_pitch( pitch );
msg.set_entidx( entindex() );
msg.set_duration( duration );
msg.set_voice_filename( voiceFilename );
SendUserMessage ( filter, CS_UM_RawAudio, msg );
GetChatter()->ResetRadioSilenceDuration();
m_voiceEndTimestamp = gpGlobals->curtime + duration;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Send voice chatter through the response rules system.
*/
bool CCSBot::SpeakAudioResponseRules( const char *pConcept, AI_CriteriaSet *criteria, float duration )
{
if( !IsAlive() )
return false;
if ( IsObserver() )
return false;
CRecipientFilter filter;
ConstructRadioFilter( filter );
AI_CriteriaSet local;
if ( !criteria )
criteria = &local;
AIConcept_t concept( pConcept );
if ( Speak( concept, criteria, NULL, 0, &filter ) )
{
GetChatter()->ResetRadioSilenceDuration();
m_voiceEndTimestamp = gpGlobals->curtime + duration;
return true;
}
return false;
}

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael Booth (mike@turtlerockstudios.com), 2003
// Author: Matthew D. Campbell (matt@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------
//
// Temporary solution until we have time to build something more elegant
// Very nasty - need to keep in sync with the buy aliases
// NOTE: Array must be NULL terminated
//

View File

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

View File

@@ -0,0 +1,151 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#ifndef _GAME_STATE_H_
#define _GAME_STATE_H_
#include "bot_util.h"
class CHostage;
class CCSBot;
/**
* This class represents the game state as known by a particular bot
*/
class CSGameState
{
public:
CSGameState( CCSBot *owner );
void Reset( void );
// Event handling
void OnHostageRescuedAll( IGameEvent *event );
void OnRoundEnd( IGameEvent *event );
void OnRoundStart( IGameEvent *event );
void OnBombPlanted( IGameEvent *event );
void OnBombDefused( IGameEvent *event );
void OnBombExploded( IGameEvent *event );
bool IsRoundOver( void ) const; ///< true if round has been won or lost (but not yet reset)
// bomb defuse scenario -----------------------------------------------------------------------------
enum BombState
{
MOVING, ///< being carried by a Terrorist
LOOSE, ///< loose on the ground somewhere
PLANTED, ///< planted and ticking
DEFUSED, ///< the bomb has been defused
EXPLODED ///< the bomb has exploded
};
bool IsBombMoving( void ) const { return (m_bombState == MOVING); }
bool IsBombLoose( void ) const { return (m_bombState == LOOSE); }
bool IsBombPlanted( void ) const { return (m_bombState == PLANTED); }
bool IsBombDefused( void ) const { return (m_bombState == DEFUSED); }
bool IsBombExploded( void ) const { return (m_bombState == EXPLODED); }
void UpdateLooseBomb( const Vector &pos ); ///< we see the loose bomb
float TimeSinceLastSawLooseBomb( void ) const; ///< how long has is been since we saw the loose bomb
bool IsLooseBombLocationKnown( void ) const; ///< do we know where the loose bomb is
void UpdateBomber( const Vector &pos ); ///< we see the bomber
float TimeSinceLastSawBomber( void ) const; ///< how long has is been since we saw the bomber
void UpdatePlantedBomb( const Vector &pos ); ///< we see the planted bomb
bool IsPlantedBombLocationKnown( void ) const; ///< do we know where the bomb was planted
void MarkBombsiteAsPlanted( int zoneIndex ); ///< mark bombsite as the location of the planted bomb
enum { UNKNOWN = -1 };
int GetPlantedBombsite( void ) const; ///< return the zone index of the planted bombsite, or UNKNOWN
bool IsAtPlantedBombsite( void ) const; ///< return true if we are currently in the bombsite where the bomb is planted
int GetNextBombsiteToSearch( void ); ///< return the zone index of the next bombsite to search
bool IsBombsiteClear( int zoneIndex ) const; ///< return true if given bombsite has been cleared
void ClearBombsite( int zoneIndex ); ///< mark bombsite as clear
const Vector *GetBombPosition( void ) const; ///< return where we think the bomb is, or NULL if we don't know
// hostage rescue scenario ------------------------------------------------------------------------
CHostage *GetNearestFreeHostage( Vector *knowPos = NULL ) const; ///< return the closest free hostage, and where we think it is (knowPos)
const Vector *GetRandomFreeHostagePosition( void ) const;
bool AreAllHostagesBeingRescued( void ) const; ///< return true if there are no free hostages
bool AreAllHostagesGone( void ) const; ///< all hostages have been rescued or are dead
void AllHostagesGone( void ); ///< someone told us all the hostages are gone
bool HaveSomeHostagesBeenTaken( void ) const ///< return true if one or more hostages have been moved by the CT's
{
return m_haveSomeHostagesBeenTaken;
}
void HostageWasTaken( void ) ///< someone told us a CT is talking to a hostage
{
m_haveSomeHostagesBeenTaken = true;
}
CHostage *GetNearestVisibleFreeHostage( void ) const;
enum ValidateStatusType
{
NO_CHANGE = 0x00,
HOSTAGE_DIED = 0x01,
HOSTAGE_GONE = 0x02,
HOSTAGES_ALL_GONE = 0x04
};
unsigned char ValidateHostagePositions( void ); ///< update our knowledge with what we currently see - returns bitflag events
BombState GetBombState( void ) const { return m_bombState; }
private:
CCSBot *m_owner; ///< who owns this gamestate
bool m_isRoundOver; ///< true if round is over, but no yet reset
// bomb defuse scenario ---------------------------------------------------------------------------
void SetBombState( BombState state );
BombState m_bombState; ///< what we think the bomb is doing
IntervalTimer m_lastSawBomber;
Vector m_bomberPos;
IntervalTimer m_lastSawLooseBomb;
Vector m_looseBombPos;
bool m_isBombsiteClear[ CCSBotManager::MAX_ZONES ]; ///< corresponds to zone indices in CCSBotManager
int m_bombsiteSearchOrder[ CCSBotManager::MAX_ZONES ]; ///< randomized order of bombsites to search
int m_bombsiteCount;
int m_bombsiteSearchIndex; ///< the next step in the search
int m_plantedBombsite; ///< zone index of the bombsite where the planted bomb is
bool m_isPlantedBombPosKnown; ///< if true, we know the exact location of the bomb
Vector m_plantedBombPos;
// hostage rescue scenario ------------------------------------------------------------------------
struct HostageInfo
{
CHandle<CHostage> hostage;
Vector knownPos;
bool isValid;
bool isAlive;
bool isFree; ///< not being escorted by a CT
}
m_hostage[ MAX_HOSTAGES ];
int m_hostageCount; ///< number of hostages left in map
CountdownTimer m_validateInterval;
CBaseEntity *GetNearestHostage( void ) const; ///< return the closest live hostage
void InitializeHostageInfo( void ); ///< initialize our knowledge of the number and location of hostages
bool m_allHostagesRescued;
bool m_haveSomeHostagesBeenTaken; ///< true if a hostage has been moved by a CT (and we've seen it)
};
#endif // _GAME_STATE_

View File

@@ -0,0 +1,732 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//
// Various tweakable parameters which control overall bot behavior.
// Note[pmf]: I moved them all to the top of the file together so it's easier to get a sense of how they interact
//
const float crouchFarRange = 750.0f; // 50% (increased) chance to crouch when enemy is farther than this
const float hysterisRange = 125.0f; // (+/-) m_combatRange, used to control dodging
const float dodgeRange = 2000.0f; // maximum range from enemy to consider dodging
const float jumpChance = 33.3f; // chance of low-skill to jump when first engaging the enemy (if they are moving)
const float lookAheadRange = 30.0f; // how far L/R to consider whether we can fall while strafing
const float sniperMinRange = 160.0f; // what distance to switch to pistol if enemy is too close. Must be larger than NO_ZOOM range in AdjustZoom()
const float shotgunMaxRange = 600.0f; // what distance to switch to pistol if enemy is too far
const float slashRange = 70.0f; // when using knife, repath to enemy if they are farther than this
const float repathInterval = 0.5f;
const float repathRange = 100.0f; // repath when enemy has moved this far from our current path endpoint
//--------------------------------------------------------------------------------------------------------------
/**
* Begin attacking
*/
void AttackState::OnEnter( CCSBot *me )
{
CBasePlayer *enemy = me->GetBotEnemy();
// store our posture when the attack began
me->PushPostureContext();
me->DestroyPath();
// if we are using a knife, try to sneak up on the enemy
if (enemy && me->IsUsingKnife() && !me->IsPlayerFacingMe( enemy ))
me->Walk();
else
me->Run();
me->GetOffLadder();
me->ResetStuckMonitor();
m_repathTimer.Invalidate();
m_haveSeenEnemy = me->IsEnemyVisible();
m_nextDodgeStateTimestamp = 0.0f;
m_firstDodge = true;
m_isEnemyHidden = false;
m_reacquireTimestamp = 0.0f;
m_pinnedDownTimestamp = gpGlobals->curtime + RandomFloat( 7.0f, 10.0f );
m_shieldToggleTimestamp = gpGlobals->curtime + RandomFloat( 2.0f, 10.0f );
m_shieldForceOpen = false;
// if we encountered someone while escaping, grab our weapon and fight!
if (me->IsEscapingFromBomb())
me->EquipBestWeapon();
if (me->IsUsingKnife())
{
// can't crouch and hold with a knife
m_crouchAndHold = false;
me->StandUp();
}
else if (me->CanSeeSniper() && !me->IsSniper())
{
// don't sit still if we see a sniper!
m_crouchAndHold = false;
me->StandUp();
}
else
{
// decide whether to crouch where we are, or run and gun (if we havent already - see CCSBot::Attack())
if (!m_crouchAndHold)
{
if (enemy)
{
float crouchChance;
// more likely to crouch if using sniper rifle or if enemy is far away
if (me->IsUsingSniperRifle())
crouchChance = 50.0f;
else if ((GetCentroid( me ) - GetCentroid( enemy )).IsLengthGreaterThan( crouchFarRange ))
crouchChance = 50.0f;
else
crouchChance = 20.0f * (1.0f - me->GetProfile()->GetAggression());
if (RandomFloat( 0.0f, 100.0f ) < crouchChance)
{
// make sure we can still see if we crouch
trace_t result;
Vector origin = GetCentroid( me );
if (!me->IsCrouching())
{
// we are standing, adjust for lower crouch origin
origin.z -= 20.0f;
}
UTIL_TraceLine( origin, enemy->EyePosition(), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
if (result.fraction == 1.0f)
{
m_crouchAndHold = true;
}
}
}
}
if (m_crouchAndHold)
{
me->Crouch();
me->PrintIfWatched( "Crouch and hold attack!\n" );
}
}
m_scopeTimestamp = 0;
m_didAmbushCheck = false;
float skill = me->GetProfile()->GetSkill();
// tendency to dodge is proportional to skill
float dodgeChance = 80.0f * skill;
// high skill bots always dodge if outnumbered, or they see a sniper
if (skill > 0.5f && (me->IsOutnumbered() || me->CanSeeSniper()))
{
dodgeChance = 100.0f;
}
m_shouldDodge = (RandomFloat( 0, 100 ) <= dodgeChance);
// decide whether we might bail out of this fight
m_isCoward = (RandomFloat( 0, 100 ) > 100.0f * me->GetProfile()->GetAggression());
// HEAVY BOTS: If we have heavy armor, attack differently
if ( me->HasHeavyArmor() )
{
m_isCoward = m_shouldDodge = false;
me->Walk();
m_pinnedDownTimestamp = gpGlobals->curtime + 60*60;
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* When we are done attacking, this is invoked
*/
void AttackState::StopAttacking( CCSBot *me )
{
if (me->GetTask() == CCSBot::SNIPING)
{
// stay in our hiding spot
me->Hide( me->GetLastKnownArea(), -1.0f, 50.0f );
}
else
{
me->StopAttacking();
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Do dodge behavior
*/
void AttackState::Dodge( CCSBot *me )
{
//
// Dodge.
// If sniping or crouching, stand still.
//
if (m_shouldDodge && !me->IsUsingSniperRifle() && !m_crouchAndHold)
{
CBasePlayer *enemy = me->GetBotEnemy();
if (enemy == NULL)
{
return;
}
Vector toEnemy = enemy->GetAbsOrigin() - me->GetAbsOrigin();
float range = toEnemy.Length();
float minRange = me->GetCombatRange() - hysterisRange;
float maxRange = me->GetCombatRange() + hysterisRange;
if (me->IsUsingKnife())
{
// dodge when far away if armed only with a knife
maxRange = 999999.9f;
}
// move towards (or away from) enemy if we are using a knife, behind a corner, or we aren't very skilled
if (me->GetProfile()->GetSkill() < 0.66f || !me->IsEnemyVisible())
{
if (range > maxRange)
me->MoveForward();
else if (range < minRange)
me->MoveBackward();
}
// don't dodge if enemy is facing away
if (!me->CanSeeSniper() && (range > dodgeRange || !me->IsPlayerFacingMe( enemy )))
{
m_dodgeState = STEADY_ON;
m_nextDodgeStateTimestamp = 0.0f;
}
else if (gpGlobals->curtime >= m_nextDodgeStateTimestamp)
{
int next;
// high-skill bots keep moving and don't jump if they see a sniper
if (me->GetProfile()->GetSkill() > 0.5f && me->CanSeeSniper())
{
// juke back and forth
if (m_firstDodge)
{
next = (RandomInt( 0, 100 ) < 50) ? SLIDE_RIGHT : SLIDE_LEFT;
}
else
{
next = (m_dodgeState == SLIDE_LEFT) ? SLIDE_RIGHT : SLIDE_LEFT;
}
}
else
{
// select next dodge state that is different that our current one
do
{
// low-skill bots may jump when first engaging the enemy (if they are moving)
if (m_firstDodge && me->GetProfile()->GetSkill() < 0.5f && RandomFloat( 0, 100 ) < jumpChance && !me->IsNotMoving())
next = RandomInt( 0, NUM_ATTACK_STATES-1 );
else
next = RandomInt( 0, NUM_ATTACK_STATES-2 );
}
while( !m_firstDodge && next == m_dodgeState );
}
m_dodgeState = (DodgeStateType)next;
m_nextDodgeStateTimestamp = gpGlobals->curtime + RandomFloat( 0.3f, 1.0f );
m_firstDodge = false;
}
Vector forward, right;
me->EyeVectors( &forward, &right );
float ground;
switch( m_dodgeState )
{
case STEADY_ON:
{
break;
}
case SLIDE_LEFT:
{
// don't move left if we will fall
Vector pos = me->GetAbsOrigin() - (lookAheadRange * right);
if (me->GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (me->GetAbsOrigin().z - ground < StepHeight)
{
me->StrafeLeft();
}
}
break;
}
case SLIDE_RIGHT:
{
// don't move left if we will fall
Vector pos = me->GetAbsOrigin() + (lookAheadRange * right);
if (me->GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (me->GetAbsOrigin().z - ground < StepHeight)
{
me->StrafeRight();
}
}
break;
}
case JUMP:
{
if (me->m_isEnemyVisible)
{
me->Jump();
}
break;
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Perform attack behavior
*/
void AttackState::OnUpdate( CCSBot *me )
{
// can't be stuck while attacking
me->ResetStuckMonitor();
// if we somehow ended up with the C4 or a grenade in our hands, grab our weapon!
CWeaponCSBase *weapon = me->GetActiveCSWeapon();
if (weapon && !CSGameRules()->IsPlayingCoopMission())
{
if (weapon->GetCSWeaponID() == WEAPON_C4 ||
weapon->GetCSWeaponID() == WEAPON_HEGRENADE ||
weapon->GetCSWeaponID() == WEAPON_FLASHBANG ||
weapon->GetCSWeaponID() == WEAPON_SMOKEGRENADE ||
weapon->GetCSWeaponID() == WEAPON_MOLOTOV ||
weapon->GetCSWeaponID() == WEAPON_INCGRENADE ||
weapon->GetCSWeaponID() == WEAPON_DECOY ||
weapon->GetCSWeaponID() == WEAPON_TAGRENADE )
{
me->EquipBestWeapon();
}
}
CBasePlayer *enemy = me->GetBotEnemy();
if (enemy == NULL)
{
StopAttacking( me );
return;
}
Vector myOrigin = GetCentroid( me );
Vector enemyOrigin = GetCentroid( enemy );
// keep track of whether we have seen our enemy at least once yet
if (!m_haveSeenEnemy)
m_haveSeenEnemy = me->IsEnemyVisible();
//
// Retreat check
// Do not retreat if the enemy is too close
//
if (m_retreatTimer.IsElapsed())
{
// If we've been fighting this battle for awhile, we're "pinned down" and
// need to do something else.
// If we are outnumbered, retreat.
// If we see a sniper and we aren't a sniper, retreat.
bool isPinnedDown = (gpGlobals->curtime > m_pinnedDownTimestamp);
if (isPinnedDown ||
(me->CanSeeSniper() && !me->IsSniper()) ||
(me->IsOutnumbered() && m_isCoward) ||
(me->OutnumberedCount() >= 2 && me->GetProfile()->GetAggression() < 1.0f))
{
// only retreat if at least one of them is aiming at me
if (me->IsAnyVisibleEnemyLookingAtMe( CHECK_FOV ))
{
// tell our teammates our plight
if (isPinnedDown)
me->GetChatter()->PinnedDown();
else if (!me->CanSeeSniper())
me->GetChatter()->Scared();
m_retreatTimer.Start( RandomFloat( 3.0f, 15.0f ) );
// try to retreat
if (me->TryToRetreat())
{
// if we are a sniper, equip our pistol so we can fire while retreating
/*
if (me->IsUsingSniperRifle())
{
// wait a moment to allow one last shot
me->Wait( 0.5f );
//me->EquipPistol();
}
*/
// request backup if outnumbered
if (me->IsOutnumbered())
{
me->GetChatter()->NeedBackup();
}
}
else
{
me->PrintIfWatched( "I want to retreat, but no safe spots nearby!\n" );
}
}
}
}
//
// Knife fighting
// We need to pathfind right to the enemy to cut him
//
if (me->IsUsingKnife())
{
// can't crouch and hold with a knife
m_crouchAndHold = false;
me->StandUp();
// if we are using a knife and our prey is looking towards us, run at him
if (me->IsPlayerFacingMe( enemy ))
{
me->ForceRun( 5.0f );
me->Hurry( 10.0f );
}
// slash our victim
me->FireWeaponAtEnemy();
// if toe to toe with our enemy, don't dodge, just slash
if ((enemy->GetAbsOrigin() - me->GetAbsOrigin()).IsLengthGreaterThan( slashRange ))
{
// if our victim has moved, repath
bool repath = false;
if (me->HasPath())
{
if ((me->GetPathEndpoint() - enemy->GetAbsOrigin()).IsLengthGreaterThan( repathRange ))
{
repath = true;
}
}
else
{
repath = true;
}
if (repath && m_repathTimer.IsElapsed())
{
Vector enemyPos = enemy->GetAbsOrigin() + Vector( 0, 0, HalfHumanHeight );
me->ComputePath( enemyPos, FASTEST_ROUTE );
m_repathTimer.Start( repathInterval );
}
// move towards victim
if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CCSBot::PROGRESSING)
{
me->DestroyPath();
}
}
return;
}
//
// Simple shield usage
//
if (me->HasShield())
{
if (me->IsEnemyVisible() && !m_shieldForceOpen)
{
if (!me->IsRecognizedEnemyReloading() && !me->IsReloading() && me->IsPlayerLookingAtMe( enemy ))
{
// close up - enemy is pointing his gun at us
if (!me->IsProtectedByShield())
me->SecondaryAttack();
}
else
{
// enemy looking away or reloading his weapon - open up and shoot him
if (me->IsProtectedByShield())
me->SecondaryAttack();
}
}
else
{
// can't see enemy, open up
if (me->IsProtectedByShield())
me->SecondaryAttack();
}
if (gpGlobals->curtime > m_shieldToggleTimestamp)
{
m_shieldToggleTimestamp = gpGlobals->curtime + RandomFloat( 0.5, 2.0f );
// toggle shield force open
m_shieldForceOpen = !m_shieldForceOpen;
}
}
// check if our weapon range is bad and we should switch to pistol
if (me->IsUsingSniperRifle())
{
// if we have a sniper rifle and our enemy is too close, switch to pistol
if ((enemyOrigin - myOrigin).IsLengthLessThan( sniperMinRange ))
me->EquipPistol();
}
else if (me->IsUsingShotgun())
{
// if we have a shotgun equipped and enemy is too far away, switch to pistol
if ((enemyOrigin - myOrigin).IsLengthGreaterThan( shotgunMaxRange ))
me->EquipPistol();
}
// if we're sniping, look through the scope - need to do this here in case a reload resets our scope
if (me->IsUsingSniperRifle())
{
// for Scouts and AWPs, we need to wait for zoom to resume
if (me->m_bResumeZoom)
{
m_scopeTimestamp = gpGlobals->curtime;
return;
}
Vector toAimSpot3D = me->m_targetSpot - myOrigin;
float targetRange = toAimSpot3D.Length();
// dont adjust zoom level if we're already zoomed in - just fire
if (me->GetZoomLevel() == CCSBot::NO_ZOOM && me->AdjustZoom( targetRange ))
m_scopeTimestamp = gpGlobals->curtime;
const float waitScopeTime = 0.3f + me->GetProfile()->GetReactionTime();
if (gpGlobals->curtime - m_scopeTimestamp < waitScopeTime)
{
// force us to wait until zoomed in before firing
return;
}
}
// see if we "notice" that our prey is dead
if (me->IsAwareOfEnemyDeath())
{
// let team know if we killed the last enemy
if (me->GetLastVictimID() == enemy->entindex() && me->GetNearbyEnemyCount() <= 1)
{
me->GetChatter()->KilledMyEnemy( enemy->entindex() );
// if there are other enemies left, wait a moment - they usually come in groups
if (me->GetEnemiesRemaining())
{
me->Wait( RandomFloat( 1.0f, 3.0f ) );
}
}
StopAttacking( me );
return;
}
float notSeenEnemyTime = gpGlobals->curtime - me->GetLastSawEnemyTimestamp();
// if we haven't seen our enemy for a moment, continue on if we dont want to fight, or decide to ambush if we do
if (!me->IsEnemyVisible())
{
// attend to nearby enemy gunfire
if (notSeenEnemyTime > 0.5f && me->CanHearNearbyEnemyGunfire())
{
// give up the attack, since we didn't want it in the first place
StopAttacking( me );
const Vector *pos = me->GetNoisePosition();
if (pos)
{
me->SetLookAt( "Nearby enemy gunfire", *pos, PRIORITY_HIGH, 0.0f );
me->PrintIfWatched( "Checking nearby threatening enemy gunfire!\n" );
return;
}
}
// check if we have lost track of our enemy during combat
if (notSeenEnemyTime > 0.25f)
{
m_isEnemyHidden = true;
}
if (notSeenEnemyTime > 0.1f)
{
if (me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
{
// decide whether we should hide and "ambush" our enemy
if (m_haveSeenEnemy && !m_didAmbushCheck)
{
float hideChance = 33.3f;
if (RandomFloat( 0.0, 100.0f ) < hideChance)
{
float ambushTime = RandomFloat( 3.0f, 15.0f );
// hide in ambush nearby
/// @todo look towards where we know enemy is
const Vector *spot = FindNearbyRetreatSpot( me, 200.0f );
if (spot)
{
me->IgnoreEnemies( 1.0f );
me->Run();
me->StandUp();
me->Hide( *spot, ambushTime, true );
return;
}
}
// don't check again
m_didAmbushCheck = true;
}
}
else
{
// give up the attack, since we didn't want it in the first place
StopAttacking( me );
return;
}
}
}
else
{
// we can see the enemy again - reset our ambush check
m_didAmbushCheck = false;
// if the enemy is coming out of hiding, we need time to react
if (m_isEnemyHidden)
{
m_reacquireTimestamp = gpGlobals->curtime + me->GetProfile()->GetReactionTime();
m_isEnemyHidden = false;
}
}
// if we haven't seen our enemy for a long time, chase after them
float chaseTime = 2.0f + 2.0f * (1.0f - me->GetProfile()->GetAggression());
// if we are sniping, be very patient
if (me->IsUsingSniperRifle())
chaseTime += 3.0f;
else if (me->IsCrouching()) // if we are crouching, be a little patient
chaseTime += 1.0f;
// if we can't see the enemy, and have either seen him but currently lost sight of him,
// or haven't yet seen him, chase after him (unless we are a sniper)
if (!me->IsEnemyVisible() && (notSeenEnemyTime > chaseTime || !m_haveSeenEnemy))
{
// snipers don't chase their prey - they wait for their prey to come to them
if ( CSGameRules()->IsPlayingCoopGuardian() == false && me->GetTask() == CCSBot::SNIPING )
{
StopAttacking( me );
return;
}
else
{
// move to last known position of enemy
me->SetTask( CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION, enemy );
me->MoveTo( me->GetLastKnownEnemyPosition() );
return;
}
}
// if we can't see our enemy at the moment, and were shot by
// a different visible enemy, engage them instead
const float hurtRecentlyTime = 3.0f;
if (!me->IsEnemyVisible() &&
me->GetTimeSinceAttacked() < hurtRecentlyTime &&
me->GetAttacker() &&
me->GetAttacker() != me->GetBotEnemy())
{
// if we can see them, attack, otherwise panic
if (me->IsVisible( me->GetAttacker(), CHECK_FOV ))
{
me->Attack( me->GetAttacker() );
me->PrintIfWatched( "Switching targets to retaliate against new attacker!\n" );
}
/*
* Rethink this
else
{
me->Panic( me->GetAttacker() );
me->PrintIfWatched( "Panicking from crossfire while attacking!\n" );
}
*/
return;
}
if (true || gpGlobals->curtime > m_reacquireTimestamp)
me->FireWeaponAtEnemy();
// do dodge behavior
Dodge( me );
if ( me->HasHeavyArmor() && me->GetBotEnemy() && me->IsEnemyVisible() )
{
me->MoveForward();
me->GetChatter()->DoPhoenixHeavyWakeTaunt();
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Finish attack
*/
void AttackState::OnExit( CCSBot *me )
{
me->PrintIfWatched( "AttackState:OnExit()\n" );
m_crouchAndHold = false;
// clear any noises we heard during battle
me->ForgetNoise();
me->ResetStuckMonitor();
// resume our original posture
me->PopPostureContext();
// put shield away
if (me->IsProtectedByShield())
me->SecondaryAttack();
//me->StopAiming();
}

View File

@@ -0,0 +1,703 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_gamerules.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
ConVar bot_loadout( "bot_loadout", "", FCVAR_CHEAT, "bots are given these items at round start" );
ConVar bot_randombuy( "bot_randombuy", "0", FCVAR_CHEAT, "should bots ignore their prefered weapons and just buy weapons at random?" );
ConVar bot_gungameselect_weapons_t( "bot_gungameselect_weapons_t", "deagle awp p90 ak47 sg556", 0, "the list of weapons that T bots start with in gun game select" );
ConVar bot_gungameselect_weapons_ct( "bot_gungameselect_weapons_ct", "deagle awp p90 aug m4a1", 0, "the list of weapons that CT bots start with in gun game select" );
ConVar sv_bot_buy_grenade_chance( "sv_bot_buy_grenade_chance", "33", FCVAR_RELEASE | FCVAR_GAMEDLL, "Chance bots will buy a grenade with leftover money (after prim, sec and armor). Input as percent (0-100.0)", true, 0.0f, true, 100.0f );
ConVar sv_bot_buy_smoke_weight ( "sv_bot_buy_smoke_weight", "1", FCVAR_RELEASE | FCVAR_GAMEDLL, "Given a bot will buy a grenade, controls the odds of the grenade type. Proportional to all other sv_bot_buy_*_weight convars.", true, 0.0f, false, 0.0f );
ConVar sv_bot_buy_flash_weight ( "sv_bot_buy_flash_weight", "1", FCVAR_RELEASE | FCVAR_GAMEDLL, "Given a bot will buy a grenade, controls the odds of the grenade type. Proportional to all other sv_bot_buy_*_weight convars.", true, 0.0f, false, 0.0f );
ConVar sv_bot_buy_decoy_weight ( "sv_bot_buy_decoy_weight", "1", FCVAR_RELEASE | FCVAR_GAMEDLL, "Given a bot will buy a grenade, controls the odds of the grenade type. Proportional to all other sv_bot_buy_*_weight convars.", true, 0.0f, false, 0.0f );
ConVar sv_bot_buy_molotov_weight ( "sv_bot_buy_molotov_weight", "1", FCVAR_RELEASE | FCVAR_GAMEDLL, "Given a bot will buy a grenade, controls the odds of the grenade type. Proportional to all other sv_bot_buy_*_weight convars.", true, 0.0f, false, 0.0f );
ConVar sv_bot_buy_hegrenade_weight ( "sv_bot_buy_hegrenade_weight", "6", FCVAR_RELEASE | FCVAR_GAMEDLL, "Given a bot will buy a grenade, controls the odds of the grenade type. Proportional to all other sv_bot_buy_*_weight convars.", true, 0.0f, false, 0.0f );
struct { const ConVar *cv; const char* szName; } g_GrenadeWeights[] =
{
{ &sv_bot_buy_smoke_weight, "smokegrenade" },
{ &sv_bot_buy_flash_weight, "flashbang" },
{ &sv_bot_buy_decoy_weight, "decoy" },
{ &sv_bot_buy_molotov_weight, "molotov" },
{ &sv_bot_buy_hegrenade_weight, "hegrenade" },
};
// Pick a grenade for bots to buy based on set weights
const char* Helper_PickBotGrenade()
{
int iNumGrenades = ARRAYSIZE( g_GrenadeWeights );
float flGrenadeWeight = 0.0f;
for ( int i = 0; i < iNumGrenades; ++i )
flGrenadeWeight += g_GrenadeWeights[ i ].cv->GetFloat();
float flRand = RandomFloat( 0.0f + FLT_EPSILON , flGrenadeWeight );
float flAccumulator = 0.0f;
for ( int i = 0; i < iNumGrenades; ++i )
{
flAccumulator += g_GrenadeWeights[ i ].cv->GetFloat();
if ( flRand <= flAccumulator )
{
return g_GrenadeWeights[ i ].szName;
}
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Debug command to give a named weapon
*/
void CCSBot::GiveWeapon( const char *weaponAlias )
{
const char *translatedAlias = GetTranslatedWeaponAlias( weaponAlias );
char wpnName[128];
Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias );
WEAPON_FILE_INFO_HANDLE hWpnInfo = LookupWeaponInfoSlot( wpnName );
if ( hWpnInfo == GetInvalidWeaponInfoHandle() )
{
return;
}
CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
if ( !pWeaponInfo )
{
return;
}
if ( !Weapon_OwnsThisType( wpnName ) )
{
CBaseCombatWeapon *pWeapon = Weapon_GetSlot( pWeaponInfo->iSlot );
if ( pWeapon )
{
if ( pWeaponInfo->iSlot == WEAPON_SLOT_PISTOL )
{
DropWeaponSlot( WEAPON_SLOT_PISTOL );
}
else if ( pWeaponInfo->iSlot == WEAPON_SLOT_RIFLE )
{
DropWeaponSlot( WEAPON_SLOT_RIFLE );
}
}
}
GiveNamedItem( wpnName );
}
//--------------------------------------------------------------------------------------------------------------
static bool HasDefaultPistol( CCSBot *me )
{
CWeaponCSBase *pistol = (CWeaponCSBase *)me->Weapon_GetSlot( WEAPON_SLOT_PISTOL );
if (pistol == NULL)
return false;
if (me->GetTeamNumber() == TEAM_TERRORIST && pistol->IsA( WEAPON_GLOCK ))
return true;
if (me->GetTeamNumber() == TEAM_CT && pistol->IsA( WEAPON_HKP2000 ))
return true;
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Buy weapons, armor, etc.
*/
void BuyState::OnEnter( CCSBot *me )
{
m_retries = 0;
m_prefRetries = 0;
m_prefIndex = 0;
const char *cheatWeaponString = bot_loadout.GetString();
if ( cheatWeaponString && *cheatWeaponString )
{
m_doneBuying = false; // we're going to be given weapons - ignore the eco limit
}
else
{
// check if we are saving money for the next round
if (me->GetAccountBalance() < cv_bot_eco_limit.GetFloat())
{
me->PrintIfWatched( "Saving money for next round.\n" );
m_doneBuying = true;
}
else
{
m_doneBuying = false;
}
}
m_isInitialDelay = true;
// this will force us to stop holding live grenade
me->EquipBestWeapon( MUST_EQUIP );
m_buyShield = false;
if (me->GetTeamNumber() == TEAM_CT)
{
// determine if we want a tactical shield
if (!me->HasPrimaryWeapon() && TheCSBots()->AllowTacticalShield())
{
if (me->GetAccountBalance() > 2500)
{
if (me->GetAccountBalance() < 4000)
m_buyShield = (RandomFloat( 0, 100.0f ) < 33.3f) ? true : false;
else
m_buyShield = (RandomFloat( 0, 100.0f ) < 10.0f) ? true : false;
}
}
}
if (TheCSBots()->AllowGrenades())
{
m_buyGrenade = (RandomFloat( 0.0f, 100.0f ) < sv_bot_buy_grenade_chance.GetFloat() ) ? true : false;
}
else
{
m_buyGrenade = false;
}
m_buyPistol = false;
if (TheCSBots()->AllowPistols())
{
// check if we have a pistol
if (me->Weapon_GetSlot( WEAPON_SLOT_PISTOL ))
{
// if we have our default pistol, think about buying a different one
if (HasDefaultPistol( me ))
{
// if everything other than pistols is disallowed, buy a pistol
if (TheCSBots()->AllowShotguns() == false &&
TheCSBots()->AllowSubMachineGuns() == false &&
TheCSBots()->AllowRifles() == false &&
TheCSBots()->AllowMachineGuns() == false &&
TheCSBots()->AllowTacticalShield() == false &&
TheCSBots()->AllowSnipers() == false)
{
m_buyPistol = (RandomFloat( 0, 100 ) < 75.0f);
}
else if (me->GetAccountBalance() < 1000)
{
// if we're low on cash, buy a pistol
m_buyPistol = (RandomFloat( 0, 100 ) < 75.0f);
}
else
{
m_buyPistol = (RandomFloat( 0, 100 ) < 33.3f);
}
}
}
else
{
// we dont have a pistol - buy one
m_buyPistol = true;
}
}
}
enum WeaponType
{
PISTOL,
SHOTGUN,
SUB_MACHINE_GUN,
RIFLE,
MACHINE_GUN,
SNIPER_RIFLE,
GRENADE,
NUM_WEAPON_TYPES,
INVALID_WEAPON_TYPE = NUM_WEAPON_TYPES
};
struct BuyInfo
{
WeaponType type;
bool preferred; ///< more challenging bots prefer these weapons
char *buyAlias; ///< the buy alias for this equipment
};
#define PRIMARY_WEAPON_BUY_COUNT 16
#define SECONDARY_WEAPON_BUY_COUNT 3
/**
* These tables MUST be kept in sync with the CT and T buy aliases
*/
static BuyInfo primaryWeaponBuyInfoCT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "nova" },
{ SHOTGUN, false, "xm1014" },
{ SHOTGUN, false, "mag7" },
{ SUB_MACHINE_GUN, false, "mp9" },
{ SUB_MACHINE_GUN, false, "bizon" },
{ SUB_MACHINE_GUN, false, "mp7" },
{ SUB_MACHINE_GUN, false, "ump45" },
{ SUB_MACHINE_GUN, false, "p90" },
{ RIFLE, true, "famas" },
{ SNIPER_RIFLE, false, "ssg08" },
{ RIFLE, true, "m4a1" },
{ RIFLE, true, "aug" },
{ SNIPER_RIFLE, true, "scar20" },
{ SNIPER_RIFLE, true, "awp" },
{ MACHINE_GUN, false, "m249" },
{ MACHINE_GUN, false, "negev" }
};
static BuyInfo secondaryWeaponBuyInfoCT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p250" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "fiveseven" }
};
static BuyInfo primaryWeaponBuyInfoT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "nova" },
{ SHOTGUN, false, "xm1014" },
{ SHOTGUN, false, "sawedoff" },
{ SUB_MACHINE_GUN, false, "mac10" },
{ SUB_MACHINE_GUN, false, "ump45" },
{ SUB_MACHINE_GUN, false, "p90" },
{ RIFLE, true, "galilar" },
{ RIFLE, true, "ak47" },
{ SNIPER_RIFLE, false, "ssg08" },
{ RIFLE, true, "sg556" },
{ SNIPER_RIFLE, true, "awp" },
{ SNIPER_RIFLE, true, "g3sg1" },
{ MACHINE_GUN, false, "m249" },
{ MACHINE_GUN, false, "negev" },
{ INVALID_WEAPON_TYPE, false, "" },
{ INVALID_WEAPON_TYPE, false, "" }
};
static BuyInfo secondaryWeaponBuyInfoT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p250" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "elites" }
};
/**
* Given a weapon alias, return the kind of weapon it is
*/
inline WeaponType GetWeaponType( const char *alias )
{
int i;
for( i=0; i<PRIMARY_WEAPON_BUY_COUNT; ++i )
{
if (!stricmp( alias, primaryWeaponBuyInfoCT[i].buyAlias ))
return primaryWeaponBuyInfoCT[i].type;
if (!stricmp( alias, primaryWeaponBuyInfoT[i].buyAlias ))
return primaryWeaponBuyInfoT[i].type;
}
for( i=0; i<SECONDARY_WEAPON_BUY_COUNT; ++i )
{
if (!stricmp( alias, secondaryWeaponBuyInfoCT[i].buyAlias ))
return secondaryWeaponBuyInfoCT[i].type;
if (!stricmp( alias, secondaryWeaponBuyInfoT[i].buyAlias ))
return secondaryWeaponBuyInfoT[i].type;
}
return INVALID_WEAPON_TYPE;
}
//--------------------------------------------------------------------------------------------------------------
void BuyState::OnUpdate( CCSBot *me )
{
char cmdBuffer[256];
// wait for a Navigation Mesh
if (!TheNavMesh->IsLoaded())
return;
// apparently we cant buy things in the first few seconds, so wait a bit
if (m_isInitialDelay)
{
const float waitToBuyTime = 0.25f;
if (gpGlobals->curtime - me->GetStateTimestamp() < waitToBuyTime)
return;
m_isInitialDelay = false;
}
// if we're done buying and still in the freeze period, wait
if (m_doneBuying)
{
if (CSGameRules()->IsMultiplayer() && CSGameRules()->IsFreezePeriod())
{
// make sure we're locked and loaded
me->EquipBestWeapon( MUST_EQUIP );
me->Reload();
me->ResetStuckMonitor();
return;
}
me->Idle();
return;
}
// If we're supposed to buy a specific weapon for debugging, do so and then bail
const char *cheatWeaponString = bot_loadout.GetString();
if ( cheatWeaponString && *cheatWeaponString )
{
CSplitString loadout( cheatWeaponString, " " );
for ( int i=0; i<loadout.Count(); ++i )
{
const char *item = loadout[i];
if ( FStrEq( item, "vest" ) )
{
me->GiveNamedItem( "item_kevlar" );
}
else if ( FStrEq( item, "vesthelm" ) )
{
me->GiveNamedItem( "item_assaultsuit" );
}
else if ( FStrEq( item, "defuser" ) )
{
if ( me->GetTeamNumber() == TEAM_CT )
{
me->GiveDefuser();
}
}
else if ( FStrEq( item, "nvgs" ) )
{
me->m_bHasNightVision = true;
}
else if ( FStrEq( item, "primammo" ) )
{
me->AttemptToBuyAmmo( 0 );
}
else if ( FStrEq( item, "secammo" ) )
{
me->AttemptToBuyAmmo( 1 );
}
else
{
me->GiveWeapon( item );
}
}
m_doneBuying = true;
return;
}
if (!me->IsInBuyZone())
{
m_doneBuying = true;
CONSOLE_ECHO( "%s bot spawned outside of a buy zone (%d, %d, %d)\n",
(me->GetTeamNumber() == TEAM_CT) ? "CT" : "Terrorist",
(int)me->GetAbsOrigin().x,
(int)me->GetAbsOrigin().y,
(int)me->GetAbsOrigin().z );
return;
}
// try to buy some weapons
const float buyInterval = 0.02f;
if (gpGlobals->curtime - me->GetStateTimestamp() > buyInterval)
{
me->m_stateTimestamp = gpGlobals->curtime;
bool isPreferredAllDisallowed = true;
// try to buy our preferred weapons first
if (m_prefIndex < me->GetProfile()->GetWeaponPreferenceCount() && bot_randombuy.GetBool() == false )
{
// need to retry because sometimes first buy fails??
const int maxPrefRetries = 2;
if (m_prefRetries >= maxPrefRetries)
{
// try to buy next preferred weapon
++m_prefIndex;
m_prefRetries = 0;
return;
}
int weaponPreference = me->GetProfile()->GetWeaponPreference( m_prefIndex );
// don't buy it again if we still have one from last round
char weaponPreferenceName[32];
Q_snprintf( weaponPreferenceName, sizeof(weaponPreferenceName), "weapon_%s", me->GetProfile()->GetWeaponPreferenceAsString( m_prefIndex ) );
if( me->Weapon_OwnsThisType(weaponPreferenceName) )//Prefs and buyalias use the short version, this uses the long
{
// done with buying preferred weapon
m_prefIndex = 9999;
return;
}
const char *buyAlias = NULL;
buyAlias = WeaponIDToAlias( weaponPreference );
WeaponType type = GetWeaponType( buyAlias );
switch( type )
{
case PISTOL:
if (!TheCSBots()->AllowPistols())
buyAlias = NULL;
break;
case SHOTGUN:
if (!TheCSBots()->AllowShotguns())
buyAlias = NULL;
break;
case SUB_MACHINE_GUN:
if (!TheCSBots()->AllowSubMachineGuns())
buyAlias = NULL;
break;
case RIFLE:
if (!TheCSBots()->AllowRifles())
buyAlias = NULL;
break;
case MACHINE_GUN:
if (!TheCSBots()->AllowMachineGuns())
buyAlias = NULL;
break;
case SNIPER_RIFLE:
if (!TheCSBots()->AllowSnipers())
buyAlias = NULL;
break;
}
if (buyAlias)
{
Q_snprintf( cmdBuffer, 256, "buy %s\n", buyAlias );
CCommand args;
args.Tokenize( cmdBuffer );
me->ClientCommand( args );
me->PrintIfWatched( "Tried to buy preferred weapon %s.\n", buyAlias );
isPreferredAllDisallowed = false;
}
++m_prefRetries;
#if 0 // Actually, DO waste money on other equipment...
// bail out so we dont waste money on other equipment
// unless everything we prefer has been disallowed, then buy at random
if (isPreferredAllDisallowed == false)
return;
#endif
}
// if we have no preferred primary weapon (or everything we want is disallowed), buy at random
if (!me->HasPrimaryWeapon() && (isPreferredAllDisallowed || !me->GetProfile()->HasPrimaryPreference()))
{
if (m_buyShield)
{
// buy a shield
CCommand args;
args.Tokenize( "buy shield" );
me->ClientCommand( args );
me->PrintIfWatched( "Tried to buy a shield.\n" );
}
else
{
// build list of allowable weapons to buy
BuyInfo *masterPrimary = (me->GetTeamNumber() == TEAM_TERRORIST) ? primaryWeaponBuyInfoT : primaryWeaponBuyInfoCT;
BuyInfo *stockPrimary[ PRIMARY_WEAPON_BUY_COUNT ];
int stockPrimaryCount = 0;
// dont choose sniper rifles as often
const float sniperRifleChance = 50.0f;
bool wantSniper = (RandomFloat( 0, 100 ) < sniperRifleChance) ? true : false;
if ( bot_randombuy.GetBool() )
{
wantSniper = true;
}
for( int i=0; i<PRIMARY_WEAPON_BUY_COUNT; ++i )
{
if ((masterPrimary[i].type == SHOTGUN && TheCSBots()->AllowShotguns()) ||
(masterPrimary[i].type == SUB_MACHINE_GUN && TheCSBots()->AllowSubMachineGuns()) ||
(masterPrimary[i].type == RIFLE && TheCSBots()->AllowRifles()) ||
(masterPrimary[i].type == SNIPER_RIFLE && TheCSBots()->AllowSnipers() && wantSniper) ||
(masterPrimary[i].type == MACHINE_GUN && TheCSBots()->AllowMachineGuns()))
{
stockPrimary[ stockPrimaryCount++ ] = &masterPrimary[i];
}
}
if (stockPrimaryCount)
{
// buy primary weapon if we don't have one
int which;
// on hard difficulty levels, bots try to buy preferred weapons on the first pass
if (m_retries == 0 && TheCSBots()->GetDifficultyLevel() >= BOT_HARD && bot_randombuy.GetBool() == false )
{
// count up available preferred weapons
int prefCount = 0;
for( which=0; which<stockPrimaryCount; ++which )
if (stockPrimary[which]->preferred)
++prefCount;
if (prefCount)
{
int whichPref = RandomInt( 0, prefCount-1 );
for( which=0; which<stockPrimaryCount; ++which )
if (stockPrimary[which]->preferred && whichPref-- == 0)
break;
}
else
{
// no preferred weapons available, just pick randomly
which = RandomInt( 0, stockPrimaryCount-1 );
}
}
else
{
which = RandomInt( 0, stockPrimaryCount-1 );
}
Q_snprintf( cmdBuffer, 256, "buy %s\n", stockPrimary[ which ]->buyAlias );
CCommand args;
args.Tokenize( cmdBuffer );
me->ClientCommand( args );
me->PrintIfWatched( "Tried to buy %s.\n", stockPrimary[ which ]->buyAlias );
}
}
}
//
// If we now have a weapon, or have tried for too long, we're done
//
if (me->HasPrimaryWeapon() || m_retries++ > 5)
{
// primary ammo
CCommand args;
if (me->HasPrimaryWeapon())
{
args.Tokenize( "buy primammo" );
me->ClientCommand( args );
}
// buy armor last, to make sure we bought a weapon first
args.Tokenize( "buy vesthelm" );
me->ClientCommand( args );
args.Tokenize( "buy vest" );
me->ClientCommand( args );
// pistols - if we have no preferred pistol, buy at random
if (TheCSBots()->AllowPistols() && !me->GetProfile()->HasPistolPreference())
{
if (m_buyPistol)
{
int which = RandomInt( 0, SECONDARY_WEAPON_BUY_COUNT-1 );
const char *what = NULL;
if (me->GetTeamNumber() == TEAM_TERRORIST)
what = secondaryWeaponBuyInfoT[ which ].buyAlias;
else
what = secondaryWeaponBuyInfoCT[ which ].buyAlias;
Q_snprintf( cmdBuffer, 256, "buy %s\n", what );
args.Tokenize( cmdBuffer );
me->ClientCommand( args );
// only buy one pistol
m_buyPistol = false;
}
// make sure we have enough pistol ammo
args.Tokenize( "buy secammo" );
me->ClientCommand( args );
}
// buy a grenade if we wish, and we don't already have one
if (m_buyGrenade && !me->HasGrenade())
{
const char *szGrenade = Helper_PickBotGrenade();
if ( szGrenade )
args.Tokenize( CFmtStr( "buy %s", szGrenade ).Access() );
/*
if (rnd < 10)//10% chance
{
args.Tokenize( "buy smokegrenade" );
}
else if (rnd < 20)//10% chance
{
// only allow Flashbangs if everyone on the team is a bot (dont want to blind our friendly humans)
if (UTIL_IsTeamAllBots( me->GetTeamNumber() ))
{
args.Tokenize( "buy flashbang" );
}
else
{
args.Tokenize( "buy hegrenade" );
}
}
else if (rnd < 30)//10% chance
{
args.Tokenize( "buy decoy" );
}
else if (rnd < 40)//10% chance
{
args.Tokenize( "buy molotov" );
}
else//60%-70% chance (because of the flashbang case for full team of bots).
{
args.Tokenize( "buy hegrenade" );
}
*/
me->ClientCommand( args );
}
m_doneBuying = true;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void BuyState::OnExit( CCSBot *me )
{
me->ResetStuckMonitor();
me->EquipBestWeapon();
}

View File

@@ -0,0 +1,83 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Begin defusing the bomb
*/
void DefuseBombState::OnEnter( CCSBot *me )
{
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->GetChatter()->Say( "DefusingBomb" );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Defuse the bomb
*/
void DefuseBombState::OnUpdate( CCSBot *me )
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
// stay in SELF_DEFENSE so we get to the bomb in time!
me->SetDisposition( CCSBot::SELF_DEFENSE );
if (bombPos == NULL)
{
me->PrintIfWatched( "In Defuse state, but don't know where the bomb is!\n" );
me->Idle();
return;
}
// look at the bomb
me->SetLookAt( "Defuse bomb", *bombPos, PRIORITY_HIGH );
// defuse...
me->UseEnvironment();
if (gpGlobals->curtime - me->GetStateTimestamp() > 1.0f)
{
// if we missed starting the defuse, give up
if (TheCSBots()->GetBombDefuser() == NULL)
{
me->PrintIfWatched( "Failed to start defuse, giving up\n" );
me->Idle();
return;
}
else if (TheCSBots()->GetBombDefuser() != me)
{
// if someone else got the defuse, give up
me->PrintIfWatched( "Someone else started defusing, giving up\n" );
me->Idle();
return;
}
}
// if bomb has been defused, give up
if (!TheCSBots()->IsBombPlanted())
{
me->Idle();
return;
}
}
//--------------------------------------------------------------------------------------------------------------
void DefuseBombState::OnExit( CCSBot *me )
{
me->ResetStuckMonitor();
me->SetTask( CCSBot::SEEK_AND_DESTROY );
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->ClearLookAt();
}

View File

@@ -0,0 +1,67 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from the bomb.
*/
void EscapeFromBombState::OnEnter( CCSBot *me )
{
me->StandUp();
me->Run();
me->DestroyPath();
me->EquipKnife();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from the bomb.
*/
void EscapeFromBombState::OnUpdate( CCSBot *me )
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
// if we don't know where the bomb is, we shouldn't be in this state
if (bombPos == NULL)
{
me->Idle();
return;
}
// grab our knife to move quickly
me->EquipKnife();
// look around
me->UpdateLookAround();
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// we have no path, or reached the end of one - create a new path far away from the bomb
FarAwayFromPositionFunctor func( *bombPos );
CNavArea *goalArea = FindMinimumCostArea( me->GetLastKnownArea(), func );
// if this fails, we'll try again next time
me->ComputePath( goalArea->GetCenter(), FASTEST_ROUTE );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from the bomb.
*/
void EscapeFromBombState::OnExit( CCSBot *me )
{
me->EquipBestWeapon();
}

View File

@@ -0,0 +1,117 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Escape from flames we're standing in!
*/
void EscapeFromFlamesState::OnEnter( CCSBot *me )
{
me->StandUp();
me->Run();
me->StopWaiting();
me->DestroyPath();
me->EquipKnife();
m_safeArea = NULL;
m_searchTimer.Invalidate();
}
//--------------------------------------------------------------------------------------------------------------
class CNonDamagingScan : public ISearchSurroundingAreasFunctor
{
public:
CNonDamagingScan( void )
{
m_safeArea = NULL;
m_safeAreaTravelRange = FLT_MAX;
}
virtual ~CNonDamagingScan() { }
virtual bool operator() ( CNavArea *area, CNavArea *priorArea, float travelDistanceSoFar )
{
const float maxSearchRange = 2000.0f;
if ( travelDistanceSoFar < maxSearchRange )
{
if ( !area->IsDamaging() && travelDistanceSoFar < m_safeAreaTravelRange )
{
m_safeArea = area;
}
}
return true;
}
CNavArea *m_safeArea;
float m_safeAreaTravelRange;
};
//--------------------------------------------------------------------------------------------------------------
CNavArea *EscapeFromFlamesState::FindNearestNonDamagingArea( CCSBot *me ) const
{
CNavArea *myArea = me->GetLastKnownArea();
if ( !myArea )
{
return NULL;
}
CNonDamagingScan scan;
SearchSurroundingAreas( myArea, scan );
return scan.m_safeArea;
}
//--------------------------------------------------------------------------------------------------------------
void EscapeFromFlamesState::OnUpdate( CCSBot *me )
{
// default behavior if we're safe
if ( me->GetTimeSinceBurnedByFlames() > 1.5f )
{
me->Idle();
return;
}
if ( m_searchTimer.IsElapsed() )
{
// because flames grow and change, periodically re-search
m_searchTimer.Start( RandomFloat( 0.5f, 1.0f ) );
m_safeArea = FindNearestNonDamagingArea( me );
}
// look around
me->UpdateLookAround();
me->EquipBestWeapon();
me->FireWeaponAtEnemy();
if ( me->UpdatePathMovement() != CCSBot::PROGRESSING )
{
if ( m_safeArea )
{
me->ComputePath( m_safeArea->GetCenter(), FASTEST_ROUTE );
}
}
}
//--------------------------------------------------------------------------------------------------------------
void EscapeFromFlamesState::OnExit( CCSBot *me )
{
me->EquipBestWeapon();
}

View File

@@ -0,0 +1,69 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Move to the bomb on the floor and pick it up
*/
void FetchBombState::OnEnter( CCSBot *me )
{
me->DestroyPath();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to the bomb on the floor and pick it up
*/
void FetchBombState::OnUpdate( CCSBot *me )
{
if (me->HasC4())
{
me->PrintIfWatched( "I picked up the bomb\n" );
me->Idle();
return;
}
CBaseEntity *bomb = TheCSBots()->GetLooseBomb();
if (bomb)
{
if (!me->HasPath())
{
// build a path to the bomb
if (me->ComputePath( bomb->GetAbsOrigin() ) == false)
{
me->PrintIfWatched( "Fetch bomb pathfind failed\n" );
// go Hunt instead of Idle to prevent continuous re-pathing to inaccessible bomb
me->Hunt();
return;
}
}
}
else
{
// someone picked up the bomb
me->PrintIfWatched( "Someone else picked up the bomb.\n" );
me->Idle();
return;
}
// look around
me->UpdateLookAround();
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
me->Idle();
}

View File

@@ -0,0 +1,366 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Follow our leader
*/
void FollowState::OnEnter( CCSBot *me )
{
me->StandUp();
me->Run();
me->DestroyPath();
m_isStopped = false;
m_stoppedTimestamp = 0.0f;
// to force immediate repath
m_lastLeaderPos.x = -99999999.9f;
m_lastLeaderPos.y = -99999999.9f;
m_lastLeaderPos.z = -99999999.9f;
m_lastSawLeaderTime = 0;
// set re-pathing frequency
m_repathInterval.Invalidate();
m_isSneaking = false;
m_walkTime.Invalidate();
m_isAtWalkSpeed = false;
m_leaderMotionState = INVALID;
m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine the leader's motion state by tracking his speed
*/
void FollowState::ComputeLeaderMotionState( float leaderSpeed )
{
// walk = 130, run = 250
const float runWalkThreshold = 140.0f;
const float walkStopThreshold = 10.0f; // 120.0f;
LeaderMotionStateType prevState = m_leaderMotionState;
if (leaderSpeed > runWalkThreshold)
{
m_leaderMotionState = RUNNING;
m_isAtWalkSpeed = false;
}
else if (leaderSpeed > walkStopThreshold)
{
// track when began to walk
if (!m_isAtWalkSpeed)
{
m_walkTime.Start();
m_isAtWalkSpeed = true;
}
const float minWalkTime = 0.25f;
if (m_walkTime.GetElapsedTime() > minWalkTime)
{
m_leaderMotionState = WALKING;
}
}
else
{
m_leaderMotionState = STOPPED;
m_isAtWalkSpeed = false;
}
// track time spent in this motion state
if (prevState != m_leaderMotionState)
{
m_leaderMotionStateTime.Start();
m_waitTime = RandomFloat( 1.0f, 3.0f );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Functor to collect all areas in the forward direction of the given player within a radius
*/
class FollowTargetCollector
{
public:
FollowTargetCollector( CBasePlayer *player )
{
m_player = player;
Vector playerVel = player->GetAbsVelocity();
m_forward.x = playerVel.x;
m_forward.y = playerVel.y;
float speed = m_forward.NormalizeInPlace();
Vector playerOrigin = GetCentroid( player );
const float walkSpeed = 100.0f;
if (speed < walkSpeed)
{
m_cutoff.x = playerOrigin.x;
m_cutoff.y = playerOrigin.y;
m_forward.x = 0.0f;
m_forward.y = 0.0f;
}
else
{
const float k = 1.5f; // 2.0f;
float trimSpeed = MIN( speed, 200.0f );
m_cutoff.x = playerOrigin.x + k * trimSpeed * m_forward.x;
m_cutoff.y = playerOrigin.y + k * trimSpeed * m_forward.y;
}
m_targetAreaCount = 0;
}
enum { MAX_TARGET_AREAS = 128 };
bool operator() ( CNavArea *area )
{
if (m_targetAreaCount >= MAX_TARGET_AREAS)
return false;
// only use two-way connections
if (!area->GetParent() || area->IsConnected( area->GetParent(), NUM_DIRECTIONS ))
{
if (m_forward.IsZero())
{
m_targetArea[ m_targetAreaCount++ ] = area;
}
else
{
// collect areas in the direction of the player's forward motion
Vector2D to( area->GetCenter().x - m_cutoff.x, area->GetCenter().y - m_cutoff.y );
to.NormalizeInPlace();
//if (DotProduct( to, m_forward ) > 0.7071f)
if ((to.x * m_forward.x + to.y * m_forward.y) > 0.7071f)
m_targetArea[ m_targetAreaCount++ ] = area;
}
}
return (m_targetAreaCount < MAX_TARGET_AREAS);
}
CBasePlayer *m_player;
Vector2D m_forward;
Vector2D m_cutoff;
CNavArea *m_targetArea[ MAX_TARGET_AREAS ];
int m_targetAreaCount;
};
//--------------------------------------------------------------------------------------------------------------
/**
* Follow our leader
* @todo Clean up this nasty mess
*/
void FollowState::OnUpdate( CCSBot *me )
{
// if we lost our leader, give up
if (m_leader == NULL || !m_leader->IsAlive())
{
me->Idle();
return;
}
// if we are carrying the bomb and at a bombsite, plant
if (me->HasC4() && me->IsAtBombsite())
{
// plant it
me->SetTask( CCSBot::PLANT_BOMB );
me->PlantBomb();
// radio to the team
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
return;
}
// look around
me->UpdateLookAround();
// if we are moving, we are not idle
if (me->IsNotMoving() == false)
m_idleTimer.Start( RandomFloat( 2.0f, 5.0f ) );
// compute the leader's speed
Vector leaderVel = m_leader->GetAbsVelocity();
float leaderSpeed = Vector2D( leaderVel.x, leaderVel.y ).Length();
// determine our leader's movement state
ComputeLeaderMotionState( leaderSpeed );
// track whether we can see the leader
bool isLeaderVisible;
Vector leaderOrigin = GetCentroid( m_leader );
if (me->IsVisible( leaderOrigin ))
{
m_lastSawLeaderTime = gpGlobals->curtime;
isLeaderVisible = true;
}
else
{
isLeaderVisible = false;
}
// determine whether we should sneak or not
const float farAwayRange = 750.0f;
Vector myOrigin = GetCentroid( me );
if ((leaderOrigin - myOrigin).IsLengthGreaterThan( farAwayRange ))
{
// far away from leader - run to catch up
m_isSneaking = false;
}
else if (isLeaderVisible)
{
// if we see leader walking and we are nearby, walk
if (m_leaderMotionState == WALKING)
m_isSneaking = true;
// if we are sneaking and our leader starts running, stop sneaking
if (m_isSneaking && m_leaderMotionState == RUNNING)
m_isSneaking = false;
}
// if we haven't seen the leader for a long time, run
const float longTime = 20.0f;
if (gpGlobals->curtime - m_lastSawLeaderTime > longTime)
m_isSneaking = false;
if (m_isSneaking)
me->Walk();
else
me->Run();
bool repath = false;
// if the leader has stopped, hide nearby
const float nearLeaderRange = 250.0f;
if (!me->HasPath() && m_leaderMotionState == STOPPED && m_leaderMotionStateTime.GetElapsedTime() > m_waitTime)
{
// throttle how often this check occurs
m_waitTime += RandomFloat( 1.0f, 3.0f );
// the leader has stopped - if we are close to him, take up a hiding spot
if ((leaderOrigin - myOrigin).IsLengthLessThan( nearLeaderRange ))
{
const float hideRange = 250.0f;
if (me->TryToHide( NULL, -1.0f, hideRange, false, USE_NEAREST ))
{
me->ResetStuckMonitor();
return;
}
}
}
// if we have been idle for awhile, move
if (m_idleTimer.IsElapsed())
{
repath = true;
// always walk when we move such a short distance
m_isSneaking = true;
}
// if our leader has moved, repath (don't repath if leading is stopping)
if (leaderSpeed > 100.0f && m_leaderMotionState != STOPPED)
{
repath = true;
}
// move along our path
if (me->UpdatePathMovement( NO_SPEED_CHANGE ) != CCSBot::PROGRESSING)
{
me->DestroyPath();
}
// recompute our path if necessary
if (repath && m_repathInterval.IsElapsed() && !me->IsOnLadder())
{
// recompute our path to keep us near our leader
m_lastLeaderPos = leaderOrigin;
me->ResetStuckMonitor();
const float runSpeed = 200.0f;
const float collectRange = (leaderSpeed > runSpeed) ? 600.0f : 400.0f; // 400, 200
FollowTargetCollector collector( m_leader );
SearchSurroundingAreas( TheNavMesh->GetNearestNavArea( m_lastLeaderPos ), m_lastLeaderPos, collector, collectRange );
if (cv_bot_debug.GetBool())
{
for( int i=0; i<collector.m_targetAreaCount; ++i )
collector.m_targetArea[i]->Draw( /*255, 0, 0, 2*/ );
}
// move to one of the collected areas
if (collector.m_targetAreaCount)
{
CNavArea *target = NULL;
Vector targetPos;
// if we are idle, pick a random area
if (m_idleTimer.IsElapsed())
{
target = collector.m_targetArea[ RandomInt( 0, collector.m_targetAreaCount-1 ) ];
targetPos = target->GetCenter();
me->PrintIfWatched( "%4.1f: Bored. Repathing to a new nearby area\n", gpGlobals->curtime );
}
else
{
me->PrintIfWatched( "%4.1f: Repathing to stay with leader.\n", gpGlobals->curtime );
// find closest area to where we are
CNavArea *area;
float closeRangeSq = 9999999999.9f;
Vector close;
for( int a=0; a<collector.m_targetAreaCount; ++a )
{
area = collector.m_targetArea[a];
area->GetClosestPointOnArea( myOrigin, &close );
float rangeSq = (myOrigin - close).LengthSqr();
if (rangeSq < closeRangeSq)
{
target = area;
targetPos = close;
closeRangeSq = rangeSq;
}
}
}
if (target == NULL || me->ComputePath( target->GetCenter(), FASTEST_ROUTE ) == NULL)
me->PrintIfWatched( "Pathfind to leader failed.\n" );
// throttle how often we repath
m_repathInterval.Start( 0.5f );
m_idleTimer.Reset();
}
}
}
//--------------------------------------------------------------------------------------------------------------
void FollowState::OnExit( CCSBot *me )
{
}

View File

@@ -0,0 +1,549 @@
//========= Copyright <20> 1996-2005, 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_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Begin moving to a nearby hidey-hole.
* NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
*/
void HideState::OnEnter( CCSBot *me )
{
m_isAtSpot = false;
m_isLookingOutward = false;
// if duration is "infinite", set it to a reasonably long time to prevent infinite camping
if (m_duration < 0.0f)
{
m_duration = RandomFloat( 30.0f, 60.0f );
}
// decide whether to "ambush" or not - never set to false so as not to override external setting
if (RandomFloat( 0.0f, 100.0f ) < 50.0f)
{
m_isHoldingPosition = true;
}
// if we are holding position, decide for how long
if (m_isHoldingPosition)
{
m_holdPositionTime = RandomFloat( 3.0f, 10.0f );
}
else
{
m_holdPositionTime = 0.0f;
}
m_heardEnemy = false;
m_firstHeardEnemyTime = 0.0f;
m_retry = 0;
if ( me->IsFollowing() && me->GetFollowLeader() )
{
m_leaderAnchorPos = GetCentroid( me->GetFollowLeader() );
}
// if we are a sniper, we need to periodically pause while we retreat to squeeze off a shot or two
if (me->IsSniper())
{
// start off paused to allow a final shot before retreating
m_isPaused = false;
m_pauseTimer.Invalidate();
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a nearby hidey-hole.
* NOTE: Do not forget this state may include a very long "move-to" time to get to our hidey spot!
*/
void HideState::OnUpdate( CCSBot *me )
{
Vector myOrigin = GetCentroid( me );
// wait until finished reloading to leave hide state
if (!me->IsReloading())
{
// if we are momentarily hiding while following someone, check to see if he has moved on
if ( me->IsFollowing() && me->GetFollowLeader() )
{
CCSPlayer *leader = static_cast<CCSPlayer *>( static_cast<CBaseEntity *>( me->GetFollowLeader() ) );
Vector leaderOrigin = GetCentroid( leader );
// BOTPORT: Determine walk/run velocity thresholds
float runThreshold = 200.0f;
if (leader->GetAbsVelocity().IsLengthGreaterThan( runThreshold ))
{
// leader is running, stay with him
me->Follow( leader );
return;
}
// if leader has moved, stay with him
const float followRange = 250.0f;
if ((m_leaderAnchorPos - leaderOrigin).IsLengthGreaterThan( followRange ))
{
me->Follow( leader );
return;
}
}
// if we see a nearby buddy in combat, join him
/// @todo - Perhaps tie in to TakeDamage(), so it works for human players, too
//
// Scenario logic
//
switch( TheCSBots()->GetScenario() )
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
if (me->GetTeamNumber() == TEAM_CT)
{
// if we are just holding position (due to a radio order) and the bomb has just planted, go defuse it
if (me->GetTask() == CCSBot::HOLD_POSITION &&
TheCSBots()->IsBombPlanted() &&
TheCSBots()->GetBombPlantTimestamp() > me->GetStateTimestamp())
{
me->Idle();
return;
}
// if we are guarding the defuser and he dies/gives up, stop hiding (to choose another defuser)
if (me->GetTask() == CCSBot::GUARD_BOMB_DEFUSER && TheCSBots()->GetBombDefuser() == NULL)
{
me->Idle();
return;
}
// if we are guarding the loose bomb and it is picked up, stop hiding
if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB && TheCSBots()->GetLooseBomb() == NULL)
{
me->GetChatter()->TheyPickedUpTheBomb();
me->Idle();
return;
}
// if we are guarding a bombsite and the bomb is dropped and we hear about it, stop guarding
if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE && me->GetGameState()->IsLooseBombLocationKnown())
{
me->Idle();
return;
}
// if we are guarding (bombsite, initial encounter, etc) and the bomb is planted, go defuse it
if (me->IsDoingScenario() && me->GetTask() != CCSBot::GUARD_BOMB_DEFUSER && TheCSBots()->IsBombPlanted())
{
me->Idle();
return;
}
}
else // TERRORIST
{
// if we are near the ticking bomb and someone starts defusing it, attack!
if (TheCSBots()->GetBombDefuser())
{
Vector defuserOrigin = GetCentroid( TheCSBots()->GetBombDefuser() );
Vector toDefuser = defuserOrigin - myOrigin;
const float hearDefuseRange = 2000.0f;
if (toDefuser.IsLengthLessThan( hearDefuseRange ))
{
// if we are nearby, attack, otherwise move to the bomb (which will cause us to attack when we see defuser)
if (me->CanSeePlantedBomb())
{
me->Attack( TheCSBots()->GetBombDefuser() );
}
else
{
me->MoveTo( defuserOrigin, FASTEST_ROUTE );
me->InhibitLookAround( 10.0f );
}
return;
}
}
}
break;
}
//--------------------------------------------------------------------------------------------------
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
// if we're guarding the hostages and they all die or are taken, do something else
if (me->GetTask() == CCSBot::GUARD_HOSTAGES)
{
if (me->GetGameState()->AreAllHostagesBeingRescued() || me->GetGameState()->AreAllHostagesGone())
{
me->Idle();
return;
}
}
else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
// if we stumble across a hostage, guard it
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
// we see a free hostage, guard it
Vector hostageOrigin = GetCentroid( hostage );
CNavArea *area = TheNavMesh->GetNearestNavArea( hostageOrigin );
if (area)
{
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm guarding hostages I found\n" );
// don't chatter here - he'll tell us when he's in his hiding spot
return;
}
}
}
}
}
bool isSettledInSniper = (me->IsSniper() && m_isAtSpot) ? true : false;
// only investigate noises if we are initiating attacks, and we aren't a "settled in" sniper
// dont investigate noises if we are reloading
if (!me->IsReloading() &&
!isSettledInSniper &&
me->GetDisposition() == CCSBot::ENGAGE_AND_INVESTIGATE)
{
// if we are holding position, and have heard the enemy nearby, investigate after our hold time is up
if (m_isHoldingPosition && m_heardEnemy && (gpGlobals->curtime - m_firstHeardEnemyTime > m_holdPositionTime))
{
/// @todo We might need to remember specific location of last enemy noise here
me->InvestigateNoise();
return;
}
// investigate nearby enemy noises
if (me->HeardInterestingNoise())
{
// if we are holding position, check if enough time has elapsed since we first heard the enemy
if (m_isAtSpot && m_isHoldingPosition)
{
if (!m_heardEnemy)
{
// first time we heard the enemy
m_heardEnemy = true;
m_firstHeardEnemyTime = gpGlobals->curtime;
me->PrintIfWatched( "Heard enemy, holding position for %f2.1 seconds...\n", m_holdPositionTime );
}
}
else
{
// not holding position - investigate enemy noise
me->InvestigateNoise();
return;
}
}
}
} // end reloading check
// look around
me->UpdateLookAround();
// if we are at our hiding spot, crouch and wait
if (m_isAtSpot)
{
me->ResetStuckMonitor();
CNavArea *area = TheNavMesh->GetNavArea( m_hidingSpot );
if ( !area || !( area->GetAttributes() & NAV_MESH_STAND ) )
{
me->Crouch();
}
// check if duration has expired
if (m_hideTimer.IsElapsed())
{
if (me->GetTask() == CCSBot::GUARD_LOOSE_BOMB)
{
// if we're guarding the loose bomb, continue to guard it but pick a new spot
me->Hide( TheCSBots()->GetLooseBombArea() );
return;
}
else if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE)
{
// if we're guarding a bombsite, continue to guard it but pick a new spot
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( myOrigin );
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
me->Hide( area );
return;
}
}
}
else if (me->GetTask() == CCSBot::GUARD_HOSTAGE_RESCUE_ZONE)
{
// if we're guarding a rescue zone, continue to guard this or another rescue zone
if (me->GuardRandomZone())
{
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
me->PrintIfWatched( "Continuing to guard hostage rescue zones\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
return;
}
}
me->Idle();
return;
}
/*
// if we are watching for an approaching noisy enemy, anticipate and fire before they round the corner
/// @todo Need to check if we are looking at an ENEMY_NOISE here
const float veryCloseNoise = 250.0f;
if (me->IsLookingAtSpot() && me->GetNoiseRange() < veryCloseNoise)
{
// fire!
me->PrimaryAttack();
me->PrintIfWatched( "Firing at anticipated enemy coming around the corner!\n" );
}
*/
// if we have a shield, hide behind it
if (me->HasShield() && !me->IsProtectedByShield())
me->SecondaryAttack();
// while sitting at our hiding spot, if we are being attacked but can't see our attacker, move somewhere else
const float hurtRecentlyTime = 1.0f;
if (!me->IsEnemyVisible() && me->GetTimeSinceAttacked() < hurtRecentlyTime)
{
me->Idle();
return;
}
// encourage the human player
if (!me->IsDoingScenario())
{
if (me->GetTeamNumber() == TEAM_CT)
{
if (me->GetTask() == CCSBot::GUARD_BOMB_ZONE &&
me->IsAtHidingSpot() &&
TheCSBots()->IsBombPlanted())
{
if (me->GetNearbyEnemyCount() == 0)
{
const float someTime = 30.0f;
const float littleTime = 11.0;
if (TheCSBots()->GetBombTimeLeft() > someTime)
me->GetChatter()->Encourage( "BombsiteSecure", RandomFloat( 10.0f, 15.0f ) );
else if (TheCSBots()->GetBombTimeLeft() > littleTime)
me->GetChatter()->Encourage( "WaitingForHumanToDefuseBomb", RandomFloat( 5.0f, 8.0f ) );
else
me->GetChatter()->Encourage( "WaitingForHumanToDefuseBombPanic", RandomFloat( 3.0f, 4.0f ) );
}
}
if (me->GetTask() == CCSBot::GUARD_HOSTAGES && me->IsAtHidingSpot())
{
if (me->GetNearbyEnemyCount() == 0)
{
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
me->GetChatter()->Encourage( "WaitingForHumanToRescueHostages", RandomFloat( 10.0f, 15.0f ) );
}
}
}
}
}
}
else
{
// we are moving to our hiding spot
// snipers periodically pause and fire while retreating
if (me->IsSniper() && me->IsEnemyVisible())
{
if (m_isPaused)
{
if (m_pauseTimer.IsElapsed())
{
// get moving
m_isPaused = false;
m_pauseTimer.Start( RandomFloat( 1.0f, 3.0f ) );
}
else
{
me->Wait( 0.2f );
}
}
else
{
if (m_pauseTimer.IsElapsed())
{
// pause for a moment
m_isPaused = true;
m_pauseTimer.Start( RandomFloat( 0.5f, 1.5f ) );
}
}
}
// if a Player is using this hiding spot, give up
float range;
CCSPlayer *camper = static_cast<CCSPlayer *>( UTIL_GetClosestPlayer( m_hidingSpot, &range ) );
const float closeRange = 75.0f;
if (camper && camper != me && range < closeRange && me->IsVisible( camper, CHECK_FOV ))
{
// player is in our hiding spot
me->PrintIfWatched( "Someone's in my hiding spot - picking another...\n" );
const int maxRetries = 3;
if (m_retry++ >= maxRetries)
{
me->PrintIfWatched( "Can't find a free hiding spot, giving up.\n" );
me->Idle();
return;
}
// pick another hiding spot near where we were planning on hiding
me->Hide( TheNavMesh->GetNavArea( m_hidingSpot ) );
return;
}
Vector toSpot;
toSpot.x = m_hidingSpot.x - myOrigin.x;
toSpot.y = m_hidingSpot.y - myOrigin.y;
toSpot.z = m_hidingSpot.z - me->GetFeetZ(); // use feet location
range = toSpot.Length();
// look outwards as we get close to our hiding spot
if (!me->IsEnemyVisible() && !m_isLookingOutward)
{
const float lookOutwardRange = 200.0f;
const float nearSpotRange = 10.0f;
if (range < lookOutwardRange && range > nearSpotRange)
{
m_isLookingOutward = true;
toSpot.x /= range;
toSpot.y /= range;
toSpot.z /= range;
me->SetLookAt( "Face outward", me->EyePosition() - 1000.0f * toSpot, PRIORITY_HIGH, 3.0f );
}
}
const float atDist = 20.0f;
if (range < atDist)
{
//-------------------------------------
// Just reached our hiding spot
//
m_isAtSpot = true;
m_hideTimer.Start( m_duration );
// make sure our approach points are valid, since we'll be watching them
me->ComputeApproachPoints();
me->ClearLookAt();
// ready our weapon and prepare to attack
me->EquipBestWeapon( me->IsUsingGrenade() );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
// if we are a sniper, update our task
if (me->GetTask() == CCSBot::MOVE_TO_SNIPER_SPOT)
{
me->SetTask( CCSBot::SNIPING );
}
else if (me->GetTask() == CCSBot::GUARD_INITIAL_ENCOUNTER)
{
const float campChatterChance = 20.0f;
if (RandomFloat( 0, 100 ) < campChatterChance)
{
me->GetChatter()->Say( "WaitingHere" );
}
}
// determine which way to look
trace_t result;
float outAngle = 0.0f;
float outAngleRange = 0.0f;
for( float angle = 0.0f; angle < 360.0f; angle += 45.0f )
{
UTIL_TraceLine( me->EyePosition(), me->EyePosition() + 1000.0f * Vector( BotCOS(angle), BotSIN(angle), 0.0f ), MASK_PLAYERSOLID, me, COLLISION_GROUP_NONE, &result );
if (result.fraction > outAngleRange)
{
outAngle = angle;
outAngleRange = result.fraction;
}
}
me->SetLookAheadAngle( outAngle );
}
// move to hiding spot
if (me->UpdatePathMovement() != CCSBot::PROGRESSING && !m_isAtSpot)
{
// we couldn't get to our hiding spot - pick another
me->PrintIfWatched( "Can't get to my hiding spot - finding another...\n" );
// search from hiding spot, since we know it was valid
const Vector *pos = FindNearbyHidingSpot( me, m_hidingSpot, m_range, me->IsSniper() );
if (pos == NULL)
{
// no available hiding spots
me->PrintIfWatched( "No available hiding spots - hiding where I'm at.\n" );
// hide where we are
m_hidingSpot.x = myOrigin.x;
m_hidingSpot.x = myOrigin.y;
m_hidingSpot.z = me->GetFeetZ();
}
else
{
m_hidingSpot = *pos;
}
// build a path to our new hiding spot
if (me->ComputePath( m_hidingSpot, FASTEST_ROUTE ) == false)
{
me->PrintIfWatched( "Can't pathfind to hiding spot\n" );
me->Idle();
return;
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
void HideState::OnExit( CCSBot *me )
{
m_isHoldingPosition = false;
me->StandUp();
me->ResetStuckMonitor();
//me->ClearLookAt();
me->ClearApproachPoints();
// if we have a shield, put it away
if (me->HasShield() && me->IsProtectedByShield())
me->SecondaryAttack();
}

View File

@@ -0,0 +1,237 @@
//========= Copyright <20> 1996-2005, 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_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Begin the hunt
*/
void HuntState::OnEnter( CCSBot *me )
{
// lurking death
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
me->Walk();
else
me->Run();
me->StandUp();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->SetTask( CCSBot::SEEK_AND_DESTROY );
me->DestroyPath();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Hunt down our enemies
*/
void HuntState::OnUpdate( CCSBot *me )
{
// if we've been hunting for a long time, drop into Idle for a moment to
// select something else to do
const float huntingTooLongTime = 30.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > huntingTooLongTime)
{
// stop being a rogue and do the scenario, since there must not be many enemies left to hunt
me->PrintIfWatched( "Giving up hunting.\n" );
me->SetRogue( false );
me->Idle();
return;
}
// scenario logic
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
// if we have the bomb and it's time to plant, or we happen to be in a bombsite and it seems safe, do it
if (me->HasC4())
{
const float safeTime = 3.0f;
if (TheCSBots()->IsTimeToPlantBomb() ||
(me->IsAtBombsite() && gpGlobals->curtime - me->GetLastSawEnemyTimestamp() > safeTime))
{
me->Idle();
return;
}
}
// if we notice the bomb lying on the ground, go get it
if (me->NoticeLooseBomb())
{
me->FetchBomb();
return;
}
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and watch it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && bombPos)
{
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
return;
}
}
else // CT
{
if (!me->IsRogue() && me->CanSeeLooseBomb())
{
// if we are near the loose bomb and can see it, hide nearby and guard it
me->SetTask( CCSBot::GUARD_LOOSE_BOMB );
me->Hide( TheCSBots()->GetLooseBombArea() );
me->GetChatter()->GuardingLooseBomb( TheCSBots()->GetLooseBomb() );
return;
}
else if (TheCSBots()->IsBombPlanted())
{
// rogues will defuse a bomb, but not guard the defuser
if (!me->IsRogue() || !TheCSBots()->GetBombDefuser())
{
// search for the planted bomb to defuse
me->Idle();
return;
}
}
}
}
else if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES)
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
if (me->GetGameState()->AreAllHostagesBeingRescued())
{
// all hostages are being rescued, head them off at the escape zones
if (me->GuardRandomZone())
{
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
me->PrintIfWatched( "Trying to beat them to an escape zone!\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
return;
}
}
// if safe time is up, and we stumble across a hostage, guard it
if (!me->IsRogue() && !me->IsSafe())
{
CHostage *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
if (area)
{
// we see a free hostage, guard it
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm guarding hostages\n" );
me->GetChatter()->GuardingHostages( area->GetPlace(), IS_PLAN );
return;
}
}
}
}
}
// listen for enemy noises
if (me->HeardInterestingNoise())
{
me->InvestigateNoise();
return;
}
// look around
me->UpdateLookAround();
if ( !TheCSBots()->AllowedToDoExpensiveBotOperationThisFrame() )
return;
// if we have reached our destination area, pick a new one
// if our path fails, pick a new one
if (me->GetLastKnownArea() == m_huntArea || me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// pick a new hunt area
const float earlyGameTime = 45.0f;
if (TheCSBots()->GetElapsedRoundTime() < earlyGameTime && !me->HasVisitedEnemySpawn())
{
// in the early game, rush the enemy spawn
CBaseEntity *enemySpawn = TheCSBots()->GetRandomSpawn( OtherTeam( me->GetTeamNumber() ) );
//ADRIAN: REVISIT
if ( enemySpawn )
{
m_huntArea = TheNavMesh->GetNavArea( enemySpawn->WorldSpaceCenter() );
}
}
else
{
m_huntArea = NULL;
float oldest = 0.0f;
int areaCount = 0;
const float minSize = 150.0f;
FOR_EACH_VEC( TheNavAreas, it )
{
CNavArea *area = TheNavAreas[ it ];
++areaCount;
// skip the small areas
Extent extent;
area->GetExtent(&extent);
if (extent.hi.x - extent.lo.x < minSize || extent.hi.y - extent.lo.y < minSize)
continue;
// keep track of the least recently cleared area
float age = gpGlobals->curtime - area->GetClearedTimestamp( me->GetTeamNumber()-1 );
if (age > oldest)
{
oldest = age;
m_huntArea = area;
}
}
// if all the areas were too small, pick one at random
int which = RandomInt( 0, areaCount-1 );
areaCount = 0;
FOR_EACH_VEC( TheNavAreas, hit )
{
m_huntArea = TheNavAreas[ hit ];
if (which == areaCount)
break;
--which;
}
}
if (m_huntArea)
{
// create a new path to a far away area of the map
me->ComputePath( m_huntArea->GetCenter() );
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Done hunting
*/
void HuntState::OnExit( CCSBot *me )
{
}

View File

@@ -0,0 +1,909 @@
//========= Copyright <20> 1996-2005, 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_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// range for snipers to select a hiding spot
const float sniperHideRange = 2000.0f;
extern ConVar mp_guardian_target_site;
//--------------------------------------------------------------------------------------------------------------
/**
* The Idle state.
* We never stay in the Idle state - it is a "home base" for the state machine that
* does various checks to determine what we should do next.
*/
void IdleState::OnEnter( CCSBot *me )
{
me->DestroyPath();
me->SetBotEnemy( NULL );
// lurking death
if (me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying())
me->Walk();
//
// Since Idle assigns tasks, we assume that coming back to Idle means our task is complete
//
me->SetTask( CCSBot::SEEK_AND_DESTROY );
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine what we should do next
*/
void IdleState::OnUpdate( CCSBot *me )
{
// all other states assume GetLastKnownArea() is valid, ensure that it is
if (me->GetLastKnownArea() == NULL && me->StayOnNavMesh() == false)
return;
// zombies never leave the Idle state
if (cv_bot_zombie.GetBool())
{
me->ResetStuckMonitor();
return;
}
// if we are in the early "safe" time, grab a knife or grenade
if (me->IsSafe())
{
// if we have a grenade, use it
if (!me->EquipGrenade())
{
// high-skill bots run with the knife
if (me->GetProfile()->GetSkill() > 0.33f)
{
me->EquipKnife();
}
}
}
// if round is over, hunt
if (me->GetGameState()->IsRoundOver())
{
// if we are escorting hostages, try to get to the rescue zone
if (me->GetHostageEscortCount())
{
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me, FASTEST_ROUTE ) );
const Vector *zonePos = TheCSBots()->GetRandomPositionInZone( zone );
if (zonePos)
{
me->SetTask( CCSBot::RESCUE_HOSTAGES );
me->Run();
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->MoveTo( *zonePos, FASTEST_ROUTE );
me->PrintIfWatched( "Trying to rescue hostages at the end of the round\n" );
return;
}
}
me->Hunt();
return;
}
const float defenseSniperCampChance = 75.0f;
const float offenseSniperCampChance = 10.0f;
// if we were following someone, continue following them
if (me->IsFollowing())
{
me->ContinueFollowing();
return;
}
if ( CSGameRules()->IsPlayingCooperativeGametype() )
{
UpdateCoop( me );
return;
}
//
// Scenario logic
//
switch (TheCSBots()->GetScenario())
{
//======================================================================================================
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
// if this is a bomb game and we have the bomb, go plant it
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
if (me->GetGameState()->IsBombPlanted())
{
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// T's always know where the bomb is - go defend it
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( me->GetGameState()->GetPlantedBombsite() );
if (zone)
{
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
Place place = TheNavMesh->GetPlace( zone->m_center );
if (place != UNDEFINED_PLACE)
{
// pick a random hiding spot in this place
const Vector *spot = FindRandomHidingSpot( me, place, me->IsSniper() );
if (spot)
{
me->Hide( *spot );
return;
}
}
// hide nearby
me->Hide( TheNavMesh->GetNearestNavArea( zone->m_center ) );
return;
}
}
else
{
// ask our teammates where the bomb is
me->GetChatter()->RequestBombLocation();
// we dont know where the bomb is - we must search the bombsites
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
const Vector *pos = TheCSBots()->GetRandomPositionInZone( TheCSBots()->GetZone( zoneIndex ) );
if (pos)
{
me->SetTask( CCSBot::FIND_TICKING_BOMB );
me->MoveTo( *pos );
return;
}
}
}
else if (me->HasC4())
{
// always pick a random spot to plant in case the spot we'd picked is inaccessible
if (TheCSBots()->IsTimeToPlantBomb())
{
// move to the closest bomb site
const CCSBotManager::Zone *zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me ) );
if (zone)
{
// pick a random spot within the bomb zone
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if (pos)
{
// move to bombsite
me->SetTask( CCSBot::PLANT_BOMB );
me->Run();
me->MoveTo( *pos );
return;
}
}
}
}
else
{
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// small chance of sniper camping on offense, if we aren't carrying the bomb
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
// if the bomb is loose (on the ground), go get it
if (me->NoticeLooseBomb())
{
me->FetchBomb();
return;
}
// if bomb has been planted, and we hear it, move to a hiding spot near the bomb and guard it
if (!me->IsRogue() && me->GetGameState()->IsBombPlanted() && me->GetGameState()->GetBombPosition())
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos)
{
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
return;
}
}
}
}
else // CT ------------------------------------------------------------------------------------------
{
if (me->GetGameState()->IsBombPlanted())
{
// if the bomb has been planted, attempt to defuse it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos)
{
// if someone is defusing the bomb, guard them
if (TheCSBots()->GetBombDefuser())
{
if (!me->IsRogue())
{
me->SetTask( CCSBot::GUARD_BOMB_DEFUSER );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
else if (me->IsDoingScenario())
{
// move to the bomb and defuse it
me->SetTask( CCSBot::DEFUSE_BOMB );
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->MoveTo( *bombPos );
return;
}
else
{
// we're not allowed to defuse, guard the bomb zone
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( TheNavMesh->GetNavArea( *bombPos ) );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
else if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// we know which bombsite, but not exactly where the bomb is, go there
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( me->GetGameState()->GetPlantedBombsite() );
if (zone)
{
if (me->IsDoingScenario())
{
me->SetTask( CCSBot::DEFUSE_BOMB );
me->MoveTo( zone->m_center );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
else
{
// we're not allowed to defuse, guard the bomb zone
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( TheNavMesh->GetNavArea( zone->m_center ) );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
else
{
// we dont know where the bomb is - we must search the bombsites
// find closest un-cleared bombsite
const CCSBotManager::Zone *zone = NULL;
float travelDistance = 9999999.9f;
for( int z=0; z<TheCSBots()->GetZoneCount(); ++z )
{
if (TheCSBots()->GetZone(z)->m_areaCount == 0)
continue;
// don't check bombsites that have been cleared
if (me->GetGameState()->IsBombsiteClear( z ))
continue;
// just use the first overlapping nav area as a reasonable approximation
ShortestPathCost cost = ShortestPathCost();
float dist = NavAreaTravelDistance( me->GetLastKnownArea(),
TheNavMesh->GetNearestNavArea( TheCSBots()->GetZone(z)->m_center ),
cost );
if (dist >= 0.0f && dist < travelDistance)
{
zone = TheCSBots()->GetZone(z);
travelDistance = dist;
}
}
if (zone)
{
const float farAwayRange = 2000.0f;
if (travelDistance > farAwayRange)
{
zone = NULL;
}
}
// if closest bombsite is "far away", pick one at random
if (zone == NULL)
{
int zoneIndex = me->GetGameState()->GetNextBombsiteToSearch();
zone = TheCSBots()->GetZone( zoneIndex );
}
// move to bombsite - if we reach it, we'll update its cleared status, causing us to select another
if (zone)
{
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if (pos)
{
me->SetTask( CCSBot::FIND_TICKING_BOMB );
me->MoveTo( *pos );
return;
}
}
}
AssertMsg( 0, "A CT bot doesn't know what to do while the bomb is planted!\n" );
}
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper() && !me->IsSafe())
{
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
{
CNavArea *snipingArea = NULL;
// if the bomb is loose, snipe near it
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (me->GetGameState()->IsLooseBombLocationKnown() && bombPos)
{
snipingArea = TheNavMesh->GetNearestNavArea( *bombPos );
me->PrintIfWatched( "Sniping near loose bomb\n" );
}
else
{
// snipe bomb zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
snipingArea = TheCSBots()->GetRandomAreaInZone( zone );
me->PrintIfWatched( "Sniping near bombsite\n" );
}
}
if (snipingArea)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( snipingArea, -1.0, sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
// if we know the bomb is dropped, hunt for enemies and the loose bomb
if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || me->GetGameState()->IsLooseBombLocationKnown())
{
me->Hunt();
return;
}
// the lower our morale gets, the more we want to camp the bomb zone(s)
// only decide to camp at the start of the round, or if we haven't seen anything for a long time
if (me->IsSafe() || me->HasNotSeenEnemyForLongTime())
{
float guardBombsiteChance = -34.0f * me->GetMorale();
if (RandomFloat( 0.0f, 100.0f ) < guardBombsiteChance)
{
float guardRange = 500.0f + 100.0f * (me->GetMorale() + 3);
// guard bomb zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
me->PrintIfWatched( "I'm guarding a bombsite\n" );
me->GetChatter()->GuardingBombsite( area->GetPlace() );
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( area, -1.0, guardRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
}
}
break;
}
//======================================================================================================
case CCSBotManager::SCENARIO_ESCORT_VIP:
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper())
{
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
{
// snipe escape zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( area, -1.0, sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping near escape zone\n" );
return;
}
}
}
}
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
if (me->IsRogue() || TheCSBots()->IsDefenseRushing())
break;
// the lower our morale gets, the more we want to camp the escape zone(s)
float guardEscapeZoneChance = -34.0f * me->GetMorale();
if (RandomFloat( 0.0f, 100.0f ) < guardEscapeZoneChance)
{
// guard escape zone(s)
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if (zone)
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if (area)
{
// guard the escape zone - stay closer if our morale is low
me->SetTask( CCSBot::GUARD_VIP_ESCAPE_ZONE );
me->PrintIfWatched( "I'm guarding an escape zone\n" );
float escapeGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3);
me->Hide( area, -1.0, escapeGuardRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
}
else // CT
{
if (me->m_bIsVIP)
{
// if early in round, pick a random zone, otherwise pick closest zone
const float earlyTime = 20.0f;
const CCSBotManager::Zone *zone = NULL;
if (TheCSBots()->GetElapsedRoundTime() < earlyTime)
{
// pick random zone
zone = TheCSBots()->GetRandomZone();
}
else
{
// pick closest zone
zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me ) );
}
if (zone)
{
// pick a random spot within the escape zone
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if (pos)
{
// move to escape zone
me->SetTask( CCSBot::VIP_ESCAPE );
me->Run();
me->MoveTo( *pos );
// tell team to follow
const float repeatTime = 30.0f;
if (me->GetFriendsRemaining() &&
TheCSBots()->GetRadioMessageInterval( RADIO_FOLLOW_ME, me->GetTeamNumber() ) > repeatTime)
me->SendRadioMessage( RADIO_FOLLOW_ME );
return;
}
}
}
else
{
// small chance of sniper camping on offense, if we aren't VIP
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
}
}
break;
}
//======================================================================================================
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
if (me->GetTeamNumber() == TEAM_TERRORIST)
{
bool campHostages;
// if we are in early game, camp the hostages
if (me->IsSafe())
{
campHostages = true;
}
else if (me->GetGameState()->HaveSomeHostagesBeenTaken() || me->GetGameState()->AreAllHostagesBeingRescued())
{
campHostages = false;
}
else
{
// later in the game, camp either hostages or escape zone
const float campZoneChance = 100.0f * (TheCSBots()->GetElapsedRoundTime() - me->GetSafeTime())/120.0f;
campHostages = (RandomFloat( 0, 100 ) > campZoneChance) ? true : false;
}
// if we have a sniper rifle, we like to camp, whether rogue or not
if (me->IsSniper())
{
// the at start of the round, snipe the initial rush
if (me->IsSafe())
{
if (me->MoveToInitialEncounter() && CSGameRules()->IsPlayingCooperativeGametype() == false )
{
me->PrintIfWatched( "I'm sniping an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
if (RandomFloat( 0, 100 ) <= defenseSniperCampChance)
{
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
if (hostagePos && campHostages)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->PrintIfWatched( "Sniping near hostages\n" );
me->Hide( TheNavMesh->GetNearestNavArea( *hostagePos ), -1.0, sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
else
{
// camp the escape zone(s)
if (me->GuardRandomZone( sniperHideRange ))
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->PrintIfWatched( "Sniping near a rescue zone\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
}
// if safe time is up, and we stumble across a hostage, guard it
if (!me->IsSafe() && !me->IsRogue())
{
CBaseEntity *hostage = me->GetGameState()->GetNearestVisibleFreeHostage();
if (hostage)
{
// we see a free hostage, guard it
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
if (area)
{
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm guarding hostages I found\n" );
// don't chatter here - he'll tell us when he's in his hiding spot
return;
}
}
}
// decide if we want to hunt, or guard
const float huntChance = 70.0f + 25.0f * me->GetMorale();
// rogues just hunt, unless they want to snipe
// if the whole team has decided to rush, hunt
if (me->GetFriendsRemaining())
{
if (me->IsRogue() || TheCSBots()->IsDefenseRushing() || RandomFloat( 0, 100 ) < huntChance)
{
me->Hunt();
return;
}
}
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance)
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// decide whether to camp the hostages or the escape zones
const Vector *hostagePos = me->GetGameState()->GetRandomFreeHostagePosition();
if (hostagePos && campHostages)
{
CNavArea *area = TheNavMesh->GetNearestNavArea( *hostagePos );
if (area)
{
// guard the hostages - stay closer to hostages if our morale is low
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->PrintIfWatched( "I'm guarding hostages\n" );
float hostageGuardRange = 750.0f + 250.0f * (me->GetMorale() + 3); // 2000
me->Hide( area, -1.0, hostageGuardRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
if (RandomFloat( 0, 100 ) < 50)
me->GetChatter()->GuardingHostages( area->GetPlace(), IS_PLAN );
return;
}
}
// guard rescue zone(s)
if (me->GuardRandomZone())
{
me->SetTask( CCSBot::GUARD_HOSTAGE_RESCUE_ZONE );
me->PrintIfWatched( "I'm guarding a rescue zone\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->GetChatter()->GuardingHostageEscapeZone( IS_PLAN );
return;
}
}
else // CT ---------------------------------------------------------------------------------
{
// only decide to do something else if we aren't already rescuing hostages
if (!me->GetHostageEscortCount())
{
// small chance of sniper camping on offense
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
if ( CSGameRules()->IsPlayingCooperativeGametype() == false )
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
}
if (me->GetFriendsRemaining() && !me->GetHostageEscortCount())
{
// rogues just hunt, unless all friends are dead
// if we have friends left, we might go hunting instead of hostage rescuing
const float huntChance = 33.3f;
if (me->IsRogue() || RandomFloat( 0.0f, 100.0f ) < huntChance)
{
me->Hunt();
return;
}
}
}
// at the start of the round, we may decide to defend "initial encounter" areas
// where we will first meet the enemy rush
if (me->IsSafe())
{
float defendRushChance = -17.0f * (me->GetMorale() - 2);
if (CSGameRules()->IsPlayingCooperativeGametype() == false &&
(me->IsSniper() || RandomFloat( 0.0f, 100.0f ) < defendRushChance) )
{
if (me->MoveToInitialEncounter())
{
me->PrintIfWatched( "I'm guarding an initial encounter area\n" );
me->SetTask( CCSBot::GUARD_INITIAL_ENCOUNTER );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return;
}
}
}
// look for free hostages - CT's have radar so they know where hostages are at all times
CHostage *hostage = me->GetGameState()->GetNearestFreeHostage();
// if we are not allowed to do the scenario, guard the hostages to clear the area for the human(s)
if (!me->IsDoingScenario())
{
if (hostage)
{
CNavArea *area = TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) );
if (area)
{
me->SetTask( CCSBot::GUARD_HOSTAGES );
me->Hide( area );
me->PrintIfWatched( "I'm securing the hostages for a human to rescue\n" );
return;
}
}
me->Hunt();
return;
}
bool fetchHostages = false;
bool rescueHostages = false;
const CCSBotManager::Zone *zone = NULL;
me->SetGoalEntity( NULL );
// if we are escorting hostages, determine where to take them
if (me->GetHostageEscortCount())
zone = TheCSBots()->GetClosestZone( me->GetLastKnownArea(), PathCost( me, FASTEST_ROUTE ) );
// if we are escorting hostages and there are more hostages to rescue,
// determine whether it's faster to rescue the ones we have, or go get the remaining ones
if ( zone && HOSTAGE_RULE_CAN_PICKUP ) // We can only carry one hostage at a time so go ahead and rescue the one we have
{
rescueHostages = true;
}
else if (hostage)
{
Vector hostageOrigin = GetCentroid( hostage );
if (zone)
{
PathCost cost( me, FASTEST_ROUTE );
float toZone = NavAreaTravelDistance( me->GetLastKnownArea(), zone->m_area[0], cost );
float toHostage = NavAreaTravelDistance( me->GetLastKnownArea(), TheNavMesh->GetNearestNavArea( GetCentroid( hostage ) ), cost );
if (toHostage < 0.0f)
{
rescueHostages = true;
}
else
{
if (toZone < toHostage)
rescueHostages = true;
else
fetchHostages = true;
}
}
else
{
fetchHostages = true;
}
}
else if (zone)
{
rescueHostages = true;
}
if (fetchHostages)
{
// go get hostages
me->SetTask( CCSBot::COLLECT_HOSTAGES );
me->Run();
me->SetGoalEntity( hostage );
me->ResetWaitForHostagePatience();
// if we already have some hostages, move to the others by the quickest route
RouteType route = (me->GetHostageEscortCount()) ? FASTEST_ROUTE : SAFEST_ROUTE;
me->MoveTo( GetCentroid( hostage ), route );
me->PrintIfWatched( "I'm collecting hostages\n" );
return;
}
const Vector *zonePos = TheCSBots()->GetRandomPositionInZone( zone );
if (rescueHostages && zonePos)
{
me->SetTask( CCSBot::RESCUE_HOSTAGES );
me->Run();
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->MoveTo( *zonePos, FASTEST_ROUTE );
me->PrintIfWatched( "I'm rescuing hostages\n" );
me->GetChatter()->EscortingHostages();
return;
}
}
break;
}
default: // deathmatch
{
// if we just spawned, cheat and make us aware of other players so players can't spawncamp us effectively
if ( me->m_spawnedTime - gpGlobals->curtime < 1.0f )
{
CUtlVector< CCSPlayer * > playerVector;
CollectPlayers( &playerVector, TEAM_ANY, COLLECT_ONLY_LIVING_PLAYERS );
for( int i=0; i<playerVector.Count(); ++i )
{
if ( me->entindex() == playerVector[i]->entindex() )
{
continue;
}
me->OnAudibleEvent( NULL, playerVector[i], 9999999.9f, PRIORITY_HIGH, true );
}
}
// sniping check
if (me->GetFriendsRemaining() && me->IsSniper() && RandomFloat( 0, 100.0f ) < offenseSniperCampChance)
{
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
me->Hide( me->GetLastKnownArea(), RandomFloat( 10.0f, 30.0f ), sniperHideRange );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
me->PrintIfWatched( "Sniping!\n" );
return;
}
break;
}
}
// if we have nothing special to do, go hunting for enemies
me->Hunt();
}

View File

@@ -0,0 +1,259 @@
//========= Copyright <20> Valve Corporation, All rights reserved. ============//
//
// Purpose: Idle update function for coop mode. Split into different file for clarity
//
//===========================================================================//
#include "cbase.h"
#include "cs_bot.h"
#include "cs_team.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar bot_coop_force_throw_grenade_chance( "bot_coop_force_throw_grenade_chance", "0.3", FCVAR_CHEAT );
bool ReactToBombState( CCSBot *me )
{
const Vector* pVecPos = me->GetGameState()->GetBombPosition();
if ( !pVecPos )
return false;
if ( me->GetTeamNumber() == TEAM_TERRORIST )
{
if ( TheCSBots()->IsBombPlanted() )
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
else if ( me->GetGameState()->IsBombLoose() )
{
me->FetchBomb();
return true;
}
else
return false;
}
else if ( me->GetTeamNumber() == TEAM_CT )
{
if ( TheCSBots()->IsBombPlanted() )
me->SetTask( CCSBot::DEFUSE_BOMB );
else if ( me->GetGameState()->IsBombLoose() )
me->SetTask( CCSBot::GUARD_LOOSE_BOMB );
/*
else if ( me->GetGameState()->GetBombState() == CSGameState::MOVING )
{
float flDistToSite0 = ( *pVecPos - TheCSBots()->GetZone( 0 )->m_center ).Length2D();
float flDistToSite1 = ( *pVecPos - TheCSBots()->GetZone( 1 )->m_center ).Length2D();
} */
else
return false;
}
me->MoveTo( *pVecPos );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
return true;
}
// Terrorists rush the chosen site
void RunStrat_Rush( CCSBot * me, int iBombSite )
{
if ( !ReactToBombState( me ) )
{
const CCSBotManager::Zone *zone = TheCSBots()->GetZone( iBombSite );
const Vector *pos = TheCSBots()->GetRandomPositionInZone( zone );
if ( !pos)
{
Warning( "ERROR: Map either has < 2 bomb sites, or one of the sites has no areas. Coop bots will be broken." );
return;
}
if ( me->IsAtBombsite() )
{
bool bIsSafe = gpGlobals->curtime - me->GetLastSawEnemyTimestamp() > 2.0f; // TODO: Might be better to use enemy death/remaining count for this
if ( me->HasC4() && bIsSafe )
{
me->SetTask( CCSBot::PLANT_BOMB );
}
else
{
Place place = TheNavMesh->GetPlace( zone->m_center );
if ( const Vector* pVecHidingSpot = FindRandomHidingSpot( me, place, me->IsSniper() ) )
{
pos = pVecHidingSpot;
}
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
}
}
else
{
me->SetTask( CCSBot::SEEK_AND_DESTROY );
}
me->MoveTo( *pos );
me->Run();
me->PrintIfWatched( "I'm rushing the bomb site\n" );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
}
}
void RunStrat_GuardSpot( CCSBot *me )
{
if ( !ReactToBombState( me ) )
{
const CCSBotManager::Zone *zone = TheCSBots()->GetRandomZone();
if ( zone )
{
CNavArea *area = TheCSBots()->GetRandomAreaInZone( zone );
if ( area )
{
me->PrintIfWatched( "I'm guarding a bombsite\n" );
me->GetChatter()->GuardingBombsite( area->GetPlace() );
me->SetTask( CCSBot::GUARD_BOMB_ZONE );
me->Hide( area, -1.0, 1000.0f );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
}
}
}
}
void MakeAwareOfCTs( CCSBot * me )
{
CUtlVector< CCSPlayer * > vecCTs;
CollectPlayers( &vecCTs, TEAM_CT, COLLECT_ONLY_LIVING_PLAYERS );
if ( vecCTs.Count() == 0 )
return;
CCSPlayer *pTargetPlayer = NULL;
float flClosePlayer = 1e6;
FOR_EACH_VEC( vecCTs, iter )
{
if ( me->IsVisible( vecCTs[ iter ] ) )
{
float dist = me->GetTravelDistanceToPlayer( vecCTs[ iter ] );
if ( dist < flClosePlayer )
{
flClosePlayer = dist;
pTargetPlayer = vecCTs[ iter ];
}
}
me->OnAudibleEvent( NULL, vecCTs[ iter ], MAX_COORD_FLOAT, PRIORITY_HIGH, true );
}
if ( !pTargetPlayer )
pTargetPlayer = vecCTs[ RandomInt( 0, vecCTs.Count() - 1 ) ];
me->SetBotEnemy( pTargetPlayer );
}
extern ConVar mp_guardian_target_site;
void IdleState::UpdateCoop( CCSBot *me )
{
if ( CSGameRules()->IsPlayingCoopGuardian() )
{
Assert( mp_guardian_target_site.GetInt() >= 0 );
// if this is a bomb game and we have the bomb, go plant it
if ( me->GetTeamNumber() == TEAM_TERRORIST )
{
switch ( TheCSBots()->GetTStrat() )
{
case CCSBotManager::k_ETStrat_Rush:
{
RunStrat_Rush( me, TheCSBots()->GetTerroristTargetSite() );
break;
}
}
}
else if ( me->GetTeamNumber() == TEAM_CT )
{
switch ( TheCSBots()->GetCTStrat() )
{
case CCSBotManager::k_ECTStrat_StackSite:
{
RunStrat_GuardSpot( me );
break;
}
}
}
}
else if ( CSGameRules()->IsPlayingCoopMission() )
{
if ( me->GetTeamNumber() == TEAM_TERRORIST )
{
SpawnPointCoopEnemy* pSpawn = me->GetLastCoopSpawnPoint();
if ( !pSpawn )
return;
// if we're holding a grenade, throw it at the victim
if ( me->HasGrenade() && CSGameRules()->IsPlayingCooperativeGametype() && RandomFloat() < bot_coop_force_throw_grenade_chance.GetFloat() )
me->EquipGrenade();
// HACK: We'd like last guy alive to stop hiding and hunt down the players
// but don't want the first spawned guy to go straight to charging... kick this clause into the future a few seconds
// to make sure our wave is in before making this check.
SpawnPointCoopEnemy::BotDefaultBehavior_t behavior = pSpawn->GetDefaultBehavior();
if ( me->GetFriendsRemaining() == 0 && gpGlobals->curtime - me->m_spawnedTime > 5.0f )
behavior = SpawnPointCoopEnemy::CHARGE_ENEMY;
switch ( behavior )
{
case SpawnPointCoopEnemy::HUNT:
{
me->Hunt();
break;
}
case SpawnPointCoopEnemy::DEFEND_AREA:
case SpawnPointCoopEnemy::DEFEND_INVESTIGATE:
{
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
if ( me->IsSniper() )
me->SetTask( CCSBot::MOVE_TO_SNIPER_SPOT );
else
me->SetTask( CCSBot::HOLD_POSITION );
if ( pSpawn->HideRadius() > 0 )
{
if ( !me->TryToHide( pSpawn->FindNearestArea(), -1.0, pSpawn->HideRadius(), true, false, &me->GetAbsOrigin() ) )
{
// This spawn isn't hideable, stop trying
pSpawn->HideRadius( 0 );
}
}
else
{
// look around
me->UpdateLookAround();
if ( pSpawn->GetDefaultBehavior() == SpawnPointCoopEnemy::DEFEND_INVESTIGATE )
{
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
// listen for enemy noises
if ( me->HeardInterestingNoise() )
{
me->InvestigateNoise();
pSpawn->SetDefaultBehavior( SpawnPointCoopEnemy::HUNT );
}
}
}
break;
}
case SpawnPointCoopEnemy::CHARGE_ENEMY:
{
MakeAwareOfCTs( me );
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
if ( me->GetBotEnemy() )
{
me->SetTask( CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION, me->GetBotEnemy() );
me->MoveTo( me->GetBotEnemy()->GetAbsOrigin() );
}
break;
}
}
}
}
}

View File

@@ -0,0 +1,139 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Move towards currently heard noise
*/
void InvestigateNoiseState::AttendCurrentNoise( CCSBot *me )
{
if (!me->IsNoiseHeard() && me->GetNoisePosition())
return;
// remember where the noise we heard was
m_checkNoisePosition = *me->GetNoisePosition();
// tell our teammates (unless the noise is obvious, like gunfire)
if (me->IsWellPastSafe() && me->HasNotSeenEnemyForLongTime() && me->GetNoisePriority() != PRIORITY_HIGH)
me->GetChatter()->HeardNoise( *me->GetNoisePosition() );
// figure out how to get to the noise
me->PrintIfWatched( "Attending to noise...\n" );
me->ComputePath( m_checkNoisePosition, FASTEST_ROUTE );
const float minAttendTime = 3.0f;
const float maxAttendTime = 10.0f;
m_minTimer.Start( RandomFloat( minAttendTime, maxAttendTime ) );
// consume the noise
me->ForgetNoise();
}
//--------------------------------------------------------------------------------------------------------------
void InvestigateNoiseState::OnEnter( CCSBot *me )
{
AttendCurrentNoise( me );
}
//--------------------------------------------------------------------------------------------------------------
/**
* @todo Use TravelDistance instead of distance...
*/
void InvestigateNoiseState::OnUpdate( CCSBot *me )
{
Vector myOrigin = GetCentroid( me );
// keep an ear out for closer noises...
if (m_minTimer.IsElapsed())
{
const float nearbyRange = 500.0f;
if (me->HeardInterestingNoise() && me->GetNoiseRange() < nearbyRange)
{
// new sound is closer
AttendCurrentNoise( me );
}
}
// if the pathfind fails, give up
if (!me->HasPath())
{
me->Idle();
return;
}
// look around
me->UpdateLookAround();
// get distance remaining on our path until we reach the source of the noise
float range = me->GetPathDistanceRemaining();
if (me->IsUsingKnife())
{
if (me->IsHurrying())
me->Run();
else
me->Walk();
}
else
{
const float closeToNoiseRange = 1500.0f;
if (range < closeToNoiseRange)
{
// if we dont have many friends left, or we are alone, and we are near noise source, sneak quietly
if ((me->GetNearbyFriendCount() == 0 || me->GetFriendsRemaining() <= 2) && !me->IsHurrying())
{
me->Walk();
}
else
{
me->Run();
}
}
else
{
me->Run();
}
}
// if we can see the noise position and we're close enough to it and looking at it,
// we don't need to actually move there (it's checked enough)
const float closeRange = 500.0f;
if (range < closeRange)
{
if (me->IsVisible( m_checkNoisePosition, CHECK_FOV ))
{
// can see noise position
me->PrintIfWatched( "Noise location is clear.\n" );
me->ForgetNoise();
me->Idle();
return;
}
}
// move towards noise
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
me->Idle();
}
}
//--------------------------------------------------------------------------------------------------------------
void InvestigateNoiseState::OnExit( CCSBot *me )
{
// reset to run mode in case we were sneaking about
me->Run();
}

View File

@@ -0,0 +1,408 @@
//========= Copyright <20> 1996-2005, 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_bot.h"
#include "cs_gamerules.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a potentially far away position.
*/
void MoveToState::OnEnter( CCSBot *me )
{
if ( ( me->IsUsingKnife() && me->IsWellPastSafe() && !me->IsHurrying() ) ||
( me->HasHeavyArmor() && me->GetBotEnemy() ) )
{
me->Walk();
}
else
{
me->Run();
}
// if we need to find the bomb, get there as quick as we can
RouteType route;
switch (me->GetTask())
{
case CCSBot::FIND_TICKING_BOMB:
case CCSBot::DEFUSE_BOMB:
case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
route = FASTEST_ROUTE;
break;
default:
route = SAFEST_ROUTE;
break;
}
// build path to, or nearly to, goal position
me->ComputePath( m_goalPosition, route );
m_radioedPlan = false;
m_askedForCover = false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move to a potentially far away position.
*/
void MoveToState::OnUpdate( CCSBot *me )
{
Vector myOrigin = GetCentroid( me );
// assume that we are paying attention and close enough to know our enemy died
if (me->GetTask() == CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION)
{
/// @todo Account for reaction time so we take some time to realized the enemy is dead
CBasePlayer *victim = static_cast<CBasePlayer *>( me->GetTaskEntity() );
if (victim == NULL || !victim->IsAlive())
{
me->PrintIfWatched( "The enemy I was chasing was killed - giving up.\n" );
me->Idle();
return;
}
}
// look around
me->UpdateLookAround();
//
// Scenario logic
//
switch (TheCSBots()->GetScenario())
{
case CCSBotManager::SCENARIO_DEFUSE_BOMB:
{
// if the bomb has been planted, find it
// NOTE: This task is used by both CT and T's to find the bomb
if (me->GetTask() == CCSBot::FIND_TICKING_BOMB)
{
if (!me->GetGameState()->IsBombPlanted())
{
// the bomb is not planted - give up this task
me->Idle();
return;
}
if (me->GetGameState()->GetPlantedBombsite() != CSGameState::UNKNOWN)
{
// we know where the bomb is planted, stop searching
me->Idle();
return;
}
// check off bombsites that we explore or happen to stumble into
for( int z=0; z<TheCSBots()->GetZoneCount(); ++z )
{
// don't re-check zones
if (me->GetGameState()->IsBombsiteClear( z ))
continue;
if (TheCSBots()->GetZone(z)->m_extent.Contains( myOrigin ))
{
// note this bombsite is clear
me->GetGameState()->ClearBombsite( z );
if (me->GetTeamNumber() == TEAM_CT)
{
// tell teammates this bombsite is clear
me->GetChatter()->BombsiteClear( z );
}
// find another zone to check
me->Idle();
return;
}
}
// move to a bombsite
break;
}
if (me->GetTeamNumber() == TEAM_CT)
{
if (me->GetGameState()->IsBombPlanted())
{
switch( me->GetTask() )
{
case CCSBot::DEFUSE_BOMB:
{
// if we are near the bombsite and there is time left, sneak in (unless all enemies are dead)
if (me->GetEnemiesRemaining())
{
const float plentyOfTime = 15.0f;
if (TheCSBots()->GetBombTimeLeft() > plentyOfTime)
{
// get distance remaining on our path until we reach the bombsite
float range = me->GetPathDistanceRemaining();
const float closeRange = 1500.0f;
if (range < closeRange)
{
me->Walk();
}
else
{
me->Run();
}
}
}
else
{
// everyone is dead - run!
me->Run();
}
// if we are trying to defuse the bomb, and someone has started defusing, guard them instead
if (me->CanSeePlantedBomb() && TheCSBots()->GetBombDefuser())
{
me->GetChatter()->Say( "CoveringFriend" );
me->Idle();
return;
}
// if we are near the bomb, defuse it (if we are reloading, don't try to defuse until we finish)
const Vector *bombPos = me->GetGameState()->GetBombPosition();
if (bombPos && !me->IsReloading())
{
if ((*bombPos - me->EyePosition()).IsLengthLessThan( 72 ) && ( me->EyePosition().AsVector2D().DistTo( bombPos->AsVector2D() ) < 48 ))
{
// make sure we can see the bomb
if (me->IsVisible( *bombPos ))
{
me->DefuseBomb();
return;
}
}
}
break;
}
default:
{
// we need to find the bomb
me->Idle();
return;
}
}
}
}
else // TERRORIST
{
if (me->GetTask() == CCSBot::PLANT_BOMB )
{
if ( me->GetFriendsRemaining() )
{
// if we are about to plant, radio for cover
if (!m_askedForCover)
{
const float nearPlantSite = 50.0f;
if (me->IsAtBombsite() && me->GetPathDistanceRemaining() < nearPlantSite)
{
// radio to the team
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
m_askedForCover = true;
}
// after we have started to move to the bombsite, tell team we're going to plant, and where
// don't do this if we have already radioed that we are starting to plant
if (!m_radioedPlan)
{
const float radioTime = 2.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > radioTime)
{
// radio to the team if we're more than 10 seconds (2400 units) out
const float nearPlantSite = 2400.0f;
if ( me->GetPathDistanceRemaining() >= nearPlantSite )
{
me->GetChatter()->GoingToPlantTheBomb( TheNavMesh->GetPlace( m_goalPosition ) );
}
m_radioedPlan = true;
}
}
}
}
}
}
break;
}
//--------------------------------------------------------------------------------------------------
case CCSBotManager::SCENARIO_RESCUE_HOSTAGES:
{
if (me->GetTask() == CCSBot::COLLECT_HOSTAGES)
{
//
// Since CT's have a radar, they can directly look at the actual hostage state
//
// check if someone else collected our hostage, or the hostage died or was rescued
CHostage *hostage = static_cast<CHostage *>( me->GetGoalEntity() );
if (hostage == NULL || !hostage->IsValid() || hostage->IsFollowingSomeone() )
{
me->Idle();
return;
}
Vector hostageOrigin = GetCentroid( hostage );
// if our hostage has moved, repath
const float repathToleranceSq = 75.0f * 75.0f;
float error = (hostageOrigin - m_goalPosition).LengthSqr();
if (error > repathToleranceSq)
{
m_goalPosition = hostageOrigin;
me->ComputePath( m_goalPosition, SAFEST_ROUTE );
}
/// @todo Generalize ladder priorities over other tasks
if (!me->IsUsingLadder())
{
Vector pos = hostage->EyePosition();
Vector to = pos - me->EyePosition(); // "Use" checks from eye position, so we should too
// look at the hostage as we approach
const float watchHostageRange = 100.0f;
if (to.IsLengthLessThan( watchHostageRange ))
{
me->SetLookAt( "Hostage", pos, PRIORITY_LOW, 0.5f );
// randomly move just a bit to avoid infinite use loops from bad hostage placement
NavRelativeDirType dir = (NavRelativeDirType)RandomInt( 0, 3 );
switch( dir )
{
case LEFT: me->StrafeLeft(); break;
case RIGHT: me->StrafeRight(); break;
case FORWARD: me->MoveForward(); break;
case BACKWARD: me->MoveBackward(); break;
}
// check if we are close enough to the hostage to talk to him
const float useRange = PLAYER_USE_RADIUS - 10.0f; // shave off a fudge factor to make sure we're within range
if (to.IsLengthLessThan( useRange ))
{
if ( HOSTAGE_RULE_CAN_PICKUP == 1 )
{
//me->PickupHostage( me->GetGoalEntity() );
bool bBeingRescued = false;
CHostage *hostage = static_cast<CHostage*>( me->GetGoalEntity() );
if ( hostage && hostage->GetHostageState() != k_EHostageStates_GettingPickedUp &&
hostage->IsFollowingSomeone() == false && me->GetNearbyFriendCount() > 0 )
{
// see if one of my friends if picking up this hostage
for ( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CCSBot *player = dynamic_cast< CCSBot * >( UTIL_PlayerByIndex( i ) );
if ( player == NULL || !player->IsAlive() ||
me->IsOtherEnemy( player ) || player->entindex() == me->entindex() )
continue;
if ( player->IsPickingupHostage() )
{
bBeingRescued = true;
break;
}
}
}
// if not, pick it up
if ( bBeingRescued == false )
me->PickupHostage( me->GetGoalEntity() );
else
{
if ( hostage && me->IsVisible( hostage->GetAbsOrigin(), false, NULL ) )
{
// if we can see the hostage, guard it
me->GetChatter()->Say( "CoveringFriend" );
me->Idle();
}
}
}
else
me->UseEntity( me->GetGoalEntity() );
return;
}
}
}
}
else if (me->GetTask() == CCSBot::RESCUE_HOSTAGES)
{
// periodically check if we lost all our hostages
if (me->GetHostageEscortCount() == 0)
{
// lost our hostages - go get 'em
me->Idle();
return;
}
}
break;
}
}
if (me->UpdatePathMovement() != CCSBot::PROGRESSING)
{
// reached destination
switch( me->GetTask() )
{
case CCSBot::PLANT_BOMB:
// if we are at bombsite with the bomb, plant it
if (me->IsAtBombsite() && me->HasC4())
{
me->PlantBomb();
return;
}
break;
case CCSBot::MOVE_TO_LAST_KNOWN_ENEMY_POSITION:
{
CBasePlayer *victim = static_cast<CBasePlayer *>( me->GetTaskEntity() );
if (victim && victim->IsAlive())
{
// if we got here and haven't re-acquired the enemy, we lost him
BotStatement *say = new BotStatement( me->GetChatter(), REPORT_ENEMY_LOST, 8.0f );
say->AppendPhrase( TheBotPhrases->GetPhrase( "LostEnemy" ) );
say->SetStartTime( gpGlobals->curtime + RandomFloat( 3.0f, 5.0f ) );
me->GetChatter()->AddStatement( say );
}
break;
}
}
// default behavior when destination is reached
me->Idle();
return;
}
}
//--------------------------------------------------------------------------------------------------------------
void MoveToState::OnExit( CCSBot *me )
{
// reset to run in case we were walking near our goal position
me->Run();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
//me->StopAiming();
}

View File

@@ -0,0 +1,120 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), April 2005
#include "cbase.h"
#include "cs_bot.h"
#include "BasePropDoor.h"
#include "doors.h"
#include "props.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-------------------------------------------------------------------------------------------------
/**
* Face the door and open it.
* NOTE: This state assumes we are standing in range of the door to be opened, with no obstructions.
*/
void OpenDoorState::OnEnter( CCSBot *me )
{
m_isDone = false;
m_timeout.Start( 0.5f );
}
//-------------------------------------------------------------------------------------------------
void OpenDoorState::SetDoor( CBaseEntity *door )
{
CBaseDoor *funcDoor = dynamic_cast< CBaseDoor * >(door);
if ( funcDoor )
{
m_funcDoor = funcDoor;
return;
}
CBasePropDoor *propDoor = dynamic_cast< CBasePropDoor * >(door);
if ( propDoor )
{
m_propDoor = propDoor;
return;
}
}
//-------------------------------------------------------------------------------------------------
void OpenDoorState::OnUpdate( CCSBot *me )
{
me->ResetStuckMonitor();
// look at the door
Vector pos;
bool isDoorMoving = false;
CBaseEntity *door = NULL;
if ( m_funcDoor.Get() )
{
door = m_funcDoor;
isDoorMoving = m_funcDoor->m_toggle_state == TS_GOING_UP || m_funcDoor->m_toggle_state == TS_GOING_DOWN;
}
else if ( m_propDoor.Get() )
{
door = m_propDoor;
isDoorMoving = m_propDoor->IsDoorOpening() || m_propDoor->IsDoorClosing();
CPropDoorRotatingBreakable *pPropDoor = dynamic_cast< CPropDoorRotatingBreakable* >( door );
if ( pPropDoor && pPropDoor->IsDoorLocked() && pPropDoor->IsBreakable() == false )
{
m_isDone = true;
return;
}
}
// wait for door to swing open before leaving state
if ( isDoorMoving || !door )
{
m_isDone = true;
return;
}
me->SetLookAt( "Open door", door->WorldSpaceCenter(), PRIORITY_UNINTERRUPTABLE );
// if we are looking at the door, "use" it and exit
if ( me->IsLookingAtPosition( door->WorldSpaceCenter() ) )
{
if ( m_timeout.IsElapsed() )
{
// possibly stuck - blow the damn door away!
me->PrimaryAttack();
if ( door )
{
AssertMsg( door->GetHealth() > 2, "Bot is stuck on a door and is going to destroy it to get free!\n" );
CTakeDamageInfo damageInfo( me, me, 2.0f, DMG_GENERIC );
door->TakeDamage( damageInfo );
}
}
// we are looking at it - use it directly to avoid complications
door->Use( me, me, USE_ON, 0.0f );
}
}
//-------------------------------------------------------------------------------------------------
void OpenDoorState::OnExit( CCSBot *me )
{
me->ClearLookAt();
me->ResetStuckMonitor();
}

View File

@@ -0,0 +1,76 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "weapon_c4.h"
#include "cs_simple_hostage.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Plant the bomb.
*/
void PickupHostageState::OnEnter( CCSBot *me )
{
me->Crouch();
me->SetDisposition( CCSBot::SELF_DEFENSE );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Plant the bomb.
*/
void PickupHostageState::OnUpdate( CCSBot *me )
{
const float timeout = 7.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > timeout)
{
// find a new spot
me->Idle();
return;
}
// look at the entity
Vector pos = m_entity->EyePosition();
me->SetLookAt( "Use entity", pos, PRIORITY_HIGH );
// if we are looking at the entity, "use" it and exit
if (me->IsLookingAtPosition( pos ))
{
me->UseEnvironment();
}
CHostage *hostage = assert_cast<CHostage *>( m_entity.Get() );
if ( hostage && hostage->IsFollowingSomeone() )
{
me->Idle();
return;
}
}
//--------------------------------------------------------------------------------------------------------------
void PickupHostageState::OnExit( CCSBot *me )
{
// equip our rifle (in case we were interrupted while holding C4)
me->EquipBestWeapon();
me->StandUp();
me->ResetStuckMonitor();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->ClearLookAt();
CHostage *hostage = assert_cast<CHostage *>( m_entity.Get() );
if ( hostage && hostage->IsFollowing( me ) )
me->IncreaseHostageEscortCount();
}

View File

@@ -0,0 +1,98 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
#include "weapon_c4.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Plant the bomb.
*/
void PlantBombState::OnEnter( CCSBot *me )
{
me->Crouch();
me->SetDisposition( CCSBot::SELF_DEFENSE );
// look at the floor
// Vector down( myOrigin.x, myOrigin.y, -1000.0f );
float yaw = me->EyeAngles().y;
Vector2D dir( BotCOS(yaw), BotSIN(yaw) );
Vector myOrigin = GetCentroid( me );
Vector down( myOrigin.x + 10.0f * dir.x, myOrigin.y + 10.0f * dir.y, me->GetFeetZ() );
me->SetLookAt( "Plant bomb on floor", down, PRIORITY_HIGH );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Plant the bomb.
*/
void PlantBombState::OnUpdate( CCSBot *me )
{
// can't be stuck while planting
me->ResetStuckMonitor();
CBaseCombatWeapon *gun = me->GetActiveWeapon();
bool holdingC4 = false;
if (gun)
{
if (FStrEq( gun->GetClassname(), "weapon_c4" ))
holdingC4 = true;
}
// if we aren't holding the C4, grab it, otherwise plant it
if ( holdingC4 )
{
me->PrimaryAttack();
CC4 *pC4 = dynamic_cast< CC4 * >( gun );
if ( pC4 && !pC4->m_bStartedArming && gpGlobals->curtime - me->GetStateTimestamp() > 2.0f )
{
// can't plant here for some reason - try another spot
me->Idle();
return;
}
}
else
{
me->SelectItem( "weapon_c4" );
}
// if we no longer have the C4, we've successfully planted
if (!me->HasC4())
{
// move to a hiding spot and watch the bomb
me->SetTask( CCSBot::GUARD_TICKING_BOMB );
me->Hide();
}
// if we time out, it's because we slipped into a non-plantable area
const float timeout = 5.0f;
if (gpGlobals->curtime - me->GetStateTimestamp() > timeout)
{
// find a new spot
me->Idle();
}
}
//--------------------------------------------------------------------------------------------------------------
void PlantBombState::OnExit( CCSBot *me )
{
// equip our rifle (in case we were interrupted while holding C4)
me->EquipBestWeapon();
me->StandUp();
me->ResetStuckMonitor();
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->ClearLookAt();
}

View File

@@ -0,0 +1,71 @@
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_bot.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
/**
* Face the entity and "use" it
* NOTE: This state assumes we are standing in range of the entity to be used, with no obstructions.
*/
void UseEntityState::OnEnter( CCSBot *me )
{
}
void UseEntityState::OnUpdate( CCSBot *me )
{
// in the case when an entity we were supposed to +use is deleted we shouldn't try to
// dereference a NULL pointer and should just go to a higher level objective
if ( !m_entity.Get() )
{
me->Idle();
return;
}
// in the very rare situation where two or more bots "used" a hostage at the same time,
// one bot will fail and needs to time out of this state
const float useTimeout = 5.0f;
if (me->GetStateTimestamp() - gpGlobals->curtime > useTimeout)
{
me->Idle();
return;
}
// look at the entity
Vector pos = m_entity->EyePosition();
me->SetLookAt( "Use entity", pos, PRIORITY_HIGH );
// if we are looking at the entity, "use" it and exit
if (me->IsLookingAtPosition( pos ))
{
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_RESCUE_HOSTAGES &&
me->GetTeamNumber() == TEAM_CT &&
me->GetTask() == CCSBot::COLLECT_HOSTAGES)
{
// we are collecting a hostage, assume we were successful - the update check will correct us if we weren't
me->IncreaseHostageEscortCount();
}
me->UseEnvironment();
me->Idle();
}
}
void UseEntityState::OnExit( CCSBot *me )
{
me->ClearLookAt();
me->ResetStuckMonitor();
}