|
|
//========= 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]; }
|