//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // // $NoKeywords: $ //============================================================================= // Author: Michael S. Booth (mike@turtlerockstudios.com), 2003 #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 "simple_checksum.h" BotProfileManager *TheBotProfiles = NULL; //-------------------------------------------------------------------------------------------------------------- // A little explanation is in order here. This file is in game_shared. It is supposed to be able to be compiled // into multiple dlls. However, all the basic utility functions (loading files, parsing strings, etc) are // accessed different ways in different dlls. To combat this, we need to redirect some calls depending on // the dll we're in. #ifdef GAMEUI_EXPORTS #include "../GameUI/EngineInterface.h" #include "../cstrike/dlls/weapontype.h" #include "../cstrike/dlls/weapontype.cpp" /// @TODO: remove this CStrike peculiarity from a game_shared file!!! void Career_Printf(const char *fmt, ...); #define CONSOLE_ECHO Career_Printf #define LOAD_FILE_FOR_ME(name, len) (engine->COM_LoadFile( (name), 5, (len) )) #define FREE_FILE (engine->COM_FreeFile) #define UTIL_IsGame( x ) 0 #else #include "extdll.h" #include "util.h" #include "cbase.h" #include "weapons.h" #include "soundent.h" #include "gamerules.h" #include "player.h" #include "client.h" #include "cmd.h" #include "pm_shared.h" #include "bot.h" #include "bot_util.h" #endif //-------------------------------------------------------------------------------------------------------- /** * Generates a filename-decorated skin name */ static const char * GetDecoratedSkinName( const char *name, const char *filename ) { #ifdef _WIN32 const int BufLen = _MAX_PATH + 64; #else const int BufLen = MAX_PATH + 64; #endif static char buf[BufLen]; snprintf( buf, BufLen, "%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( filename ), &dataLength ); const char *dataFile = dataPointer; if (dataFile == NULL) { if ( UTIL_IsGame( "czero" ) ) { CONSOLE_ECHO( "WARNING: Cannot access bot profile database '%s'\n", filename ); } return; } // compute simple checksum if (checksum) { *checksum = ComputeSimpleChecksum( (const unsigned char *)dataPointer, dataLength ); } // keep list of templates used for inheritance BotProfileList templateList; 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 ); FREE_FILE( dataPointer ); return; } token = SharedGetToken(); snprintf( skinName, BufLen, "%s", token ); // get attribute name dataFile = SharedParse( dataFile ); if (!dataFile) { CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename ); FREE_FILE( dataPointer ); return; } token = SharedGetToken(); if (stricmp( "Model", token )) { CONSOLE_ECHO( "Error parsing %s - expected 'Model'\n", filename ); FREE_FILE( dataPointer ); return; } // eat '=' dataFile = SharedParse( dataFile ); if (!dataFile) { CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); FREE_FILE( dataPointer ); return; } token = SharedGetToken(); if (strcmp( "=", token )) { CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); FREE_FILE( dataPointer ); return; } // get attribute value dataFile = SharedParse( dataFile ); if (!dataFile) { CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename ); FREE_FILE( 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 ]; sprintf( 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 ); FREE_FILE( dataPointer ); return; } token = SharedGetToken(); if (strcmp( "End", token )) { CONSOLE_ECHO( "Error parsing %s - expected 'End'\n", filename ); FREE_FILE( 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( BotProfileList::iterator iter = templateList.begin(); iter != templateList.end(); ++iter ) { if (!stricmp( (*iter)->GetName(), token )) { inherit = *iter; break; } } if (inherit == NULL) { CONSOLE_ECHO( "Error parsing '%s' - invalid template reference '%s'\n", filename, token ); FREE_FILE( 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 ); FREE_FILE( 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 ); FREE_FILE( dataPointer ); return; } token = SharedGetToken(); // check for End delimiter if (!stricmp( token, "End" )) break; // found attribute name - keep it char attributeName[64]; strncpy( attributeName, token, sizeof( attributeName ) - 1 ); attributeName[ sizeof( attributeName ) - 1 ] = '\0'; // eat '=' dataFile = SharedParse( dataFile ); if (!dataFile) { CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); FREE_FILE( dataPointer ); return; } token = SharedGetToken(); if (strcmp( "=", token )) { CONSOLE_ECHO( "Error parsing %s - expected '='\n", filename ); FREE_FILE( dataPointer ); return; } // get attribute value dataFile = SharedParse( dataFile ); if (!dataFile) { CONSOLE_ECHO( "Error parsing %s - expected attribute value\n", filename ); FREE_FILE( dataPointer ); return; } token = SharedGetToken(); // store value in appropriate attribute if (!stricmp( "Aggression", attributeName )) { profile->m_aggression = atof(token) / 100.0f; } else if (!stricmp( "Skill", attributeName )) { profile->m_skill = 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 = 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 = atof(token); #ifndef GAMEUI_EXPORTS // subtract off latency due to "think" update rate. // In GameUI, we don't really care. profile->m_reactionTime -= g_flBotFullThinkInterval; #endif } else if (!stricmp( "AttackDelay", attributeName )) { profile->m_attackDelay = 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; im_difficultyFlags |= (1 << i); if (c == NULL) break; token = c+1; } } else if (!stricmp( "Team", attributeName )) { if ( !stricmp( token, "T" ) ) { profile->m_teams = BOT_TEAM_T; } else if ( !stricmp( token, "CT" ) ) { profile->m_teams = BOT_TEAM_CT; } else { profile->m_teams = BOT_TEAM_ANY; } } else { CONSOLE_ECHO( "Error parsing %s - unknown attribute '%s'\n", filename, attributeName ); } } if (!isDefault) { if (isTemplate) { // add to template list templateList.push_back( profile ); } else { // add profile to the master list m_profileList.push_back( profile ); } } } FREE_FILE( dataPointer ); // free the templates for( BotProfileList::iterator iter = templateList.begin(); iter != templateList.end(); ++iter ) delete *iter; } //-------------------------------------------------------------------------------------------------------------- BotProfileManager::~BotProfileManager( void ) { Reset(); VoiceBankList::iterator it; for ( it = m_voiceBanks.begin(); it != m_voiceBanks.end(); ++it ) { delete[] *it; } m_voiceBanks.clear(); } //-------------------------------------------------------------------------------------------------------------- /** * Free all bot profiles */ void BotProfileManager::Reset( void ) { for( BotProfileList::iterator iter = m_profileList.begin(); iter != m_profileList.end(); ++iter ) delete *iter; m_profileList.clear(); for (int i=0; i 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; iIsDifficulty( difficulty ) && !UTIL_IsNameTaken( profile->GetName() ) && profile->IsValidForTeam( team )) ++validCount; } if (validCount == 0) return NULL; // select one at random int which = RANDOM_LONG( 0, validCount-1 ); for( iter = m_profileList.begin(); iter != m_profileList.end(); ++iter ) { const BotProfile *profile = *iter; if (profile->IsDifficulty( difficulty ) && !UTIL_IsNameTaken( profile->GetName() ) && profile->IsValidForTeam( team )) if (which-- == 0) return profile; } return NULL; #endif; }