summaryrefslogtreecommitdiff
path: root/game/shared/cstrike/bot/bot_profile.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/shared/cstrike/bot/bot_profile.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/shared/cstrike/bot/bot_profile.cpp')
-rw-r--r--game/shared/cstrike/bot/bot_profile.cpp704
1 files changed, 704 insertions, 0 deletions
diff --git a/game/shared/cstrike/bot/bot_profile.cpp b/game/shared/cstrike/bot/bot_profile.cpp
new file mode 100644
index 0000000..13da6b0
--- /dev/null
+++ b/game/shared/cstrike/bot/bot_profile.cpp
@@ -0,0 +1,704 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+// Author: Michael S. Booth ([email protected]), 2003
+
+#include "cbase.h"
+
+#pragma warning( disable : 4530 ) // STL uses exceptions, but we are not compiling with them - ignore warning
+
+#define DEFINE_DIFFICULTY_NAMES
+#include "bot_profile.h"
+#include "shared_util.h"
+
+#include "bot.h"
+#include "bot_util.h"
+#include "cs_bot.h" // BOTPORT: Remove this CS dependency
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+BotProfileManager *TheBotProfiles = NULL;
+
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Generates a filename-decorated skin name
+ */
+static const char * GetDecoratedSkinName( const char *name, const char *filename )
+{
+ const int BufLen = _MAX_PATH + 64;
+ static char buf[BufLen];
+ Q_snprintf( buf, sizeof( buf ), "%s/%s", filename, name );
+ return buf;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+const char* BotProfile::GetWeaponPreferenceAsString( int i ) const
+{
+ if ( i < 0 || i >= m_weaponPreferenceCount )
+ return NULL;
+
+ return WeaponIDToAlias( m_weaponPreference[ i ] );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this profile has a primary weapon preference
+ */
+bool BotProfile::HasPrimaryPreference( void ) const
+{
+ for( int i=0; i<m_weaponPreferenceCount; ++i )
+ {
+ if (IsPrimaryWeapon( m_weaponPreference[i] ))
+ return true;
+ }
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this profile has a pistol weapon preference
+ */
+bool BotProfile::HasPistolPreference( void ) const
+{
+ for( int i=0; i<m_weaponPreferenceCount; ++i )
+ if (IsSecondaryWeapon( m_weaponPreference[i] ))
+ return true;
+
+ return false;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this profile is valid for the specified team
+ */
+bool BotProfile::IsValidForTeam( int team ) const
+{
+ return ( team == TEAM_UNASSIGNED || m_teams == TEAM_UNASSIGNED || team == m_teams );
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+* Return true if this profile inherits from the specified template
+*/
+bool BotProfile::InheritsFrom( const char *name ) const
+{
+ if ( WildcardMatch( name, GetName() ) )
+ return true;
+
+ for ( int i=0; i<m_templates.Count(); ++i )
+ {
+ const BotProfile *queryTemplate = m_templates[i];
+ if ( queryTemplate->InheritsFrom( name ) )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Constructor
+ */
+BotProfileManager::BotProfileManager( void )
+{
+ m_nextSkin = 0;
+ for (int i=0; i<NumCustomSkins; ++i)
+ {
+ m_skins[i] = NULL;
+ m_skinFilenames[i] = NULL;
+ m_skinModelnames[i] = NULL;
+ }
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Load the bot profile database
+ */
+void BotProfileManager::Init( const char *filename, unsigned int *checksum )
+{
+ FileHandle_t file = filesystem->Open( filename, "r" );
+
+ if (!file)
+ {
+ if ( true ) // UTIL_IsGame( "czero" ) )
+ {
+ CONSOLE_ECHO( "WARNING: Cannot access bot profile database '%s'\n", filename );
+ }
+ return;
+ }
+
+ int dataLength = filesystem->Size( filename );
+ char *dataPointer = new char[ dataLength ];
+ int dataReadLength = filesystem->Read( dataPointer, dataLength, file );
+ filesystem->Close( file );
+ if ( dataReadLength > 0 )
+ {
+ // NULL-terminate based on the length read in, since Read() can transform \r\n to \n and
+ // return fewer bytes than we were expecting.
+ dataPointer[ dataReadLength - 1 ] = 0;
+ }
+
+ const char *dataFile = dataPointer;
+
+ // compute simple checksum
+ if (checksum)
+ {
+ *checksum = 0; // ComputeSimpleChecksum( (const unsigned char *)dataPointer, dataLength );
+ }
+
+ BotProfile defaultProfile;
+
+ //
+ // Parse the BotProfile.db into BotProfile instances
+ //
+ while( true )
+ {
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ break;
+
+ char *token = SharedGetToken();
+
+ bool isDefault = (!stricmp( token, "Default" ));
+ bool isTemplate = (!stricmp( token, "Template" ));
+ bool isCustomSkin = (!stricmp( token, "Skin" ));
+
+ if ( isCustomSkin )
+ {
+ const int BufLen = 64;
+ char skinName[BufLen];
+
+ // get skin name
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected skin name\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+ Q_snprintf( skinName, sizeof( skinName ), "%s", token );
+
+ // get attribute name
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+ if (stricmp( "Model", token ))
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+
+ // eat '='
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+ if (strcmp( "=", token ))
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+
+ // get attribute value
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+
+ const char *decoratedName = GetDecoratedSkinName( skinName, filename );
+ bool skinExists = GetCustomSkinIndex( decoratedName ) > 0;
+ if ( m_nextSkin < NumCustomSkins && !skinExists )
+ {
+ // decorate the name
+ m_skins[ m_nextSkin ] = CloneString( decoratedName );
+
+ // construct the model filename
+ m_skinModelnames[ m_nextSkin ] = CloneString( token );
+ m_skinFilenames[ m_nextSkin ] = new char[ strlen(token)*2 + strlen("models/player//.mdl") + 1 ];
+ Q_snprintf( m_skinFilenames[ m_nextSkin ], sizeof( m_skinFilenames[ m_nextSkin ] ), "models/player/%s/%s.mdl", token, token );
+ ++m_nextSkin;
+ }
+
+ // eat 'End'
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+ if (strcmp( "End", token ))
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+
+ continue; // it's just a custom skin - no need to do inheritance on a bot profile, etc.
+ }
+
+ // encountered a new profile
+ BotProfile *profile;
+
+ if (isDefault)
+ {
+ profile = &defaultProfile;
+ }
+ else
+ {
+ profile = new BotProfile;
+
+ // always inherit from Default
+ *profile = defaultProfile;
+ }
+
+ // do inheritance in order of appearance
+ if (!isTemplate && !isDefault)
+ {
+ const BotProfile *inherit = NULL;
+
+ // template names are separated by "+"
+ while(true)
+ {
+ char *c = strchr( token, '+' );
+ if (c)
+ *c = '\000';
+
+ // find the given template name
+ FOR_EACH_LL( m_templateList, it )
+ {
+ BotProfile *profile = m_templateList[ it ];
+ if (!stricmp( profile->GetName(), token ))
+ {
+ inherit = profile;
+ break;
+ }
+ }
+
+ if (inherit == NULL)
+ {
+ CONSOLE_ECHO( "Error parsing '%s' - invalid template reference '%s'\n", filename, token );
+ delete [] dataPointer;
+ return;
+ }
+
+ // inherit the data
+ profile->Inherit( inherit, &defaultProfile );
+
+ if (c == NULL)
+ break;
+
+ token = c+1;
+ }
+ }
+
+
+ // get name of this profile
+ if (!isDefault)
+ {
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing '%s' - expected name\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ profile->m_name = CloneString( SharedGetToken() );
+
+ /**
+ * HACK HACK
+ * Until we have a generalized means of storing bot preferences, we're going to hardcode the bot's
+ * preference towards silencers based on his name.
+ */
+ if ( profile->m_name[0] % 2 )
+ {
+ profile->m_prefersSilencer = true;
+ }
+ }
+
+ // read attributes for this profile
+ bool isFirstWeaponPref = true;
+ while( true )
+ {
+ // get next token
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+
+ // check for End delimiter
+ if (!stricmp( token, "End" ))
+ break;
+
+ // found attribute name - keep it
+ char attributeName[64];
+ strcpy( attributeName, token );
+
+ // eat '='
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+
+ token = SharedGetToken();
+ if (strcmp( "=", token ))
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+
+ // get attribute value
+ dataFile = SharedParse( dataFile );
+ if (!dataFile)
+ {
+ CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename );
+ delete [] dataPointer;
+ return;
+ }
+ token = SharedGetToken();
+
+ // store value in appropriate attribute
+ if (!stricmp( "Aggression", attributeName ))
+ {
+ profile->m_aggression = (float)atof(token) / 100.0f;
+ }
+ else if (!stricmp( "Skill", attributeName ))
+ {
+ profile->m_skill = (float)atof(token) / 100.0f;
+ }
+ else if (!stricmp( "Skin", attributeName ))
+ {
+ profile->m_skin = atoi(token);
+ if ( profile->m_skin == 0 )
+ {
+ // atoi() failed - try to look up a custom skin by name
+ profile->m_skin = GetCustomSkinIndex( token, filename );
+ }
+ }
+ else if (!stricmp( "Teamwork", attributeName ))
+ {
+ profile->m_teamwork = (float)atof(token) / 100.0f;
+ }
+ else if (!stricmp( "Cost", attributeName ))
+ {
+ profile->m_cost = atoi(token);
+ }
+ else if (!stricmp( "VoicePitch", attributeName ))
+ {
+ profile->m_voicePitch = atoi(token);
+ }
+ else if (!stricmp( "VoiceBank", attributeName ))
+ {
+ profile->m_voiceBank = FindVoiceBankIndex( token );
+ }
+ else if (!stricmp( "WeaponPreference", attributeName ))
+ {
+ // weapon preferences override parent prefs
+ if (isFirstWeaponPref)
+ {
+ isFirstWeaponPref = false;
+ profile->m_weaponPreferenceCount = 0;
+ }
+
+ if (!stricmp( token, "none" ))
+ {
+ profile->m_weaponPreferenceCount = 0;
+ }
+ else
+ {
+ if (profile->m_weaponPreferenceCount < BotProfile::MAX_WEAPON_PREFS)
+ {
+ profile->m_weaponPreference[ profile->m_weaponPreferenceCount++ ] = AliasToWeaponID( token );
+ }
+ }
+ }
+ else if (!stricmp( "ReactionTime", attributeName ))
+ {
+ profile->m_reactionTime = (float)atof(token);
+
+#ifndef GAMEUI_EXPORTS
+ // subtract off latency due to "think" update rate.
+ // In GameUI, we don't really care.
+ //profile->m_reactionTime -= g_BotUpdateInterval;
+#endif
+
+ }
+ else if (!stricmp( "AttackDelay", attributeName ))
+ {
+ profile->m_attackDelay = (float)atof(token);
+ }
+ else if (!stricmp( "Difficulty", attributeName ))
+ {
+ // override inheritance
+ profile->m_difficultyFlags = 0;
+
+ // parse bit flags
+ while(true)
+ {
+ char *c = strchr( token, '+' );
+ if (c)
+ *c = '\000';
+
+ for( int i=0; i<NUM_DIFFICULTY_LEVELS; ++i )
+ if (!stricmp( BotDifficultyName[i], token ))
+ profile->m_difficultyFlags |= (1 << i);
+
+ if (c == NULL)
+ break;
+
+ token = c+1;
+ }
+ }
+ else if (!stricmp( "Team", attributeName ))
+ {
+ if ( !stricmp( token, "T" ) )
+ {
+ profile->m_teams = TEAM_TERRORIST;
+ }
+ else if ( !stricmp( token, "CT" ) )
+ {
+ profile->m_teams = TEAM_CT;
+ }
+ else
+ {
+ profile->m_teams = TEAM_UNASSIGNED;
+ }
+ }
+ else
+ {
+ CONSOLE_ECHO( "Error parsing %s - unknown attribute '%s'\n", filename, attributeName );
+ }
+ }
+
+ if (!isDefault)
+ {
+ if (isTemplate)
+ {
+ // add to template list
+ m_templateList.AddToTail( profile );
+ }
+ else
+ {
+ // add profile to the master list
+ m_profileList.AddToTail( profile );
+ }
+ }
+ }
+
+ delete [] dataPointer;
+}
+
+//--------------------------------------------------------------------------------------------------------------
+BotProfileManager::~BotProfileManager( void )
+{
+ Reset();
+}
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Free all bot profiles
+ */
+void BotProfileManager::Reset( void )
+{
+ m_profileList.PurgeAndDeleteElements();
+ m_templateList.PurgeAndDeleteElements();
+
+ int i;
+
+ for (i=0; i<NumCustomSkins; ++i)
+ {
+ if ( m_skins[i] )
+ {
+ delete[] m_skins[i];
+ m_skins[i] = NULL;
+ }
+ if ( m_skinFilenames[i] )
+ {
+ delete[] m_skinFilenames[i];
+ m_skinFilenames[i] = NULL;
+ }
+ if ( m_skinModelnames[i] )
+ {
+ delete[] m_skinModelnames[i];
+ m_skinModelnames[i] = NULL;
+ }
+ }
+
+ for ( i=0; i<m_voiceBanks.Count(); ++i )
+ {
+ delete[] m_voiceBanks[i];
+ }
+ m_voiceBanks.RemoveAll();
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns custom skin name at a particular index
+ */
+const char * BotProfileManager::GetCustomSkin( int index )
+{
+ if ( index < FirstCustomSkin || index > LastCustomSkin )
+ {
+ return NULL;
+ }
+
+ return m_skins[ index - FirstCustomSkin ];
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns custom skin filename at a particular index
+ */
+const char * BotProfileManager::GetCustomSkinFname( int index )
+{
+ if ( index < FirstCustomSkin || index > LastCustomSkin )
+ {
+ return NULL;
+ }
+
+ return m_skinFilenames[ index - FirstCustomSkin ];
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Returns custom skin modelname at a particular index
+ */
+const char * BotProfileManager::GetCustomSkinModelname( int index )
+{
+ if ( index < FirstCustomSkin || index > LastCustomSkin )
+ {
+ return NULL;
+ }
+
+ return m_skinModelnames[ index - FirstCustomSkin ];
+}
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * Looks up a custom skin index by filename-decorated name (will decorate the name if filename is given)
+ */
+int BotProfileManager::GetCustomSkinIndex( const char *name, const char *filename )
+{
+ const char * skinName = name;
+ if ( filename )
+ {
+ skinName = GetDecoratedSkinName( name, filename );
+ }
+
+ for (int i=0; i<NumCustomSkins; ++i)
+ {
+ if ( m_skins[i] )
+ {
+ if ( !stricmp( skinName, m_skins[i] ) )
+ {
+ return FirstCustomSkin + i;
+ }
+ }
+ }
+ return 0;
+}
+
+
+//--------------------------------------------------------------------------------------------------------
+/**
+ * return index of the (custom) bot phrase db, inserting it if needed
+ */
+int BotProfileManager::FindVoiceBankIndex( const char *filename )
+{
+ int index = 0;
+
+ for ( int i=0; i<m_voiceBanks.Count(); ++i )
+ {
+ if ( !stricmp( filename, m_voiceBanks[i] ) )
+ {
+ return index;
+ }
+ }
+
+ m_voiceBanks.AddToTail( CloneString( filename ) );
+ return index;
+}
+
+
+//--------------------------------------------------------------------------------------------------------------
+/**
+ * Return random unused profile that matches the given difficulty level
+ */
+const BotProfile *BotProfileManager::GetRandomProfile( BotDifficultyType difficulty, int team, CSWeaponType weaponType ) const
+{
+ // count up valid profiles
+ CUtlVector< const BotProfile * > profiles;
+ FOR_EACH_LL( m_profileList, it )
+ {
+ const BotProfile *profile = m_profileList[ it ];
+
+ // Match difficulty
+ if ( !profile->IsDifficulty( difficulty ) )
+ continue;
+
+ // Prevent duplicate names
+ if ( UTIL_IsNameTaken( profile->GetName() ) )
+ continue;
+
+ // Match team choice
+ if ( !profile->IsValidForTeam( team ) )
+ continue;
+
+ // Match desired weapon
+ if ( weaponType != WEAPONTYPE_UNKNOWN )
+ {
+ if ( !profile->GetWeaponPreferenceCount() )
+ continue;
+
+ if ( weaponType != WeaponClassFromWeaponID( (CSWeaponID)profile->GetWeaponPreference( 0 ) ) )
+ continue;
+ }
+
+ profiles.AddToTail( profile );
+ }
+
+ if ( !profiles.Count() )
+ return NULL;
+
+ // select one at random
+ int which = RandomInt( 0, profiles.Count()-1 );
+ return profiles[which];
+}
+