initial
This commit is contained in:
1230
game/server/cstrike15/bot/cs_bot.cpp
Normal file
1230
game/server/cstrike15/bot/cs_bot.cpp
Normal file
File diff suppressed because it is too large
Load Diff
2152
game/server/cstrike15/bot/cs_bot.h
Normal file
2152
game/server/cstrike15/bot/cs_bot.h
Normal file
File diff suppressed because it is too large
Load Diff
2679
game/server/cstrike15/bot/cs_bot_chatter.cpp
Normal file
2679
game/server/cstrike15/bot/cs_bot_chatter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
676
game/server/cstrike15/bot/cs_bot_chatter.h
Normal file
676
game/server/cstrike15/bot/cs_bot_chatter.h
Normal 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
|
||||
82
game/server/cstrike15/bot/cs_bot_chatter_coop.cpp
Normal file
82
game/server/cstrike15/bot/cs_bot_chatter_coop.cpp
Normal 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 );
|
||||
}
|
||||
480
game/server/cstrike15/bot/cs_bot_event.cpp
Normal file
480
game/server/cstrike15/bot/cs_bot_event.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
165
game/server/cstrike15/bot/cs_bot_event_bomb.cpp
Normal file
165
game/server/cstrike15/bot/cs_bot_event_bomb.cpp
Normal 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 );
|
||||
}
|
||||
|
||||
|
||||
|
||||
238
game/server/cstrike15/bot/cs_bot_event_player.cpp
Normal file
238
game/server/cstrike15/bot/cs_bot_event_player.cpp
Normal 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
|
||||
}
|
||||
|
||||
|
||||
156
game/server/cstrike15/bot/cs_bot_event_weapon.cpp
Normal file
156
game/server/cstrike15/bot/cs_bot_event_weapon.cpp
Normal 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
|
||||
}
|
||||
|
||||
|
||||
|
||||
545
game/server/cstrike15/bot/cs_bot_init.cpp
Normal file
545
game/server/cstrike15/bot/cs_bot_init.cpp
Normal 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() );
|
||||
}
|
||||
|
||||
217
game/server/cstrike15/bot/cs_bot_listen.cpp
Normal file
217
game/server/cstrike15/bot/cs_bot_listen.cpp
Normal 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;
|
||||
}
|
||||
|
||||
2764
game/server/cstrike15/bot/cs_bot_manager.cpp
Normal file
2764
game/server/cstrike15/bot/cs_bot_manager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
452
game/server/cstrike15/bot/cs_bot_manager.h
Normal file
452
game/server/cstrike15/bot/cs_bot_manager.h
Normal 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
|
||||
985
game/server/cstrike15/bot/cs_bot_nav.cpp
Normal file
985
game/server/cstrike15/bot/cs_bot_nav.cpp
Normal 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;
|
||||
}
|
||||
|
||||
2097
game/server/cstrike15/bot/cs_bot_pathfind.cpp
Normal file
2097
game/server/cstrike15/bot/cs_bot_pathfind.cpp
Normal file
File diff suppressed because it is too large
Load Diff
380
game/server/cstrike15/bot/cs_bot_radio.cpp
Normal file
380
game/server/cstrike15/bot/cs_bot_radio.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
732
game/server/cstrike15/bot/cs_bot_statemachine.cpp
Normal file
732
game/server/cstrike15/bot/cs_bot_statemachine.cpp
Normal 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;
|
||||
}
|
||||
|
||||
1326
game/server/cstrike15/bot/cs_bot_update.cpp
Normal file
1326
game/server/cstrike15/bot/cs_bot_update.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1956
game/server/cstrike15/bot/cs_bot_vision.cpp
Normal file
1956
game/server/cstrike15/bot/cs_bot_vision.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1457
game/server/cstrike15/bot/cs_bot_weapon.cpp
Normal file
1457
game/server/cstrike15/bot/cs_bot_weapon.cpp
Normal file
File diff suppressed because it is too large
Load Diff
22
game/server/cstrike15/bot/cs_bot_weapon_id.cpp
Normal file
22
game/server/cstrike15/bot/cs_bot_weapon_id.cpp
Normal 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
|
||||
//
|
||||
777
game/server/cstrike15/bot/cs_gamestate.cpp
Normal file
777
game/server/cstrike15/bot/cs_gamestate.cpp
Normal 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;
|
||||
}
|
||||
|
||||
151
game/server/cstrike15/bot/cs_gamestate.h
Normal file
151
game/server/cstrike15/bot/cs_gamestate.h
Normal 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_
|
||||
732
game/server/cstrike15/bot/states/cs_bot_attack.cpp
Normal file
732
game/server/cstrike15/bot/states/cs_bot_attack.cpp
Normal 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();
|
||||
}
|
||||
|
||||
703
game/server/cstrike15/bot/states/cs_bot_buy.cpp
Normal file
703
game/server/cstrike15/bot/states/cs_bot_buy.cpp
Normal 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();
|
||||
}
|
||||
|
||||
83
game/server/cstrike15/bot/states/cs_bot_defuse_bomb.cpp
Normal file
83
game/server/cstrike15/bot/states/cs_bot_defuse_bomb.cpp
Normal 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();
|
||||
}
|
||||
67
game/server/cstrike15/bot/states/cs_bot_escape_from_bomb.cpp
Normal file
67
game/server/cstrike15/bot/states/cs_bot_escape_from_bomb.cpp
Normal 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();
|
||||
}
|
||||
117
game/server/cstrike15/bot/states/cs_bot_escape_from_flames.cpp
Normal file
117
game/server/cstrike15/bot/states/cs_bot_escape_from_flames.cpp
Normal 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();
|
||||
}
|
||||
69
game/server/cstrike15/bot/states/cs_bot_fetch_bomb.cpp
Normal file
69
game/server/cstrike15/bot/states/cs_bot_fetch_bomb.cpp
Normal 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();
|
||||
}
|
||||
366
game/server/cstrike15/bot/states/cs_bot_follow.cpp
Normal file
366
game/server/cstrike15/bot/states/cs_bot_follow.cpp
Normal 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 )
|
||||
{
|
||||
}
|
||||
549
game/server/cstrike15/bot/states/cs_bot_hide.cpp
Normal file
549
game/server/cstrike15/bot/states/cs_bot_hide.cpp
Normal 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();
|
||||
}
|
||||
237
game/server/cstrike15/bot/states/cs_bot_hunt.cpp
Normal file
237
game/server/cstrike15/bot/states/cs_bot_hunt.cpp
Normal 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 )
|
||||
{
|
||||
}
|
||||
909
game/server/cstrike15/bot/states/cs_bot_idle.cpp
Normal file
909
game/server/cstrike15/bot/states/cs_bot_idle.cpp
Normal 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();
|
||||
}
|
||||
|
||||
259
game/server/cstrike15/bot/states/cs_bot_idle_coop.cpp
Normal file
259
game/server/cstrike15/bot/states/cs_bot_idle_coop.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
game/server/cstrike15/bot/states/cs_bot_investigate_noise.cpp
Normal file
139
game/server/cstrike15/bot/states/cs_bot_investigate_noise.cpp
Normal 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();
|
||||
}
|
||||
408
game/server/cstrike15/bot/states/cs_bot_move_to.cpp
Normal file
408
game/server/cstrike15/bot/states/cs_bot_move_to.cpp
Normal 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();
|
||||
}
|
||||
120
game/server/cstrike15/bot/states/cs_bot_open_door.cpp
Normal file
120
game/server/cstrike15/bot/states/cs_bot_open_door.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
76
game/server/cstrike15/bot/states/cs_bot_pickup_hostage.cpp
Normal file
76
game/server/cstrike15/bot/states/cs_bot_pickup_hostage.cpp
Normal 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();
|
||||
}
|
||||
98
game/server/cstrike15/bot/states/cs_bot_plant_bomb.cpp
Normal file
98
game/server/cstrike15/bot/states/cs_bot_plant_bomb.cpp
Normal 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();
|
||||
}
|
||||
71
game/server/cstrike15/bot/states/cs_bot_use_entity.cpp
Normal file
71
game/server/cstrike15/bot/states/cs_bot_use_entity.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user