This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+656
View File
@@ -0,0 +1,656 @@
//========= Copyright 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
const Vector *GetRandomSpotAtPlace( Place place );
//----------------------------------------------------------------------------------------------------
/**
* 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
// It is a best practice to always have a virtual destructor in an interface
// class. Otherwise if the derived classes have destructors they will not be
// called.
virtual ~BotMeme() {}
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
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
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;
};
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;
}
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();
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 );
~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 );
void EnemySpotted( void ); ///< report enemy sightings
void KilledMyEnemy( int victimID );
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
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 );
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( 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
};
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 );
}
#endif // CS_BOT_CHATTER_H
+427
View File
@@ -0,0 +1,427 @@
//========= Copyright 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
if (!IsEnemy( player ))
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->GetName() )) ? "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
m_noiseTravelDistance = GetTravelDistanceToPlayer( (CCSPlayer *)player );
// 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::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 )
{
// 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;
}
}
@@ -0,0 +1,158 @@
//========= Copyright 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;
// don't react to our own events
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
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 );
}
@@ -0,0 +1,227 @@
//========= Copyright 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 && !victim->IsBot() && victim->GetTeamNumber() == GetTeamNumber())
{
GetChatter()->Say( "CommanderDown", 20.0f );
}
// keep track of the last player we killed
if (killer == this)
{
m_lastVictimID = victim ? victim->entindex() : 0;
}
// react to teammate death
if (victim && 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 (IsAttacking())
{
if (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 = (victim) ? GetCentroid( victim ) : Vector( 0, 0, 0 );
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)
{
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
}
@@ -0,0 +1,167 @@
//========= Copyright 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 (GetEnemy() == 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.
CWeaponCSBase *weapon = (CWeaponCSBase *)((player)?player->GetActiveWeapon():NULL);
if (weapon == NULL)
return;
switch( weapon->GetWeaponID() )
{
// silent "firing"
case WEAPON_HEGRENADE:
case WEAPON_SMOKEGRENADE:
case WEAPON_FLASHBANG:
case WEAPON_SHIELDGUN:
case WEAPON_C4:
return;
// quiet
case WEAPON_KNIFE:
case WEAPON_TMP:
range = ShortRange;
break;
// M4A1 - check for silencer
case WEAPON_M4A1:
{
if (weapon->IsSilenced())
{
range = ShortRange;
}
else
{
range = NormalRange;
}
break;
}
// USP - check for silencer
case WEAPON_USP:
{
if (weapon->IsSilenced())
{
range = ShortRange;
}
else
{
range = NormalRange;
}
break;
}
// loud
case WEAPON_AWP:
range = 99999.0f;
break;
// normal
default:
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 (GetEnemy() == 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 (GetEnemy() == 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
}
+359
View File
@@ -0,0 +1,359 @@
//========= Copyright 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"
// 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
//--------------------------------------------------------------------------------------------------------------
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, "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", "0", FCVAR_REPLICATED | FCVAR_NOTIFY, "Determines the total number of bots in the game." );
ConVar cv_bot_quota_mode( "bot_quota_mode", "normal", FCVAR_REPLICATED, "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_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_REPLICATED, "If nonzero, bots may use pistols." );
ConVar cv_bot_allow_shotguns( "bot_allow_shotguns", "1", FCVAR_REPLICATED, "If nonzero, bots may use shotguns." );
ConVar cv_bot_allow_sub_machine_guns( "bot_allow_sub_machine_guns", "1", FCVAR_REPLICATED, "If nonzero, bots may use sub-machine guns." );
ConVar cv_bot_allow_rifles( "bot_allow_rifles", "1", FCVAR_REPLICATED, "If nonzero, bots may use rifles." );
ConVar cv_bot_allow_machine_guns( "bot_allow_machine_guns", "1", FCVAR_REPLICATED, "If nonzero, bots may use the machine gun." );
ConVar cv_bot_allow_grenades( "bot_allow_grenades", "1", FCVAR_REPLICATED, "If nonzero, bots may use grenades." );
ConVar cv_bot_allow_snipers( "bot_allow_snipers", "1", 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, "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, "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( "bot_defer_to_human", "0", FCVAR_REPLICATED, "If nonzero and there is a human on the team, the bots will not do the scenario tasks." );
ConVar cv_bot_chatter( "bot_chatter", "normal", FCVAR_REPLICATED, "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_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 | FCVAR_CHEAT, "If nonzero, bots use no CPU for AI. Instead, they run around randomly." );
extern void FinishClientPutInServer( CCSPlayer *pPlayer );
//--------------------------------------------------------------------------------------------------------------
// Engine callback for custom server commands
void Bot_ServerCommand( void )
{
}
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor
*/
CCSBot::CCSBot( void ) : m_chatter( this ), m_gameState( this )
{
m_hasJoined = false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Destructor
*/
CCSBot::~CCSBot()
{
}
//--------------------------------------------------------------------------------------------------------------
/**
* Prepare bot for action
*/
bool CCSBot::Initialize( const BotProfile *profile, int team )
{
// extend
BaseClass::Initialize( profile, team );
// CS bot initialization
m_diedLastRound = false;
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 );
int desiredClass = GetProfile()->GetSkin();
if ( m_desiredTeam == TEAM_CT && desiredClass )
{
desiredClass = FIRST_CT_CLASS + desiredClass - 1;
}
else if ( m_desiredTeam == TEAM_TERRORIST && desiredClass )
{
desiredClass = FIRST_T_CLASS + desiredClass - 1;
}
HandleCommand_JoinClass( desiredClass );
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset internal data to initial state
*/
void CCSBot::ResetValues( void )
{
m_chatter.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_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_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_aimOffsetTimestamp = 0.0f;
m_aimSpreadTimestamp = 0.0f;
m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
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;
}
// 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 )
{
// do the normal player spawn process
BaseClass::Spawn();
ResetValues();
V_strcpy_safe( m_name, GetPlayerName() );
Buy();
}
+230
View File
@@ -0,0 +1,230 @@
//========= Copyright 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;
// chance of investigating is inversely proportional to distance
const float maxNoiseDist = 3000.0f;
float chance = 100.0f * (1.0f - (GetNoiseRange()/maxNoiseDist));
// modify chance by number of friends remaining
// if we have lots of friends, presumably one of them is closer and will check it out
if (GetFriendsRemaining() >= 3)
{
float friendFactor = 5.0f * GetFriendsRemaining();
if (friendFactor > 50.0f)
friendFactor = 50.0f;
chance -= friendFactor;
}
if (RandomFloat( 0.0f, 100.0f ) <= chance)
{
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we hear nearby threatening enemy gunfire within given range
* -1 == infinite range
*/
bool CCSBot::CanHearNearbyEnemyGunfire( float range ) const
{
Vector myOrigin = GetCentroid( this );
// only attend to noise if it just happened
if (gpGlobals->curtime - m_noiseTimestamp > 0.5f)
return false;
// gunfire is high priority
if (m_noisePriority < PRIORITY_HIGH)
return false;
// check noise range
if (range > 0.0f && (myOrigin - m_noisePosition).IsLengthGreaterThan( range ))
return false;
// if we dont have line of sight, it's not threatening (cant get shot)
if (!CanSeeNoisePosition())
return false;
if (IsAttacking() && m_enemy != NULL && GetTimeSinceLastSawEnemy() < 1.0f)
{
// gunfire is only threatening if it is closer than our current enemy
float gunfireDistSq = (m_noisePosition - myOrigin).LengthSqr();
float enemyDistSq = (GetCentroid( m_enemy ) - myOrigin).LengthSqr();
const float muchCloserSq = 100.0f * 100.0f;
if (gunfireDistSq > enemyDistSq - muchCloserSq)
return false;
}
return true;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we directly see where we think the noise came from
* NOTE: Dont check FOV, since this is used to determine if we should turn our head to look at the noise
* NOTE: Dont use IsVisible(), because smoke shouldnt cause us to not look toward noises
*/
bool CCSBot::CanSeeNoisePosition( void ) const
{
trace_t result;
CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
UTIL_TraceLine( EyePositionConst(), m_noisePosition + Vector( 0, 0, HalfHumanHeight ), MASK_VISIBLE_AND_NPCS, &traceFilter, &result );
if (result.fraction == 1.0f)
{
// we can see the source of the noise
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we decided to look towards the most recent noise source
* Assumes m_noisePosition is valid.
*/
bool CCSBot::UpdateLookAtNoise( void )
{
// make sure a noise exists
if (!IsNoiseHeard())
{
return false;
}
Vector spot;
// if we have clear line of sight to noise position, look directly at it
if (CanSeeNoisePosition())
{
/// @todo adjust noise Z to keep consistent with current height while fighting
spot = m_noisePosition + Vector( 0, 0, HalfHumanHeight );
// since we can see the noise spot, forget about it
ForgetNoise();
}
else
{
// line of sight is blocked, bend it
// the bending algorithm is very expensive, throttle how often it is done
if (m_noiseBendTimer.IsElapsed())
{
const float noiseBendLOSInterval = RandomFloat( 0.2f, 0.3f );
m_noiseBendTimer.Start( noiseBendLOSInterval );
// line of sight is blocked, bend it
if (BendLineOfSight( EyePosition(), m_noisePosition, &spot ) == false)
{
m_bendNoisePositionValid = false;
return false;
}
m_bentNoisePosition = spot;
m_bendNoisePositionValid = true;
}
else if (m_bendNoisePositionValid)
{
// use result of prior bend computation
spot = m_bentNoisePosition;
}
else
{
// prior bend failed
return false;
}
}
// it's always important to look at enemy noises, because they come from ... enemies!
PriorityType pri = PRIORITY_HIGH;
// look longer if we're hiding
if (IsAtHidingSpot())
{
// if there is only one enemy left, look for a long time
if (GetEnemiesRemaining() == 1)
{
SetLookAt( "Noise", spot, pri, RandomFloat( 5.0f, 15.0f ), true );
}
else
{
SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
}
}
else
{
const float closeRange = 500.0f;
if (GetNoiseRange() < closeRange)
{
// look at nearby enemy noises for a longer time
SetLookAt( "Noise", spot, pri, RandomFloat( 3.0f, 5.0f ), true );
}
else
{
SetLookAt( "Noise", spot, pri, RandomFloat( 1.0f, 2.0f ), true );
}
}
return true;
}
File diff suppressed because it is too large Load Diff
+400
View File
@@ -0,0 +1,400 @@
//========= Copyright 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 friendlyfire;
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 ); \
} \
}; \
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
*/
#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 = dynamic_cast< CCSBot * >(player); \
if ( !bot ) continue; \
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 )
{
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 &m_zone[i]; }
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 );
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 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
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
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
// 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( 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
+963
View File
@@ -0,0 +1,963 @@
//========= Copyright 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 )
{
#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;
}
}
}
shouldShoot = shouldShoot && !IsFriendInLineOfFire();
if ( shouldShoot )
{
PrimaryAttack();
}
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Check for doors that need +use to open.
*/
void CCSBot::DoorCheck( void )
{
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_HIGH ) )
{
if ( !IsOpeningDoor() )
{
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_areaEnteredTimestamp = gpGlobals->curtime;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Test if we have become stuck
*/
void CCSBot::StuckCheck( void )
{
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
float stuckVel = (IsUsingLadder()) ? 10.0f : 20.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.0f && 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)) &&
!IsOnLadder())
{
float ground;
Vector aheadRay( pos.x - myOrigin.x, pos.y - myOrigin.y, 0 );
aheadRay.NormalizeInPlace();
// look far ahead to allow us to smoothly jump over gaps, ledges, etc
// only jump if ground is flat at lookahead spot to avoid jumping up slopes
bool jumped = false;
if (IsRunning())
{
const float farLookAheadRange = 80.0f; // 60
Vector normal;
Vector stepAhead = myOrigin + farLookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground, &normal ))
{
if (normal.z > 0.9f)
jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN );
}
}
if (!jumped)
{
// close up jumping
const float lookAheadRange = 30.0f; // cant be less or will miss jumps over low walls
Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
{
jumped = DiscontinuityJump( ground );
}
}
if (!jumped)
{
// about to fall gap-jumping
const float lookAheadRange = 10.0f;
Vector stepAhead = myOrigin + lookAheadRange * aheadRay;
stepAhead.z += HalfHumanHeight;
if (GetSimpleGroundHeightWithFloor( stepAhead, &ground ))
{
jumped = DiscontinuityJump( ground, ONLY_JUMP_DOWN, MUST_JUMP );
}
}
}
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - myOrigin.x, pos.y - myOrigin.y );
to.NormalizeInPlace();
// move towards the position independant of our view direction
float toProj = to.x * dir.x + to.y * dir.y;
float latProj = to.x * lat.x + to.y * lat.y;
const float c = 0.25f; // 0.5
if (toProj > c)
MoveForward();
else if (toProj < -c)
MoveBackward();
// if we are avoiding someone via strafing, don't override
if (m_avoid != NULL)
return;
if (latProj >= c)
StrafeLeft();
else if (latProj <= -c)
StrafeRight();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Move away from position, independant of view angle
*/
void CCSBot::MoveAwayFromPosition( const Vector &pos )
{
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
to.NormalizeInPlace();
// move away from the position independant of our view direction
float toProj = to.x * dir.x + to.y * dir.y;
float latProj = to.x * lat.x + to.y * lat.y;
const float c = 0.5f;
if (toProj > c)
MoveBackward();
else if (toProj < -c)
MoveForward();
if (latProj >= c)
StrafeRight();
else if (latProj <= -c)
StrafeLeft();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Strafe (sidestep) away from position, independant of view angle
*/
void CCSBot::StrafeAwayFromPosition( const Vector &pos )
{
// compute our current forward and lateral vectors
float angle = EyeAngles().y;
Vector2D dir( BotCOS(angle), BotSIN(angle) );
Vector2D lat( -dir.y, dir.x );
// compute unit vector to goal position
Vector2D to( pos.x - GetAbsOrigin().x, pos.y - GetAbsOrigin().y );
to.NormalizeInPlace();
float latProj = to.x * lat.x + to.y * lat.y;
if (latProj >= 0.0f)
StrafeRight();
else
StrafeLeft();
}
//--------------------------------------------------------------------------------------------------------------
/**
* For getting un-stuck
*/
void CCSBot::Wiggle( void )
{
if (IsCrouching())
{
return;
}
// for wiggling
if (m_wiggleTimer.IsElapsed())
{
m_wiggleDirection = (NavRelativeDirType)RandomInt( 0, 3 );
m_wiggleTimer.Start( RandomFloat( 0.3f, 0.5f ) ); // 0.3, 0.5
}
Vector forward, right;
EyeVectors( &forward, &right );
const float lookAheadRange = (m_lastKnownArea && (m_lastKnownArea->GetAttributes() & NAV_MESH_WALK)) ? 5.0f : 30.0f;
float ground;
switch( m_wiggleDirection )
{
case LEFT:
{
// don't move left if we will fall
Vector pos = GetAbsOrigin() - (lookAheadRange * right);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
StrafeLeft();
}
}
break;
}
case RIGHT:
{
// don't move right if we will fall
Vector pos = GetAbsOrigin() + (lookAheadRange * right);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
StrafeRight();
}
}
break;
}
case FORWARD:
{
// don't move forward if we will fall
Vector pos = GetAbsOrigin() + (lookAheadRange * forward);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
MoveForward();
}
}
break;
}
case BACKWARD:
{
// don't move backward if we will fall
Vector pos = GetAbsOrigin() - (lookAheadRange * forward);
if (GetSimpleGroundHeightWithFloor( pos, &ground ))
{
if (GetAbsOrigin().z - ground < StepHeight)
{
MoveBackward();
}
}
break;
}
}
if (m_stuckJumpTimer.IsElapsed() && m_lastKnownArea && !(m_lastKnownArea->GetAttributes() & NAV_MESH_NO_JUMP))
{
if (Jump())
{
m_stuckJumpTimer.Start( RandomFloat( 1.0f, 2.0f ) );
}
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Determine approach points from eye position and approach areas of current area
*/
void CCSBot::ComputeApproachPoints( void )
{
m_approachPointCount = 0;
if (m_lastKnownArea == NULL)
{
return;
}
// assume we're crouching for now
Vector eye = GetCentroid( this ); // + pev->view_ofs; // eye position
Vector ap;
float halfWidth;
for( int i=0; i<m_lastKnownArea->GetApproachInfoCount() && m_approachPointCount < MAX_APPROACH_POINTS; ++i )
{
const CCSNavArea::ApproachInfo *info = m_lastKnownArea->GetApproachInfo( i );
if (info->here.area == NULL || info->prev.area == NULL)
{
continue;
}
// compute approach point (approach area is "info->here")
if (info->prevToHereHow <= GO_WEST)
{
info->prev.area->ComputePortal( info->here.area, (NavDirType)info->prevToHereHow, &ap, &halfWidth );
ap.z = info->here.area->GetZ( ap );
}
else
{
// use the area's center as an approach point
ap = info->here.area->GetCenter();
}
// "bend" our line of sight around corners until we can see the approach point
Vector bendPoint;
if (BendLineOfSight( eye, ap + Vector( 0, 0, HalfHumanHeight ), &bendPoint ))
{
// put point on the ground
if (TheNavMesh->GetGroundHeight( bendPoint, &bendPoint.z ) == false)
{
bendPoint.z = ap.z;
}
m_approachPoint[ m_approachPointCount ].m_pos = bendPoint;
m_approachPoint[ m_approachPointCount ].m_area = info->here.area;
++m_approachPointCount;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void CCSBot::DrawApproachPoints( void ) const
{
for( int i=0; i<m_approachPointCount; ++i )
{
if (TheCSBots()->GetElapsedRoundTime() >= m_approachPoint[i].m_area->GetEarliestOccupyTime( OtherTeam( GetTeamNumber() ) ))
NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 255, 0, 255, true, 0.1f );
else
NDebugOverlay::Cross3D( m_approachPoint[i].m_pos, 10.0f, 100, 100, 100, true, 0.1f );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Find the approach point that is nearest to our current path, ahead of us
*/
bool CCSBot::FindApproachPointNearestPath( Vector *pos )
{
if (!HasPath())
return false;
// make sure approach points are accurate
ComputeApproachPoints();
if (m_approachPointCount == 0)
return false;
Vector target = Vector( 0, 0, 0 ), close;
float targetRangeSq = 0.0f;
bool found = false;
int start = m_pathIndex;
int end = m_pathLength;
//
// We dont want the strictly closest point, but the farthest approach point
// from us that is near our path
//
const float nearPathSq = 10000.0f; // (100)
for( int i=0; i<m_approachPointCount; ++i )
{
if (FindClosestPointOnPath( m_approachPoint[i].m_pos, start, end, &close ) == false)
continue;
float rangeSq = (m_approachPoint[i].m_pos - close).LengthSqr();
if (rangeSq > nearPathSq)
continue;
if (rangeSq > targetRangeSq)
{
target = close;
targetRangeSq = rangeSq;
found = true;
}
}
if (found)
{
*pos = target + Vector( 0, 0, HalfHumanHeight );
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if we are at the/an enemy spawn right now
*/
bool CCSBot::IsAtEnemySpawn( void ) const
{
CBaseEntity *spot;
const char *spawnName = (GetTeamNumber() == TEAM_TERRORIST) ? "info_player_counterterrorist" : "info_player_terrorist";
// check if we are at any of the enemy's spawn points
for( spot = gEntList.FindEntityByClassname( NULL, spawnName ); spot; spot = gEntList.FindEntityByClassname( spot, spawnName ) )
{
CNavArea *area = TheNavMesh->GetNearestNavArea( spot->WorldSpaceCenter() );
if (area && GetLastKnownArea() == area)
{
return true;
}
}
return false;
}
File diff suppressed because it is too large Load Diff
+346
View File
@@ -0,0 +1,346 @@
//========= Copyright 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"
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
const Vector *pos = GetRandomSpotAtPlace( place );
if (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 - RADIO_START_1 );
}
else if (event > RADIO_START_2 && event < RADIO_START_3)
{
HandleMenu_Radio2( event - RADIO_START_2 );
}
else
{
HandleMenu_Radio3( event - RADIO_START_3 );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* 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 );
UserMessageBegin ( filter, "RawAudio" );
WRITE_BYTE( pitch );
WRITE_BYTE( entindex() );
WRITE_FLOAT( duration );
WRITE_STRING( voiceFilename );
MessageEnd();
GetChatter()->ResetRadioSilenceDuration();
m_voiceEndTimestamp = gpGlobals->curtime + duration;
}
@@ -0,0 +1,693 @@
//========= Copyright 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::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 )
{
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 )
{
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" );
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 )
{
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 we're holding a grenade, throw it at the victim
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_aimSpreadTimestamp = gpGlobals->curtime;
// compute the angle difference between where are looking, and where we need to look
Vector toEnemy = victimOrigin - GetCentroid( this );
QAngle idealAngle;
VectorAngles( toEnemy, idealAngle );
float deltaYaw = (float)fabs(m_lookYaw - idealAngle.y);
while( deltaYaw > 180.0f )
deltaYaw -= 360.0f;
if (deltaYaw < 0.0f)
deltaYaw = -deltaYaw;
// immediately aim at enemy - accuracy penalty depending on how far we must turn to aim
// accuracy is halved if we have to turn 180 degrees
float turn = deltaYaw / 180.0f;
float accuracy = GetProfile()->GetSkill() / (1.0f + turn);
SetAimOffset( accuracy );
// define time when aim offset will automatically be updated
// longer time the more we had to turn (surprise)
m_aimOffsetTimestamp = gpGlobals->curtime + RandomFloat( 0.25f + turn, 1.5f );
// 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 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 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;
}
//--------------------------------------------------------------------------------------------------------------
/**
* 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 );
}
//--------------------------------------------------------------------------------------------------------------
/**
* 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;
}
// build a path from us to the enemy spawn
CCSNavPath path;
PathCost cost( this, FASTEST_ROUTE );
path.Compute( WorldSpaceCenter(), enemySpawn->GetAbsOrigin(), cost );
if (!path.IsValid())
{
PrintIfWatched( "MoveToInitialEncounter: Pathfind failed.\n" );
return false;
}
// find battlefront area where teams will first meet along this path
int i;
for( i=0; i<path.GetSegmentCount(); ++i )
{
if (path[i]->area->GetEarliestOccupyTime( myTeam ) > path[i]->area->GetEarliestOccupyTime( enemyTeam ))
{
break;
}
}
if (i == path.GetSegmentCount())
{
PrintIfWatched( "MoveToInitialEncounter: Can't find battlefront!\n" );
return false;
}
/// @todo Remove this evil side-effect
SetInitialEncounterArea( path[i]->area );
// find a hiding spot on our side of the battlefront that has LOS to it
const float maxRange = 1500.0f;
const HidingSpot *spot = FindInitialEncounterSpot( this, path[i]->area->GetCenter(), path[i]->area->GetEarliestOccupyTime( enemyTeam ), maxRange, IsSniper() );
if (spot == NULL)
{
PrintIfWatched( "MoveToInitialEncounter: Can't find a hiding spot\n" );
return false;
}
float timeToWait = path[i]->area->GetEarliestOccupyTime( enemyTeam ) - spot->GetArea()->GetEarliestOccupyTime( myTeam );
float minWaitTime = 4.0f * GetProfile()->GetAggression() + 3.0f;
if (timeToWait < minWaitTime)
{
timeToWait = minWaitTime;
}
Hide( spot->GetPosition(), timeToWait );
return true;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
//========= Copyright 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
//
+767
View File
@@ -0,0 +1,767 @@
//========= Copyright 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 -----------------------------------------------------------------------
m_bombState = MOVING;
m_lastSawBomber.Invalidate();
m_lastSawLooseBomb.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 (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;
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
View File
@@ -0,0 +1,151 @@
//========= Copyright 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
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 GetBombState( void ) const { return m_bombState; }
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_
@@ -0,0 +1,710 @@
//========= Copyright 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 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)
{
const float crouchFarRange = 750.0f;
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());
}
//--------------------------------------------------------------------------------------------------------------
/**
* 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();
const float hysterisRange = 125.0f; // (+/-) m_combatRange
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
const float dodgeRange = 2000.0f;
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)
const float jumpChance = 33.3f;
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 );
const float lookAheadRange = 30.0f;
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)
{
if (weapon->GetWeaponID() == WEAPON_C4 ||
weapon->GetWeaponID() == WEAPON_HEGRENADE ||
weapon->GetWeaponID() == WEAPON_FLASHBANG ||
weapon->GetWeaponID() == WEAPON_SMOKEGRENADE)
{
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
const float slashRange = 70.0f;
if ((enemy->GetAbsOrigin() - me->GetAbsOrigin()).IsLengthGreaterThan( slashRange ))
{
const float repathInterval = 0.5f;
// if our victim has moved, repath
bool repath = false;
if (me->HasPath())
{
const float repathRange = 100.0f; // 50
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
const float sniperMinRange = 160.0f; // NOTE: Must be larger than NO_ZOOM range in AdjustZoom()
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
const float shotgunMaxRange = 600.0f;
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_aimSpot - 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 (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 );
}
//--------------------------------------------------------------------------------------------------------------
/**
* 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();
}
@@ -0,0 +1,690 @@
//========= Copyright 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?" );
//--------------------------------------------------------------------------------------------------------------
/**
* 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 )
{
DropPistol();
}
else if ( pWeaponInfo->iSlot == WEAPON_SLOT_RIFLE )
{
DropRifle();
}
}
}
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_USP ))
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->m_iAccount < 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_buyDefuseKit = false;
m_buyShield = false;
if (me->GetTeamNumber() == TEAM_CT)
{
if (TheCSBots()->GetScenario() == CCSBotManager::SCENARIO_DEFUSE_BOMB)
{
// CT's sometimes buy defuse kits in the bomb scenario (except in career mode, where the player should defuse)
if (CSGameRules()->IsCareer() == false)
{
const float buyDefuseKitChance = 100.0f * (me->GetProfile()->GetSkill() + 0.2f);
if (RandomFloat( 0.0f, 100.0f ) < buyDefuseKitChance)
{
m_buyDefuseKit = true;
}
}
}
// determine if we want a tactical shield
if (!me->HasPrimaryWeapon() && TheCSBots()->AllowTacticalShield())
{
if (me->m_iAccount > 2500)
{
if (me->m_iAccount < 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 ) < 33.3f) ? 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->m_iAccount < 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
};
struct BuyInfo
{
WeaponType type;
bool preferred; ///< more challenging bots prefer these weapons
const char *buyAlias; ///< the buy alias for this equipment
};
#define PRIMARY_WEAPON_BUY_COUNT 13
#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, "m3" }, // WEAPON_M3
{ SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
{ SUB_MACHINE_GUN, false, "tmp" }, // WEAPON_TMP
{ SUB_MACHINE_GUN, false, "mp5navy" }, // WEAPON_MP5N
{ SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
{ SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
{ RIFLE, true, "famas" }, // WEAPON_FAMAS
{ SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
{ RIFLE, true, "m4a1" }, // WEAPON_M4A1
{ RIFLE, false, "aug" }, // WEAPON_AUG
{ SNIPER_RIFLE, true, "sg550" }, // WEAPON_SG550
{ SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
{ MACHINE_GUN, false, "m249" } // WEAPON_M249
};
static BuyInfo secondaryWeaponBuyInfoCT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p228" },
{ PISTOL, true, "deagle" },
{ PISTOL, true, "fn57" }
};
static BuyInfo primaryWeaponBuyInfoT[ PRIMARY_WEAPON_BUY_COUNT ] =
{
{ SHOTGUN, false, "m3" }, // WEAPON_M3
{ SHOTGUN, false, "xm1014" }, // WEAPON_XM1014
{ SUB_MACHINE_GUN, false, "mac10" }, // WEAPON_MAC10
{ SUB_MACHINE_GUN, false, "mp5navy" }, // WEAPON_MP5N
{ SUB_MACHINE_GUN, false, "ump45" }, // WEAPON_UMP45
{ SUB_MACHINE_GUN, false, "p90" }, // WEAPON_P90
{ RIFLE, true, "galil" }, // WEAPON_GALIL
{ RIFLE, true, "ak47" }, // WEAPON_AK47
{ SNIPER_RIFLE, false, "scout" }, // WEAPON_SCOUT
{ RIFLE, true, "sg552" }, // WEAPON_SG552
{ SNIPER_RIFLE, true, "awp" }, // WEAPON_AWP
{ SNIPER_RIFLE, true, "g3sg1" }, // WEAPON_G3SG1
{ MACHINE_GUN, false, "m249" } // WEAPON_M249
};
static BuyInfo secondaryWeaponBuyInfoT[ SECONDARY_WEAPON_BUY_COUNT ] =
{
// { PISTOL, false, "glock" },
// { PISTOL, false, "usp" },
{ PISTOL, true, "p228" },
{ 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 NUM_WEAPON_TYPES;
}
//--------------------------------------------------------------------------------------------------------------
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 )
{
CUtlVector<char*, CUtlMemory<char*> > loadout;
Q_SplitString( cheatWeaponString, " ", loadout );
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;
}
if (me->HasShield() && weaponPreference == WEAPON_SHIELDGUN)
{
// done with buying preferred weapon
m_prefIndex = 9999;
return;
}
const char *buyAlias = NULL;
if (weaponPreference == WEAPON_SHIELDGUN)
{
if (TheCSBots()->AllowTacticalShield())
buyAlias = "shield";
}
else
{
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;
// 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;
}
// 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())
{
if (UTIL_IsTeamAllBots( me->GetTeamNumber() ))
{
// only allow Flashbangs if everyone on the team is a bot (dont want to blind our friendly humans)
float rnd = RandomFloat( 0, 100 );
if (rnd < 10)
{
args.Tokenize( "buy smokegrenade" );
me->ClientCommand( args ); // smoke grenade
}
else if (rnd < 35)
{
args.Tokenize( "buy flashbang" );
me->ClientCommand( args ); // flashbang
}
else
{
args.Tokenize( "buy hegrenade" );
me->ClientCommand( args ); // he grenade
}
}
else
{
if (RandomFloat( 0, 100 ) < 10)
{
args.Tokenize( "buy smokegrenade" ); // smoke grenade
me->ClientCommand( args );
}
else
{
args.Tokenize( "buy hegrenade" ); // he grenade
me->ClientCommand( args );
}
}
}
if (m_buyDefuseKit)
{
args.Tokenize( "buy defuser" );
me->ClientCommand( args );
}
m_doneBuying = true;
}
}
}
//--------------------------------------------------------------------------------------------------------------
void BuyState::OnExit( CCSBot *me )
{
me->ResetStuckMonitor();
me->EquipBestWeapon();
}
@@ -0,0 +1,82 @@
//========= Copyright 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->Crouch();
me->SetDisposition( CCSBot::SELF_DEFENSE );
me->GetChatter()->Say( "DefusingBomb" );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Defuse the bomb
*/
void DefuseBombState::OnUpdate( CCSBot *me )
{
const Vector *bombPos = me->GetGameState()->GetBombPosition();
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->StandUp();
me->ResetStuckMonitor();
me->SetTask( CCSBot::SEEK_AND_DESTROY );
me->SetDisposition( CCSBot::ENGAGE_AND_INVESTIGATE );
me->ClearLookAt();
}
@@ -0,0 +1,67 @@
//========= Copyright 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();
}
@@ -0,0 +1,69 @@
//========= Copyright 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();
}
@@ -0,0 +1,366 @@
//========= Copyright 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 ) == false)
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 )
{
}
@@ -0,0 +1,549 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_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())
{
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())
{
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();
}
@@ -0,0 +1,234 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_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 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 )
{
}
@@ -0,0 +1,887 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_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;
//--------------------------------------------------------------------------------------------------------------
/**
* 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, unless using the Scout (which moves faster)
if (me->GetProfile()->GetSkill() > 0.33f && !me->IsUsing( WEAPON_SCOUT ))
{
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;
}
//
// 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())
{
// if we're at a bomb site, plant the bomb
if (me->IsAtBombsite())
{
// plant it
me->SetTask( CCSBot::PLANT_BOMB );
me->PlantBomb();
// radio to the team
me->GetChatter()->PlantingTheBomb( me->GetPlace() );
return;
}
else 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 ) );
return;
}
}
else if (me->IsDoingScenario())
{
// move to the bomb and defuse it
me->SetTask( CCSBot::DEFUSE_BOMB );
me->SetDisposition( CCSBot::OPPORTUNITY_FIRE );
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())
{
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)
{
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 (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 (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
{
// 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();
}
@@ -0,0 +1,139 @@
//========= Copyright 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();
}
@@ -0,0 +1,366 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "cs_simple_hostage.h"
#include "cs_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->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())
{
const float defuseRange = 100.0f; // 50
if ((*bombPos - me->EyePosition()).IsLengthLessThan( defuseRange ))
{
// 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 ))
{
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();
}
@@ -0,0 +1,94 @@
//========= Copyright 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"
// 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( 1.0f );
}
//-------------------------------------------------------------------------------------------------
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();
// wait for door to swing open before leaving state
if (m_timeout.IsElapsed())
{
m_isDone = true;
return;
}
// look at the door
Vector pos;
bool isDoorMoving = false;
if ( m_funcDoor )
{
pos = m_funcDoor->WorldSpaceCenter();
isDoorMoving = m_funcDoor->m_toggle_state == TS_GOING_UP || m_funcDoor->m_toggle_state == TS_GOING_DOWN;
}
else
{
pos = m_propDoor->WorldSpaceCenter();
isDoorMoving = m_propDoor->IsDoorOpening() || m_propDoor->IsDoorClosing();
}
me->SetLookAt( "Open door", pos, PRIORITY_HIGH );
// if we are looking at the door, "use" it and exit
if (me->IsLookingAtPosition( pos ))
{
me->UseEnvironment();
}
}
//-------------------------------------------------------------------------------------------------
void OpenDoorState::OnExit( CCSBot *me )
{
me->ClearLookAt();
me->ResetStuckMonitor();
}
@@ -0,0 +1,79 @@
//========= Copyright 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"
//--------------------------------------------------------------------------------------------------------------
/**
* 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 )
{
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();
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)
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();
}
@@ -0,0 +1,63 @@
//========= Copyright 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 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();
}
+49
View File
@@ -0,0 +1,49 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Data for Autobuy and Rebuy
//
//=============================================================================//
#include "cbase.h"
#include "cs_autobuy.h"
// Weapon class information for each weapon including the class name and the buy command alias.
AutoBuyInfoStruct g_autoBuyInfo[] =
{
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "galil", "weapon_galil" }, // galil
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "ak47", "weapon_ak47" }, // ak47
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "scout", "weapon_scout" }, // scout
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "sg552", "weapon_sg552" }, // sg552
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "awp", "weapon_awp" }, // awp
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "g3sg1", "weapon_g3sg1" }, // g3sg1
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "famas", "weapon_famas" }, // famas
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "m4a1", "weapon_m4a1" }, // m4a1
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_RIFLE), "aug", "weapon_aug" }, // aug
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SNIPERRIFLE), "sg550", "weapon_sg550" }, // sg550
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "glock", "weapon_glock" }, // glock
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "usp", "weapon_usp" }, // usp
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "p228", "weapon_p228" }, // p228
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "deagle", "weapon_deagle" }, // deagle
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "elite", "weapon_elite" }, // elites
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_PISTOL), "fn57", "weapon_fiveseven" }, // fn57
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), "m3", "weapon_m3" }, // m3
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHOTGUN), "xm1014", "weapon_xm1014" }, // xm1014
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "mac10", "weapon_mac10" }, // mac10
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "tmp", "weapon_tmp" }, // tmp
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "mp5navy", "weapon_mp5navy" }, // mp5navy
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "ump45", "weapon_ump45" }, // ump45
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SMG), "p90", "weapon_p90" }, // p90
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_MACHINEGUN), "m249", "weapon_m249" }, // m249
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_AMMO), "primammo", "primammo" }, // primammo
{ (AutoBuyClassType)(AUTOBUYCLASS_SECONDARY | AUTOBUYCLASS_AMMO), "secammo", "secammo" }, // secmmo
{ (AutoBuyClassType)(AUTOBUYCLASS_ARMOR), "vest", "item_kevlar" }, // vest
{ (AutoBuyClassType)(AUTOBUYCLASS_ARMOR), "vesthelm", "item_assaultsuit" }, // vesthelm
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "flashbang", "weapon_flashbang" }, // flash
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "hegrenade", "weapon_hegrenade" }, // hegren
{ (AutoBuyClassType)(AUTOBUYCLASS_GRENADE), "smokegrenade", "weapon_smokegrenade" }, // sgren
{ (AutoBuyClassType)(AUTOBUYCLASS_NIGHTVISION), "nvgs", "nvgs" }, // nvgs
{ (AutoBuyClassType)(AUTOBUYCLASS_DEFUSER), "defuser", "defuser" }, // defuser
{ (AutoBuyClassType)(AUTOBUYCLASS_PRIMARY | AUTOBUYCLASS_SHIELD), "shield", "shield" }, // shield
{ (AutoBuyClassType)0, "", "" } // last one, must be at end.
};
+53
View File
@@ -0,0 +1,53 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Headers and defines for Autobuy and Rebuy
//
//=============================================================================//
/**
* Weapon classes as used by the AutoBuy
* Has to be different that the previous ones because these are bitmasked values as a weapon can be from
* more than one class. This also includes all the classes of equipment that a player can buy.
*/
enum AutoBuyClassType
{
AUTOBUYCLASS_PRIMARY = 1,
AUTOBUYCLASS_SECONDARY = 2,
AUTOBUYCLASS_AMMO = 4,
AUTOBUYCLASS_ARMOR = 8,
AUTOBUYCLASS_DEFUSER = 16,
AUTOBUYCLASS_PISTOL = 32,
AUTOBUYCLASS_SMG = 64,
AUTOBUYCLASS_RIFLE = 128,
AUTOBUYCLASS_SNIPERRIFLE = 256,
AUTOBUYCLASS_SHOTGUN = 512,
AUTOBUYCLASS_MACHINEGUN = 1024,
AUTOBUYCLASS_GRENADE = 2048,
AUTOBUYCLASS_NIGHTVISION = 4096,
AUTOBUYCLASS_SHIELD = 8192,
};
struct AutoBuyInfoStruct
{
AutoBuyClassType m_class;
const char *m_command;
const char *m_classname;
};
struct RebuyStruct
{
char m_szPrimaryWeapon[64]; //"weapon_" string of the primary weapon
char m_szSecondaryWeapon[64]; //"weapon_" string of the secondary weapon
int m_primaryAmmo; // number of rounds the player had (not including rounds in the gun)
int m_secondaryAmmo; // number of rounds the player had (not including rounds in the gun)
int m_heGrenade; // number of grenades to buy
int m_flashbang; // number of grenades to buy
int m_smokeGrenade; // number of grenades to buy
int m_armor; // 0, 1, or 2 (0 = none, 1 = vest, 2 = vest + helmet)
bool m_defuser; // do we want a defuser
bool m_nightVision; // do we want night vision
};
extern AutoBuyInfoStruct g_autoBuyInfo[];
+554
View File
@@ -0,0 +1,554 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Basic BOT handling.
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_player.h"
#include "in_buttons.h"
#include "movehelper_server.h"
#include "team.h"
#include "cs_gamerules.h"
#include "client.h"
void Bot_Think( CCSPlayer *pBot );
ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." );
ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." );
ConVar bot_forceattackon( "bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." );
ConVar bot_flipout( "bot_flipout", "0", 0, "When on, all bots fire their guns." );
ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
static ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." );
static int BotNumber = 1;
static int g_iNextBotTeam = -1;
typedef struct
{
bool backwards;
float nextturntime;
bool lastturntoright;
float nextstrafetime;
float sidemove;
QAngle forwardAngle;
QAngle lastAngles;
int m_WantedTeam;
float m_flJoinTeamTime;
bool m_bTempBot; // Is this slot a dump temp bot or a real bot?
} botdata_t;
static botdata_t g_BotData[ MAX_PLAYERS ];
//-----------------------------------------------------------------------------
// Purpose: Create a new Bot and put it in the game.
// Output : Pointer to the new Bot, or NULL if there's no free clients.
//-----------------------------------------------------------------------------
CBasePlayer *BotPutInServer( bool bFrozen, int iTeam )
{
g_iNextBotTeam = iTeam;
char botname[ 64 ];
Q_snprintf( botname, sizeof( botname ), "Bot%02i", BotNumber );
edict_t *pEdict = engine->CreateFakeClient( botname );
if (!pEdict)
{
Msg( "Failed to create Bot.\n");
return NULL;
}
// Allocate a CBasePlayer for the bot, and call spawn
//ClientPutInServer( pEdict, botname );
//ClientActive( pEdict, false );
CCSPlayer *pPlayer = ((CCSPlayer *)CBaseEntity::Instance( pEdict ));
pPlayer->ClearFlags();
pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );
if ( bFrozen )
pPlayer->AddEFlags( EFL_BOT_FROZEN );
if ( iTeam == -1 )
iTeam = ( pPlayer->entindex() & 1 ) ? TEAM_TERRORIST : TEAM_CT;
botdata_t *pData = &g_BotData[pPlayer->entindex()-1];
pData->m_WantedTeam = iTeam;
pData->m_flJoinTeamTime = gpGlobals->curtime + 0.3;
pData->m_bTempBot = true;
BotNumber++;
return pPlayer;
}
bool IsTempBot( CBaseEntity *pEnt )
{
if ( !pEnt )
return false;
if ( !(pEnt->GetFlags() & FL_FAKECLIENT) )
return false;
int i = pEnt->entindex();
if ( i >= 1 && i < MAX_PLAYERS )
return g_BotData[i-1].m_bTempBot;
else
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Run through all the Bots in the game and let them think.
//-----------------------------------------------------------------------------
void Bot_RunAll( void )
{
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pPlayer = ToCSPlayer( UTIL_PlayerByIndex( i ) );
if ( IsTempBot( pPlayer ) )
{
Bot_Think( pPlayer );
}
}
}
bool RunMimicCommand( CUserCmd& cmd )
{
if ( bot_mimic.GetInt() <= 0 )
return false;
if ( bot_mimic.GetInt() > gpGlobals->maxClients )
return false;
CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() );
if ( !pPlayer )
return false;
if ( !pPlayer->GetLastUserCommand() )
return false;
cmd = *pPlayer->GetLastUserCommand();
cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat();
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Simulates a single frame of movement for a player
// Input : *fakeclient -
// *viewangles -
// forwardmove -
// sidemove -
// upmove -
// buttons -
// impulse -
// msec -
// Output : virtual void
//-----------------------------------------------------------------------------
static void RunPlayerMove( CCSPlayer *fakeclient, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
{
if ( !fakeclient )
return;
CUserCmd cmd;
// Store off the globals.. they're gonna get whacked
float flOldFrametime = gpGlobals->frametime;
float flOldCurtime = gpGlobals->curtime;
float flTimeBase = gpGlobals->curtime;
fakeclient->SetTimeBase( flTimeBase );
Q_memset( &cmd, 0, sizeof( cmd ) );
if ( !RunMimicCommand( cmd ) )
{
VectorCopy( viewangles, cmd.viewangles );
cmd.forwardmove = forwardmove;
cmd.sidemove = sidemove;
cmd.upmove = upmove;
cmd.buttons = buttons;
cmd.impulse = impulse;
cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
}
MoveHelperServer()->SetHost( fakeclient );
fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() );
// save off the last good usercmd
fakeclient->SetLastUserCommand( cmd );
// Clear out any fixangle that has been set
fakeclient->pl.fixangle = FIXANGLE_NONE;
// Restore the globals..
gpGlobals->frametime = flOldFrametime;
gpGlobals->curtime = flOldCurtime;
}
//-----------------------------------------------------------------------------
// Purpose: Run this Bot's AI for one frame.
//-----------------------------------------------------------------------------
void Bot_Think( CCSPlayer *pBot )
{
// Make sure we stay being a bot
pBot->AddFlag( FL_FAKECLIENT );
botdata_t *botdata = &g_BotData[ pBot->entindex() - 1 ];
float forwardmove = 0.0;
float sidemove = botdata->sidemove;
float upmove = 0.0;
unsigned short buttons = 0;
byte impulse = 0;
float frametime = gpGlobals->frametime;
if ( pBot->GetTeamNumber() == TEAM_UNASSIGNED && gpGlobals->curtime > botdata->m_flJoinTeamTime )
{
pBot->HandleCommand_JoinTeam( botdata->m_WantedTeam );
}
else if ( pBot->GetTeamNumber() != TEAM_UNASSIGNED && pBot->PlayerClass() == CS_CLASS_NONE )
{
// If they're on a team but haven't picked a class, choose a random class..
pBot->HandleCommand_JoinClass( 0 );
}
else
{
QAngle vecViewAngles;
vecViewAngles = pBot->GetLocalAngles();
// Create some random values
if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
{
trace_t trace;
// Stop when shot
if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) )
{
if ( pBot->m_iHealth == 100 )
{
forwardmove = 600 * ( botdata->backwards ? -1 : 1 );
if ( botdata->sidemove != 0.0f )
{
forwardmove *= random->RandomFloat( 0.1, 1.0f );
}
}
else
{
forwardmove = 0;
}
}
// Only turn if I haven't been hurt
if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 )
{
Vector vecEnd;
Vector forward;
QAngle angle;
float angledelta = 15.0;
int maxtries = (int)360.0/angledelta;
if ( botdata->lastturntoright )
{
angledelta = -angledelta;
}
angle = pBot->GetLocalAngles();
Vector vecSrc;
while ( --maxtries >= 0 )
{
AngleVectors( angle, &forward, NULL, NULL );
vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 );
vecEnd = vecSrc + forward * 10;
UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN_SCALED( pBot ), VEC_HULL_MAX_SCALED( pBot ),
MASK_PLAYERSOLID, pBot, COLLISION_GROUP_NONE, &trace );
if ( trace.fraction == 1.0 )
{
//if ( gpGlobals->curtime < botdata->nextturntime )
//{
break;
//}
}
angle.y += angledelta;
if ( angle.y > 180 )
angle.y -= 360;
else if ( angle.y < -180 )
angle.y += 360;
botdata->nextturntime = gpGlobals->curtime + 2.0;
botdata->lastturntoright = random->RandomInt( 0, 1 ) == 0 ? true : false;
botdata->forwardAngle = angle;
botdata->lastAngles = angle;
}
/*
if ( gpGlobals->curtime >= botdata->nextstrafetime )
{
botdata->nextstrafetime = gpGlobals->curtime + 1.0f;
if ( random->RandomInt( 0, 5 ) == 0 )
{
botdata->sidemove = -600.0f + 1200.0f * random->RandomFloat( 0, 2 );
}
else
{
botdata->sidemove = 0;
}
sidemove = botdata->sidemove;
if ( random->RandomInt( 0, 20 ) == 0 )
{
botdata->backwards = true;
}
else
{
botdata->backwards = false;
}
}
*/
pBot->SetLocalAngles( angle );
vecViewAngles = angle;
}
// If bots are being forced to fire a weapon, see if I have it
else if ( bot_forcefireweapon.GetString() )
{
CBaseCombatWeapon *pWeapon = pBot->Weapon_OwnsThisType( bot_forcefireweapon.GetString() );
if ( pWeapon )
{
// Switch to it if we don't have it out
CBaseCombatWeapon *pActiveWeapon = pBot->GetActiveWeapon();
// Switch?
if ( pActiveWeapon != pWeapon )
{
pBot->Weapon_Switch( pWeapon );
}
else
{
// Start firing
// Some weapons require releases, so randomise firing
if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
{
buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
}
}
}
}
if ( bot_flipout.GetInt() )
{
if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
{
buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
}
}
}
else
{
// Wait for Reinforcement wave
if ( !pBot->IsAlive() )
{
// Try hitting my buttons occasionally
if ( random->RandomInt( 0, 100 ) > 80 )
{
// Respawn the bot
if ( random->RandomInt( 0, 1 ) == 0 )
{
buttons |= IN_JUMP;
}
else
{
buttons = 0;
}
}
}
}
if ( bot_flipout.GetInt() >= 2 )
{
QAngle angOffset = RandomAngle( -1, 1 );
botdata->lastAngles += angOffset;
for ( int i = 0 ; i < 2; i++ )
{
if ( fabs( botdata->lastAngles[ i ] - botdata->forwardAngle[ i ] ) > 15.0f )
{
if ( botdata->lastAngles[ i ] > botdata->forwardAngle[ i ] )
{
botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] + 15;
}
else
{
botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] - 15;
}
}
}
botdata->lastAngles[ 2 ] = 0;
pBot->SetLocalAngles( botdata->lastAngles );
}
}
pBot->SetPunchAngle( QAngle( 0, 0, 0 ) );
RunPlayerMove( pBot, pBot->GetLocalAngles(), forwardmove, sidemove, upmove, buttons, impulse, frametime );
}
// Handler for the "bot" command.
CON_COMMAND_F( "bot_old", "Add a bot.", FCVAR_CHEAT )
{
// Disable the CS bots, otherwise they'll interfere with the bot code here.
//extern bool g_bEnableCSBots;
//g_bEnableCSBots = false;
CCSPlayer *pPlayer = CCSPlayer::Instance( UTIL_GetCommandClientIndex() );
// The bot command uses switches like command-line switches.
// -count <count> tells how many bots to spawn.
// -team <index> selects the bot's team. Default is -1 which chooses randomly.
// Note: if you do -team !, then it
// -class <index> selects the bot's class. Default is -1 which chooses randomly.
// -frozen prevents the bots from running around when they spawn in.
int count = args.FindArgInt( "-count", 1 );
count = clamp( count, 1, 16 );
int iTeam = -1;
const char *pVal = args.FindArg( "-team" );
if ( pVal )
{
if ( pVal[0] == '!' )
{
if ( pPlayer->GetTeamNumber() == TEAM_TERRORIST )
iTeam = TEAM_CT;
else
iTeam = TEAM_TERRORIST;
}
else if ( pVal[0] == 't' || pVal[0] == 'T' )
{
iTeam = TEAM_TERRORIST;
}
else if ( pVal[0] == 'c' || pVal[0] == 'C' )
{
iTeam = TEAM_CT;
}
else
{
iTeam = atoi( pVal );
if ( iTeam == 1 )
iTeam = TEAM_TERRORIST;
else
iTeam = TEAM_CT;
}
}
// Look at -frozen.
bool bFrozen = !!args.FindArg( "-frozen" );
// Ok, spawn all the bots.
while ( --count >= 0 )
{
extern CBasePlayer *BotPutInServer( bool bFrozen, int iTeam );
BotPutInServer( bFrozen, iTeam );
}
}
// Handle the "PossessBot" command.
void PossessBot_f( const CCommand &args )
{
CCSPlayer *pPlayer = CCSPlayer::Instance( UTIL_GetCommandClientIndex() );
if ( !pPlayer )
return;
// Put the local player in control of this bot.
if ( args.ArgC() != 2 )
{
Warning( "PossessBot <client index>\n" );
return;
}
int iBotClient = atoi( args[1] );
int iBotEnt = iBotClient + 1;
if ( iBotClient < 0 ||
iBotClient >= gpGlobals->maxClients ||
pPlayer->entindex() == iBotEnt )
{
Warning( "PossessBot <client index>\n" );
}
else
{
edict_t *pPlayerData = pPlayer->edict();
edict_t *pBotData = engine->PEntityOfEntIndex( iBotEnt );
if ( pBotData && pBotData )
{
// SWAP EDICTS
// Backup things we don't want to swap.
edict_t oldPlayerData = *pPlayerData;
edict_t oldBotData = *pBotData;
// Swap edicts.
edict_t tmp = *pPlayerData;
*pPlayerData = *pBotData;
*pBotData = tmp;
// Restore things we didn't want to swap.
//pPlayerData->m_EntitiesTouched = oldPlayerData.m_EntitiesTouched;
//pBotData->m_EntitiesTouched = oldBotData.m_EntitiesTouched;
CBaseEntity *pPlayerBaseEnt = CBaseEntity::Instance( pPlayerData );
CBaseEntity *pBotBaseEnt = CBaseEntity::Instance( pBotData );
// Make the other a bot and make the player not a bot.
pPlayerBaseEnt->RemoveFlag( FL_FAKECLIENT );
pBotBaseEnt->AddFlag( FL_FAKECLIENT );
// Point the CBaseEntities at the right players.
pPlayerBaseEnt->NetworkProp()->SetEdict( pPlayerData );
pBotBaseEnt->NetworkProp()->SetEdict( pBotData );
// Freeze the bot.
pBotBaseEnt->AddEFlags( EFL_BOT_FROZEN );
}
}
}
ConCommand cc_PossessBot( "PossessBot", PossessBot_f, "Toggle. Possess a bot.\n\tArguments: <bot client number>", FCVAR_CHEAT );
+17
View File
@@ -0,0 +1,17 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef CS_BOT_TEMP_H
#define CS_BOT_TEMP_H
#ifdef _WIN32
#pragma once
#endif
extern ConVar bot_mimic;
#endif // CS_BOT_TEMP_H
+205
View File
@@ -0,0 +1,205 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
/*
===== tf_client.cpp ========================================================
HL2 client/server game specific stuff
*/
#include "cbase.h"
#include "player.h"
#include "gamerules.h"
#include "entitylist.h"
#include "physics.h"
#include "game.h"
#include "ai_network.h"
#include "ai_node.h"
#include "ai_hull.h"
#include "shake.h"
#include "player_resource.h"
#include "engine/IEngineSound.h"
#include "cs_player.h"
#include "cs_gamerules.h"
#include "cs_bot.h"
#include "tier0/vprof.h"
#include "teamplayroundbased_gamerules.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer );
extern bool g_fGameOver;
void FinishClientPutInServer( CCSPlayer *pPlayer )
{
pPlayer->InitialSpawn();
pPlayer->Spawn();
if (!pPlayer->IsBot())
{
// When the player first joins the server, they
pPlayer->m_iNumSpawns = 0;
pPlayer->m_takedamage = DAMAGE_NO;
pPlayer->pl.deadflag = true;
pPlayer->m_lifeState = LIFE_DEAD;
pPlayer->AddEffects( EF_NODRAW );
pPlayer->ChangeTeam( TEAM_UNASSIGNED );
pPlayer->SetThink( NULL );
pPlayer->AddAccount( CSGameRules()->GetStartMoney() );
// Move them to the first intro camera.
pPlayer->MoveToNextIntroCamera();
pPlayer->SetMoveType( MOVETYPE_NONE );
}
char sName[128];
Q_strncpy( sName, pPlayer->GetPlayerName(), sizeof( sName ) );
// First parse the name and remove any %'s
for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ )
{
// Replace it with a space
if ( *pApersand == '%' )
*pApersand = ' ';
}
// notify other clients of player joining the game
UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "#Game_connected", sName[0] != 0 ? sName : "<unconnected>" );
}
/*
===========
ClientPutInServer
called each time a player is spawned into the game
============
*/
void ClientPutInServer( edict_t *pEdict, const char *playername )
{
// Allocate a CBaseTFPlayer for pev, and call spawn
CCSPlayer *pPlayer = CCSPlayer::CreatePlayer( "player", pEdict );
pPlayer->SetPlayerName( playername );
}
void ClientActive( edict_t *pEdict, bool bLoadGame )
{
// Can't load games in CS!
Assert( !bLoadGame );
CCSPlayer *pPlayer = ToCSPlayer( CBaseEntity::Instance( pEdict ) );
FinishClientPutInServer( pPlayer );
CSingleUserRecipientFilter user( pPlayer );
user.MakeReliable();
// send the 4 end of match conditions. long frag limit, long max rounds, long rounds needed won, and long time
UserMessageBegin( user, "MatchEndConditions" );
WRITE_LONG( fraglimit.GetInt() );
WRITE_LONG( mp_maxrounds.GetInt() );
WRITE_LONG( mp_winlimit.GetInt() );
WRITE_LONG( mp_timelimit.GetInt() );
MessageEnd();
}
/*
===============
const char *GetGameDescription()
Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2
===============
*/
const char *GetGameDescription()
{
if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized
return g_pGameRules->GetGameDescription();
else
return "Counter-Strike: Source";
}
//-----------------------------------------------------------------------------
// Purpose: Precache game-specific models & sounds
//-----------------------------------------------------------------------------
void ClientGamePrecache( void )
{
// Materials used by the client effects
CBaseEntity::PrecacheModel( "sprites/white.vmt" );
CBaseEntity::PrecacheModel( "sprites/physbeam.vmt" );
// Legacy temp ents sounds
CBaseEntity::PrecacheScriptSound( "Bounce.PistolShell" );
CBaseEntity::PrecacheScriptSound( "Bounce.RifleShell" );
CBaseEntity::PrecacheScriptSound( "Bounce.ShotgunShell" );
// Moved to pure_server_minimal.txt
// // Flashbang-related files
// engine->ForceExactFile( "sprites/white.vmt" );
// engine->ForceExactFile( "sprites/white.vtf" );
// engine->ForceExactFile( "vgui/white.vmt" );
// engine->ForceExactFile( "vgui/white.vtf" );
// engine->ForceExactFile( "effects/flashbang.vmt" );
// engine->ForceExactFile( "effects/flashbang_white.vmt" );
//
// // Smoke grenade-related files
// engine->ForceExactFile( "particle/particle_smokegrenade1.vmt" );
// engine->ForceExactFile( "particle/particle_smokegrenade.vtf" );
//
// // Sniper scope
// engine->ForceExactFile( "sprites/scope_arc.vmt" );
// engine->ForceExactFile( "sprites/scope_arc.vtf" );
//
// // DSP presets - don't want people avoiding the deafening + ear ring
// engine->ForceExactFile( "scripts/dsp_presets.txt" );
}
// called by ClientKill and DeadThink
void respawn( CBaseEntity *pEdict, bool fCopyCorpse )
{
if (gpGlobals->coop || gpGlobals->deathmatch)
{
if ( fCopyCorpse )
{
// make a copy of the dead body for appearances sake
dynamic_cast< CBasePlayer* >( pEdict )->CreateCorpse();
}
// respawn player
pEdict->Spawn();
}
else
{ // restart the entire server
engine->ServerCommand("reload\n");
}
}
void GameStartFrame( void )
{
VPROF( "GameStartFrame" );
if ( g_fGameOver )
return;
gpGlobals->teamplay = teamplay.GetInt() ? true : false;
}
//=========================================================
// instantiate the proper game rules object
//=========================================================
void InstallGameRules()
{
CreateGameRulesObject( "CCSGameRules" );
}
+19
View File
@@ -0,0 +1,19 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef CS_CLIENT_H
#define CS_CLIENT_H
#ifdef _WIN32
#pragma once
#endif
void respawn( CBaseEntity *pEdict, bool fCopyCorpse );
void FinishClientPutInServer( CCSPlayer *pPlayer );
#endif // CS_CLIENT_H
+321
View File
@@ -0,0 +1,321 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "../EventLog.h"
#include "team.h"
#include "cs_gamerules.h"
#include "KeyValues.h"
#define LOG_DETAIL_ENEMY_ATTACKS 0x01
#define LOG_DETAIL_TEAMMATE_ATTACKS 0x02
ConVar mp_logdetail( "mp_logdetail", "0", FCVAR_NONE, "Logs attacks. Values are: 0=off, 1=enemy, 2=teammate, 3=both)", true, 0.0f, true, 3.0f );
class CCSEventLog : public CEventLog
{
private:
typedef CEventLog BaseClass;
public:
bool PrintEvent( IGameEvent *event ) // override virtual function
{
if ( !PrintCStrikeEvent( event ) ) // allow CS to override logging
{
return BaseClass::PrintEvent( event );
}
else
{
return true;
}
}
bool Init()
{
BaseClass::Init();
// listen to CS events
ListenForGameEvent( "round_end" );
ListenForGameEvent( "round_start" );
ListenForGameEvent( "bomb_pickup" );
ListenForGameEvent( "bomb_begindefuse" );
ListenForGameEvent( "bomb_dropped" );
ListenForGameEvent( "bomb_defused" );
ListenForGameEvent( "bomb_planted" );
ListenForGameEvent( "hostage_rescued" );
ListenForGameEvent( "hostage_killed" );
ListenForGameEvent( "hostage_follows" );
ListenForGameEvent( "player_hurt" );
return true;
}
protected:
bool PrintCStrikeEvent( IGameEvent *event ) // print Mod specific logs
{
const char *eventName = event->GetName();
// messages that don't have a user associated to them
if ( !Q_strncmp( eventName, "round_end", Q_strlen("round_end") ) )
{
const int winner = event->GetInt( "winner" );
const int reason = event->GetInt( "reason" );
const char *msg = event->GetString( "message" );
msg++; // remove the '#' char
switch( reason )
{
case Game_Commencing:
UTIL_LogPrintf( "World triggered \"Game_Commencing\"\n" );
return true;
break;
}
CTeam *ct = GetGlobalTeam( TEAM_CT );
CTeam *ter = GetGlobalTeam( TEAM_TERRORIST );
Assert( ct && ter );
switch ( winner )
{
case WINNER_CT:
UTIL_LogPrintf( "Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", ct->GetName(), msg, ct->GetScore(), ter->GetScore() );
break;
case WINNER_TER:
UTIL_LogPrintf( "Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", ter->GetName(), msg, ct->GetScore(), ter->GetScore() );
break;
case WINNER_DRAW:
default:
UTIL_LogPrintf( "World triggered \"%s\" (CT \"%i\") (T \"%i\")\n", msg, ct->GetScore(), ter->GetScore() );
break;
}
UTIL_LogPrintf( "Team \"CT\" scored \"%i\" with \"%i\" players\n", ct->GetScore(), ct->GetNumPlayers() );
UTIL_LogPrintf( "Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", ter->GetScore(), ter->GetNumPlayers() );
UTIL_LogPrintf("World triggered \"Round_End\"\n");
return true;
}
else if ( !Q_strncmp( eventName, "server_", strlen("server_")) )
{
return false; // ignore server_ messages
}
const int userid = event->GetInt( "userid" );
CBasePlayer *pPlayer = UTIL_PlayerByUserId( userid );
if ( !pPlayer )
{
return false;
}
if ( FStrEq( eventName, "player_hurt" ) )
{
const int attackerid = event->GetInt("attacker" );
const char *weapon = event->GetString( "weapon" );
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
if ( !pAttacker )
{
return false;
}
bool isTeamAttack = ( (pPlayer->GetTeamNumber() == pAttacker->GetTeamNumber() ) && (pPlayer != pAttacker) );
int detail = mp_logdetail.GetInt();
if ( ( isTeamAttack && ( detail & LOG_DETAIL_TEAMMATE_ATTACKS ) ) ||
( !isTeamAttack && ( detail & LOG_DETAIL_ENEMY_ATTACKS ) ) )
{
int hitgroup = event->GetInt( "hitgroup" );
const char *hitgroupStr = "GENERIC";
switch ( hitgroup )
{
case HITGROUP_GENERIC:
hitgroupStr = "generic";
break;
case HITGROUP_HEAD:
hitgroupStr = "head";
break;
case HITGROUP_CHEST:
hitgroupStr = "chest";
break;
case HITGROUP_STOMACH:
hitgroupStr = "stomach";
break;
case HITGROUP_LEFTARM:
hitgroupStr = "left arm";
break;
case HITGROUP_RIGHTARM:
hitgroupStr = "right arm";
break;
case HITGROUP_LEFTLEG:
hitgroupStr = "left leg";
break;
case HITGROUP_RIGHTLEG:
hitgroupStr = "right leg";
break;
}
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" attacked \"%s<%i><%s><%s>\" with \"%s\" (damage \"%d\") (damage_armor \"%d\") (health \"%d\") (armor \"%d\") (hitgroup \"%s\")\n",
pAttacker->GetPlayerName(),
attackerid,
pAttacker->GetNetworkIDString(),
pAttacker->GetTeam()->GetName(),
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
weapon,
event->GetInt( "dmg_health" ),
event->GetInt( "dmg_armor" ),
event->GetInt( "health" ),
event->GetInt( "armor" ),
hitgroupStr );
}
return true;
}
else if ( !Q_strncmp( eventName, "player_death", Q_strlen("player_death") ) )
{
const int attackerid = event->GetInt("attacker" );
const char *weapon = event->GetString( "weapon" );
const bool headShot = (event->GetInt( "headshot" ) == 1);
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
if ( pPlayer == pAttacker )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
weapon
);
}
else if ( pAttacker )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"%s\n",
pAttacker->GetPlayerName(),
attackerid,
pAttacker->GetNetworkIDString(),
pAttacker->GetTeam()->GetName(),
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName(),
weapon,
headShot ? " (headshot)":""
);
}
else
{
// killed by the world
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"world\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName()
);
}
return true;
}
else if ( !Q_strncmp( eventName, "round_start", Q_strlen("round_start") ) )
{
UTIL_LogPrintf("World triggered \"Round_Start\"\n");
return true;
}
else if ( !Q_strncmp( eventName, "hostage_follows", Q_strlen("hostage_follows") ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><CT>\" triggered \"Touched_A_Hostage\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( !Q_strncmp( eventName, "hostage_killed", Q_strlen("hostage_killed") ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" triggered \"Killed_A_Hostage\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
pPlayer->GetTeam()->GetName()
);
return true;
}
else if ( !Q_strncmp( eventName, "hostage_rescued", Q_strlen("hostage_rescued") ) )
{
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"Rescued_A_Hostage\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( !Q_strncmp( eventName, "bomb_planted", Q_strlen("bomb_planted") ) )
{
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Planted_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( !Q_strncmp( eventName, "bomb_defused", Q_strlen("bomb_defused") ) )
{
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"Defused_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( !Q_strncmp( eventName, "bomb_dropped", Q_strlen("bomb_dropped") ) )
{
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Dropped_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( !Q_strncmp( eventName, "bomb_begindefuse", Q_strlen("bomb_begindefuse") ) )
{
const bool haskit = (event->GetInt( "haskit" ) == 1);
UTIL_LogPrintf("\"%s<%i><%s><CT>\" triggered \"%s\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
haskit ? "Begin_Bomb_Defuse_With_Kit" : "Begin_Bomb_Defuse_Without_Kit"
);
return true;
}
else if ( !Q_strncmp( eventName, "bomb_pickup", Q_strlen("bomb_pickup") ) )
{
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Got_The_Bomb\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
// unused events:
//hostage_hurt
//bomb_exploded
return false;
}
};
CCSEventLog g_CSEventLog;
//-----------------------------------------------------------------------------
// Singleton access
//-----------------------------------------------------------------------------
IGameSystem* GameLogSystem()
{
return &g_CSEventLog;
}
+40
View File
@@ -0,0 +1,40 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "gameinterface.h"
#include "mapentities.h"
#include "cs_gameinterface.h"
#include "AI_ResponseSystem.h"
// -------------------------------------------------------------------------------------------- //
// Mod-specific CServerGameClients implementation.
// -------------------------------------------------------------------------------------------- //
void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int &defaultMaxPlayers ) const
{
minplayers = 1; // allow single player for the test maps (but we default to multi)
maxplayers = MAX_PLAYERS;
defaultMaxPlayers = 32; // Default to 32 players unless they change it.
}
// -------------------------------------------------------------------------------------------- //
// Mod-specific CServerGameDLL implementation.
// -------------------------------------------------------------------------------------------- //
void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities )
{
if ( Q_strcmp( STRING(gpGlobals->mapname), "cs_" ) )
{
// don't precache AI responses (hostages) if it's not a hostage rescure map
extern IResponseSystem *g_pResponseSystem;
g_pResponseSystem->PrecacheResponses( false );
}
}
+14
View File
@@ -0,0 +1,14 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef CS_GAMEINTERFACE_H
#define CS_GAMEINTERFACE_H
#ifdef _WIN32
#pragma once
#endif
#endif // CS_GAMEINTERFACE_H
File diff suppressed because it is too large Load Diff
+340
View File
@@ -0,0 +1,340 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: The CS game stats header
//
// $NoKeywords: $
//=============================================================================//
#ifndef CS_GAMESTATS_H
#define CS_GAMESTATS_H
#ifdef _WIN32
#pragma once
#endif
#include "cs_blackmarket.h"
#include "gamestats.h"
#include "cs_gamestats_shared.h"
#include "GameEventListener.h"
#include "steamworks_gamestats.h"
#include "weapon_csbase.h"
// forward declares
class CBreakableProp;
#define CS_STATS_BLOB_VERSION 3
const float cDisseminationTimeHigh = 0.25f; // Time interval for high priority stats sent to the player
const float cDisseminationTimeLow = 2.5f; // Time interval for medium priority stats sent to the player
int GetCSLevelIndex( const char *pLevelName );
typedef struct
{
char szGameName[8];
byte iVersion;
char szMapName[32];
char ipAddr[4];
short port;
int serverid;
} gamestats_header_t;
typedef struct
{
gamestats_header_t header;
short iMinutesPlayed;
short iTerroristVictories[CS_NUM_LEVELS];
short iCounterTVictories[CS_NUM_LEVELS];
short iBlackMarketPurchases[WEAPON_MAX];
short iAutoBuyPurchases;
short iReBuyPurchases;
short iAutoBuyM4A1Purchases;
short iAutoBuyAK47Purchases;
short iAutoBuyFamasPurchases;
short iAutoBuyGalilPurchases;
short iAutoBuyVestHelmPurchases;
short iAutoBuyVestPurchases;
} cs_gamestats_t;
extern short g_iWeaponPurchases[WEAPON_MAX];
extern float g_flGameStatsUpdateTime;
extern short g_iTerroristVictories[CS_NUM_LEVELS];
extern short g_iCounterTVictories[CS_NUM_LEVELS];
extern short g_iWeaponPurchases[WEAPON_MAX];
extern short g_iAutoBuyPurchases;
extern short g_iReBuyPurchases;
extern short g_iAutoBuyM4A1Purchases;
extern short g_iAutoBuyAK47Purchases;
extern short g_iAutoBuyFamasPurchases;
extern short g_iAutoBuyGalilPurchases;
extern short g_iAutoBuyVestHelmPurchases;
extern short g_iAutoBuyVestPurchases;
struct sHappyCamperSnipePosition
{
sHappyCamperSnipePosition( int userid, Vector pos ) : m_iUserID(userid), m_vPos(pos) {}
int m_iUserID;
Vector m_vPos;
};
struct SMarketPurchases : public BaseStatData
{
SMarketPurchases( uint64 ulPlayerID, int iPrice, const char *pName ) : m_nPlayerID(ulPlayerID), ItemCost(iPrice)
{
if ( pName )
{
Q_strncpy( ItemID, pName, ARRAYSIZE(ItemID) );
}
else
{
Q_strncpy( ItemID, "unknown", ARRAYSIZE(ItemID) );
}
}
uint64 m_nPlayerID;
int ItemCost;
char ItemID[64];
BEGIN_STAT_TABLE( "CSSMarketPurchase" )
REGISTER_STAT( m_nPlayerID )
REGISTER_STAT( ItemCost )
REGISTER_STAT_STRING( ItemID )
END_STAT_TABLE()
};
typedef CUtlVector< SMarketPurchases* > VectorMarketPurchaseData;
struct WeaponStats
{
int shots;
int hits;
int kills;
int damage;
};
struct SCSSWeaponData : public BaseStatData
{
SCSSWeaponData( const char *pWeaponName, const WeaponStats &wpnData )
{
if ( pWeaponName )
{
Q_strncpy( WeaponID, pWeaponName, ARRAYSIZE(WeaponID) );
}
else
{
Q_strncpy( WeaponID, "unknown", ARRAYSIZE(WeaponID) );
}
Shots = wpnData.shots;
Hits = wpnData.hits;
Kills = wpnData.kills;
Damage = wpnData.damage;
}
char WeaponID[64];
int Shots;
int Hits;
int Kills;
int Damage;
BEGIN_STAT_TABLE( "CSSWeaponData" )
REGISTER_STAT_STRING( WeaponID )
REGISTER_STAT( Shots )
REGISTER_STAT( Hits )
REGISTER_STAT( Kills )
REGISTER_STAT( Damage )
END_STAT_TABLE()
};
typedef CUtlVector< SCSSWeaponData* > CSSWeaponData;
struct SCSSDeathData : public BaseStatData
{
SCSSDeathData( CBasePlayer *pVictim, const CTakeDamageInfo &info )
{
m_bUseGlobalData = false;
m_DeathPos = info.GetDamagePosition();
m_iVictimTeam = pVictim->GetTeamNumber();
CCSPlayer *pCSPlayer = ToCSPlayer( info.GetAttacker() );
if ( pCSPlayer )
{
m_iKillerTeam = pCSPlayer->GetTeamNumber();
}
else
{
m_iKillerTeam = -1;
}
const char *pszWeaponName = info.GetInflictor() ? info.GetInflictor()->GetClassname() : "unknown";
if ( pszWeaponName )
{
if ( V_strcmp(pszWeaponName, "player") == 0 )
{
// get the player's weapon
if ( pCSPlayer && pCSPlayer->GetActiveCSWeapon() )
{
pszWeaponName = pCSPlayer->GetActiveCSWeapon()->GetClassname();
}
}
}
m_uiDeathParam = WEAPON_NONE;
if ( (m_uiDeathParam = AliasToWeaponID( pszWeaponName )) == WEAPON_NONE )
{
m_uiDeathParam = AliasToWeaponID( pszWeaponName );
}
const char *pszMapName = gpGlobals->mapname.ToCStr() ? gpGlobals->mapname.ToCStr() : "unknown";
Q_strncpy( m_szMapName, pszMapName, ARRAYSIZE(m_szMapName) );
}
Vector m_DeathPos;
int m_iVictimTeam;
int m_iKillerTeam;
int m_iDamageType;
uint64 m_uiDeathParam;
char m_szMapName[64];
BEGIN_STAT_TABLE( "Deaths" )
REGISTER_STAT_NAMED( m_DeathPos.x, "XCoord" )
REGISTER_STAT_NAMED( m_DeathPos.y, "YCoord" )
REGISTER_STAT_NAMED( m_DeathPos.z, "ZCoord" )
REGISTER_STAT_NAMED( m_iVictimTeam, "Team" )
REGISTER_STAT_NAMED( m_iKillerTeam, "DeathCause" )
REGISTER_STAT_NAMED( m_uiDeathParam, "DeathParam" )
REGISTER_STAT_NAMED( m_szMapName, "DeathMap" )
END_STAT_TABLE()
};
typedef CUtlVector< SCSSDeathData* > CSSDeathData;
//=============================================================================
//
// CS Game Stats Class
//
class CCSGameStats : public CBaseGameStats, public CGameEventListener, public CAutoGameSystemPerFrame, public IGameStatTracker
{
public:
// Constructor/Destructor.
CCSGameStats( void );
~CCSGameStats( void );
virtual void Clear( void );
virtual bool Init();
virtual void PreClientUpdate();
virtual void PostInit();
virtual void LevelShutdownPreClearSteamAPIContext();
void UploadRoundStats( void );
// Overridden events
virtual void Event_LevelInit( void );
virtual void Event_LevelShutdown( float flElapsed );
virtual void Event_ShotFired( CBasePlayer *pPlayer, CBaseCombatWeapon* pWeapon );
virtual void Event_ShotHit( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
virtual void Event_PlayerKilled_PreWeaponDrop( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
void UpdatePlayerRoundStats(int winner);
virtual void Event_PlayerConnected( CBasePlayer *pPlayer );
virtual void Event_PlayerDisconnected( CBasePlayer *pPlayer );
virtual void Event_WindowShattered( CBasePlayer *pPlayer );
virtual void Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info );
// CSS specific events
void Event_BombPlanted( CCSPlayer *pPlayer );
void Event_BombDefused( CCSPlayer *pPlayer );
void Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info );
void Event_BombExploded( CCSPlayer *pPlayer );
void Event_MoneyEarned( CCSPlayer *pPlayer, int moneyEarned );
void Event_MoneySpent( CCSPlayer *pPlayer, int moneySpent, const char *pItemName );
void Event_HostageRescued( CCSPlayer *pPlayer );
void Event_PlayerSprayedDecal( CCSPlayer*pPlayer );
void Event_AllHostagesRescued();
void Event_BreakProp( CCSPlayer *pPlayer, CBreakableProp *pProp );
void Event_PlayerDonatedWeapon (CCSPlayer* pPlayer);
void Event_PlayerDominatedOther( CCSPlayer* pAttacker, CCSPlayer* pVictim);
void Event_PlayerRevenge( CCSPlayer* pAttacker );
void Event_PlayerAvengedTeammate( CCSPlayer* pAttacker, CCSPlayer* pAvengedPlayer );
void Event_MVPEarned( CCSPlayer* pPlayer );
void Event_KnifeUse( CCSPlayer* pPlayer, bool bStab, int iDamage );
virtual void FireGameEvent( IGameEvent *event );
void DumpMatchWeaponMetrics();
const PlayerStats_t& FindPlayerStats( CBasePlayer *pPlayer ) const;
void ResetPlayerStats( CBasePlayer *pPlayer );
void ResetKillHistory( CBasePlayer *pPlayer );
void ResetRoundStats();
void ResetPlayerClassMatchStats();
const StatsCollection_t& GetTeamStats( int iTeamIndex ) const;
void ResetAllTeamStats();
void ResetAllStats();
void ResetWeaponStats();
void IncrementTeamStat( int iTeamIndex, int iStatIndex, int iAmount );
void CalcDominationAndRevenge( CCSPlayer *pAttacker, CCSPlayer *pVictim, int *piDeathFlags );
void CalculateOverkill( CCSPlayer* pAttacker, CCSPlayer* pVictim );
void PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &info );
void IncrementStat( CCSPlayer* pPlayer, CSStatType_t statId, int iValue, bool bPlayerOnly = false );
// Steamworks Gamestats
virtual void SubmitGameStats( KeyValues *pKV );
virtual StatContainerList_t* GetStatContainerList( void )
{
return s_StatLists;
}
protected:
void SetStat( CCSPlayer *pPlayer, CSStatType_t statId, int iValue );
void TrackKillStats( CCSPlayer *pAttacker, CCSPlayer *pVictim );
void ComputeRollingStatAverages();
void ComputeDirectStatAverages();
void SendRollingStatsAveragesToAllPlayers();
void SendDirectStatsAveragesToAllPlayers();
void SendStatsToPlayer( CCSPlayer * pPlayer, int iMinStatPriority );
private:
PlayerStats_t m_aPlayerStats[MAX_PLAYERS+1]; // List of stats for each player for current life - reset after each death
StatsCollection_t m_aTeamStats[TEAM_MAXCOUNT - FIRST_GAME_TEAM];
RoundStatsRollingAverage_t m_rollingCTStatAverages;
RoundStatsRollingAverage_t m_rollingTStatAverages;
RoundStatsRollingAverage_t m_rollingPlayerStatAverages;
RoundStatsDirectAverage_t m_directCTStatAverages;
RoundStatsDirectAverage_t m_directTStatAverages;
RoundStatsDirectAverage_t m_directPlayerStatAverages;
float m_fDisseminationTimerLow; // how long since last medium priority stat update
float m_fDisseminationTimerHigh; // how long since last high priority stat update
int m_numberOfRoundsForDirectAverages;
int m_numberOfTerroristEntriesForDirectAverages;
int m_numberOfCounterTerroristEntriesForDirectAverages;
CUtlDict< CSStatType_t, short > m_PropStatTable;
CUtlLinkedList<sHappyCamperSnipePosition, int> m_PlayerSnipedPosition;
WeaponStats m_weaponStats[WEAPON_MAX][2];
// Steamworks Gamestats
VectorMarketPurchaseData m_MarketPurchases;
CSSWeaponData m_WeaponData;
CSSDeathData m_DeathData;
// A static list of all the stat containers, one for each data structure being tracked
static StatContainerList_t * s_StatLists;
bool m_bInRound;
};
extern CCSGameStats CCS_GameStats;
#endif // CS_GAMESTATS_H
+149
View File
@@ -0,0 +1,149 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "hltvdirector.h"
#include "igameevents.h"
class CCSHLTVDirector : public CHLTVDirector
{
public:
DECLARE_CLASS( CCSHLTVDirector, CHLTVDirector );
const char** GetModEvents();
void SetHLTVServer( IHLTVServer *hltv );
void CreateShotFromEvent( CHLTVGameEvent *event );
};
void CCSHLTVDirector::SetHLTVServer( IHLTVServer *hltv )
{
BaseClass::SetHLTVServer( hltv );
if ( m_pHLTVServer )
{
// mod specific events the director uses to find interesting shots
ListenForGameEvent( "hostage_rescued" );
ListenForGameEvent( "hostage_killed" );
ListenForGameEvent( "hostage_hurt" );
ListenForGameEvent( "hostage_follows" );
ListenForGameEvent( "bomb_pickup" );
ListenForGameEvent( "bomb_dropped" );
ListenForGameEvent( "bomb_exploded" );
ListenForGameEvent( "bomb_defused" );
ListenForGameEvent( "bomb_planted" );
ListenForGameEvent( "vip_escaped" );
ListenForGameEvent( "vip_killed" );
}
}
void CCSHLTVDirector::CreateShotFromEvent( CHLTVGameEvent *event )
{
// show event at least for 2 more seconds after it occured
const char *name = event->m_Event->GetName();
IGameEvent *shot = NULL;
if ( !Q_strcmp( "hostage_rescued", name ) ||
!Q_strcmp( "hostage_hurt", name ) ||
!Q_strcmp( "hostage_follows", name ) ||
!Q_strcmp( "hostage_killed", name ) )
{
CBaseEntity *player = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
if ( !player )
return;
// shot player as primary, hostage as secondary target
shot = gameeventmanager->CreateEvent( "hltv_chase", true );
shot->SetInt( "target1", player->entindex() );
shot->SetInt( "target2", event->m_Event->GetInt("hostage") );
shot->SetFloat( "distance", 96.0f );
shot->SetInt( "theta", 40 );
shot->SetInt( "phi", 20 );
// shot 2 seconds after event
m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
m_iPVSEntity = player->entindex();
}
else if ( !Q_strcmp( "bomb_pickup", name ) ||
!Q_strcmp( "bomb_dropped", name ) ||
!Q_strcmp( "bomb_planted", name ) ||
!Q_strcmp( "bomb_defused", name ) )
{
CBaseEntity *player = UTIL_PlayerByUserId( event->m_Event->GetInt("userid") );
if ( !player )
return;
shot = gameeventmanager->CreateEvent( "hltv_chase", true );
shot->SetInt( "target1", player->entindex() );
shot->SetInt( "target2", 0 );
shot->SetFloat( "distance", 64.0f );
shot->SetInt( "theta", 200 );
shot->SetInt( "phi", 10 );
// shot 2 seconds after pickup
m_nNextShotTick = MIN( m_nNextShotTick, (event->m_Tick+TIME_TO_TICKS(2.0)) );
m_iPVSEntity = player->entindex();
}
else
{
// let baseclass create a shot
BaseClass::CreateShotFromEvent( event );
return;
}
if ( shot )
{
m_pHLTVServer->BroadcastEvent( shot );
gameeventmanager->FreeEvent( shot );
DevMsg("DrcCmd: %s\n", name );
}
}
const char** CCSHLTVDirector::GetModEvents()
{
// game events relayed to spectator clients
static const char *s_modevents[] =
{
"hltv_status",
"hltv_chat",
"player_connect",
"player_disconnect",
"player_team",
"player_info",
"server_cvar",
"player_death",
"player_chat",
"round_start",
"round_end",
// additional CS:S events:
"bomb_planted",
"bomb_defused",
"hostage_killed",
"hostage_hurt",
NULL
};
return s_modevents;
}
static CCSHLTVDirector s_HLTVDirector; // singleton
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CHLTVDirector, IHLTVDirector, INTERFACEVERSION_HLTVDIRECTOR, s_HLTVDirector );
CHLTVDirector* HLTVDirector()
{
return &s_HLTVDirector;
}
IGameSystem* HLTVDirectorSystem()
{
return &s_HLTVDirector;
}
+73
View File
@@ -0,0 +1,73 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav.h
// Data structures and constants for the Navigation Mesh system
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#ifndef _CS_NAV_H_
#define _CS_NAV_H_
#include "nav.h"
/**
* Below are several constants used by the navigation system.
* @todo Move these into TheNavMesh singleton.
*/
const float BotRadius = 10.0f; ///< circular extent that contains bot
class CNavArea;
class CSNavNode;
#if 0
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if given entity can be ignored when moving
*/
#define WALK_THRU_DOORS 0x01
#define WALK_THRU_BREAKABLES 0x02
#define WALK_THRU_TOGGLE_BRUSHES 0x04
#define WALK_THRU_EVERYTHING (WALK_THRU_DOORS | WALK_THRU_BREAKABLES | WALK_THRU_TOGGLE_BRUSHES)
inline bool IsEntityWalkable( CBaseEntity *entity, unsigned int flags )
{
if (FClassnameIs( entity, "worldspawn" ))
return false;
if (FClassnameIs( entity, "player" ))
return false;
// if we hit a door, assume its walkable because it will open when we touch it
if (FClassnameIs( entity, "prop_door*" ) || FClassnameIs( entity, "func_door*" ))
return (flags & WALK_THRU_DOORS) ? true : false;
// if we hit a clip brush, ignore it if it is not BRUSHSOLID_ALWAYS
if (FClassnameIs( entity, "func_brush" ))
{
CFuncBrush *brush = (CFuncBrush *)entity;
switch ( brush->m_iSolidity )
{
case CFuncBrush::BRUSHSOLID_ALWAYS:
return false;
case CFuncBrush::BRUSHSOLID_NEVER:
return true;
case CFuncBrush::BRUSHSOLID_TOGGLE:
return (flags & WALK_THRU_TOGGLE_BRUSHES) ? true : false;
}
}
// if we hit a breakable object, assume its walkable because we will shoot it when we touch it
if (FClassnameIs( entity, "func_breakable" ) && entity->GetHealth() && entity->m_takedamage == DAMAGE_YES)
return (flags & WALK_THRU_BREAKABLES) ? true : false;
if (FClassnameIs( entity, "func_breakable_surf" ) && entity->m_takedamage == DAMAGE_YES)
return (flags & WALK_THRU_BREAKABLES) ? true : false;
return false;
}
#endif
#endif // _CS_NAV_H_
+473
View File
@@ -0,0 +1,473 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_area.cpp
// AI Navigation areas
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#include "cbase.h"
#include "cs_nav_mesh.h"
#include "cs_nav_area.h"
#include "nav_pathfind.h"
#include "nav_colors.h"
#include "fmtstr.h"
#include "props_shared.h"
#include "func_breakablesurf.h"
#include "Color.h"
#include "collisionutils.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
#ifdef _WIN32
#pragma warning (disable:4701) // disable warning that variable *may* not be initialized
#endif
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor used during normal runtime.
*/
CCSNavArea::CCSNavArea( void )
{
m_approachCount = 0;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Destructor
*/
CCSNavArea::~CCSNavArea()
{
}
void CCSNavArea::OnServerActivate( void )
{
CNavArea::OnServerActivate();
}
void CCSNavArea::OnRoundRestart( void )
{
CNavArea::OnRoundRestart();
}
void CCSNavArea::Save( CUtlBuffer &fileBuffer, unsigned int version ) const
{
CNavArea::Save( fileBuffer, version );
//
// Save the approach areas for this area
//
// save number of approach areas
fileBuffer.PutUnsignedChar(m_approachCount);
// save approach area info
for( int a=0; a<m_approachCount; ++a )
{
if (m_approach[a].here.area)
fileBuffer.PutUnsignedInt(m_approach[a].here.area->GetID());
else
fileBuffer.PutUnsignedInt(0);
if (m_approach[a].prev.area)
fileBuffer.PutUnsignedInt(m_approach[a].prev.area->GetID());
else
fileBuffer.PutUnsignedInt(0);
fileBuffer.PutUnsignedChar(m_approach[a].prevToHereHow);
if (m_approach[a].next.area)
fileBuffer.PutUnsignedInt(m_approach[a].next.area->GetID());
else
fileBuffer.PutUnsignedInt(0);
fileBuffer.PutUnsignedChar(m_approach[a].hereToNextHow);
}
}
NavErrorType CCSNavArea::Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
{
if ( version < 15 )
return LoadLegacy(fileBuffer, version, subVersion);
// load base class data
NavErrorType error = CNavArea::Load( fileBuffer, version, subVersion );
switch ( subVersion )
{
case 1:
//
// Load number of approach areas
//
m_approachCount = fileBuffer.GetUnsignedChar();
// load approach area info (IDs)
for( int a = 0; a < m_approachCount; ++a )
{
m_approach[a].here.id = fileBuffer.GetUnsignedInt();
m_approach[a].prev.id = fileBuffer.GetUnsignedInt();
m_approach[a].prevToHereHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
m_approach[a].next.id = fileBuffer.GetUnsignedInt();
m_approach[a].hereToNextHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
}
if ( !fileBuffer.IsValid() )
error = NAV_INVALID_FILE;
// fall through
case 0:
// legacy version
break;
default:
Warning( "Unknown NavArea sub-version number\n" );
error = NAV_INVALID_FILE;
}
return error;
}
NavErrorType CCSNavArea::PostLoad( void )
{
NavErrorType error = CNavArea::PostLoad();
// resolve approach area IDs
for ( int a = 0; a < m_approachCount; ++a )
{
m_approach[a].here.area = TheNavMesh->GetNavAreaByID( m_approach[a].here.id );
if (m_approach[a].here.id && m_approach[a].here.area == NULL)
{
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (here).\n" );
error = NAV_CORRUPT_DATA;
}
m_approach[a].prev.area = TheNavMesh->GetNavAreaByID( m_approach[a].prev.id );
if (m_approach[a].prev.id && m_approach[a].prev.area == NULL)
{
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (prev).\n" );
error = NAV_CORRUPT_DATA;
}
m_approach[a].next.area = TheNavMesh->GetNavAreaByID( m_approach[a].next.id );
if (m_approach[a].next.id && m_approach[a].next.area == NULL)
{
Msg( "CNavArea::PostLoad: Corrupt navigation data. Missing Approach Area (next).\n" );
error = NAV_CORRUPT_DATA;
}
}
return error;
}
void CCSNavArea::Draw( void ) const
{
CNavArea::Draw();
}
void CCSNavArea::CustomAnalysis( bool isIncremental /*= false */ )
{
ComputeApproachAreas();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Load legacy navigation area from the file
*/
NavErrorType CCSNavArea::LoadLegacy( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion )
{
// load ID
m_id = fileBuffer.GetUnsignedInt();
// update nextID to avoid collisions
if (m_id >= m_nextID)
m_nextID = m_id+1;
// load attribute flags
if ( version <= 8 )
{
m_attributeFlags = fileBuffer.GetUnsignedChar();
}
else if ( version < 13 )
{
m_attributeFlags = fileBuffer.GetUnsignedShort();
}
else
{
m_attributeFlags = fileBuffer.GetInt();
}
// load extent of area
fileBuffer.Get( &m_nwCorner, 3*sizeof(float) );
fileBuffer.Get( &m_seCorner, 3*sizeof(float) );
m_center.x = (m_nwCorner.x + m_seCorner.x)/2.0f;
m_center.y = (m_nwCorner.y + m_seCorner.y)/2.0f;
m_center.z = (m_nwCorner.z + m_seCorner.z)/2.0f;
if ( ( m_seCorner.x - m_nwCorner.x ) > 0.0f && ( m_seCorner.y - m_nwCorner.y ) > 0.0f )
{
m_invDxCorners = 1.0f / ( m_seCorner.x - m_nwCorner.x );
m_invDyCorners = 1.0f / ( m_seCorner.y - m_nwCorner.y );
}
else
{
m_invDxCorners = m_invDyCorners = 0;
DevWarning( "Degenerate Navigation Area #%d at setpos %g %g %g\n",
m_id, m_center.x, m_center.y, m_center.z );
}
// load heights of implicit corners
m_neZ = fileBuffer.GetFloat();
m_swZ = fileBuffer.GetFloat();
CheckWaterLevel();
// load connections (IDs) to adjacent areas
// in the enum order NORTH, EAST, SOUTH, WEST
for( int d=0; d<NUM_DIRECTIONS; d++ )
{
// load number of connections for this direction
unsigned int count = fileBuffer.GetUnsignedInt();
Assert( fileBuffer.IsValid() );
m_connect[d].EnsureCapacity( count );
for( unsigned int i=0; i<count; ++i )
{
NavConnect connect;
connect.id = fileBuffer.GetUnsignedInt();
Assert( fileBuffer.IsValid() );
// don't allow self-referential connections
if ( connect.id != m_id )
{
m_connect[d].AddToTail( connect );
}
}
}
//
// Load hiding spots
//
// load number of hiding spots
unsigned char hidingSpotCount = fileBuffer.GetUnsignedChar();
if (version == 1)
{
// load simple vector array
Vector pos;
for( int h=0; h<hidingSpotCount; ++h )
{
fileBuffer.Get( &pos, 3 * sizeof(float) );
// create new hiding spot and put on master list
HidingSpot *spot = TheNavMesh->CreateHidingSpot();
spot->SetPosition( pos );
spot->SetFlags( HidingSpot::IN_COVER );
m_hidingSpots.AddToTail( spot );
}
}
else
{
// load HidingSpot objects for this area
for( int h=0; h<hidingSpotCount; ++h )
{
// create new hiding spot and put on master list
HidingSpot *spot = TheNavMesh->CreateHidingSpot();
spot->Load( fileBuffer, version );
m_hidingSpots.AddToTail( spot );
}
}
if ( version < 15 )
{
//
// Load number of approach areas
//
m_approachCount = fileBuffer.GetUnsignedChar();
// load approach area info (IDs)
for( int a = 0; a < m_approachCount; ++a )
{
m_approach[a].here.id = fileBuffer.GetUnsignedInt();
m_approach[a].prev.id = fileBuffer.GetUnsignedInt();
m_approach[a].prevToHereHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
m_approach[a].next.id = fileBuffer.GetUnsignedInt();
m_approach[a].hereToNextHow = (NavTraverseType)fileBuffer.GetUnsignedChar();
}
}
//
// Load encounter paths for this area
//
unsigned int count = fileBuffer.GetUnsignedInt();
if (version < 3)
{
// old data, read and discard
for( unsigned int e=0; e<count; ++e )
{
SpotEncounter encounter;
encounter.from.id = fileBuffer.GetUnsignedInt();
encounter.to.id = fileBuffer.GetUnsignedInt();
fileBuffer.Get( &encounter.path.from.x, 3 * sizeof(float) );
fileBuffer.Get( &encounter.path.to.x, 3 * sizeof(float) );
// read list of spots along this path
unsigned char spotCount = fileBuffer.GetUnsignedChar();
for( int s=0; s<spotCount; ++s )
{
fileBuffer.GetFloat();
fileBuffer.GetFloat();
fileBuffer.GetFloat();
fileBuffer.GetFloat();
}
}
return NAV_OK;
}
for( unsigned int e=0; e<count; ++e )
{
SpotEncounter *encounter = new SpotEncounter;
encounter->from.id = fileBuffer.GetUnsignedInt();
unsigned char dir = fileBuffer.GetUnsignedChar();
encounter->fromDir = static_cast<NavDirType>( dir );
encounter->to.id = fileBuffer.GetUnsignedInt();
dir = fileBuffer.GetUnsignedChar();
encounter->toDir = static_cast<NavDirType>( dir );
// read list of spots along this path
unsigned char spotCount = fileBuffer.GetUnsignedChar();
SpotOrder order;
for( int s=0; s<spotCount; ++s )
{
order.id = fileBuffer.GetUnsignedInt();
unsigned char t = fileBuffer.GetUnsignedChar();
order.t = (float)t/255.0f;
encounter->spots.AddToTail( order );
}
m_spotEncounters.AddToTail( encounter );
}
if (version < 5)
return NAV_OK;
//
// Load Place data
//
PlaceDirectory::IndexType entry = fileBuffer.GetUnsignedShort();
// convert entry to actual Place
SetPlace( placeDirectory.IndexToPlace( entry ) );
if ( version < 7 )
return NAV_OK;
// load ladder data
for ( int dir=0; dir<CNavLadder::NUM_LADDER_DIRECTIONS; ++dir )
{
count = fileBuffer.GetUnsignedInt();
for( unsigned int i=0; i<count; ++i )
{
NavLadderConnect connect;
connect.id = fileBuffer.GetUnsignedInt();
bool alreadyConnected = false;
FOR_EACH_VEC( m_ladder[dir], j )
{
if ( m_ladder[dir][j].id == connect.id )
{
alreadyConnected = true;
break;
}
}
if ( !alreadyConnected )
{
m_ladder[dir].AddToTail( connect );
}
}
}
if ( version < 8 )
return NAV_OK;
// load earliest occupy times
for( int i=0; i<MAX_NAV_TEAMS; ++i )
{
// no spot in the map should take longer than this to reach
m_earliestOccupyTime[i] = fileBuffer.GetFloat();
}
if ( version < 11 )
return NAV_OK;
// load light intensity
for ( int i=0; i<NUM_CORNERS; ++i )
{
m_lightIntensity[i] = fileBuffer.GetFloat();
}
if ( version < 16 )
return NAV_OK;
// load visibility information
unsigned int visibleAreaCount = fileBuffer.GetUnsignedInt();
if ( !IsX360() )
{
m_potentiallyVisibleAreas.EnsureCapacity( visibleAreaCount );
}
else
{
/* TODO: Re-enable when latest 360 code gets integrated (MSB 5/5/09)
size_t nBytes = visibleAreaCount * sizeof( AreaBindInfo );
m_potentiallyVisibleAreas.~CAreaBindInfoArray();
new ( &m_potentiallyVisibleAreas ) CAreaBindInfoArray( (AreaBindInfo *)engine->AllocLevelStaticData( nBytes ), visibleAreaCount );
*/
}
for( unsigned int j=0; j<visibleAreaCount; ++j )
{
AreaBindInfo info;
info.id = fileBuffer.GetUnsignedInt();
info.attributes = fileBuffer.GetUnsignedChar();
m_potentiallyVisibleAreas.AddToTail( info );
}
// read area from which we inherit visibility
m_inheritVisibilityFrom.id = fileBuffer.GetUnsignedInt();
return NAV_OK;
}
+81
View File
@@ -0,0 +1,81 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_area.h
// Navigation areas
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#ifndef _CS_NAV_AREA_H_
#define _CS_NAV_AREA_H_
#include "nav_area.h"
//-------------------------------------------------------------------------------------------------------------------
/**
* A CNavArea is a rectangular region defining a walkable area in the environment
*/
class CCSNavArea : public CNavArea
{
public:
DECLARE_CLASS( CCSNavArea, CNavArea );
CCSNavArea( void );
~CCSNavArea();
virtual void OnServerActivate( void ); // (EXTEND) invoked when map is initially loaded
virtual void OnRoundRestart( void ); // (EXTEND) invoked for each area when the round restarts
virtual void Draw( void ) const; // draw area for debugging & editing
virtual void Save( CUtlBuffer &fileBuffer, unsigned int version ) const; // (EXTEND)
virtual NavErrorType Load( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion ); // (EXTEND)
virtual NavErrorType PostLoad( void ); // (EXTEND) invoked after all areas have been loaded - for pointer binding, etc
virtual void CustomAnalysis( bool isIncremental = false ); // for game-specific analysis
//- approach areas ----------------------------------------------------------------------------------
struct ApproachInfo
{
NavConnect here; ///< the approach area
NavConnect prev; ///< the area just before the approach area on the path
NavTraverseType prevToHereHow;
NavConnect next; ///< the area just after the approach area on the path
NavTraverseType hereToNextHow;
};
const ApproachInfo *GetApproachInfo( int i ) const { return &m_approach[i]; }
int GetApproachInfoCount( void ) const { return m_approachCount; }
void ComputeApproachAreas( void ); ///< determine the set of "approach areas" - for map learning
//- player counting --------------------------------------------------------------------------------
void ClearPlayerCount( void ); ///< set the player count to zero
protected:
NavErrorType LoadLegacy( CUtlBuffer &fileBuffer, unsigned int version, unsigned int subVersion );
private:
//- approach areas ----------------------------------------------------------------------------------
enum { MAX_APPROACH_AREAS = 16 };
ApproachInfo m_approach[ MAX_APPROACH_AREAS ];
unsigned char m_approachCount;
};
//--------------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------------
//
// Inlines
//
inline void CCSNavArea::ClearPlayerCount( void )
{
for( int i=0; i<MAX_NAV_TEAMS; ++i )
{
m_playerCount[ i ] = 0;
}
}
#endif // _CS_NAV_AREA_H_
+69
View File
@@ -0,0 +1,69 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_edit.cpp
// Implementation of Navigation Mesh edit mode
// Author: Michael Booth, 2003-2004
#include "cbase.h"
#include "nav_mesh.h"
#include "cs_nav_pathfind.h"
#include "cs_nav_node.h"
#include "nav_colors.h"
#include "Color.h"
#include "tier0/vprof.h"
#include "collisionutils.h"
ConVar nav_show_area_info( "nav_show_area_info", "0.5", FCVAR_GAMEDLL, "Duration in seconds to show nav area ID and attributes while editing" );
ConVar nav_snap_to_grid( "nav_snap_to_grid", "0", FCVAR_GAMEDLL, "Snap to the nav generation grid when creating new nav areas" );
ConVar nav_create_place_on_ground( "nav_create_place_on_ground", "0", FCVAR_GAMEDLL, "If true, nav areas will be placed flush with the ground when created by hand." );
#if DEBUG_NAV_NODES
extern ConVar nav_show_nodes;
#endif // DEBUG_NAV_NODES
//--------------------------------------------------------------------------------------------------------------
void EditNav_Precache(void *pUser)
{
CBaseEntity::PrecacheScriptSound( "Bot.EditSwitchOn" );
CBaseEntity::PrecacheScriptSound( "EDIT_TOGGLE_PLACE_MODE" );
CBaseEntity::PrecacheScriptSound( "Bot.EditSwitchOff" );
CBaseEntity::PrecacheScriptSound( "EDIT_PLACE_PICK" );
CBaseEntity::PrecacheScriptSound( "EDIT_DELETE" );
CBaseEntity::PrecacheScriptSound( "EDIT.ToggleAttribute" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLIT.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLIT.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MERGE.Enable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MERGE.Disable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK.Enable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK.Disable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.Enable" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MARK_UNNAMED.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_CONNECT.AllDirections" );
CBaseEntity::PrecacheScriptSound( "EDIT_CONNECT.Added" );
CBaseEntity::PrecacheScriptSound( "EDIT_DISCONNECT.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_DISCONNECT.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLICE.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SPLICE.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SELECT_CORNER.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_SELECT_CORNER.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MOVE_CORNER.MarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_MOVE_CORNER.NoMarkedArea" );
CBaseEntity::PrecacheScriptSound( "EDIT_BEGIN_AREA.Creating" );
CBaseEntity::PrecacheScriptSound( "EDIT_BEGIN_AREA.NotCreating" );
CBaseEntity::PrecacheScriptSound( "EDIT_END_AREA.Creating" );
CBaseEntity::PrecacheScriptSound( "EDIT_END_AREA.NotCreating" );
CBaseEntity::PrecacheScriptSound( "EDIT_WARP_TO_MARK" );
}
#ifdef CSTRIKE_DLL
PRECACHE_REGISTER_FN( EditNav_Precache );
#endif
File diff suppressed because it is too large Load Diff
+269
View File
@@ -0,0 +1,269 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_generate.cpp
// Auto-generate a Navigation Mesh by sampling the current map
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
#include "cbase.h"
#include "util_shared.h"
#include "nav_mesh.h"
#include "cs_nav_area.h"
#include "cs_nav_node.h"
#include "cs_nav_pathfind.h"
#include "viewport_panel_names.h"
enum { MAX_BLOCKED_AREAS = 256 };
static unsigned int blockedID[ MAX_BLOCKED_AREAS ];
static int blockedIDCount = 0;
static float lastMsgTime = 0.0f;
//ConVar nav_slope_limit( "nav_slope_limit", "0.7", FCVAR_GAMEDLL, "The ground unit normal's Z component must be greater than this for nav areas to be generated." );
ConVar nav_restart_after_analysis( "nav_restart_after_analysis", "1", FCVAR_GAMEDLL, "When nav nav_restart_after_analysis finishes, restart the server. Turning this off can cause crashes, but is useful for incremental generation." );
//--------------------------------------------------------------------------------------------------------------
/**
* Shortest path cost, paying attention to "blocked" areas
*/
class ApproachAreaCost
{
public:
// HPE_TODO[pmf]: check that these new parameters are okay to be ignored
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
{
// check if this area is "blocked"
for( int i=0; i<blockedIDCount; ++i )
{
if (area->GetID() == blockedID[i])
{
return -1.0f;
}
}
if (fromArea == NULL)
{
// first area in path, no cost
return 0.0f;
}
else
{
// compute distance traveled along path so far
float dist;
if (ladder)
{
dist = ladder->m_length;
}
else
{
dist = (area->GetCenter() - fromArea->GetCenter()).Length();
}
float cost = dist + fromArea->GetCostSoFar();
return cost;
}
}
};
/*
* Determine the set of "approach areas".
* An approach area is an area representing a place where players
* move into/out of our local neighborhood of areas.
* @todo Optimize by search from eye outward and modifying pathfinder to treat all links as bi-directional
*/
void CCSNavArea::ComputeApproachAreas( void )
{
m_approachCount = 0;
if (nav_quicksave.GetBool())
return;
// use the center of the nav area as the "view" point
Vector eye = m_center;
if (TheNavMesh->GetGroundHeight( eye, &eye.z ) == false)
return;
// approximate eye position
if (GetAttributes() & NAV_MESH_CROUCH)
eye.z += 0.9f * HalfHumanHeight;
else
eye.z += 0.9f * HumanHeight;
enum { MAX_PATH_LENGTH = 256 };
CNavArea *path[ MAX_PATH_LENGTH ];
ApproachAreaCost cost;
enum SearchType
{
FROM_EYE, ///< start search from our eyepoint outward to farArea
TO_EYE, ///< start search from farArea beack towards our eye
SEARCH_FINISHED
};
//
// In order to *completely* enumerate all of the approach areas, we
// need to search from our eyepoint outward, as well as from outwards
// towards our eyepoint
//
for( int searchType = FROM_EYE; searchType != SEARCH_FINISHED; ++searchType )
{
//
// In order to enumerate all of the approach areas, we need to
// run the algorithm many times, once for each "far away" area
// and keep the union of the approach area sets
//
int it;
for( it = 0; it < TheNavAreas.Count(); ++it )
{
CNavArea *farArea = TheNavAreas[ it ];
blockedIDCount = 0;
// skip the small areas
const float minSize = 200.0f; // 150
Extent extent;
farArea->GetExtent(&extent);
if (extent.SizeX() < minSize || extent.SizeY() < minSize)
{
continue;
}
// if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak
if (farArea->IsVisible( eye ))
{
continue;
}
//
// Keep building paths to farArea and blocking them off until we
// cant path there any more.
// As areas are blocked off, all exits will be enumerated.
//
while( m_approachCount < MAX_APPROACH_AREAS )
{
CNavArea *from, *to;
if (searchType == FROM_EYE)
{
// find another path *to* 'farArea'
// we must pathfind from us in order to pick up one-way paths OUT OF our area
from = this;
to = farArea;
}
else // TO_EYE
{
// find another path *from* 'farArea'
// we must pathfind to us in order to pick up one-way paths INTO our area
from = farArea;
to = this;
}
// build the actual path
if (NavAreaBuildPath( from, to, NULL, cost ) == false)
{
break;
}
// find number of areas on path
int count = 0;
CNavArea *area;
for( area = to; area; area = area->GetParent() )
{
++count;
}
if (count > MAX_PATH_LENGTH)
{
count = MAX_PATH_LENGTH;
}
// if the path is only two areas long, there can be no approach points
if (count <= 2)
{
break;
}
// build path starting from eye
int i = 0;
if (searchType == FROM_EYE)
{
for( area = to; i < count && area; area = area->GetParent() )
{
path[ count-i-1 ] = area;
++i;
}
}
else // TO_EYE
{
for( area = to; i < count && area; area = area->GetParent() )
{
path[ i++ ] = area;
}
}
// traverse path to find first area we cannot see (skip the first area)
for( i=1; i<count; ++i )
{
// if we see this area, continue on
if (path[i]->IsVisible( eye ))
{
continue;
}
// we can't see this area - mark this area as "blocked" and unusable by subsequent approach paths
if (blockedIDCount == MAX_BLOCKED_AREAS)
{
Msg( "Overflow computing approach areas for area #%d.\n", GetID());
return;
}
// if the area to be blocked is actually farArea, block the one just prior
// (blocking farArea will cause all subsequent pathfinds to fail)
int block = (path[i] == farArea) ? i-1 : i;
// dont block the start area, or all subsequence pathfinds will fail
if (block == 0)
{
continue;
}
blockedID[ blockedIDCount++ ] = path[ block ]->GetID();
// store new approach area if not already in set
int a;
for( a=0; a<m_approachCount; ++a )
{
if (m_approach[a].here.area == path[block-1])
{
break;
}
}
if (a == m_approachCount)
{
m_approach[ m_approachCount ].prev.area = (block >= 2) ? path[block-2] : NULL;
m_approach[ m_approachCount ].here.area = path[block-1];
m_approach[ m_approachCount ].prevToHereHow = path[block-1]->GetParentHow();
m_approach[ m_approachCount ].next.area = path[block];
m_approach[ m_approachCount ].hereToNextHow = path[block]->GetParentHow();
++m_approachCount;
}
// we are done with this path
break;
}
}
}
}
}
+127
View File
@@ -0,0 +1,127 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// NavMesh.cpp
// Implementation of Navigation Mesh interface
// Author: Michael S. Booth, 2003-2004
#include "cbase.h"
#include "filesystem.h"
#include "cs_nav_mesh.h"
#include "cs_nav_node.h"
#include "cs_nav_area.h"
#include "fmtstr.h"
#include "utlbuffer.h"
#include "tier0/vprof.h"
//--------------------------------------------------------------------------------------------------------------
CSNavMesh::CSNavMesh( void )
{
}
//--------------------------------------------------------------------------------------------------------------
CSNavMesh::~CSNavMesh()
{
}
CNavArea * CSNavMesh::CreateArea( void ) const
{
return new CCSNavArea;
}
//-------------------------------------------------------------------------
void CSNavMesh::BeginCustomAnalysis( bool bIncremental )
{
}
//-------------------------------------------------------------------------
// invoked when custom analysis step is complete
void CSNavMesh::PostCustomAnalysis( void )
{
}
//-------------------------------------------------------------------------
void CSNavMesh::EndCustomAnalysis()
{
}
//-------------------------------------------------------------------------
/**
* Returns sub-version number of data format used by derived classes
*/
unsigned int CSNavMesh::GetSubVersionNumber( void ) const
{
// 1: initial implementation - added ApproachArea data
return 1;
}
//-------------------------------------------------------------------------
/**
* Store custom mesh data for derived classes
*/
void CSNavMesh::SaveCustomData( CUtlBuffer &fileBuffer ) const
{
}
//-------------------------------------------------------------------------
/**
* Load custom mesh data for derived classes
*/
void CSNavMesh::LoadCustomData( CUtlBuffer &fileBuffer, unsigned int subVersion )
{
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset the Navigation Mesh to initial values
*/
void CSNavMesh::Reset( void )
{
CNavMesh::Reset();
}
//--------------------------------------------------------------------------------------------------------------
/**
* Zero player counts in all areas
*/
void CSNavMesh::ClearPlayerCounts( void )
{
FOR_EACH_VEC( TheNavAreas, it )
{
CCSNavArea *area = (CCSNavArea*)TheNavAreas[ it ];
area->ClearPlayerCount();
}
}
void CSNavMesh::Update( void )
{
CNavMesh::Update();
}
NavErrorType CSNavMesh::Load( void )
{
return CNavMesh::Load();
}
bool CSNavMesh::Save( void ) const
{
return CNavMesh::Save();
}
NavErrorType CSNavMesh::PostLoad( unsigned int version )
{
return CNavMesh::PostLoad(version);
}
+68
View File
@@ -0,0 +1,68 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// cs_nav_mesh.h
// The Navigation Mesh interface
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
//
// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003
//
// NOTE: The Navigation code uses Doxygen-style comments. If you run Doxygen over this code, it will
// auto-generate documentation. Visit www.doxygen.org to download the system for free.
//
#ifndef _CS_NAV_MESH_H_
#define _CS_NAV_MESH_H_
#include "filesystem.h"
#include "nav_mesh.h"
#include "cs_nav.h"
#include "nav_area.h"
#include "nav_colors.h"
class CNavArea;
class CCSNavArea;
class CBaseEntity;
//--------------------------------------------------------------------------------------------------------
/**
* The CSNavMesh is the global interface to the Navigation Mesh.
* @todo Make this an abstract base class interface, and derive mod-specific implementations.
*/
class CSNavMesh : public CNavMesh
{
public:
CSNavMesh( void );
virtual ~CSNavMesh();
virtual CNavArea *CreateArea( void ) const; // CNavArea factory
virtual unsigned int GetSubVersionNumber( void ) const; // returns sub-version number of data format used by derived classes
virtual void SaveCustomData( CUtlBuffer &fileBuffer ) const; // store custom mesh data for derived classes
virtual void LoadCustomData( CUtlBuffer &fileBuffer, unsigned int subVersion ); // load custom mesh data for derived classes
virtual void Reset( void ); ///< destroy Navigation Mesh data and revert to initial state
virtual void Update( void ); ///< invoked on each game frame
virtual NavErrorType Load( void ); // load navigation data from a file
virtual NavErrorType PostLoad( unsigned int version ); // (EXTEND) invoked after all areas have been loaded - for pointer binding, etc
virtual bool Save( void ) const; ///< store Navigation Mesh to a file
void ClearPlayerCounts( void ); ///< zero player counts in all areas
protected:
virtual void BeginCustomAnalysis( bool bIncremental );
virtual void PostCustomAnalysis( void ); // invoked when custom analysis step is complete
virtual void EndCustomAnalysis();
private:
};
#endif // _CS_NAV_MESH_H_
+383
View File
@@ -0,0 +1,383 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_node.cpp
// AI Navigation Nodes
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#include "cbase.h"
#include "cs_nav_node.h"
#include "nav_colors.h"
#include "nav_mesh.h"
NavDirType Opposite[ NUM_DIRECTIONS ] = { SOUTH, WEST, NORTH, EAST };
CSNavNode *CSNavNode::m_list = NULL;
unsigned int CSNavNode::m_listLength = 0;
unsigned int CSNavNode::m_nextID = 1;
ConVar nav_show_nodes( "nav_show_nodes", "0" );
//--------------------------------------------------------------------------------------------------------------
class LookAtTarget
{
public:
LookAtTarget( const Vector &target )
{
m_target = target;
}
bool operator()( CBasePlayer *player )
{
QAngle angles;
Vector to = m_target - player->GetAbsOrigin();
VectorAngles( to, angles );
player->SetLocalAngles( angles );
player->SnapEyeAngles( angles );
return true;
}
private:
Vector m_target;
};
//--------------------------------------------------------------------------------------------------------------
/**
* Constructor
*/
CSNavNode::CSNavNode( const Vector &pos, const Vector &normal, CSNavNode *parent )
{
m_pos = pos;
m_normal = normal;
m_id = m_nextID++;
int i;
for( i=0; i<NUM_DIRECTIONS; ++i )
{
m_to[ i ] = NULL;
}
for ( i=0; i<NUM_CORNERS; ++i )
{
m_crouch[ i ] = false;
}
m_visited = 0;
m_parent = parent;
m_next = m_list;
m_list = this;
m_listLength++;
m_isCovered = false;
m_area = NULL;
m_attributeFlags = 0;
if ( nav_show_nodes.GetBool() )
{
NDebugOverlay::Cross3D( m_pos, 10.0f, 128, 128, 128, true, 10.0f );
NDebugOverlay::Cross3D( m_pos, 10.0f, 255, 255, 255, false, 10.0f );
LookAtTarget lookAt( m_pos );
ForEachPlayer( lookAt );
}
}
//--------------------------------------------------------------------------------------------------------------
#if DEBUG_NAV_NODES
ConVar nav_show_node_id( "nav_show_node_id", "0" );
ConVar nav_test_node( "nav_test_node", "0" );
ConVar nav_test_node_crouch( "nav_test_node_crouch", "0" );
ConVar nav_test_node_crouch_dir( "nav_test_node_crouch_dir", "4" );
#endif // DEBUG_NAV_NODES
//--------------------------------------------------------------------------------------------------------------
void CSNavNode::Draw( void )
{
#if DEBUG_NAV_NODES
if ( !nav_show_nodes.GetBool() )
return;
int r = 0, g = 0, b = 0;
if ( m_isCovered )
{
if ( GetAttributes() & NAV_MESH_CROUCH )
{
b = 255;
}
else
{
r = 255;
}
}
else
{
if ( GetAttributes() & NAV_MESH_CROUCH )
{
b = 255;
}
g = 255;
}
NDebugOverlay::Cross3D( m_pos, 2, r, g, b, true, 0.1f );
if ( (!m_isCovered && nav_show_node_id.GetBool()) || (m_isCovered && nav_show_node_id.GetInt() < 0) )
{
char text[16];
Q_snprintf( text, sizeof( text ), "%d", m_id );
NDebugOverlay::Text( m_pos, text, true, 0.1f );
}
if ( (unsigned int)(nav_test_node.GetInt()) == m_id )
{
TheNavMesh->TestArea( this, 1, 1 );
nav_test_node.SetValue( 0 );
}
if ( (unsigned int)(nav_test_node_crouch.GetInt()) == m_id )
{
CheckCrouch();
nav_test_node_crouch.SetValue( 0 );
}
if ( GetAttributes() & NAV_MESH_CROUCH )
{
int i;
for( i=0; i<NUM_CORNERS; i++ )
{
if ( m_crouch[i] )
{
Vector2D dir;
CornerToVector2D( (NavCornerType)i, &dir );
const float scale = 3.0f;
Vector scaled( dir.x * scale, dir.y * scale, 0 );
NDebugOverlay::HorzArrow( m_pos, m_pos + scaled, 0.5, 0, 0, 255, 255, true, 0.1f );
}
}
}
#endif // DEBUG_NAV_NODES
}
//--------------------------------------------------------------------------------------------------------------
void CSNavNode::CheckCrouch( void )
{
CTraceFilterWalkableEntities filter( NULL, COLLISION_GROUP_PLAYER_MOVEMENT, WALK_THRU_EVERYTHING );
trace_t tr;
// Trace downward from duck height to find the max floor height for the node's surroundings
Vector mins( -HalfHumanWidth, -HalfHumanWidth, 0 );
Vector maxs( HalfHumanWidth, HalfHumanWidth, 0 );
Vector start( m_pos.x, m_pos.y, m_pos.z + VEC_DUCK_HULL_MAX.z - 0.1f );
UTIL_TraceHull(
start,
m_pos,
mins,
maxs,
MASK_PLAYERSOLID_BRUSHONLY,
&filter,
&tr );
Vector groundPos = tr.endpos;
if ( tr.startsolid && !tr.allsolid )
{
// Try going down out of the solid and re-check for the floor height
start.z -= tr.endpos.z - 0.1f;
UTIL_TraceHull(
start,
m_pos,
mins,
maxs,
MASK_PLAYERSOLID_BRUSHONLY,
&filter,
&tr );
groundPos = tr.endpos;
}
if ( tr.startsolid )
{
// we don't even have duck height clear. try a simple check to find floor height.
float x, y;
// Find the highest floor z - for a player to stand in this area, we need a full
// VEC_HULL_MAX.z of clearance above this height at all points.
float maxFloorZ = m_pos.z;
for( y = -HalfHumanWidth; y <= HalfHumanWidth + 0.1f; y += HalfHumanWidth )
{
for( x = -HalfHumanWidth; x <= HalfHumanWidth + 0.1f; x += HalfHumanWidth )
{
float floorZ;
if ( TheNavMesh->GetGroundHeight( m_pos, &floorZ ) )
{
maxFloorZ = MAX( maxFloorZ, floorZ + 0.1f );
}
}
}
groundPos.Init( m_pos.x, m_pos.y, maxFloorZ );
}
// For each direction, trace upwards from our best ground height to VEC_HULL_MAX.z to see if we have standing room.
for ( int i=0; i<NUM_CORNERS; ++i )
{
#if DEBUG_NAV_NODES
if ( nav_test_node_crouch_dir.GetInt() != NUM_CORNERS && i != nav_test_node_crouch_dir.GetInt() )
continue;
#endif // DEBUG_NAV_NODES
NavCornerType corner = (NavCornerType)i;
Vector2D cornerVec;
CornerToVector2D( corner, &cornerVec );
Vector actualGroundPos = groundPos; // we might need to adjust this if the tracehull failed above and we fell back to m_pos.z
// Build a mins/maxs pair for the HumanWidth x HalfHumanWidth box facing the appropriate direction
mins.Init();
maxs.Init( cornerVec.x * HalfHumanWidth, cornerVec.y * HalfHumanWidth, 0 );
// now make sure that mins is smaller than maxs
for ( int j=0; j<3; ++j )
{
if ( mins[j] > maxs[j] )
{
float tmp = mins[j];
mins[j] = maxs[j];
maxs[j] = tmp;
}
}
UTIL_TraceHull(
actualGroundPos + Vector( 0, 0, 0.1f ),
actualGroundPos + Vector( 0, 0, VEC_HULL_MAX.z - 0.2f ),
mins,
maxs,
MASK_PLAYERSOLID_BRUSHONLY,
&filter,
&tr );
actualGroundPos.z += tr.fractionleftsolid * VEC_HULL_MAX.z;
float maxHeight = actualGroundPos.z + VEC_DUCK_HULL_MAX.z;
for ( ; tr.startsolid && actualGroundPos.z <= maxHeight; actualGroundPos.z += 1.0f )
{
// In case we didn't find a good ground pos above, we could start in the ground. Move us up some.
UTIL_TraceHull(
actualGroundPos + Vector( 0, 0, 0.1f ),
actualGroundPos + Vector( 0, 0, VEC_HULL_MAX.z - 0.2f ),
mins,
maxs,
MASK_PLAYERSOLID_BRUSHONLY,
&filter,
&tr );
}
if (tr.startsolid || tr.fraction != 1.0f)
{
SetAttributes( NAV_MESH_CROUCH );
m_crouch[corner] = true;
}
#if DEBUG_NAV_NODES
if ( nav_show_nodes.GetBool() )
{
if ( nav_test_node_crouch_dir.GetInt() == i || nav_test_node_crouch_dir.GetInt() == NUM_CORNERS )
{
if ( tr.startsolid )
{
NDebugOverlay::Box( actualGroundPos, mins, maxs+Vector( 0, 0, VEC_HULL_MAX.z), 255, 0, 0, 10, 20.0f );
}
else if ( m_crouch[corner] )
{
NDebugOverlay::Box( actualGroundPos, mins, maxs+Vector( 0, 0, VEC_HULL_MAX.z), 0, 0, 255, 10, 20.0f );
}
else
{
NDebugOverlay::Box( actualGroundPos, mins, maxs+Vector( 0, 0, VEC_HULL_MAX.z), 0, 255, 0, 10, 10.0f );
}
}
}
#endif // DEBUG_NAV_NODES
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Create a connection FROM this node TO the given node, in the given direction
*/
void CSNavNode::ConnectTo( CSNavNode *node, NavDirType dir )
{
m_to[ dir ] = node;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return node at given position.
* @todo Need a hash table to make this lookup fast
*/
CSNavNode *CSNavNode::GetNode( const Vector &pos )
{
const float tolerance = 0.45f * GenerationStepSize; // 1.0f
for( CSNavNode *node = m_list; node; node = node->m_next )
{
float dx = fabs( node->m_pos.x - pos.x );
float dy = fabs( node->m_pos.y - pos.y );
float dz = fabs( node->m_pos.z - pos.z );
if (dx < tolerance && dy < tolerance && dz < tolerance)
return node;
}
return NULL;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if this node is bidirectionally linked to
* another node in the given direction
*/
BOOL CSNavNode::IsBiLinked( NavDirType dir ) const
{
if (m_to[ dir ] && m_to[ dir ]->m_to[ Opposite[dir] ] == this)
{
return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------------------
/**
* Return true if this node is the NW corner of a quad of nodes
* that are all bidirectionally linked.
*/
BOOL CSNavNode::IsClosedCell( void ) const
{
if (IsBiLinked( SOUTH ) &&
IsBiLinked( EAST ) &&
m_to[ EAST ]->IsBiLinked( SOUTH ) &&
m_to[ SOUTH ]->IsBiLinked( EAST ) &&
m_to[ EAST ]->m_to[ SOUTH ] == m_to[ SOUTH ]->m_to[ EAST ])
{
return true;
}
return false;
}
+135
View File
@@ -0,0 +1,135 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_node.h
// Navigation Nodes are used when generating a Navigation Mesh by point sampling the map
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#ifndef _CS_NAV_NODE_H_
#define _CS_NAV_NODE_H_
#include "cs_nav.h"
// If DEBUG_NAV_NODES is true, nav_show_nodes controls drawing node positions, and
// nav_show_node_id allows you to show the IDs of nodes that didn't get used to create areas.
#ifdef _DEBUG
#define DEBUG_NAV_NODES 1
#else
#define DEBUG_NAV_NODES 0
#endif
//--------------------------------------------------------------------------------------------------------------
/**
* Navigation Nodes.
* These Nodes encapsulate world locations, and ways to get from one location to an adjacent one.
* Note that these links are not necessarily commutative (falling off of a ledge, for example).
*/
class CSNavNode
{
public:
CSNavNode( const Vector &pos, const Vector &normal, CSNavNode *parent = NULL );
static CSNavNode *GetNode( const Vector &pos ); ///< return navigation node at the position, or NULL if none exists
CSNavNode *GetConnectedNode( NavDirType dir ) const; ///< get navigation node connected in given direction, or NULL if cant go that way
const Vector *GetPosition( void ) const;
const Vector *GetNormal( void ) const { return &m_normal; }
unsigned int GetID( void ) const { return m_id; }
static CSNavNode *GetFirst( void ) { return m_list; }
static unsigned int GetListLength( void ) { return m_listLength; }
CSNavNode *GetNext( void ) { return m_next; }
void Draw( void );
void ConnectTo( CSNavNode *node, NavDirType dir ); ///< create a connection FROM this node TO the given node, in the given direction
CSNavNode *GetParent( void ) const;
void MarkAsVisited( NavDirType dir ); ///< mark the given direction as having been visited
BOOL HasVisited( NavDirType dir ); ///< return TRUE if the given direction has already been searched
BOOL IsBiLinked( NavDirType dir ) const; ///< node is bidirectionally linked to another node in the given direction
BOOL IsClosedCell( void ) const; ///< node is the NW corner of a bi-linked quad of nodes
void Cover( void ) { m_isCovered = true; } ///< @todo Should pass in area that is covering
BOOL IsCovered( void ) const { return m_isCovered; } ///< return true if this node has been covered by an area
void AssignArea( CNavArea *area ); ///< assign the given area to this node
CNavArea *GetArea( void ) const; ///< return associated area
void SetAttributes( unsigned char bits ) { m_attributeFlags = bits; }
unsigned char GetAttributes( void ) const { return m_attributeFlags; }
private:
friend class CNavMesh;
void CheckCrouch( void );
Vector m_pos; ///< position of this node in the world
Vector m_normal; ///< surface normal at this location
CSNavNode *m_to[ NUM_DIRECTIONS ]; ///< links to north, south, east, and west. NULL if no link
unsigned int m_id; ///< unique ID of this node
unsigned char m_attributeFlags; ///< set of attribute bit flags (see NavAttributeType)
static CSNavNode *m_list; ///< the master list of all nodes for this map
static unsigned int m_listLength;
static unsigned int m_nextID;
CSNavNode *m_next; ///< next link in master list
// below are only needed when generating
unsigned char m_visited; ///< flags for automatic node generation. If direction bit is clear, that direction hasn't been explored yet.
CSNavNode *m_parent; ///< the node prior to this in the search, which we pop back to when this node's search is done (a stack)
bool m_isCovered; ///< true when this node is "covered" by a CNavArea
CNavArea *m_area; ///< the area this node is contained within
bool m_crouch[ NUM_CORNERS ];
};
//--------------------------------------------------------------------------------------------------------------
//
// Inlines
//
inline CSNavNode *CSNavNode::GetConnectedNode( NavDirType dir ) const
{
return m_to[ dir ];
}
inline const Vector *CSNavNode::GetPosition( void ) const
{
return &m_pos;
}
inline CSNavNode *CSNavNode::GetParent( void ) const
{
return m_parent;
}
inline void CSNavNode::MarkAsVisited( NavDirType dir )
{
m_visited |= (1 << dir);
}
inline BOOL CSNavNode::HasVisited( NavDirType dir )
{
if (m_visited & (1 << dir))
return true;
return false;
}
inline void CSNavNode::AssignArea( CNavArea *area )
{
m_area = area;
}
inline CNavArea *CSNavNode::GetArea( void ) const
{
return m_area;
}
#endif // _CS_NAV_NODE_H_
File diff suppressed because it is too large Load Diff
+246
View File
@@ -0,0 +1,246 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_path.h
// Navigation Path encapsulation
// Author: Michael S. Booth (mike@turtlerockstudios.com), November 2003
#ifndef _CS_NAV_PATH_H_
#define _CS_NAV_PATH_H_
#include "nav_area.h"
#include "bot_util.h"
class CImprovLocomotor;
//--------------------------------------------------------------------------------------------------------
/**
* The CCSNavPath class encapsulates a path through space
*/
class CCSNavPath
{
public:
CCSNavPath( void )
{
m_segmentCount = 0;
}
struct PathSegment
{
CNavArea *area; ///< the area along the path
NavTraverseType how; ///< how to enter this area from the previous one
Vector pos; ///< our movement goal position at this point in the path
const CNavLadder *ladder; ///< if "how" refers to a ladder, this is it
};
const PathSegment * operator[] ( int i ) const { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; }
const PathSegment *GetSegment( int i ) const { return (i >= 0 && i < m_segmentCount) ? &m_path[i] : NULL; }
int GetSegmentCount( void ) const { return m_segmentCount; }
const Vector &GetEndpoint( void ) const { return m_path[ m_segmentCount-1 ].pos; }
bool IsAtEnd( const Vector &pos ) const; ///< return true if position is at the end of the path
float GetLength( void ) const; ///< return length of path from start to finish
bool GetPointAlongPath( float distAlong, Vector *pointOnPath ) const; ///< return point a given distance along the path - if distance is out of path bounds, point is clamped to start/end
/// return the node index closest to the given distance along the path without going over - returns (-1) if error
int GetSegmentIndexAlongPath( float distAlong ) const;
bool IsValid( void ) const { return (m_segmentCount > 0); }
void Invalidate( void ) { m_segmentCount = 0; }
void Draw( const Vector &color = Vector( 1.0f, 0.3f, 0 ) ); ///< draw the path for debugging
/// compute closest point on path to given point
bool FindClosestPointOnPath( const Vector *worldPos, int startIndex, int endIndex, Vector *close ) const;
void Optimize( void );
/**
* Compute shortest path from 'start' to 'goal' via A* algorithm.
* If returns true, path was build to the goal position.
* If returns false, path may either be invalid (use IsValid() to check), or valid but
* doesn't reach all the way to the goal.
*/
template< typename CostFunctor >
bool Compute( const Vector &start, const Vector &goal, CostFunctor &costFunc )
{
Invalidate();
CNavArea *startArea = TheNavMesh->GetNearestNavArea( start + Vector( 0.0f, 0.0f, 1.0f ) );
if (startArea == NULL)
{
return false;
}
CNavArea *goalArea = TheNavMesh->GetNavArea( goal );
// if we are already in the goal area, build trivial path
if (startArea == goalArea)
{
BuildTrivialPath( start, goal );
return true;
}
// make sure path end position is on the ground
Vector pathEndPosition = goal;
if (goalArea)
{
pathEndPosition.z = goalArea->GetZ( pathEndPosition );
}
else
{
TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
}
//
// Compute shortest path to goal
//
CNavArea *closestArea;
bool pathResult = NavAreaBuildPath( startArea, goalArea, &goal, costFunc, &closestArea );
//
// Build path by following parent links
//
// get count
int count = 0;
CNavArea *area;
for( area = closestArea; area; area = area->GetParent() )
{
++count;
}
// save room for endpoint
if (count > MAX_PATH_SEGMENTS-1)
{
count = MAX_PATH_SEGMENTS-1;
}
if (count == 0)
{
return false;
}
if (count == 1)
{
BuildTrivialPath( start, goal );
return true;
}
// build path
m_segmentCount = count;
for( area = closestArea; count && area; area = area->GetParent() )
{
--count;
m_path[ count ].area = area;
m_path[ count ].how = area->GetParentHow();
}
// compute path positions
if (ComputePathPositions() == false)
{
//PrintIfWatched( "CNavPath::Compute: Error building path\n" );
Invalidate();
return false;
}
// append path end position
m_path[ m_segmentCount ].area = closestArea;
m_path[ m_segmentCount ].pos = pathEndPosition;
m_path[ m_segmentCount ].ladder = NULL;
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
++m_segmentCount;
return pathResult;
}
private:
enum { MAX_PATH_SEGMENTS = 256 };
PathSegment m_path[ MAX_PATH_SEGMENTS ];
int m_segmentCount;
bool ComputePathPositions( void ); ///< determine actual path positions
bool BuildTrivialPath( const Vector &start, const Vector &goal ); ///< utility function for when start and goal are in the same area
int FindNextOccludedNode( int anchor ); ///< used by Optimize()
};
//--------------------------------------------------------------------------------------------------------
/**
* Monitor improv movement and determine if it becomes stuck
*/
class CStuckMonitor
{
public:
CStuckMonitor( void );
void Reset( void );
void Update( CImprovLocomotor *improv );
bool IsStuck( void ) const { return m_isStuck; }
float GetDuration( void ) const { return (m_isStuck) ? m_stuckTimer.GetElapsedTime() : 0.0f; }
private:
bool m_isStuck; ///< if true, we are stuck
Vector m_stuckSpot; ///< the location where we became stuck
IntervalTimer m_stuckTimer; ///< how long we have been stuck
enum { MAX_VEL_SAMPLES = 5 };
float m_avgVel[ MAX_VEL_SAMPLES ];
int m_avgVelIndex;
int m_avgVelCount;
Vector m_lastCentroid;
float m_lastTime;
};
//--------------------------------------------------------------------------------------------------------
/**
* The CNavPathFollower class implements path following behavior
*/
class CNavPathFollower
{
public:
CNavPathFollower( void );
void SetImprov( CImprovLocomotor *improv ) { m_improv = improv; }
void SetPath( CCSNavPath *path ) { m_path = path; }
void Reset( void );
#define DONT_AVOID_OBSTACLES false
void Update( float deltaT, bool avoidObstacles = true ); ///< move improv along path
void Debug( bool status ) { m_isDebug = status; } ///< turn debugging on/off
bool IsStuck( void ) const { return m_stuckMonitor.IsStuck(); } ///< return true if improv is stuck
void ResetStuck( void ) { m_stuckMonitor.Reset(); }
float GetStuckDuration( void ) const { return m_stuckMonitor.GetDuration(); } ///< return how long we've been stuck
void FeelerReflexAdjustment( Vector *goalPosition, float height = -1.0f ); ///< adjust goal position if "feelers" are touched
private:
CImprovLocomotor *m_improv; ///< who is doing the path following
CCSNavPath *m_path; ///< the path being followed
int m_segmentIndex; ///< the point on the path the improv is moving towards
int m_behindIndex; ///< index of the node on the path just behind us
Vector m_goal; ///< last computed follow goal
bool m_isLadderStarted;
bool m_isDebug;
int FindOurPositionOnPath( Vector *close, bool local ) const; ///< return the closest point to our current position on current path
int FindPathPoint( float aheadRange, Vector *point, int *prevIndex ); ///< compute a point a fixed distance ahead along our path.
CStuckMonitor m_stuckMonitor;
};
#endif // _CS_NAV_PATH_H_
+65
View File
@@ -0,0 +1,65 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// nav_pathfind.h
// Path-finding mechanisms using the Navigation Mesh
// Author: Michael S. Booth (mike@turtlerockstudios.com), January 2003
#ifndef _CS_NAV_PATHFIND_H_
#define _CS_NAV_PATHFIND_H_
#include "nav_pathfind.h"
//--------------------------------------------------------------------------------------------------------------
/**
* Compute travel distance along shortest path from startPos to goalPos.
* Return -1 if can't reach endPos from goalPos.
*/
template< typename CostFunctor >
float NavAreaTravelDistance( const Vector &startPos, const Vector &goalPos, CostFunctor &costFunc )
{
CNavArea *startArea = TheNavMesh->GetNearestNavArea( startPos );
if (startArea == NULL)
{
return -1.0f;
}
// compute path between areas using given cost heuristic
CNavArea *goalArea = NULL;
if (NavAreaBuildPath( startArea, NULL, &goalPos, costFunc, &goalArea ) == false)
{
return -1.0f;
}
// compute distance along path
if (goalArea->GetParent() == NULL)
{
// both points are in the same area - return euclidean distance
return (goalPos - startPos).Length();
}
else
{
CNavArea *area;
float distance;
// goalPos is assumed to be inside goalArea (or very close to it) - skip to next area
area = goalArea->GetParent();
distance = (goalPos - area->GetCenter()).Length();
for( ; area->GetParent(); area = area->GetParent() )
{
distance += (area->GetCenter() - area->GetParent()->GetCenter()).Length();
}
// add in distance to startPos
distance += (startPos - area->GetCenter()).Length();
return distance;
}
}
#endif // _CS_NAV_PATHFIND_H_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+343
View File
@@ -0,0 +1,343 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: CS's custom CPlayerResource
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_player.h"
#include "player_resource.h"
#include "cs_simple_hostage.h"
#include "cs_player_resource.h"
#include "weapon_c4.h"
#include <coordsize.h>
#include "cs_bot_manager.h"
#include "cs_gamerules.h"
// Datatable
IMPLEMENT_SERVERCLASS_ST(CCSPlayerResource, DT_CSPlayerResource)
SendPropInt( SENDINFO( m_iPlayerC4 ), 8, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iPlayerVIP ), 8, SPROP_UNSIGNED ),
SendPropVector( SENDINFO(m_vecC4), -1, SPROP_COORD),
SendPropArray3( SENDINFO_ARRAY3(m_bHostageAlive), SendPropInt( SENDINFO_ARRAY(m_bHostageAlive), 1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_isHostageFollowingSomeone), SendPropInt( SENDINFO_ARRAY(m_isHostageFollowingSomeone), 1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iHostageEntityIDs), SendPropInt( SENDINFO_ARRAY(m_iHostageEntityIDs), -1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iHostageY), SendPropInt( SENDINFO_ARRAY(m_iHostageY), COORD_INTEGER_BITS+1, 0 ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iHostageX), SendPropInt( SENDINFO_ARRAY(m_iHostageX), COORD_INTEGER_BITS+1, 0 ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iHostageZ), SendPropInt( SENDINFO_ARRAY(m_iHostageZ), COORD_INTEGER_BITS+1, 0 ) ),
SendPropVector( SENDINFO(m_bombsiteCenterA), -1, SPROP_COORD),
SendPropVector( SENDINFO(m_bombsiteCenterB), -1, SPROP_COORD),
SendPropArray3( SENDINFO_ARRAY3(m_hostageRescueX), SendPropInt( SENDINFO_ARRAY(m_hostageRescueX), COORD_INTEGER_BITS+1, 0 ) ),
SendPropArray3( SENDINFO_ARRAY3(m_hostageRescueY), SendPropInt( SENDINFO_ARRAY(m_hostageRescueY), COORD_INTEGER_BITS+1, 0 ) ),
SendPropArray3( SENDINFO_ARRAY3(m_hostageRescueZ), SendPropInt( SENDINFO_ARRAY(m_hostageRescueZ), COORD_INTEGER_BITS+1, 0 ) ),
SendPropBool( SENDINFO( m_bBombSpotted ) ),
SendPropArray3( SENDINFO_ARRAY3(m_bPlayerSpotted), SendPropInt( SENDINFO_ARRAY(m_bPlayerSpotted), 1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_iMVPs), SendPropInt( SENDINFO_ARRAY(m_iMVPs), COORD_INTEGER_BITS+1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_bHasDefuser), SendPropInt( SENDINFO_ARRAY(m_bHasDefuser), 1, SPROP_UNSIGNED ) ),
SendPropArray3( SENDINFO_ARRAY3(m_szClan), SendPropStringT( SENDINFO_ARRAY(m_szClan) ) ),
END_SEND_TABLE()
//=============================================================================
// HPE_END
//=============================================================================
BEGIN_DATADESC( CCSPlayerResource )
// DEFINE_ARRAY( m_iPing, FIELD_INTEGER, MAX_PLAYERS+1 ),
// DEFINE_ARRAY( m_iPacketloss, FIELD_INTEGER, MAX_PLAYERS+1 ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( cs_player_manager, CCSPlayerResource );
CCSPlayerResource::CCSPlayerResource( void )
{
}
//--------------------------------------------------------------------------------------------------------
class Spotter
{
public:
Spotter( CBaseEntity *entity, const Vector &target, int spottingTeam )
{
m_targetEntity = entity;
m_target = target;
m_team = spottingTeam;
m_spotted = false;
}
bool operator()( CBasePlayer *player )
{
if ( !player->IsAlive() || player->GetTeamNumber() != m_team )
return true;
CCSPlayer *csPlayer = ToCSPlayer( player );
if ( !csPlayer )
return true;
if ( csPlayer->IsBlind() )
return true;
Vector eye, forward;
player->EyePositionAndVectors( &eye, &forward, NULL, NULL );
Vector path( m_target - eye );
float distance = path.Length();
path.NormalizeInPlace();
float dot = DotProduct( forward, path );
if( (dot > 0.995f )
|| (dot > 0.98f && distance < 900)
|| (dot > 0.8f && distance < 250)
)
{
trace_t tr;
CTraceFilterSkipTwoEntities filter( player, m_targetEntity, COLLISION_GROUP_DEBRIS );
UTIL_TraceLine( eye, m_target,
(CONTENTS_OPAQUE|CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_DEBRIS), &filter, &tr );
if( tr.fraction == 1.0f )
{
if ( TheCSBots()->IsLineBlockedBySmoke( eye, m_target ) )
{
return true;
}
m_spotted = true;
return false; // spotted already, so no reason to check for other players spotting the same thing.
}
}
return true;
}
bool Spotted( void ) const
{
return m_spotted;
}
private:
CBaseEntity *m_targetEntity;
Vector m_target;
int m_team;
bool m_spotted;
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CCSPlayerResource::UpdatePlayerData( void )
{
int i;
m_iPlayerC4 = 0;
m_iPlayerVIP = 0;
for ( i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pPlayer = (CCSPlayer*)UTIL_PlayerByIndex( i );
if ( pPlayer && pPlayer->IsConnected() )
{
if ( pPlayer->IsVIP() )
{
// we should only have one VIP
Assert( m_iPlayerVIP == 0 );
m_iPlayerVIP = i;
}
if ( pPlayer->HasC4() )
{
// we should only have one bomb
m_iPlayerC4 = i;
}
m_szClan.Set(i, AllocPooledString( pPlayer->GetClanTag() ) );
m_iMVPs.Set(i, pPlayer->GetNumMVPs());
m_bHasDefuser.Set(i, pPlayer->HasDefuser());
}
else
{
m_szClan.Set( i, MAKE_STRING( "" ) );
m_iMVPs.Set( i, 0 );
}
}
CBaseEntity *c4 = NULL;
if ( m_iPlayerC4 == 0 )
{
// no player has C4, update C4 position
if ( g_C4s.Count() > 0 )
{
c4 = g_C4s[0];
m_vecC4 = c4->GetAbsOrigin();
}
else
{
m_vecC4.Init();
}
}
int numHostages = g_Hostages.Count();
for ( i = 0; i < MAX_HOSTAGES; i++ )
{
if ( i >= numHostages )
{
// engine->Con_NPrintf( i, "Dead" );
m_bHostageAlive.Set( i, false );
m_isHostageFollowingSomeone.Set( i, false );
continue;
}
CHostage* pHostage = g_Hostages[i];
m_bHostageAlive.Set( i, pHostage->IsRescuable() );
if ( pHostage->IsValid() )
{
m_iHostageX.Set( i, (int) pHostage->GetAbsOrigin().x );
m_iHostageY.Set( i, (int) pHostage->GetAbsOrigin().y );
m_iHostageZ.Set( i, (int) pHostage->GetAbsOrigin().z );
m_iHostageEntityIDs.Set( i, pHostage->entindex() );
m_isHostageFollowingSomeone.Set( i, pHostage->IsFollowingSomeone() );
// engine->Con_NPrintf( i, "ID:%d Pos:(%.0f,%.0f,%.0f)", pHostage->entindex(), pHostage->GetAbsOrigin().x, pHostage->GetAbsOrigin().y, pHostage->GetAbsOrigin().z );
}
else
{
// engine->Con_NPrintf( i, "Invalid" );
}
}
if( !m_foundGoalPositions )
{
// We only need to update these once a map, but we need the client to know about them.
CBaseEntity* ent = NULL;
while ( ( ent = gEntList.FindEntityByClassname( ent, "func_bomb_target" ) ) != NULL )
{
const Vector &pos = ent->WorldSpaceCenter();
CNavArea *area = TheNavMesh->GetNearestNavArea( pos, true, 10000.0f, false, false );
const char *placeName = (area) ? TheNavMesh->PlaceToName( area->GetPlace() ) : NULL;
if ( placeName == NULL )
{
// The bomb site has no area or place name, so just choose A then B
if ( m_bombsiteCenterA.Get().IsZero() )
{
m_bombsiteCenterA = pos;
}
else
{
m_bombsiteCenterB = pos;
}
}
else
{
// The bomb site has a place name, so choose accordingly
if( FStrEq( placeName, "BombsiteA" ) )
{
m_bombsiteCenterA = pos;
}
else
{
m_bombsiteCenterB = pos;
}
}
m_foundGoalPositions = true;
}
int hostageRescue = 0;
while ( (( ent = gEntList.FindEntityByClassname( ent, "func_hostage_rescue" ) ) != NULL) && (hostageRescue < MAX_HOSTAGE_RESCUES) )
{
const Vector &pos = ent->WorldSpaceCenter();
m_hostageRescueX.Set( hostageRescue, (int) pos.x );
m_hostageRescueY.Set( hostageRescue, (int) pos.y );
m_hostageRescueZ.Set( hostageRescue, (int) pos.z );
hostageRescue++;
m_foundGoalPositions = true;
}
}
bool bombSpotted = false;
if ( c4 )
{
Spotter spotter( c4, m_vecC4, TEAM_CT );
ForEachPlayer( spotter );
if ( spotter.Spotted() )
{
bombSpotted = true;
}
}
for ( int i=0; i < MAX_PLAYERS+1; i++ )
{
CCSPlayer *target = ToCSPlayer( UTIL_PlayerByIndex( i ) );
if ( !target || !target->IsAlive() )
{
m_bPlayerSpotted.Set( i, 0 );
continue;
}
Spotter spotter( target, target->EyePosition(), (target->GetTeamNumber()==TEAM_CT) ? TEAM_TERRORIST : TEAM_CT );
ForEachPlayer( spotter );
if ( spotter.Spotted() )
{
if ( target->HasC4() )
{
bombSpotted = true;
}
m_bPlayerSpotted.Set( i, 1 );
}
else
{
m_bPlayerSpotted.Set( i, 0 );
}
}
if ( bombSpotted )
{
m_bBombSpotted = true;
}
else
{
m_bBombSpotted = false;
}
BaseClass::UpdatePlayerData();
}
void CCSPlayerResource::Spawn( void )
{
m_vecC4.Init();
m_iPlayerC4 = 0;
m_iPlayerVIP = 0;
m_bombsiteCenterA.Init();
m_bombsiteCenterB.Init();
m_foundGoalPositions = false;
for ( int i=0; i < MAX_HOSTAGES; i++ )
{
m_bHostageAlive.Set( i, 0 );
m_isHostageFollowingSomeone.Set( i, 0 );
m_iHostageEntityIDs.Set(i, 0);
}
for ( int i=0; i < MAX_HOSTAGE_RESCUES; i++ )
{
m_hostageRescueX.Set( i, 0 );
m_hostageRescueY.Set( i, 0 );
m_hostageRescueZ.Set( i, 0 );
}
m_bBombSpotted = false;
for ( int i=0; i < MAX_PLAYERS+1; i++ )
{
m_bPlayerSpotted.Set( i, 0 );
m_szClan.Set( i, MAKE_STRING( "" ) );
m_iMVPs.Set( i, 0 );
m_bHasDefuser.Set(i, false);
}
BaseClass::Spawn();
}
+55
View File
@@ -0,0 +1,55 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: CS's custom CPlayerResource
//
// $NoKeywords: $
//=============================================================================//
#ifndef CS_PLAYER_RESOURCE_H
#define CS_PLAYER_RESOURCE_H
#ifdef _WIN32
#pragma once
#endif
class CCSPlayerResource : public CPlayerResource
{
DECLARE_CLASS( CCSPlayerResource, CPlayerResource );
public:
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
CCSPlayerResource();
virtual void UpdatePlayerData( void );
virtual void Spawn( void );
protected:
CNetworkVar( int, m_iPlayerC4 ); // entity index of C4 carrier or 0
CNetworkVar( int, m_iPlayerVIP ); // entity index of VIP player or 0
CNetworkVector( m_vecC4 ); // position of C4
CNetworkArray( bool, m_bHostageAlive, MAX_HOSTAGES );
CNetworkArray( bool, m_isHostageFollowingSomeone, MAX_HOSTAGES );
CNetworkArray( int, m_iHostageEntityIDs, MAX_HOSTAGES );
CNetworkArray( int, m_iHostageX, MAX_HOSTAGES );
CNetworkArray( int, m_iHostageY, MAX_HOSTAGES );
CNetworkArray( int, m_iHostageZ, MAX_HOSTAGES );
CNetworkVector( m_bombsiteCenterA );// Location of bombsite A
CNetworkVector( m_bombsiteCenterB );// Location of bombsite B
CNetworkArray( int, m_hostageRescueX, MAX_HOSTAGE_RESCUES );// Locations of all hostage rescue spots
CNetworkArray( int, m_hostageRescueY, MAX_HOSTAGE_RESCUES );
CNetworkArray( int, m_hostageRescueZ, MAX_HOSTAGE_RESCUES );
CNetworkVar( bool, m_bBombSpotted );
CNetworkArray( bool, m_bPlayerSpotted, MAX_PLAYERS+1 );
CNetworkArray( string_t, m_szClan, MAX_PLAYERS+1 );
CNetworkArray( int, m_iMVPs, MAX_PLAYERS + 1 );
CNetworkArray( bool, m_bHasDefuser, MAX_PLAYERS + 1);
private:
bool m_foundGoalPositions;
};
#endif // CS_PLAYER_RESOURCE_H
+98
View File
@@ -0,0 +1,98 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "player_command.h"
#include "igamemovement.h"
#include "in_buttons.h"
#include "ipredictionsystem.h"
#include "iservervehicle.h"
#include "cs_player.h"
static CMoveData g_MoveData;
CMoveData *g_pMoveData = &g_MoveData;
IPredictionSystem *IPredictionSystem::g_pPredictionSystems = NULL;
//-----------------------------------------------------------------------------
// Sets up the move data for TF2
//-----------------------------------------------------------------------------
class CCSPlayerMove : public CPlayerMove
{
DECLARE_CLASS( CCSPlayerMove, CPlayerMove );
public:
virtual void StartCommand( CBasePlayer *player, CUserCmd *cmd );
virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
virtual void FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move );
};
// PlayerMove Interface
static CCSPlayerMove g_PlayerMove;
//-----------------------------------------------------------------------------
// Singleton accessor
//-----------------------------------------------------------------------------
CPlayerMove *PlayerMove()
{
return &g_PlayerMove;
}
//-----------------------------------------------------------------------------
// Main setup, finish
//-----------------------------------------------------------------------------
void CCSPlayerMove::StartCommand( CBasePlayer *player, CUserCmd *cmd )
{
CCSPlayer *pPlayer = ToCSPlayer( player );
// Reset this.. it gets reset each frame that we're in a bomb zone.
pPlayer->m_bInBombZone = false;
pPlayer->m_bInBuyZone = false;
pPlayer->m_bInHostageRescueZone = false;
BaseClass::StartCommand( player, cmd );
}
//-----------------------------------------------------------------------------
// Purpose: This is called pre player movement and copies all the data necessary
// from the player for movement. (Server-side, the client-side version
// of this code can be found in prediction.cpp.)
//-----------------------------------------------------------------------------
void CCSPlayerMove::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
{
player->AvoidPhysicsProps( ucmd );
BaseClass::SetupMove( player, ucmd, pHelper, move );
IServerVehicle *pVehicle = player->GetVehicle();
if (pVehicle && gpGlobals->frametime != 0)
{
pVehicle->SetupMove( player, ucmd, pHelper, move );
}
}
//-----------------------------------------------------------------------------
// Purpose: This is called post player movement to copy back all data that
// movement could have modified and that is necessary for future
// movement. (Server-side, the client-side version of this code can
// be found in prediction.cpp.)
//-----------------------------------------------------------------------------
void CCSPlayerMove::FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move )
{
// Call the default FinishMove code.
BaseClass::FinishMove( player, ucmd, move );
IServerVehicle *pVehicle = player->GetVehicle();
if (pVehicle && gpGlobals->frametime != 0)
{
pVehicle->FinishMove( player, ucmd, move );
}
}
+101
View File
@@ -0,0 +1,101 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Team management class. Contains all the details for a specific team
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "cs_team.h"
#include "entitylist.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// Datatable
IMPLEMENT_SERVERCLASS_ST(CCSTeam, DT_CSTeam)
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( cs_team_manager, CCSTeam );
//-----------------------------------------------------------------------------
// Purpose: Get a pointer to the specified TF team manager
//-----------------------------------------------------------------------------
CCSTeam *GetGlobalTFTeam( int iIndex )
{
return (CCSTeam*)GetGlobalTeam( iIndex );
}
//-----------------------------------------------------------------------------
// Purpose: Needed because this is an entity, but should never be used
//-----------------------------------------------------------------------------
void CCSTeam::Init( const char *pName, int iNumber )
{
BaseClass::Init( pName, iNumber );
// Only detect changes every half-second.
NetworkProp()->SetUpdateInterval( 0.75f );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CCSTeam::~CCSTeam( void )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CCSTeam::Precache( void )
{
}
//-----------------------------------------------------------------------------
// Purpose: Called every frame
//-----------------------------------------------------------------------------
void CCSTeam::Think( void )
{
}
//------------------------------------------------------------------------------------------------------------------
// PLAYERS
//-----------------------------------------------------------------------------
// Purpose: Add the specified player to this team. Remove them from their current team, if any.
//-----------------------------------------------------------------------------
void CCSTeam::AddPlayer( CBasePlayer *pPlayer )
{
BaseClass::AddPlayer( pPlayer );
}
//-----------------------------------------------------------------------------
// Purpose: Clean up the player's objects when they leave
//-----------------------------------------------------------------------------
void CCSTeam::RemovePlayer( CBasePlayer *pPlayer )
{
BaseClass::RemovePlayer( pPlayer );
}
//------------------------------------------------------------------------------------------------------------------
// UTILITY FUNCS
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CCSTeam* CCSTeam::GetEnemyTeam()
{
// Look for nearby enemy objects we can capture.
int iMyTeam = GetTeamNumber();
if( iMyTeam == 0 )
return NULL;
int iEnemyTeam = !(iMyTeam - 1) + 1;
return (CCSTeam*)GetGlobalTeam( iEnemyTeam );
}
+60
View File
@@ -0,0 +1,60 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Team management class. Contains all the details for a specific team
//
// $NoKeywords: $
//=============================================================================//
#ifndef CS_TEAM_H
#define CS_TEAM_H
#ifdef _WIN32
#pragma once
#endif
#include "utlvector.h"
#include "team.h"
//-----------------------------------------------------------------------------
// Purpose: Team Manager
//-----------------------------------------------------------------------------
class CCSTeam : public CTeam
{
DECLARE_CLASS( CCSTeam, CTeam );
public:
virtual ~CCSTeam( void );
DECLARE_SERVERCLASS();
// Initialization
virtual void Init( const char *pName, int iNumber );
virtual void Precache( void );
virtual void Think( void );
//-----------------------------------------------------------------------------
// Players
//-----------------------------------------------------------------------------
virtual void AddPlayer( CBasePlayer *pPlayer );
virtual void RemovePlayer( CBasePlayer *pPlayer );
//-----------------------------------------------------------------------------
// Utility funcs
//-----------------------------------------------------------------------------
CCSTeam* GetEnemyTeam();
private:
// Used to distribute resources to a team
float m_flNextResourceTime;
int m_iLastUpdateSentAt;
};
extern CCSTeam *GetGlobalTFTeam( int iIndex );
#endif // TF_TEAM_H
File diff suppressed because it is too large Load Diff
+84
View File
@@ -0,0 +1,84 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Bomb target area
//
//=============================================================================//
#include "cbase.h"
#include "cs_player.h"
#include "func_bomb_target.h"
#include "cs_gamerules.h"
LINK_ENTITY_TO_CLASS( func_bomb_target, CBombTarget );
BEGIN_DATADESC( CBombTarget )
DEFINE_FUNCTION( BombTargetTouch ),
DEFINE_FUNCTION( BombTargetUse ), //needed?
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "BombExplode", OnBombExplode ),
DEFINE_INPUTFUNC( FIELD_VOID, "BombPlanted", OnBombPlanted ),
DEFINE_INPUTFUNC( FIELD_VOID, "BombDefused", OnBombDefused ),
// Outputs
DEFINE_OUTPUT( m_OnBombExplode, "BombExplode" ),
DEFINE_OUTPUT( m_OnBombPlanted, "BombPlanted" ),
DEFINE_OUTPUT( m_OnBombDefused, "BombDefused" ),
DEFINE_KEYFIELD( m_bIsHeistBombTarget, FIELD_BOOLEAN, "heistbomb" ),
DEFINE_KEYFIELD( m_szMountTarget, FIELD_STRING, "bomb_mount_target" ),
END_DATADESC()
CBombTarget::CBombTarget( void )
{
m_bIsHeistBombTarget = false;
m_szMountTarget = NULL_STRING;
}
void CBombTarget::Spawn()
{
InitTrigger();
SetTouch( &CBombTarget::BombTargetTouch );
SetUse( &CBombTarget::BombTargetUse );
}
void CBombTarget::BombTargetTouch( CBaseEntity* pOther )
{
CCSPlayer *p = dynamic_cast< CCSPlayer* >( pOther );
if ( p )
{
if ( p->HasC4() && CSGameRules()->m_bBombPlanted == false )
{
p->m_bInBombZone = true;
p->m_iBombSiteIndex = entindex();
if ( !(p->m_iDisplayHistoryBits & DHF_IN_TARGET_ZONE) )
{
p->HintMessage( "#Hint_you_are_in_targetzone", false );
p->m_iDisplayHistoryBits |= DHF_IN_TARGET_ZONE;
}
}
}
}
void CBombTarget::BombTargetUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
//SUB_UseTargets( NULL, USE_TOGGLE, 0 );
DevMsg( 2, "BombTargetUse does nothing\n" );
}
// Relay to our outputs
void CBombTarget::OnBombExplode( inputdata_t &inputdata )
{
m_OnBombExplode.FireOutput(this, this);
}
// Relay to our outputs
void CBombTarget::OnBombPlanted( inputdata_t &inputdata )
{
m_OnBombPlanted.FireOutput(this, this);
}
// Relay to our outputs
void CBombTarget::OnBombDefused( inputdata_t &inputdata )
{
m_OnBombDefused.FireOutput(this, this);
}
+37
View File
@@ -0,0 +1,37 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Bomb Target Area ent
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "triggers.h"
class CBombTarget : public CBaseTrigger
{
public:
DECLARE_CLASS( CBombTarget, CBaseTrigger );
DECLARE_DATADESC();
CBombTarget();
void Spawn();
void EXPORT BombTargetTouch( CBaseEntity* pOther );
void EXPORT BombTargetUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void OnBombExplode( inputdata_t &inputdata );
void OnBombPlanted( inputdata_t &inputdata );
void OnBombDefused( inputdata_t &inputdata );
bool IsHeistBombTarget( void ) { return m_bIsHeistBombTarget; }
const char *GetBombMountTarget( void ){ return STRING( m_szMountTarget ); }
private:
COutputEvent m_OnBombExplode; //Fired when the bomb explodes
COutputEvent m_OnBombPlanted; //Fired when the bomb is planted
COutputEvent m_OnBombDefused; //Fired when the bomb is defused
bool m_bIsHeistBombTarget;
string_t m_szMountTarget;
};
+77
View File
@@ -0,0 +1,77 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "triggers.h"
#include "cs_player.h"
//======================================
// Bomb target area
//
//
class CBuyZone : public CBaseTrigger
{
public:
DECLARE_CLASS( CBuyZone, CBaseTrigger );
DECLARE_DATADESC();
CBuyZone();
void Spawn();
void EXPORT BuyZoneTouch( CBaseEntity* pOther );
public:
int m_LegacyTeamNum;
};
LINK_ENTITY_TO_CLASS( func_buyzone, CBuyZone );
BEGIN_DATADESC( CBuyZone )
DEFINE_FUNCTION( BuyZoneTouch ),
// This is here to support maps that haven't updated to using "teamnum" yet.
DEFINE_INPUT( m_LegacyTeamNum, FIELD_INTEGER, "team" )
END_DATADESC()
CBuyZone::CBuyZone()
{
m_LegacyTeamNum = -1;
}
void CBuyZone::Spawn()
{
InitTrigger();
SetTouch( &CBuyZone::BuyZoneTouch );
// Support for legacy-style teamnums.
if ( m_LegacyTeamNum == 1 )
{
ChangeTeam( TEAM_TERRORIST );
}
else if ( m_LegacyTeamNum == 2 )
{
ChangeTeam( TEAM_CT );
}
}
void CBuyZone::BuyZoneTouch( CBaseEntity* pOther )
{
CCSPlayer *p = dynamic_cast< CCSPlayer* >( pOther );
if ( p )
{
// compare player team with buy zone team number
if ( p->GetTeamNumber() == GetTeamNumber() )
{
p->m_bInBuyZone = true;
}
}
}
@@ -0,0 +1,45 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "triggers.h"
class CHostageRescueZone : public CBaseTrigger
{
public:
DECLARE_CLASS( CHostageRescueZone, CBaseTrigger );
DECLARE_DATADESC();
void CHostageRescue();
void Spawn();
void HostageRescueTouch( CBaseEntity* pOther );
};
LINK_ENTITY_TO_CLASS( func_hostage_rescue, CHostageRescueZone );
BEGIN_DATADESC( CHostageRescueZone )
//Functions
DEFINE_FUNCTION( HostageRescueTouch ),
END_DATADESC()
void CHostageRescueZone::Spawn()
{
InitTrigger();
SetTouch( &CHostageRescueZone::HostageRescueTouch );
}
void CHostageRescueZone::HostageRescueTouch( CBaseEntity *pOther )
{
variant_t emptyVariant;
pOther->AcceptInput( "OnRescueZoneTouch", NULL, NULL, emptyVariant, 0 );
}
+793
View File
@@ -0,0 +1,793 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "cs_gamerules.h"
#include "cs_gamestats.h"
#include "funfactmgr_cs.h"
#include "funfact_cs.h"
#include "../../game/shared/cstrike/weapon_csbase.h"
#include "cs_achievement_constants.h"
#define FIRST_BLOOD_TIME 45.0f
#define FIRST_KILL_TIME 45.0f
#define SHORT_ROUND_TIME 30.0f
#define MIN_SHOTS_FOR_ACCURACY 10
enum FunFactId
{
FUNFACT_CT_WIN_NO_KILLS,
FUNFACT_T_WIN_NO_KILLS,
FUNFACT_KILL_DEFUSER,
FUNFACT_KILL_RESCUER,
FUNFACT_T_WIN_NO_CASUALTIES,
FUNFACT_CT_WIN_NO_CASUALTIES,
FUNFACT_DAMAGE_WITH_GRENADES,
FUNFACT_KILLS_WITH_GRENADES,
FUNFACT_KILLS_WITH_SINGLE_GRENADE,
FUNFACT_DAMAGE_NO_KILLS,
FUNFACT_KILLED_ENEMIES,
FUNFACT_FIRST_KILL,
FUNFACT_FIRST_BLOOD,
FUNFACT_SHORT_ROUND,
FUNFACT_BEST_ACCURACY,
FUNFACT_KNIFE_KILLS,
FUNFACT_BLIND_KILLS,
FUNFACT_KILLS_WITH_LAST_ROUND,
FUNFACT_DONATED_WEAPONS,
FUNFACT_POSTHUMOUS_KILLS_WITH_GRENADE,
FUNFACT_KNIFE_IN_GUNFIGHT,
FUNFACT_NUM_TIMES_JUMPED,
FUNFACT_FALL_DAMAGE,
FUNFACT_ITEMS_PURCHASED,
FUNFACT_WON_AS_LAST_MEMBER,
FUNFACT_NUMBER_OF_OVERKILLS,
FUNFACT_SHOTS_FIRED,
FUNFACT_MONEY_SPENT,
FUNFACT_SURVIVED_MULTIPLE_ATTACKERS,
FUNFACT_DIED_FROM_MULTIPLE_ATTACKERS,
FUNFACT_DAMAGE_MULTIPLE_ENEMIES,
FUNFACT_GRENADES_THROWN,
FUNFACT_USED_ALL_AMMO,
FUNFACT_DEFENDED_BOMB,
FUNFACT_ITEMS_DROPPED_VALUE,
FUNFACT_KILL_WOUNDED_ENEMIES,
FUNFACT_USED_MULTIPLE_WEAPONS,
FUNFACT_TERRORIST_ACCURACY,
FUNFACT_CT_ACCURACY,
FUNFACT_SAME_UNIFORM_TERRORIST,
FUNFACT_SAME_UNIFORM_CT,
FUNFACT_BEST_TERRORIST_ACCURACY,
FUNFACT_BEST_COUNTERTERRORIST_ACCURACY,
FUNFACT_FALLBACK1,
FUNFACT_FALLBACK2,
FUNFACT_KILLS_HEADSHOTS,
FUNFACT_BROKE_WINDOWS,
FUNFACT_NIGHTVISION_DAMAGE,
FUNFACT_DEFUSED_WITH_DROPPED_KIT,
FUNFACT_KILLED_HALF_OF_ENEMIES,
};
CFunFactHelper *CFunFactHelper::s_pFirst = NULL;
//=============================================================================
// Generic evaluation Fun Fact
// This fun fact will evaluate the specified function to determine when it is
// valid. This is basically just a glue class for simple evaluation functions.
//=============================================================================
// Function type that we use to evaluate our fun facts. The data is returned as ints then floats that are passed in as reference parameters
typedef bool (*fFunFactEval)( int &iPlayer, int &data1, int &data2, int &data3 );
class CFunFact_GenericEvalFunction : public FunFactEvaluator
{
public:
CFunFact_GenericEvalFunction(FunFactId id, const char* szLocalizationToken, float fCoolness, fFunFactEval pfnEval ) :
FunFactEvaluator(id, szLocalizationToken, fCoolness),
m_pfnEval(pfnEval)
{}
virtual bool Evaluate( FunFactVector& results ) const
{
FunFact funfact;
if (m_pfnEval(funfact.iPlayer, funfact.iData1, funfact.iData2, funfact.iData3))
{
funfact.id = GetId();
funfact.szLocalizationToken = GetLocalizationToken();
funfact.fMagnitude = 0.0f;
results.AddToTail(funfact);
return true;
}
else
return false;
}
private:
fFunFactEval m_pfnEval;
};
#define DECLARE_FUNFACT_EVALFUNC(funfactId, szLocalizationToken, fCoolness, pfnEval) \
static FunFactEvaluator *CreateFunFact_##funfactId( void ) \
{ \
return new CFunFact_GenericEvalFunction(funfactId, szLocalizationToken, fCoolness, pfnEval);\
}; \
static CFunFactHelper g_##funfactId##_Helper( CreateFunFact_##funfactId );
//=============================================================================
// Per-player evaluation Fun Fact
// Evaluate the function per player and generate a fun fact for each valid or
// highest valid player
//=============================================================================
namespace EvalFlags
{
enum Type
{
All = 0x00,
TeamCT = 0x01,
TeamTerrorist = 0x02,
HighestOnly = 0x04, // when not set, generates fun facts for all valid testees
Alive = 0x08,
Dead = 0x10,
WinningTeam = 0x20,
LosingTeam = 0x40,
};
};
bool PlayerQualifies( const CBasePlayer* pPlayer, int flags )
{
if ( (flags & EvalFlags::TeamCT) && pPlayer->GetTeamNumber() != TEAM_CT )
return false;
if ( (flags & EvalFlags::TeamTerrorist) && pPlayer->GetTeamNumber() != TEAM_TERRORIST )
return false;
if ( (flags & EvalFlags::Dead) && const_cast<CBasePlayer*>(pPlayer)->IsAlive() ) // IsAlive() really isn't const correct
return false;
if ( (flags & EvalFlags::Alive) && !const_cast<CBasePlayer*>(pPlayer)->IsAlive() )
return false;
if ( (flags & EvalFlags::WinningTeam) && pPlayer->GetTeamNumber() != CSGameRules()->m_iRoundWinStatus )
return false;
if ( (flags & EvalFlags::LosingTeam) && pPlayer->GetTeamNumber() == CSGameRules()->m_iRoundWinStatus )
return false;
return true;
}
typedef int (*PlayerEvalFunction)(CCSPlayer* pPlayer);
class CFunFact_PlayerEvalFunction : public FunFactEvaluator
{
public:
CFunFact_PlayerEvalFunction(FunFactId id, const char* szLocalizationToken, float fCoolness, PlayerEvalFunction pfnEval,
int iMin, int flags ) :
FunFactEvaluator(id, szLocalizationToken, fCoolness),
m_pfnEval(pfnEval),
m_min(iMin),
m_flags(flags)
{}
virtual bool Evaluate( FunFactVector& results ) const
{
int iBestValue = 0;
int iBestPlayer = 0;
bool bResult = false;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer* pPlayer = ToCSPlayer(UTIL_PlayerByIndex( i ) );
if ( pPlayer )
{
if (!PlayerQualifies(pPlayer, m_flags))
continue;
int iValue = m_pfnEval(pPlayer);
if (m_flags & EvalFlags::HighestOnly)
{
if ( iValue > iBestValue )
{
iBestValue = iValue;
iBestPlayer = i;
}
}
else
{
// generate fun facts for any player who meets the validation requirement
if ( iValue >= m_min )
{
FunFact funfact;
funfact.id = GetId();
funfact.szLocalizationToken = GetLocalizationToken();
funfact.iPlayer = i;
funfact.iData1 = iValue;
funfact.fMagnitude = 1.0f - ((float)m_min / iValue);
results.AddToTail(funfact);
bResult = true;
}
}
}
}
if ( (m_flags & EvalFlags::HighestOnly) && iBestValue >= m_min )
{
FunFact funfact;
funfact.id = GetId();
funfact.szLocalizationToken = GetLocalizationToken();
funfact.iPlayer = iBestPlayer;
funfact.iData1 = iBestValue;
funfact.fMagnitude = 1.0f - ((float)m_min / iBestValue);
results.AddToTail(funfact);
bResult = true;
}
return bResult;
}
private:
PlayerEvalFunction m_pfnEval;
int m_min;
int m_flags;
};
#define DECLARE_FUNFACT_PLAYERFUNC(funfactId, szLocalizationToken, fCoolness, pfnEval, iMin, iFlags) \
static FunFactEvaluator *CreateFunFact_##funfactId( void ) \
{ \
return new CFunFact_PlayerEvalFunction(funfactId, szLocalizationToken, fCoolness, pfnEval, iMin, iFlags); \
}; \
static CFunFactHelper g_##funfactId##_Helper( CreateFunFact_##funfactId );
//=============================================================================
// Per-team evaluation Fun Fact
//=============================================================================
typedef bool (*TeamEvalFunction)(int iTeam, int &data1, int &data2, int &data3);
class CFunFact_TeamEvalFunction : public FunFactEvaluator
{
public:
CFunFact_TeamEvalFunction(FunFactId id, const char* szLocalizationToken, float fCoolness, TeamEvalFunction pfnEval, int iTeam ) :
FunFactEvaluator(id, szLocalizationToken, fCoolness),
m_pfnEval(pfnEval),
m_team(iTeam)
{}
virtual bool Evaluate( FunFactVector& results ) const
{
int iData1, iData2, iData3;
if ( m_pfnEval(m_team, iData1, iData2, iData3) )
{
FunFact funfact;
funfact.id = GetId();
funfact.szLocalizationToken = GetLocalizationToken();
funfact.fMagnitude = 0.0f;
results.AddToTail(funfact);
return true;
}
return false;
}
private:
TeamEvalFunction m_pfnEval;
int m_team;
};
#define DECLARE_FUNFACT_TEAMFUNC(funfactId, szLocalizationToken, fCoolness, pfnEval, iTeam) \
static FunFactEvaluator *CreateFunFact_##funfactId( void ) \
{ \
return new CFunFact_TeamEvalFunction(funfactId, szLocalizationToken, fCoolness, pfnEval, iTeam);\
}; \
static CFunFactHelper g_##funfactId##_Helper( CreateFunFact_##funfactId );
//=============================================================================
// High Stat-based Fun Fact
// This fun fact will find the player with the highest value for a particular
// stat, and validate when that stat exceeds a specified minimum
//=============================================================================
class CFunFact_StatBest : public FunFactEvaluator
{
public:
CFunFact_StatBest(FunFactId id, const char* szLocalizationToken, float fCoolness, CSStatType_t statId, int iMin, int flags ) :
FunFactEvaluator(id, szLocalizationToken, fCoolness),
m_statId(statId),
m_min(iMin),
m_flags(flags)
{
V_strncpy(m_singularLocalizationToken, szLocalizationToken, sizeof(m_singularLocalizationToken));
if (m_min == 1)
{
V_strncat(m_singularLocalizationToken, "_singular", sizeof(m_singularLocalizationToken));
}
}
virtual bool Evaluate( FunFactVector& results ) const
{
int iBestValue = 0;
int iBestPlayer = 0;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( pPlayer )
{
if (!PlayerQualifies(pPlayer, m_flags))
continue;
int iValue = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[m_statId];
if ( iValue > iBestValue )
{
iBestValue = iValue;
iBestPlayer = i;
}
}
}
if ( iBestValue >= m_min )
{
FunFact funfact;
funfact.id = GetId();
funfact.szLocalizationToken = iBestValue == 1 ? m_singularLocalizationToken : GetLocalizationToken();
funfact.iPlayer = iBestPlayer;
funfact.iData1 = iBestValue;
funfact.fMagnitude = 1.0f - ((float)m_min / iBestValue);
results.AddToTail(funfact);
return true;
}
return false;
}
private:
CSStatType_t m_statId;
int m_min;
char m_singularLocalizationToken[128];
int m_flags;
};
#define DECLARE_FUNFACT_STATBEST(funfactId, szLocalizationToken, fCoolness, statId, iMin, flags) \
static FunFactEvaluator *CreateFunFact_##funfactId( void ) \
{ \
return new CFunFact_StatBest(funfactId, szLocalizationToken, fCoolness, statId, iMin, flags); \
}; \
static CFunFactHelper g_##funfactId##_Helper( CreateFunFact_##funfactId );
//=============================================================================
// Sum-based Fun Fact
// This fun fact will add up a stat for all players, and is valid when the
// sum exceeds a threshold
//=============================================================================
class CFunFact_StatSum : public FunFactEvaluator
{
public:
CFunFact_StatSum(FunFactId id, const char* szLocalizationToken, float fCoolness, CSStatType_t statId, int iMin, EvalFlags::Type flags ) :
FunFactEvaluator(id, szLocalizationToken, fCoolness),
m_statId(statId),
m_min(iMin),
m_flags(flags)
{}
virtual bool Evaluate( FunFactVector& results ) const
{
int iSum = 0;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if ( pPlayer )
{
if (!PlayerQualifies(pPlayer, m_flags))
continue;
iSum += CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[m_statId];
}
}
if ( iSum >= m_min )
{
FunFact funfact;
funfact.id = GetId();
funfact.szLocalizationToken = GetLocalizationToken();
funfact.iPlayer = 0;
funfact.iData1 = iSum;
funfact.fMagnitude = 1.0f - ((float)m_min / iSum);
results.AddToTail(funfact);
return true;
}
return false;
}
private:
CSStatType_t m_statId;
int m_min;
int m_flags;
};
#define DECLARE_FUNFACT_STATSUM(funfactId, szLocalizationToken, fCoolness, statId, iMin, flags) \
static FunFactEvaluator *CreateFunFact_##funfactId( void ) \
{ \
return new CFunFact_StatSum(funfactId, szLocalizationToken, fCoolness, statId, iMin, flags); \
}; \
static CFunFactHelper g_##funfactId##_Helper( CreateFunFact_##funfactId );
//=============================================================================
// Helper function to calculate team accuracy
//=============================================================================
float GetTeamAccuracy( int teamNumber )
{
int teamShots = 0;
int teamHits = 0;
//Add up hits and shots
CBasePlayer *pPlayer = NULL;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
pPlayer = UTIL_PlayerByIndex( i );
if (pPlayer)
{
if (pPlayer->GetTeamNumber() == teamNumber)
{
teamShots += CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_FIRED];;
teamHits += CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_HIT];;
}
}
}
if (teamShots > MIN_SHOTS_FOR_ACCURACY)
return (float)teamHits / teamShots;
return 0.0f;
}
//=============================================================================
// fun fact explicit evaluation functions
//=============================================================================
bool FFEVAL_ALWAYS_TRUE( int &iPlayer, int &data1, int &data2, int &data3 )
{
return true;
}
bool FFEVAL_CT_WIN_NO_KILLS( int &iPlayer, int &data1, int &data2, int &data3 )
{
return ( CSGameRules()->m_iRoundWinStatus == WINNER_CT && CSGameRules()->m_bNoTerroristsKilled );
}
bool FFEVAL_T_WIN_NO_KILLS( int &iPlayer, int &data1, int &data2, int &data3 )
{
return ( CSGameRules()->m_iRoundWinStatus == WINNER_TER && CSGameRules()->m_bNoCTsKilled );
}
bool FFEVAL_T_WIN_NO_CASUALTIES( int &iPlayer, int &data1, int &data2, int &data3 )
{
return ( CSGameRules()->m_iRoundWinStatus == WINNER_TER && CSGameRules()->m_bNoTerroristsKilled );
}
bool FFEVAL_CT_WIN_NO_CASUALTIES( int &iPlayer, int &data1, int &data2, int &data3 )
{
return ( CSGameRules()->m_iRoundWinStatus == WINNER_CT && CSGameRules()->m_bNoCTsKilled );
}
int FFEVAL_KILLED_DEFUSER( CCSPlayer* pPlayer )
{
return pPlayer->GetKilledDefuser() ? 1 : 0;
}
int FFEVAL_KILLED_RESCUER( CCSPlayer* pPlayer )
{
return pPlayer->GetKilledRescuer() ? 1 : 0;
}
int FFEVAL_KILLS_WITH_GRENADE( CCSPlayer* pPlayer )
{
return pPlayer->GetMaxGrenadeKills();
}
int FFEVAL_DAMAGE_NO_KILLS( CCSPlayer* pPlayer )
{
if (CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_KILLS] == 0)
return CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_DAMAGE];
else
return 0;
}
int FFEVAL_FIRST_KILL( CCSPlayer* pPlayer )
{
if ( pPlayer == CSGameRules()->m_pFirstKill && CSGameRules()->m_firstKillTime < FIRST_KILL_TIME )
return CSGameRules()->m_firstKillTime;
else
return 0;
}
int FFEVAL_FIRST_BLOOD( CCSPlayer* pPlayer )
{
if ( pPlayer == CSGameRules()->m_pFirstBlood && CSGameRules()->m_firstBloodTime < FIRST_BLOOD_TIME )
return CSGameRules()->m_firstBloodTime;
else
return 0;
}
bool FFEVAL_SHORT_ROUND( int &iPlayer, int &data1, int &data2, int &data3 )
{
if ( CSGameRules()->GetRoundLength() - CSGameRules()->GetRoundRemainingTime() < SHORT_ROUND_TIME )
{
data1 = CSGameRules()->GetRoundLength() - CSGameRules()->GetRoundRemainingTime();
return true;
}
return false;
}
int FFEVAL_ACCURACY( CCSPlayer* pPlayer )
{
float shots = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_FIRED];
float hits = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_HIT];
if (shots >= MIN_SHOTS_FOR_ACCURACY)
return RoundFloatToInt(100.0f * hits / shots);
return 0;
}
int FFEVAL_KILLED_HALF_OF_ENEMIES( CCSPlayer* pPlayer )
{
return pPlayer->GetPercentageOfEnemyTeamKilled();
}
bool FFEVAL_WON_AS_LAST_MEMBER( int &iPlayer, int &data1, int &data2, int &data3 )
{
CCSPlayer *pCSPlayer = NULL;
int winningTeam = CSGameRules()->m_iRoundWinStatus;
if (winningTeam != TEAM_TERRORIST && winningTeam != TEAM_CT)
{
return false;
}
int losingTeam = (winningTeam == TEAM_TERRORIST) ? TEAM_CT : TEAM_TERRORIST;
CCSGameRules::TeamPlayerCounts playerCounts[TEAM_MAXCOUNT];
CSGameRules()->GetPlayerCounts(playerCounts);
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
pCSPlayer = ToCSPlayer(UTIL_PlayerByIndex( i ) );
if( pCSPlayer && pCSPlayer->GetTeamNumber() == winningTeam && pCSPlayer->IsAlive())
{
//Check if the player is still the only living member of his team ( on the off chance that a player joins late)
//This check is a little hacky. We make sure that there are no enemies alive. Since the bomb causes the round to end before exploding,
//the only way for only 1 person to be alive at round win time is extermination or defuse (in both cases, the last living player caused the win)
if (playerCounts[winningTeam].totalAlivePlayers == 1 && playerCounts[losingTeam].totalAlivePlayers == 0)
{
const PlayerStats_t& playerStats = CCS_GameStats.FindPlayerStats( pCSPlayer );
iPlayer = i;
data1 = playerStats.statsCurrentRound[CSSTAT_KILLS_WHILE_LAST_PLAYER_ALIVE];
if (data1 >= 2)
{
return true;
}
}
}
}
return false;
}
int FFEVAL_KNIFE_IN_GUNFIGHT( CCSPlayer* pPlayer )
{
return pPlayer->WasWieldingKnifeAndKilledByGun() ? 1 : 0;
}
int FFEVAL_MULTIPLE_ATTACKER_COUNT( CCSPlayer* pPlayer )
{
return pPlayer->GetNumEnemyDamagers();
}
int FFEVAL_USED_ALL_AMMO( CCSPlayer* pPlayer )
{
CWeaponCSBase *pRifleWeapon = dynamic_cast< CWeaponCSBase * >(pPlayer->Weapon_GetSlot( WEAPON_SLOT_RIFLE ));
CWeaponCSBase *pHandgunWeapon = dynamic_cast< CWeaponCSBase * >(pPlayer->Weapon_GetSlot( WEAPON_SLOT_PISTOL ));
if ( pRifleWeapon && !pRifleWeapon->HasAmmo() && pHandgunWeapon && !pHandgunWeapon->HasAmmo() )
return 1;
else
return 0;
}
int FFEVAL_DAMAGE_MULTIPLE_ENEMIES( CCSPlayer* pPlayer )
{
return pPlayer->GetNumEnemiesDamaged();
}
int FFEVAL_USED_MULTIPLE_WEAPONS( CCSPlayer* pPlayer )
{
return pPlayer->GetNumFirearmsUsed();
}
int FFEVAL_DEFUSED_WITH_DROPPED_KIT( CCSPlayer* pPlayer )
{
return pPlayer->GetDefusedWithPickedUpKit() ? 1 : 0;
}
bool FFEVAL_TERRORIST_ACCURACY( int &iPlayer, int &data1, int &data2, int &data3 )
{
float terroristAccuracy = GetTeamAccuracy(TEAM_TERRORIST);
float ctAccuracy = GetTeamAccuracy(TEAM_CT);
if (terroristAccuracy > 0.2f && terroristAccuracy > ctAccuracy)
{
data1 = RoundFloatToInt(terroristAccuracy * 100.0f);
return true;
}
return false;
}
bool FFEVAL_CT_ACCURACY( int &iPlayer, int &data1, int &data2, int &data3 )
{
float terroristAccuracy = GetTeamAccuracy(TEAM_TERRORIST);
float ctAccuracy = GetTeamAccuracy(TEAM_CT);
if (ctAccuracy > 0.2f && ctAccuracy > terroristAccuracy)
{
data1 = RoundFloatToInt(ctAccuracy * 100.0f);
return true;
}
return false;
}
bool FFEVAL_SAME_UNIFORM( int iTeam, int &iData1, int &iData2, int &iData3 )
{
int numberInUniform = 0;
int iUniform = -1;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
CCSPlayer *pCSPlayer = ToCSPlayer(UTIL_PlayerByIndex( i ) );
if ( pCSPlayer && pCSPlayer->GetTeamNumber() == iTeam && pCSPlayer->State_Get() != STATE_PICKINGCLASS)
{
if (iUniform == -1)
{
iUniform = pCSPlayer->PlayerClass();
}
else if (pCSPlayer->PlayerClass() != iUniform)
{
return false;
}
++numberInUniform;
}
}
return numberInUniform >= 3;
}
bool FFEVAL_BEST_TERRORIST_ACCURACY( int &iPlayer, int &data1, int &data2, int &data3 )
{
float fAccuracy = 0.0f, fBestAccuracy = 0.0f;
CBasePlayer *pPlayer = NULL;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
pPlayer = UTIL_PlayerByIndex( i );
// Look only at terrorist players
if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_TERRORIST )
{
// Calculate accuracy the terrorist
float shots = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_FIRED];
float hits = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_HIT];
if (shots > MIN_SHOTS_FOR_ACCURACY)
{
fAccuracy = (float)hits / shots;
}
// Track the most accurate terrorist
if ( fAccuracy > fBestAccuracy )
{
fBestAccuracy = fAccuracy;
iPlayer = i;
}
}
}
if ( fBestAccuracy - GetTeamAccuracy( TEAM_TERRORIST ) >= 0.10f )
{
data1 = RoundFloatToInt(fBestAccuracy * 100.0f);
data2 = RoundFloatToInt(GetTeamAccuracy( TEAM_TERRORIST ) * 100.0f);
return true;
}
return false;
}
bool FFEVAL_BEST_COUNTERTERRORIST_ACCURACY( int &iPlayer, int &data1, int &data2, int &data3 )
{
float fAccuracy = 0.0f, fBestAccuracy = 0.0f;
CBasePlayer *pPlayer = NULL;
for ( int i = 1; i <= gpGlobals->maxClients; i++ )
{
pPlayer = UTIL_PlayerByIndex( i );
// Look only at counter-terrorist players
if ( pPlayer && pPlayer->GetTeamNumber() == TEAM_CT )
{
// Calculate accuracy the counter-terrorist
float shots = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_FIRED];
float hits = CCS_GameStats.FindPlayerStats(pPlayer).statsCurrentRound[CSSTAT_SHOTS_HIT];
if (shots > MIN_SHOTS_FOR_ACCURACY)
{
fAccuracy = (float)hits / shots;
}
// Track the most accurate counter-terrorist
if ( fAccuracy > fBestAccuracy )
{
fBestAccuracy = fAccuracy;
iPlayer = i;
}
}
}
if ( fBestAccuracy - GetTeamAccuracy( TEAM_CT ) >= 0.10f )
{
data1 = RoundFloatToInt(fBestAccuracy * 100.0f);
data2 = RoundFloatToInt(GetTeamAccuracy( TEAM_CT ) * 100.0f);
return true;
}
return false;
}
//=============================================================================
// Fun Fact Declarations
//=============================================================================
DECLARE_FUNFACT_STATBEST( FUNFACT_DAMAGE_WITH_GRENADES, "#funfact_damage_with_grenade", 0.5f, CSSTAT_GRENADE_DAMAGE, 200, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_KNIFE_KILLS, "#funfact_knife_kills", 0.5f, CSSTAT_KILLS_KNIFE, 1, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_KILLS_WITH_GRENADES, "#funfact_kills_grenades", 0.7f, CSSTAT_KILLS_HEGRENADE, 2, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_BLIND_KILLS, "#funfact_blind_kills", 0.9f, CSSTAT_KILLS_WHILE_BLINDED, 1, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_KILLED_ENEMIES, "#funfact_killed_enemies", 0.6f, CSSTAT_KILLS, 3, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_KILLS_WITH_LAST_ROUND, "#funfact_kills_with_last_round", 0.6f, CSSTAT_KILLS_WITH_LAST_ROUND, 1, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_DONATED_WEAPONS, "#funfact_donated_weapons", 0.3f, CSSTAT_WEAPONS_DONATED, 2, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_NUM_TIMES_JUMPED, "#funfact_num_times_jumped", 0.2f, CSSTAT_TOTAL_JUMPS, 10, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_FALL_DAMAGE, "#funfact_fall_damage", 0.2f, CSSTAT_FALL_DAMAGE, 50, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_POSTHUMOUS_KILLS_WITH_GRENADE, "#funfact_posthumous_kills_with_grenade", 1.0f, CSSTAT_GRENADE_POSTHUMOUSKILLS, 1, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_ITEMS_PURCHASED, "#funfact_items_purchased", 0.2f, CSSTAT_ITEMS_PURCHASED, 5, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_NUMBER_OF_OVERKILLS, "#funfact_number_of_overkills", 0.5f, CSSTAT_DOMINATION_OVERKILLS, 2, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_MONEY_SPENT, "#funfact_money_spent", 0.2f, CSSTAT_MONEY_SPENT, 5000, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_GRENADES_THROWN, "#funfact_grenades_thrown", 0.3f, CSSTAT_GRENADES_THROWN, 2, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_DEFENDED_BOMB, "#funfact_defended_bomb", 0.5f, CSSTAT_KILLS_WHILE_DEFENDING_BOMB, 2, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_ITEMS_DROPPED_VALUE, "#funfact_items_dropped_value", 0.5f, CSTAT_ITEMS_DROPPED_VALUE, 10000, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_KILL_WOUNDED_ENEMIES, "#funfact_kill_wounded_enemies", 0.4f, CSSTAT_KILLS_ENEMY_WOUNDED, 3, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_KILLS_HEADSHOTS, "#funfact_kills_headshots", 0.7f, CSSTAT_KILLS_HEADSHOT, 3, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_BROKE_WINDOWS, "#funfact_broke_windows", 0.3f, CSSTAT_NUM_BROKEN_WINDOWS, 5, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATBEST( FUNFACT_NIGHTVISION_DAMAGE, "#funfact_nightvision_damage", 0.5f, CSSTAT_NIGHTVISION_DAMAGE, 100, EvalFlags::HighestOnly);
DECLARE_FUNFACT_STATSUM( FUNFACT_SHOTS_FIRED, "#funfact_shots_fired", 0.1f, CSSTAT_SHOTS_FIRED, 200, EvalFlags::All);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_KILL_DEFUSER, "#funfact_kill_defuser", 0.6f, FFEVAL_KILLED_DEFUSER, 1, EvalFlags::TeamTerrorist | EvalFlags::WinningTeam);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_KILL_RESCUER, "#funfact_kill_rescuer", 0.6f, FFEVAL_KILLED_RESCUER, 1, EvalFlags::TeamTerrorist);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_KILLS_WITH_SINGLE_GRENADE, "#funfact_kills_with_single_grenade", 0.8f, FFEVAL_KILLS_WITH_GRENADE, 2, EvalFlags::HighestOnly);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_DAMAGE_NO_KILLS, "#funfact_damage_no_kills", 0.4f, FFEVAL_DAMAGE_NO_KILLS, 200, EvalFlags::HighestOnly);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_FIRST_KILL, "#funfact_first_kill", 0.2f, FFEVAL_FIRST_KILL, 1, EvalFlags::HighestOnly);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_FIRST_BLOOD, "#funfact_first_blood", 0.2f, FFEVAL_FIRST_BLOOD, 1, EvalFlags::HighestOnly);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_BEST_ACCURACY, "#funfact_best_accuracy", 0.4f, FFEVAL_ACCURACY, 20, EvalFlags::HighestOnly);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_KNIFE_IN_GUNFIGHT, "#funfact_knife_in_gunfight", 0.6f, FFEVAL_KNIFE_IN_GUNFIGHT , 1, EvalFlags::All);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_SURVIVED_MULTIPLE_ATTACKERS, "#funfact_survived_multiple_attackers", 0.3f, FFEVAL_MULTIPLE_ATTACKER_COUNT, 3, EvalFlags::HighestOnly | EvalFlags::Alive);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_DIED_FROM_MULTIPLE_ATTACKERS, "#funfact_died_from_multiple_attackers", 0.5f, FFEVAL_MULTIPLE_ATTACKER_COUNT, 3, EvalFlags::HighestOnly | EvalFlags::Dead);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_USED_ALL_AMMO, "#funfact_used_all_ammo", 0.5f, FFEVAL_USED_ALL_AMMO, 1, EvalFlags::All );
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_DAMAGE_MULTIPLE_ENEMIES, "#funfact_damage_multiple_enemies", 0.5f, FFEVAL_DAMAGE_MULTIPLE_ENEMIES, 3, EvalFlags::HighestOnly);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_USED_MULTIPLE_WEAPONS, "#funfact_used_multiple_weapons", 0.5f, FFEVAL_USED_MULTIPLE_WEAPONS, 4, EvalFlags::HighestOnly);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_DEFUSED_WITH_DROPPED_KIT, "#funfact_defused_with_dropped_kit", 0.4f, FFEVAL_DEFUSED_WITH_DROPPED_KIT, 1, EvalFlags::TeamCT);
DECLARE_FUNFACT_PLAYERFUNC( FUNFACT_KILLED_HALF_OF_ENEMIES, "#funfact_killed_half_of_enemies", 0.5f, FFEVAL_KILLED_HALF_OF_ENEMIES, 50, EvalFlags::WinningTeam | EvalFlags::HighestOnly);
DECLARE_FUNFACT_EVALFUNC( FUNFACT_CT_WIN_NO_KILLS, "#funfact_ct_win_no_kills", 0.4f, FFEVAL_CT_WIN_NO_KILLS);
DECLARE_FUNFACT_EVALFUNC( FUNFACT_T_WIN_NO_KILLS, "#funfact_t_win_no_kills", 0.4f, FFEVAL_T_WIN_NO_KILLS );
DECLARE_FUNFACT_EVALFUNC( FUNFACT_T_WIN_NO_CASUALTIES, "#funfact_t_win_no_casualties", 0.2f, FFEVAL_T_WIN_NO_CASUALTIES );
DECLARE_FUNFACT_EVALFUNC( FUNFACT_CT_WIN_NO_CASUALTIES, "#funfact_ct_win_no_casualties", 0.2f, FFEVAL_CT_WIN_NO_CASUALTIES );
DECLARE_FUNFACT_EVALFUNC( FUNFACT_SHORT_ROUND, "#funfact_short_round", 0.3f, FFEVAL_SHORT_ROUND );
DECLARE_FUNFACT_EVALFUNC( FUNFACT_WON_AS_LAST_MEMBER, "#funfact_won_as_last_member", 0.6f, FFEVAL_WON_AS_LAST_MEMBER );
DECLARE_FUNFACT_EVALFUNC( FUNFACT_TERRORIST_ACCURACY, "#funfact_terrorist_accuracy", 0.2f, FFEVAL_TERRORIST_ACCURACY);
DECLARE_FUNFACT_EVALFUNC( FUNFACT_CT_ACCURACY, "#funfact_ct_accuracy", 0.2f, FFEVAL_CT_ACCURACY);
DECLARE_FUNFACT_EVALFUNC( FUNFACT_BEST_TERRORIST_ACCURACY, "#funfact_best_terrorist_accuracy", 0.3f, FFEVAL_BEST_TERRORIST_ACCURACY);
DECLARE_FUNFACT_EVALFUNC( FUNFACT_BEST_COUNTERTERRORIST_ACCURACY, "#funfact_best_counterterrorist_accuracy", 0.3f, FFEVAL_BEST_COUNTERTERRORIST_ACCURACY);
DECLARE_FUNFACT_EVALFUNC( FUNFACT_FALLBACK1, "#funfact_fallback1", 0.0f, FFEVAL_ALWAYS_TRUE);
DECLARE_FUNFACT_EVALFUNC( FUNFACT_FALLBACK2, "#funfact_fallback2", 0.0f, FFEVAL_ALWAYS_TRUE);
DECLARE_FUNFACT_TEAMFUNC( FUNFACT_SAME_UNIFORM_TERRORIST, "#funfact_same_uniform_terrorist", 0.5f, FFEVAL_SAME_UNIFORM, TEAM_TERRORIST);
DECLARE_FUNFACT_TEAMFUNC( FUNFACT_SAME_UNIFORM_CT, "#funfact_same_uniform_ct", 0.5f, FFEVAL_SAME_UNIFORM, TEAM_CT);
+71
View File
@@ -0,0 +1,71 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef INCLUDED_funfact_cs
#define INCLUDED_funfact_cs
#pragma once
#include "cs_player.h"
struct FunFact
{
FunFact() :
id(-1),
szLocalizationToken(NULL),
iPlayer(0),
iData1(0),
iData2(0),
iData3(0),
fMagnitude(0.0f)
{}
int id;
const char* szLocalizationToken;
int iPlayer;
int iData1;
int iData2;
int iData3;
float fMagnitude;
};
typedef CUtlVector<FunFact> FunFactVector;
class FunFactEvaluator
{
DECLARE_CLASS_NOBASE( FunFactEvaluator );
public:
FunFactEvaluator( int id, const char* szLocalizationToken, float fCoolness ) :
m_id(id),
m_pLocalizationToken(szLocalizationToken),
m_fCoolness(fCoolness)
{}
virtual ~FunFactEvaluator() {}
int GetId() const { return m_id; }
const char* GetLocalizationToken() const { return m_pLocalizationToken; }
float GetCoolness() const { return m_fCoolness; }
virtual bool Evaluate( FunFactVector& results ) const = 0;
private:
int m_id;
const char* m_pLocalizationToken;
float m_fCoolness;
};
typedef FunFactEvaluator* (*funfactCreateFunc) (void);
class CFunFactHelper
{
public:
CFunFactHelper ( funfactCreateFunc createFunc )
{
m_pfnCreate = createFunc;
m_pNext = s_pFirst;
s_pFirst = this;
}
funfactCreateFunc m_pfnCreate;
CFunFactHelper *m_pNext;
static CFunFactHelper *s_pFirst;
};
#endif // INCLUDED_funfact_cs
+204
View File
@@ -0,0 +1,204 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "cbase.h"
#include "usermessages.h"
#include "funfactmgr_cs.h"
const float kCooldownRatePlayer = 0.4f;
const float kCooldownRateFunFact = 0.2f;
const float kWeightPlayerCooldown = 0.8f;
const float kWeightFunFactCooldown = 1.0f;
const float kWeightCoolness = 2.0f;
const float kWeightRarity = 1.0f;
#define DEBUG_FUNFACT_SCORING 0
//-----------------------------------------------------------------------------
// Purpose: constructor
//-----------------------------------------------------------------------------
CCSFunFactMgr::CCSFunFactMgr() :
CAutoGameSystemPerFrame( "CCSFunFactMgr" ),
m_funFactDatabase(0, 100, DefLessFunc(int) )
{
for ( int i = 0; i < MAX_PLAYERS; ++i )
{
m_playerCooldown[i] = 0.0f;
}
}
CCSFunFactMgr::~CCSFunFactMgr()
{
Shutdown();
}
//-----------------------------------------------------------------------------
// Purpose: Initializes the fun fact manager
//-----------------------------------------------------------------------------
bool CCSFunFactMgr::Init()
{
ListenForGameEvent( "player_connect" );
CFunFactHelper *pFunFactHelper = CFunFactHelper::s_pFirst;
// create database of all fun fact evaluators (and initial usage metrics)
while ( pFunFactHelper )
{
FunFactDatabaseEntry entry;
entry.fCooldown = 0.0f;
entry.iOccurrences = 0;
entry.pEvaluator = pFunFactHelper->m_pfnCreate();
m_funFactDatabase.Insert(entry.pEvaluator->GetId(), entry);
pFunFactHelper = pFunFactHelper->m_pNext;
}
for (int i = 0; i < ARRAYSIZE(m_playerCooldown); ++i)
{
m_playerCooldown[i] = 0.0f;
}
m_numRounds = 0;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Shuts down the fun fact manager
//-----------------------------------------------------------------------------
void CCSFunFactMgr::Shutdown()
{
FOR_EACH_MAP( m_funFactDatabase, iter )
{
delete m_funFactDatabase[iter].pEvaluator;
}
m_funFactDatabase.RemoveAll();
}
//-----------------------------------------------------------------------------
// Purpose: Per frame processing
//-----------------------------------------------------------------------------
void CCSFunFactMgr::Update( float frametime )
{
}
//-----------------------------------------------------------------------------
// Purpose: Listens for game events. Clears out map based stats and player based stats when necessary
//-----------------------------------------------------------------------------
void CCSFunFactMgr::FireGameEvent( IGameEvent *event )
{
const char *eventname = event->GetName();
if ( Q_strcmp( "player_connect", eventname ) == 0 )
{
int index = event->GetInt("index");// player slot (entity index-1)
ASSERT( index >= 0 && index < MAX_PLAYERS );
if( index >= 0 && index < MAX_PLAYERS )
{
m_playerCooldown[index] = 0.0f;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Finds the best fun fact to display and returns all necessary information through the parameters
//-----------------------------------------------------------------------------
bool CCSFunFactMgr::GetRoundEndFunFact( int iWinningTeam, int iRoundResult, FunFact& funfact )
{
FunFactVector validFunFacts;
// Generate a vector of all valid fun facts for this round
FOR_EACH_MAP( m_funFactDatabase, i )
{
FunFact funFact;
if ( m_funFactDatabase[i].pEvaluator->Evaluate(validFunFacts) )
{
m_funFactDatabase[i].iOccurrences++;
}
}
m_numRounds++;
if (validFunFacts.Size() == 0)
return false;
// pick the fun fact with the highest score
float fBestScore = -FLT_MAX;
int iFunFactIndex = -1;
#if DEBUG_FUNFACT_SCORING
Msg("Scoring fun facts:\n");
#endif
FOR_EACH_VEC(validFunFacts, i)
{
float fScore = ScoreFunFact(validFunFacts[i]);
#if DEBUG_FUNFACT_SCORING
char szPlayerName[64];
const FunFact& funfact = validFunFacts[i];
if (funfact.iPlayer > 0)
V_strncpy(szPlayerName, ToCSPlayer(UTIL_PlayerByIndex(funfact.iPlayer))->GetPlayerName(), sizeof(szPlayerName));
else
V_strcpy(szPlayerName, "");
Msg("(%5.4f) %s, %s, %i, %i, %i\n", fScore, funfact.szLocalizationToken, szPlayerName, funfact.iData1, funfact.iData2, funfact.iData3);
#endif
if (fScore > fBestScore)
{
fBestScore = fScore;
iFunFactIndex = i;
}
}
if (iFunFactIndex < 0)
return false;
funfact = validFunFacts[iFunFactIndex];
// decay player cooldowns
for (int i = 0; i < MAX_PLAYERS; ++i )
{
m_playerCooldown[i] *= (1.0f - kCooldownRatePlayer);
}
// decay funfact cooldowns
FOR_EACH_MAP(m_funFactDatabase, i)
{
m_funFactDatabase[i].fCooldown *= (1.0f - kCooldownRateFunFact);
}
// set player cooldown for player in funfact
if ( funfact.iPlayer )
{
m_playerCooldown[funfact.iPlayer - 1] = 1.0f;
}
// set funfact cooldown for current funfact
m_funFactDatabase[m_funFactDatabase.Find(funfact.id)].fCooldown = 1.0f;
return true;
}
float CCSFunFactMgr::ScoreFunFact( const FunFact& funfact )
{
float fScore = 0.0f;
const FunFactDatabaseEntry& dbEntry = m_funFactDatabase[m_funFactDatabase.Find(funfact.id)];
// add the coolness score for the funfact
fScore += kWeightCoolness * dbEntry.pEvaluator->GetCoolness() * (1.0f + funfact.fMagnitude);
// subtract the cooldown for the funfact
fScore -= kWeightFunFactCooldown * dbEntry.fCooldown;
// subtract the cooldown for the player
if ( funfact.iPlayer ) {
fScore -= kWeightPlayerCooldown * m_playerCooldown[funfact.iPlayer - 1];
}
// add the rarity bonus
fScore += kWeightRarity * powf((1.0f - (float)dbEntry.iOccurrences / m_numRounds), 2.0f);
return fScore;
}
+45
View File
@@ -0,0 +1,45 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#ifndef FUNFACTMGR_H
#define FUNFACTMGR_H
#ifdef _WIN32
#pragma once
#endif
#include "GameEventListener.h"
#include "funfact_cs.h"
#include "utlmap.h"
class FunFactEvaluator;
class CCSFunFactMgr : public CAutoGameSystemPerFrame, public CGameEventListener
{
public:
CCSFunFactMgr();
~CCSFunFactMgr();
virtual bool Init();
virtual void Shutdown();
virtual void Update( float frametime );
bool GetRoundEndFunFact( int iWinningTeam, int iRoundResult, FunFact& funfact );
protected:
float ScoreFunFact( const FunFact& funfact );
void FireGameEvent( IGameEvent *event );
private:
float m_playerCooldown[MAX_PLAYERS]; // Weights for all players. Updated every round
struct FunFactDatabaseEntry
{
const FunFactEvaluator* pEvaluator;
int iOccurrences;
float fCooldown;
};
CUtlMap<int, FunFactDatabaseEntry> m_funFactDatabase;
int m_numRounds;
};
#endif
+128
View File
@@ -0,0 +1,128 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "smokegrenade_projectile.h"
#include "sendproxy.h"
#include "holiday_gift.h"
#include "cs_player.h"
#include "KeyValues.h"
#include "bot_manager.h"
#include "weapon_csbase.h"
#define CHRISTMAS_MODEL "models/items/cs_gift.mdl"
LINK_ENTITY_TO_CLASS( holiday_gift, CHolidayGift );
PRECACHE_WEAPON_REGISTER( holiday_gift );
//-----------------------------------------------------------------------------
CHolidayGift* CHolidayGift::Create( const Vector &position, const QAngle &angles, const QAngle &eyeAngles, const Vector &velocity, CBaseCombatCharacter *pOwner )
{
CHolidayGift *pGift = (CHolidayGift*)CBaseEntity::Create( "holiday_gift", position, angles, pOwner );
if ( pGift )
{
pGift->AddSpawnFlags( SF_NORESPAWN );
Vector vecRight, vecUp;
AngleVectors( eyeAngles, NULL, &vecRight, &vecUp );
// Calculate the initial impulse on the gift.
Vector vecImpulse( 0.0f, 0.0f, 0.0f );
vecImpulse += vecUp * random->RandomFloat( 0, 0.25 );
vecImpulse += vecRight * random->RandomFloat( -0.25, 0.25 );
VectorNormalize( vecImpulse );
vecImpulse *= random->RandomFloat( 100.0, 150.0 );
vecImpulse += velocity;
// Cap the impulse.
float flSpeed = vecImpulse.Length();
if ( flSpeed > 300.0 )
{
VectorScale( vecImpulse, 300.0 / flSpeed, vecImpulse );
}
pGift->SetMoveType( MOVETYPE_FLYGRAVITY );
pGift->SetAbsVelocity( vecImpulse * 2.f + Vector(0,0,200) );
pGift->SetAbsAngles( QAngle(0,0,0) );
pGift->UseClientSideAnimation();
pGift->ResetSequence( pGift->LookupSequence("idle") );
pGift->EmitSound( "Christmas.GiftDrop" );
pGift->ActivateWhenAtRest();
}
return pGift;
}
//-----------------------------------------------------------------------------
void CHolidayGift::Precache()
{
BaseClass::Precache();
PrecacheModel( CHRISTMAS_MODEL );
PrecacheScriptSound( "Christmas.GiftDrop" );
PrecacheScriptSound( "Christmas.GiftPickup" );
}
//-----------------------------------------------------------------------------
void CHolidayGift::Spawn( void )
{
BaseClass::Spawn();
SetModel( CHRISTMAS_MODEL );
// Die in 30 seconds
SetContextThink( &CBaseEntity::SUB_Remove, gpGlobals->curtime + 30, "DIE_THINK" );
SetContextThink( &CHolidayGift::DropSoundThink, gpGlobals->curtime + 0.2f, "SOUND_THINK" );
}
//-----------------------------------------------------------------------------
void CHolidayGift::DropSoundThink( void )
{
EmitSound( "Christmas.GiftDrop" );
}
//-----------------------------------------------------------------------------
bool CHolidayGift::MyTouch( CBasePlayer *pPlayer )
{
if( !pPlayer )
return false;
if( !pPlayer->IsAlive() )
return false;
if ( pPlayer->IsBot() )
return false;
if ( ( pPlayer->GetTeamNumber() != TEAM_CT ) && ( pPlayer->GetTeamNumber() != TEAM_TERRORIST ) )
return false;
// Send a message for the achievement tracking.
IGameEvent *event = gameeventmanager->CreateEvent( "christmas_gift_grab" );
if ( event )
{
event->SetInt( "userid", pPlayer->GetUserID() );
gameeventmanager->FireEvent( event );
}
pPlayer->EmitSound( "Christmas.GiftPickup" );
return true;
}
//-----------------------------------------------------------------------------
void CHolidayGift::ItemTouch( CBaseEntity *pOther )
{
if ( pOther->IsWorld() )
{
Vector absVel = GetAbsVelocity();
SetAbsVelocity( Vector( 0,0,absVel.z ) );
return;
}
BaseClass::ItemTouch( pOther );
}
+36
View File
@@ -0,0 +1,36 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef HOLIDAY_GIFT_H
#define HOLIDAY_GIFT_H
#ifdef _WIN32
#pragma once
#endif
#include "items.h"
class CHolidayGift: public CItem
{
public:
DECLARE_CLASS( CHolidayGift, CItem );
public:
virtual void Precache();
virtual void Spawn( void );
virtual bool MyTouch( CBasePlayer *pBasePlayer );
virtual void ItemTouch( CBaseEntity *pOther );
void DropSoundThink( void );
public:
static CHolidayGift* Create( const Vector &position, const QAngle &angles, const QAngle &eyeAngles, const Vector &velocity, CBaseCombatCharacter *pOwner );
};
#endif // HOLIDAY_GIFT_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,256 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// cs_simple_hostage.h
// Simple CS1.6 level hostage
// Author: Michael S. Booth, July 2004
#ifndef _CS_SIMPLE_HOSTAGE_H_
#define _CS_SIMPLE_HOSTAGE_H_
#include "nav_mesh.h"
#include "cs_nav_path.h"
#include "cs_nav_pathfind.h"
#include "improv_locomotor.h"
#include "cs_playeranimstate.h"
class CCSPlayer;
//----------------------------------------------------------------------------------------------------------------
/**
* A Counter-Strike Hostage
*/
class CHostage : public CBaseCombatCharacter, public CImprovLocomotor, public ICSPlayerAnimStateHelpers
{
public:
DECLARE_CLASS( CHostage, CBaseCombatCharacter );
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
CHostage( void );
virtual ~CHostage();
public:
virtual void Spawn( void );
virtual void Precache();
int ObjectCaps( void ) { return (BaseClass::ObjectCaps() | FCAP_IMPULSE_USE); } // make hostage "useable"
virtual void PhysicsSimulate( void );
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
virtual void Event_Killed( const CTakeDamageInfo &info );
virtual void Touch( CBaseEntity *other ); // in contact with "other"
void HostageRescueZoneTouch( inputdata_t &inputdata ); // invoked when hostage touches a rescue zone
void HostageThink( void ); // periodic update to initiate behaviors
void GiveCTUseBonus( CCSPlayer *rescuer ); // give bonus to CT's for talking to a hostage
void CheckForHostageAbuse( CCSPlayer *player ); // check for hostage-killer abuse
// queries
bool IsFollowingSomeone( void );
bool IsFollowing( const CBaseEntity *entity );
bool IsValid( void ) const;
bool IsRescuable( void ) const;
bool IsRescued( void ) const;
bool IsOnGround( void ) const;
bool IsVisible( const Vector &pos, bool testFOV = false ) const; // return true if hostage can see position
// hostage states
void Idle( void ); // stand idle
void Follow( CCSPlayer *leader ); // begin following "leader"
CCSPlayer *GetLeader( void ) const; // return our leader, or NULL
void FaceTowards( const Vector &target, float deltaT ); // rotate body to face towards "target"
void ApplyForce( const Vector &force ) { m_accel += force; } // apply a force to the hostage
unsigned int PhysicsSolidMaskForEntity() const;
Class_T Classify ( void ) { return CLASS_PLAYER_ALLY; }
public:
// begin CImprovLocomotor -----------------------------------------------------------------------------------------------------------------
virtual const Vector &GetCentroid( void ) const;
virtual const Vector &GetFeet( void ) const; // return position of "feet" - point below centroid of improv at feet level
virtual const Vector &GetEyes( void ) const;
virtual float GetMoveAngle( void ) const; // return direction of movement
virtual CNavArea *GetLastKnownArea( void ) const;
virtual bool GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal = NULL ); // find "simple" ground height, treating current nav area as part of the floor
virtual void Crouch( void );
virtual void StandUp( void ); // "un-crouch"
virtual bool IsCrouching( void ) const;
virtual void Jump( void ); // initiate a jump
virtual bool IsJumping( void ) const;
virtual void Run( void ); // set movement speed to running
virtual void Walk( void ); // set movement speed to walking
virtual bool IsRunning( void ) const;
virtual void StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos ); // invoked when a ladder is encountered while following a path
virtual bool TraverseLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos, float deltaT ); // traverse given ladder
virtual bool IsUsingLadder( void ) const;
virtual void TrackPath( const Vector &pathGoal, float deltaT ); // move along path by following "pathGoal"
virtual void OnMoveToSuccess( const Vector &goal ); // invoked when an improv reaches its MoveTo goal
virtual void OnMoveToFailure( const Vector &goal, MoveToFailureType reason ); // invoked when an improv fails to reach a MoveTo goal
// end CImprovLocomotor -------------------------------------------------------------------------------------------------------------------
// ICSPlayerAnimState overrides.
public:
virtual CWeaponCSBase* CSAnim_GetActiveWeapon();
virtual bool CSAnim_CanMove();
protected:
virtual void HostageUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
private:
float GetModifiedDamage( float flDamage, int nHitGroup );
ICSPlayerAnimState *m_PlayerAnimState;
IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iMaxHealth );
IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iHealth );
IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_lifeState );
CNetworkVar( bool, m_isRescued ); // true if the hostage has been rescued
CNetworkVar( EHANDLE, m_leader ); // the player we are following
void UpdateFollowing( float deltaT ); // do following behavior
int m_lastLeaderID;
CountdownTimer m_reuseTimer; // to throttle how often hostage can be used
bool m_hasBeenUsed; // flag to give first rescuer bonus money
Vector m_vel;
Vector m_accel;
bool m_isRunning; // true if hostage move speed is to run (walk if false)
bool m_isCrouching; // true if hostage is crouching
CountdownTimer m_jumpTimer; // if zero, we can jump
bool m_isWaitingForLeader; // true if we are waiting for our rescuer to move
CCSNavPath m_path; // current path to follow
CountdownTimer m_repathTimer; // throttle pathfinder
CountdownTimer m_inhibitDoorTimer;
CNavPathFollower m_pathFollower; // path tracking mechanism
CountdownTimer m_inhibitObstacleAvoidanceTimer; // when active, turn off path following feelers
CNavArea *m_lastKnownArea; // last area we were in
void Wiggle( void ); // attempt to wiggle-out of begin stuck
CountdownTimer m_wiggleTimer; // for wiggling
NavRelativeDirType m_wiggleDirection;
bool m_isAdjusted; // hack for adjusting bounding box
float m_disappearTime; // has finished fading, remove me
void PushawayThink( void ); // pushes physics objects away from the hostage
void AvoidPhysicsProps( void ); // guides the hostage away from physics props
};
//--------------------------------------------------------------------------------------------------------------
/**
* Functor used with NavAreaBuildPath() for building Hostage paths.
* Once we hook up crouching and ladders, this can be removed and ShortestPathCost() can be used instead.
*/
class HostagePathCost
{
public:
// HPE_TODO[pmf]: check that these new parameters are okay to be ignored
float operator() ( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length )
{
if (fromArea == NULL)
{
// first area in path, no cost
return 0.0f;
}
else
{
// if this area isn't available to hostages, skip it
if ( area->GetAttributes() & NAV_MESH_NO_HOSTAGES )
{
return -1.0f;
}
// compute distance travelled along path so far
float dist;
if (ladder)
{
// can't traverse ladders
return -1.0f;
}
else
{
dist = (area->GetCenter() - fromArea->GetCenter()).Length();
}
float cost = dist + fromArea->GetCostSoFar();
if (area->GetAttributes() & NAV_MESH_CROUCH) // && !(area->GetAttributes() & NAV_MESH_JUMP))
{
// can't traverse areas that require crouching
return -1.0f;
}
// if this is a "jump" area, add penalty
if (area->GetAttributes() & NAV_MESH_JUMP)
{
const float jumpPenalty = 5.0f;
cost += jumpPenalty * dist;
}
return cost;
}
}
};
//--------------------------------------------------------------------------------------------------------------
// All the hostage entities.
extern CUtlVector< CHostage * > g_Hostages;
//--------------------------------------------------------------------------------------------------------------
/**
* Iterate over all active hostages in the game, invoking functor on each.
* If functor returns false, stop iteration and return false.
*/
template < typename Functor >
bool ForEachHostage( Functor &func )
{
for( int i=0; i<g_Hostages.Count(); ++i )
{
CHostage *hostage = g_Hostages[i];
if ( hostage == NULL || !hostage->IsValid() )
continue;
if ( func( hostage ) == false )
return false;
}
return true;
}
#endif // _CS_SIMPLE_HOSTAGE_H_
@@ -0,0 +1,18 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "info_view_parameters.h"
BEGIN_DATADESC( CInfoViewParameters )
DEFINE_KEYFIELD( m_nViewMode, FIELD_INTEGER, "ViewMode" )
END_DATADESC()
LINK_ENTITY_TO_CLASS( info_view_parameters, CInfoViewParameters );
@@ -0,0 +1,24 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#ifndef INFO_VIEW_PARAMETERS_H
#define INFO_VIEW_PARAMETERS_H
#ifdef _WIN32
#pragma once
#endif
class CInfoViewParameters : public CBaseEntity
{
public:
DECLARE_CLASS( CInfoViewParameters, CBaseEntity );
DECLARE_DATADESC();
int m_nViewMode;
};
#endif // INFO_VIEW_PARAMETERS_H
+147
View File
@@ -0,0 +1,147 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "items.h"
#include "cs_player.h"
#include "weapon_csbase.h"
#include "cs_ammodef.h"
//-----------------------------------------------------------------------------
class CItemAmmo : public CItem
{
public:
DECLARE_CLASS( CItemAmmo, CItem );
bool MyTouch( CBasePlayer *pBasePlayer )
{
CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
if ( !pPlayer )
{
Assert( false );
return false;
}
int ammoIndex = GetCSAmmoDef()->Index( GetAmmoName() );
if ( ammoIndex < 0 )
{
Assert( false );
return false;
}
pPlayer->GiveAmmo( GetCSAmmoDef()->GetBuySize( ammoIndex ), ammoIndex );
return true;
}
virtual const char * GetAmmoName( void ) const { return NULL; }
};
//-----------------------------------------------------------------------------
class CItemAmmo50AE : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo50AE, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_50AE; }
};
LINK_ENTITY_TO_CLASS( ammo_50ae, CItemAmmo50AE );
//-----------------------------------------------------------------------------
class CItemAmmo762MM : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo762MM, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_762MM; }
};
LINK_ENTITY_TO_CLASS( ammo_762mm, CItemAmmo762MM );
//-----------------------------------------------------------------------------
class CItemAmmo556MM : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo556MM, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_556MM; }
};
LINK_ENTITY_TO_CLASS( ammo_556mm, CItemAmmo556MM );
//-----------------------------------------------------------------------------
class CItemAmmo556MM_BOX : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo556MM_BOX, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_556MM_BOX; }
};
LINK_ENTITY_TO_CLASS( ammo_556mm_box, CItemAmmo556MM_BOX );
//-----------------------------------------------------------------------------
class CItemAmmo338MAG : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo338MAG, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_338MAG; }
};
LINK_ENTITY_TO_CLASS( ammo_338mag, CItemAmmo338MAG );
//-----------------------------------------------------------------------------
class CItemAmmo9MM : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo9MM, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_9MM; }
};
LINK_ENTITY_TO_CLASS( ammo_9mm, CItemAmmo9MM );
//-----------------------------------------------------------------------------
class CItemAmmoBuckshot : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmoBuckshot, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_BUCKSHOT; }
};
LINK_ENTITY_TO_CLASS( ammo_buckshot, CItemAmmoBuckshot );
//-----------------------------------------------------------------------------
class CItemAmmo45ACP : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo45ACP, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_45ACP; }
};
LINK_ENTITY_TO_CLASS( ammo_45acp, CItemAmmo45ACP );
//-----------------------------------------------------------------------------
class CItemAmmo357SIG : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo357SIG, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_357SIG; }
};
LINK_ENTITY_TO_CLASS( ammo_357sig, CItemAmmo357SIG );
//-----------------------------------------------------------------------------
class CItemAmmo57MM : public CItemAmmo
{
public:
DECLARE_CLASS( CItemAmmo57MM, CItemAmmo );
virtual const char * GetAmmoName( void ) const { return BULLET_PLAYER_57MM; }
};
LINK_ENTITY_TO_CLASS( ammo_57mm, CItemAmmo57MM );
//-----------------------------------------------------------------------------
+56
View File
@@ -0,0 +1,56 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "items.h"
#include "cs_player.h"
class CItemAssaultSuit : public CItem
{
void Spawn( void )
{
Precache( );
CItem::Spawn( );
}
void Precache( void )
{
PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
}
bool MyTouch( CBasePlayer *pBasePlayer )
{
CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
if ( !pPlayer )
{
Assert( false );
return false;
}
pPlayer->m_bHasHelmet = true;
pPlayer->SetArmorValue( 100 );
if ( pPlayer->IsDead() == false )
{
CPASAttenuationFilter filter( pBasePlayer );
EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
CSingleUserRecipientFilter user( pPlayer );
UserMessageBegin( user, "ItemPickup" );
WRITE_STRING( "item_assaultsuit" );
MessageEnd();
}
return true;
}
};
LINK_ENTITY_TO_CLASS( item_assaultsuit, CItemAssaultSuit );
+107
View File
@@ -0,0 +1,107 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Defuser kit that drops from counter-strike CTS
//
//=============================================================================//
#include "cbase.h"
#include "items.h"
#include "cs_player.h"
class CItemDefuser : public CItem
{
public:
DECLARE_CLASS( CItemDefuser, CItem );
void Spawn( void );
void Precache( void );
void DefuserTouch( CBaseEntity *pOther );
void ActivateThink( void );
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS( item_defuser, CItemDefuser );
PRECACHE_REGISTER(item_defuser);
BEGIN_DATADESC( CItemDefuser )
//Functions
DEFINE_THINKFUNC( ActivateThink ),
DEFINE_ENTITYFUNC( DefuserTouch ),
END_DATADESC()
void CItemDefuser::Spawn( void )
{
Precache( );
SetModel( "models/weapons/w_defuser.mdl" );
BaseClass::Spawn();
SetNextThink( gpGlobals->curtime + 0.5f );
SetThink( &CItemDefuser::ActivateThink );
SetTouch( NULL );
}
void CItemDefuser::Precache( void )
{
PrecacheModel( "models/weapons/w_defuser.mdl" );
PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
}
void CItemDefuser::ActivateThink( void )
{
//since we can't stop the item from being touched while its in the air,
//activate 1 second after being dropped
SetTouch( &CItemDefuser::DefuserTouch );
SetThink( NULL );
}
void CItemDefuser::DefuserTouch( CBaseEntity *pOther )
{
if ( !pOther->IsPlayer() )
{
return;
}
//if( GetFlags() & FL_ONGROUND )
{
CCSPlayer *pPlayer = (CCSPlayer *)pOther;
if ( !pPlayer )
{
Assert( false );
return;
}
if( pPlayer->GetTeamNumber() == TEAM_CT && !pPlayer->HasDefuser() )
{
//=============================================================================
// HPE_BEGIN:
// [dwenger] Added for fun-fact support
//=============================================================================
pPlayer->GiveDefuser( true );
//=============================================================================
// HPE_END
//=============================================================================
if ( pPlayer->IsDead() == false )
{
CPASAttenuationFilter filter( pPlayer );
EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
}
UTIL_Remove( this );
return;
}
}
}
+59
View File
@@ -0,0 +1,59 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "items.h"
#include "cs_player.h"
class CItemKevlar : public CItem
{
public:
DECLARE_CLASS( CItemKevlar, CItem );
void Spawn( void )
{
Precache( );
BaseClass::Spawn( );
}
void Precache( void )
{
PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
}
bool MyTouch( CBasePlayer *pBasePlayer )
{
CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
if ( !pPlayer )
{
Assert( false );
return false;
}
pPlayer->SetArmorValue( 100 );
if ( pPlayer->IsDead() == false )
{
CPASAttenuationFilter filter( pBasePlayer );
EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
CSingleUserRecipientFilter user( pPlayer );
UserMessageBegin( user, "ItemPickup" );
WRITE_STRING( "item_kevlar" );
MessageEnd();
}
return true;
}
};
LINK_ENTITY_TO_CLASS( item_kevlar, CItemKevlar );
+59
View File
@@ -0,0 +1,59 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "items.h"
#include "cs_player.h"
class CItemNvgs : public CItem
{
public:
DECLARE_CLASS( CItemNvgs, CItem );
void Spawn( void )
{
Precache( );
BaseClass::Spawn( );
}
void Precache( void )
{
PrecacheScriptSound( "BaseCombatCharacter.ItemPickup2" );
}
bool MyTouch( CBasePlayer *pBasePlayer )
{
CCSPlayer *pPlayer = dynamic_cast< CCSPlayer* >( pBasePlayer );
if ( !pPlayer )
{
Assert( false );
return false;
}
pPlayer->m_bHasNightVision = true;
if ( pPlayer->IsDead() == false )
{
CPASAttenuationFilter filter( pPlayer );
EmitSound( filter, entindex(), "BaseCombatCharacter.ItemPickup2" );
CSingleUserRecipientFilter user( pPlayer );
UserMessageBegin( user, "ItemPickup" );
WRITE_STRING( "item_nvgs" );
MessageEnd();
}
return true;
}
};
LINK_ENTITY_TO_CLASS( item_nvgs, CItemNvgs );
+76
View File
@@ -0,0 +1,76 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "mapinfo.h"
#include "cs_gamerules.h"
LINK_ENTITY_TO_CLASS( info_map_parameters, CMapInfo );
BEGIN_DATADESC( CMapInfo )
DEFINE_INPUTFUNC( FIELD_INTEGER, "FireWinCondition", InputFireWinCondition ),
END_DATADESC()
CMapInfo *g_pMapInfo = NULL;
CMapInfo::CMapInfo()
{
m_flBombRadius = 500.0f;
m_iBuyingStatus = 0;
if ( g_pMapInfo )
{
// Should only be one of these.
Warning( "Warning: Multiple info_map_parameters entities in map!\n" );
}
else
{
g_pMapInfo = this;
}
}
CMapInfo::~CMapInfo()
{
if ( g_pMapInfo == this )
g_pMapInfo = NULL;
}
bool CMapInfo::KeyValue( const char *szKeyName, const char *szValue )
{
if (FStrEq(szKeyName, "buying"))
{
m_iBuyingStatus = atoi(szValue);
return true;
}
else if (FStrEq(szKeyName, "bombradius"))
{
m_flBombRadius = (float)(atoi(szValue));
if (m_flBombRadius > 2048)
m_flBombRadius = 2048;
return true;
}
return BaseClass::KeyValue( szKeyName, szValue );
}
void CMapInfo::Spawn( void )
{
SetMoveType( MOVETYPE_NONE );
SetSolid( SOLID_NONE );
AddEffects( EF_NODRAW );
}
void CMapInfo::InputFireWinCondition(inputdata_t &inputdata )
{
CSGameRules()->TerminateRound( 5, inputdata.value.Int() );
}
+42
View File
@@ -0,0 +1,42 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef MAPINFO_H
#define MAPINFO_H
#ifdef _WIN32
#pragma once
#endif
#include "baseentity.h"
class CMapInfo : public CPointEntity
{
public :
DECLARE_DATADESC();
DECLARE_CLASS( CMapInfo, CPointEntity );
CMapInfo();
virtual ~CMapInfo();
bool KeyValue( const char *szKeyName, const char *szValue );
void Spawn();
void InputFireWinCondition( inputdata_t &inputdata );
public:
int m_iBuyingStatus;
float m_flBombRadius;
};
// The info_map_parameters entity in this map (only one is allowed for).
extern CMapInfo *g_pMapInfo;
#endif // MAPINFO_H
@@ -0,0 +1,71 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "mapinfo.h"
class CSurroundTest : public CPointEntity
{
public:
DECLARE_CLASS( CSurroundTest, CPointEntity );
void FireCorrectOutput( inputdata_t &inputdata );
void Spawn( void );
private:
COutputEvent m_On2Speakers;
COutputEvent m_On4Speakers;
COutputEvent m_On51Speakers;
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS( point_surroundtest, CSurroundTest );
BEGIN_DATADESC( CSurroundTest )
DEFINE_INPUTFUNC( FIELD_VOID, "FireCorrectOutput", FireCorrectOutput ),
DEFINE_OUTPUT( m_On2Speakers, "On2Speakers" ),
DEFINE_OUTPUT( m_On4Speakers, "On4Speakers" ),
DEFINE_OUTPUT( m_On51Speakers, "On51Speakers" ),
END_DATADESC()
enum
{
SND_SURROUND_HEADPHONES = 0,
SND_SURROUND_2SPEAKERS = 2,
SND_SURROUND_4SPEAKERS = 4,
SND_SURROUND_51SPEAKERS,
};
void CSurroundTest::FireCorrectOutput( inputdata_t &inputdata )
{
ConVar const *pSurroundCVar = cvar->FindVar( "snd_surround_speakers" );
if ( pSurroundCVar )
{
int iSetting = pSurroundCVar->GetInt();
if ( iSetting == SND_SURROUND_HEADPHONES || iSetting == SND_SURROUND_2SPEAKERS )
{
m_On2Speakers.FireOutput( this, this );
}
else if ( iSetting == SND_SURROUND_4SPEAKERS )
{
m_On4Speakers.FireOutput( this, this );
}
else if ( iSetting == SND_SURROUND_51SPEAKERS )
{
m_On51Speakers.FireOutput( this, this );
}
}
}
void CSurroundTest::Spawn( void )
{
BaseClass::Spawn();
}
@@ -0,0 +1,189 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "smokegrenade_projectile.h"
#include "sendproxy.h"
#include "particle_smokegrenade.h"
#include "cs_player.h"
#include "KeyValues.h"
#include "bot_manager.h"
#include "weapon_csbase.h"
#define GRENADE_MODEL "models/Weapons/w_eq_smokegrenade_thrown.mdl"
LINK_ENTITY_TO_CLASS( smokegrenade_projectile, CSmokeGrenadeProjectile );
PRECACHE_WEAPON_REGISTER( smokegrenade_projectile );
BEGIN_DATADESC( CSmokeGrenadeProjectile )
DEFINE_THINKFUNC( Think_Detonate ),
DEFINE_THINKFUNC( Think_Fade ),
DEFINE_THINKFUNC( Think_Remove )
END_DATADESC()
CSmokeGrenadeProjectile* CSmokeGrenadeProjectile::Create(
const Vector &position,
const QAngle &angles,
const Vector &velocity,
const AngularImpulse &angVelocity,
CBaseCombatCharacter *pOwner )
{
CSmokeGrenadeProjectile *pGrenade = (CSmokeGrenadeProjectile*)CBaseEntity::Create( "smokegrenade_projectile", position, angles, pOwner );
// Set the timer for 1 second less than requested. We're going to issue a SOUND_DANGER
// one second before detonation.
pGrenade->SetTimer( 1.5 );
pGrenade->SetAbsVelocity( velocity );
pGrenade->SetupInitialTransmittedGrenadeVelocity( velocity );
pGrenade->SetThrower( pOwner );
pGrenade->SetGravity( 0.55 );
pGrenade->SetFriction( 0.7 );
pGrenade->m_flDamage = 100;
pGrenade->ChangeTeam( pOwner->GetTeamNumber() );
pGrenade->ApplyLocalAngularVelocityImpulse( angVelocity );
pGrenade->SetTouch( &CBaseGrenade::BounceTouch );
pGrenade->SetGravity( BaseClass::GetGrenadeGravity() );
pGrenade->SetFriction( BaseClass::GetGrenadeFriction() );
pGrenade->SetElasticity( BaseClass::GetGrenadeElasticity() );
pGrenade->m_bDidSmokeEffect = false;
pGrenade->m_pWeaponInfo = GetWeaponInfo( WEAPON_SMOKEGRENADE );
return pGrenade;
}
void CSmokeGrenadeProjectile::SetTimer( float timer )
{
SetThink( &CSmokeGrenadeProjectile::Think_Detonate );
SetNextThink( gpGlobals->curtime + timer );
TheBots->SetGrenadeRadius( this, 0.0f );
}
void CSmokeGrenadeProjectile::Think_Detonate()
{
if ( GetAbsVelocity().Length() > 0.1 )
{
// Still moving. Don't detonate yet.
SetNextThink( gpGlobals->curtime + 0.2 );
return;
}
TheBots->SetGrenadeRadius( this, SmokeGrenadeRadius );
// Ok, we've stopped rolling or whatever. Now detonate.
ParticleSmokeGrenade *pGren = (ParticleSmokeGrenade*)CBaseEntity::Create( PARTICLESMOKEGRENADE_ENTITYNAME, GetAbsOrigin(), QAngle(0,0,0), NULL );
if ( pGren )
{
pGren->FillVolume();
pGren->SetFadeTime( 15, 20 );
pGren->SetAbsOrigin( GetAbsOrigin() );
//tell the hostages about the smoke!
CBaseEntity *pEntity = NULL;
variant_t var; //send the location of the smoke?
var.SetVector3D( GetAbsOrigin() );
while ( ( pEntity = gEntList.FindEntityByClassname( pEntity, "hostage_entity" ) ) != NULL)
{
//send to hostages that have a resonable chance of being in it while its still smoking
if( (GetAbsOrigin() - pEntity->GetAbsOrigin()).Length() < 1000 )
pEntity->AcceptInput( "smokegrenade", this, this, var, 0 );
}
// tell the bots a smoke grenade has exploded
CCSPlayer *player = ToCSPlayer(GetThrower());
if ( player )
{
IGameEvent * event = gameeventmanager->CreateEvent( "smokegrenade_detonate" );
if ( event )
{
event->SetInt( "userid", player->GetUserID() );
event->SetFloat( "x", GetAbsOrigin().x );
event->SetFloat( "y", GetAbsOrigin().y );
event->SetFloat( "z", GetAbsOrigin().z );
gameeventmanager->FireEvent( event );
}
}
}
m_hSmokeEffect = pGren;
m_bDidSmokeEffect = true;
EmitSound( "BaseSmokeEffect.Sound" );
m_nRenderMode = kRenderTransColor;
SetNextThink( gpGlobals->curtime + 5 );
SetThink( &CSmokeGrenadeProjectile::Think_Fade );
}
// Fade the projectile out over time before making it disappear
void CSmokeGrenadeProjectile::Think_Fade()
{
SetNextThink( gpGlobals->curtime );
color32 c = GetRenderColor();
c.a -= 1;
SetRenderColor( c.r, c.b, c.g, c.a );
if ( !c.a )
{
TheBots->RemoveGrenade( this );
SetModelName( NULL_STRING );//invisible
SetNextThink( gpGlobals->curtime + 20 );
SetThink( &CSmokeGrenadeProjectile::Think_Remove ); // Spit out smoke for 10 seconds.
SetSolid( SOLID_NONE );
}
}
void CSmokeGrenadeProjectile::Think_Remove()
{
if ( m_hSmokeEffect.Get() )
UTIL_Remove( m_hSmokeEffect );
TheBots->RemoveGrenade( this );
SetModelName( NULL_STRING );//invisible
SetSolid( SOLID_NONE );
SetMoveType( MOVETYPE_NONE );
}
//Implement this so we never call the base class,
//but this should never be called either.
void CSmokeGrenadeProjectile::Detonate( void )
{
Assert(!"Smoke grenade handles its own detonation");
}
void CSmokeGrenadeProjectile::Spawn()
{
SetModel( GRENADE_MODEL );
BaseClass::Spawn();
}
void CSmokeGrenadeProjectile::Precache()
{
PrecacheModel( GRENADE_MODEL );
PrecacheScriptSound( "BaseSmokeEffect.Sound" );
PrecacheScriptSound( "SmokeGrenade.Bounce" );
BaseClass::Precache();
}
void CSmokeGrenadeProjectile::BounceSound( void )
{
if ( !m_bDidSmokeEffect )
{
EmitSound( "SmokeGrenade.Bounce" );
}
}
@@ -0,0 +1,53 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef HEGRENADE_PROJECTILE_H
#define HEGRENADE_PROJECTILE_H
#ifdef _WIN32
#pragma once
#endif
#include "basecsgrenade_projectile.h"
class CSmokeGrenadeProjectile : public CBaseCSGrenadeProjectile
{
public:
DECLARE_CLASS( CSmokeGrenadeProjectile, CBaseCSGrenadeProjectile );
DECLARE_DATADESC();
// Overrides.
public:
virtual void Spawn();
virtual void Precache();
virtual void Detonate();
virtual void BounceSound( void );
void Think_Detonate();
void Think_Fade();
void Think_Remove();
// Grenade stuff.
public:
static CSmokeGrenadeProjectile* Create(
const Vector &position,
const QAngle &angles,
const Vector &velocity,
const AngularImpulse &angVelocity,
CBaseCombatCharacter *pOwner );
void SetTimer( float timer );
EHANDLE m_hSmokeEffect;
bool m_bDidSmokeEffect;
};
#endif // HEGRENADE_PROJECTILE_H
+77
View File
@@ -0,0 +1,77 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "basetempentity.h"
//-----------------------------------------------------------------------------
// Purpose: Dispatches blood stream tempentity
//-----------------------------------------------------------------------------
class CTERadioIcon : public CBaseTempEntity
{
public:
DECLARE_CLASS( CTERadioIcon, CBaseTempEntity );
CTERadioIcon( const char *name );
virtual ~CTERadioIcon( void );
void Precache( void );
DECLARE_SERVERCLASS();
public:
CNetworkVar( int, m_iAttachToClient );
};
//-----------------------------------------------------------------------------
// Purpose:
// Input : *name -
//-----------------------------------------------------------------------------
CTERadioIcon::CTERadioIcon( const char *name ) :
CBaseTempEntity( name )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTERadioIcon::~CTERadioIcon( void )
{
}
void CTERadioIcon::Precache( void )
{
CBaseEntity::PrecacheModel("sprites/radio.vmt");
}
IMPLEMENT_SERVERCLASS_ST(CTERadioIcon, DT_TERadioIcon)
SendPropInt( SENDINFO(m_iAttachToClient), 8, SPROP_UNSIGNED ),
END_SEND_TABLE()
// Singleton to fire StickyBolt objects
static CTERadioIcon g_TERadioIcon( "RadioIcon" );
//-----------------------------------------------------------------------------
// Purpose:
// Input : filter -
// delay -
// pPlayer -
//-----------------------------------------------------------------------------
void TE_RadioIcon( IRecipientFilter& filter, float delay, CBaseEntity *pPlayer )
{
g_TERadioIcon.m_iAttachToClient = pPlayer->entindex();
// Send it over the wire
g_TERadioIcon.Create( filter, delay );
}
+161
View File
@@ -0,0 +1,161 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "basetempentity.h"
#include "fx_cs_shared.h"
#define NUM_BULLET_SEED_BITS 8
//-----------------------------------------------------------------------------
// Purpose: Display's a blood sprite
//-----------------------------------------------------------------------------
class CTEFireBullets : public CBaseTempEntity
{
public:
DECLARE_CLASS( CTEFireBullets, CBaseTempEntity );
DECLARE_SERVERCLASS();
CTEFireBullets( const char *name );
virtual ~CTEFireBullets( void );
public:
CNetworkVar( int, m_iPlayer );
CNetworkVector( m_vecOrigin );
CNetworkQAngle( m_vecAngles );
CNetworkVar( int, m_iWeaponID );
CNetworkVar( int, m_iMode );
CNetworkVar( int, m_iSeed );
CNetworkVar( float, m_fInaccuracy );
CNetworkVar( float, m_fSpread );
};
//-----------------------------------------------------------------------------
// Purpose:
// Input : *name -
//-----------------------------------------------------------------------------
CTEFireBullets::CTEFireBullets( const char *name ) :
CBaseTempEntity( name )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTEFireBullets::~CTEFireBullets( void )
{
}
IMPLEMENT_SERVERCLASS_ST_NOBASE(CTEFireBullets, DT_TEFireBullets)
SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD ),
SendPropAngle( SENDINFO_VECTORELEM( m_vecAngles, 0 ), 13, 0 ),
SendPropAngle( SENDINFO_VECTORELEM( m_vecAngles, 1 ), 13, 0 ),
SendPropInt( SENDINFO( m_iWeaponID ), 5, SPROP_UNSIGNED ), // max 31 weapons
SendPropInt( SENDINFO( m_iMode ), 1, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iSeed ), NUM_BULLET_SEED_BITS, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_iPlayer ), 6, SPROP_UNSIGNED ), // max 64 players, see MAX_PLAYERS
SendPropFloat( SENDINFO( m_fInaccuracy ), 10, 0, 0, 1 ),
SendPropFloat( SENDINFO( m_fSpread ), 8, 0, 0, 0.1f ),
END_SEND_TABLE()
// Singleton
static CTEFireBullets g_TEFireBullets( "Shotgun Shot" );
void TE_FireBullets(
int iPlayerIndex,
const Vector &vOrigin,
const QAngle &vAngles,
int iWeaponID,
int iMode,
int iSeed,
float fInaccuracy,
float fSpread
)
{
CPASFilter filter( vOrigin );
filter.UsePredictionRules();
g_TEFireBullets.m_iPlayer = iPlayerIndex-1;
g_TEFireBullets.m_vecOrigin = vOrigin;
g_TEFireBullets.m_vecAngles = vAngles;
g_TEFireBullets.m_iSeed = iSeed;
g_TEFireBullets.m_fInaccuracy = fInaccuracy;
g_TEFireBullets.m_fSpread = fSpread;
g_TEFireBullets.m_iMode = iMode;
g_TEFireBullets.m_iWeaponID = iWeaponID;
Assert( iSeed < (1 << NUM_BULLET_SEED_BITS) );
g_TEFireBullets.Create( filter, 0 );
}
//-----------------------------------------------------------------------------
// Purpose: Displays a bomb plant animation
//-----------------------------------------------------------------------------
class CTEPlantBomb : public CBaseTempEntity
{
public:
DECLARE_CLASS( CTEPlantBomb, CBaseTempEntity );
DECLARE_SERVERCLASS();
CTEPlantBomb( const char *name );
virtual ~CTEPlantBomb( void );
public:
CNetworkVar( int, m_iPlayer );
CNetworkVector( m_vecOrigin );
CNetworkVar( PlantBombOption_t, m_option );
};
//-----------------------------------------------------------------------------
// Purpose:
// Input : *name -
//-----------------------------------------------------------------------------
CTEPlantBomb::CTEPlantBomb( const char *name ) :
CBaseTempEntity( name )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CTEPlantBomb::~CTEPlantBomb( void )
{
}
IMPLEMENT_SERVERCLASS_ST_NOBASE(CTEPlantBomb, DT_TEPlantBomb)
SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_COORD ),
SendPropInt( SENDINFO( m_iPlayer ), 6, SPROP_UNSIGNED ), // max 64 players, see MAX_PLAYERS
SendPropInt( SENDINFO( m_option ), 1, SPROP_UNSIGNED ),
END_SEND_TABLE()
// Singleton
static CTEPlantBomb g_TEPlantBomb( "Bomb Plant" );
void TE_PlantBomb( int iPlayerIndex, const Vector &vOrigin, PlantBombOption_t option )
{
CPASFilter filter( vOrigin );
filter.UsePredictionRules();
g_TEPlantBomb.m_iPlayer = iPlayerIndex-1;
g_TEPlantBomb.m_option = option;
g_TEPlantBomb.Create( filter, 0 );
}
+28
View File
@@ -0,0 +1,28 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef TE_SHOTGUN_SHOT_H
#define TE_SHOTGUN_SHOT_H
#ifdef _WIN32
#pragma once
#endif
void TE_FireBullets(
int iPlayerIndex,
const Vector &vOrigin,
const QAngle &vAngles,
int iWeaponID,
int iMode,
int iSeed,
float fInaccuracy,
float fSpread
);
void TE_PlantBomb( int iPlayerIndex, const Vector &vOrigin, PlantBombOption_t option );
#endif // TE_SHOTGUN_SHOT_H