mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-11 19:29:08 +00:00
1
This commit is contained in:
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,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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
//
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
};
|
||||
@@ -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[];
|
||||
@@ -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 );
|
||||
|
||||
@@ -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
|
||||
@@ -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" );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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_
|
||||
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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() );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user