Files
Justin Marshall 31a3805769 Bot fixes.
2026-05-20 09:10:15 -07:00

365 lines
7.6 KiB
C++

// Bot_char.cpp
//
#include "precompiled.h"
#include "../Game_local.h"
idBotCharacterStatsManager botCharacterStatsManager;
/*
====================
idBotCharacterStatsManager::idBotCharacterStatsManager
====================
*/
idBotCharacterStatsManager::idBotCharacterStatsManager()
{
default_char_profile = nullptr;
}
/*
====================
idBotCharacterStatsManager::Init
====================
*/
void idBotCharacterStatsManager::Init( void )
{
default_char_profile = BotLoadCharacterFromFile( "bots/default_c.c", 1 );
if( default_char_profile == NULL )
{
common->FatalError( "Failed to load default characteristic def file\n" );
}
}
/*
============================
idBotCharacterStatsManager::AllocBotCharacter
============================
*/
bot_character_t* idBotCharacterStatsManager::AllocBotCharacter( void )
{
bot_character_t* ch = NULL;
for( int i = 0; i < MAX_CHAR_STATS; i++ )
{
if( charStatsList[i].inUse == false )
{
charStatsList[i].inUse = true;
ch = &charStatsList[i];
break;
}
}
if( ch == nullptr )
{
gameLocal.Error( "idBotCharacterStatsManager::AllocBotCharacter: Too many bot characters!" );
return nullptr;
}
if( default_char_profile == NULL )
{
memset( &ch->c[0], 0, MAX_CHARACTERISTICS * sizeof( bot_characteristic_t ) );
ch->filename = "";
ch->skill = 0;
}
else
{
//memcpy( ch, default_char_profile, sizeof( bot_character_t ) + MAX_CHARACTERISTICS * sizeof( bot_characteristic_t ) );
*ch = *default_char_profile;
}
ch->inUse = true;
return ch;
}
/*
============================
idBotCharacterStatsManager::FreeCharacterFile
============================
*/
void idBotCharacterStatsManager::FreeCharacterFile( bot_character_t* ch )
{
ch->inUse = false;
}
/*
============================
idBotCharacterStatsManager::BotLoadCharacterFromFile
============================
*/
bot_character_t* idBotCharacterStatsManager::BotLoadCharacterFromFile( const char* charfile, int skill )
{
int indent, index;
bool foundcharacter;
bot_character_t* ch;
idParser parser;
idToken token;
foundcharacter = false;
// Check to see if we already loaded this bot file.
for( int i = 0; i < MAX_CHAR_STATS; i++ )
{
if(charStatsList[i].inUse && charStatsList[i].filename == charfile )
{
return &charStatsList[i];
}
}
rvmScopedLexerBaseFolder scopedBaseFolder( BOTFILESBASEFOLDER );
//a bot character is parsed in two phases
if( !parser.LoadFile( charfile ) )
{
common->Warning( "BotLoadCharacterFromFile: counldn't load %s\n", charfile );
return NULL;
}
ch = AllocBotCharacter();
ch->filename = charfile;
while( parser.ReadToken( &token ) )
{
if( token == "skill" )
{
if( !parser.ExpectTokenType( TT_NUMBER, 0, &token ) )
{
FreeCharacterFile( ch );
parser.Warning( "Expected token type number\n" );
return NULL;
}
if( !parser.ExpectTokenString( "{" ) )
{
FreeCharacterFile( ch );
parser.Warning( "Expected token {\n" );
return NULL;
}
//if it's the correct skill
if( skill < 0 || token.GetIntValue() == skill )
{
foundcharacter = true;
ch->skill = token.GetIntValue();
while( parser.ReadToken( &token ) )
{
if( token == "}" )
{
break;
}
if( token.type != TT_NUMBER || !( token.subtype & TT_INTEGER ) )
{
FreeCharacterFile( ch );
parser.Error( "expected integer index, found %s\n", token.c_str() );
return NULL;
}
index = token.GetIntValue();
if( index < 0 || index >= MAX_CHARACTERISTICS )
{
FreeCharacterFile( ch );
parser.Error( "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS );
return NULL;
}
// jmarshall - not sure what we loose by removing this check, basically duplicate definition check?
//if (ch->c[index].type)
//{
// G_Error("characteristic %d already initialized\n", index);
// trap_PC_FreeSource(source);
// BotFreeCharacterStrings(ch);
// //FreeMemory(ch);
// return NULL;
//}
// jmarshall end
if( !parser.ReadToken( &token ) )
{
FreeCharacterFile( ch );
parser.Error( "Unexpected EOF during parse characesistic" );
return NULL;
}
if( token.type == TT_NUMBER )
{
if( token.subtype & TT_FLOAT )
{
ch->c[index].value._float = token.GetFloatValue();
ch->c[index].type = CT_FLOAT;
}
else
{
ch->c[index].value.integer = token.GetIntValue();
ch->c[index].type = CT_INTEGER;
}
}
else if( token.type == TT_STRING )
{
token.StripDoubleQuotes();
ch->c[index].value.string = token;
ch->c[index].type = CT_STRING;
}
else
{
FreeCharacterFile( ch );
gameLocal.Error( "expected integer, float or string, found %s\n", token.c_str() );
return NULL;
}
}
break;
}
else
{
indent = 1;
while( indent )
{
if( !parser.ReadToken( &token ) )
{
FreeCharacterFile( ch );
return NULL;
}
if( token == "{" )
{
indent++;
}
else if( token == "}" )
{
indent--;
}
}
}
}
else
{
FreeCharacterFile( ch );
gameLocal.Error( "unknown definition %s\n", token.c_str() );
return NULL;
}
}
if( !foundcharacter )
{
FreeCharacterFile( ch );
return NULL;
}
return ch;
}
/*
============================
idBotCharacterStatsManager::CheckCharacteristicIndex
============================
*/
int idBotCharacterStatsManager::CheckCharacteristicIndex( bot_character_t* ch, int index )
{
if( ch == NULL )
{
gameLocal.Error( "NULL bot character" );
return false;
}
if( index < 0 || index >= MAX_CHARACTERISTICS )
{
gameLocal.Error( "characteristic %d does not exist\n", index );
return false;
}
if( !ch->c[index].type )
{
gameLocal.Error( "characteristic %d is not initialized\n", index );
return false;
}
return true;
}
/*
============================
idBotCharacterStatsManager::Characteristic_Float
============================
*/
float idBotCharacterStatsManager::Characteristic_Float( bot_character_t* ch, int index )
{
if( !ch )
{
return 0;
}
//check if the index is in range
if( !CheckCharacteristicIndex( ch, index ) )
{
return 0;
}
if( ch->c[index].type == CT_INTEGER )
{
//an integer will be converted to a float
return ( float )ch->c[index].value.integer;
}
else if( ch->c[index].type == CT_FLOAT )
{
//floats are just returned
return ch->c[index].value._float;
}
//cannot convert a string pointer to a float
gameLocal.Error( "characteristic %d is not a float\n", index );
return 0;
}
/*
============================
Characteristic_String
============================
*/
void idBotCharacterStatsManager::Characteristic_String( bot_character_t* ch, int index, char* buf, int size )
{
if( buf == NULL || size <= 0 )
{
return;
}
buf[0] = '\0';
//check if the index is in range
if( !CheckCharacteristicIndex( ch, index ) )
{
return;
}
//an integer will be converted to a float
if( ch->c[index].type == CT_STRING )
{
idStr::Copynz( buf, ch->c[index].value.string, size );
return;
}
gameLocal.Error( "characteristic %d is not a string\n", index );
}
/*
============================
idBotCharacterStatsManager::Characteristic_BFloat
============================
*/
float idBotCharacterStatsManager::Characteristic_BFloat( bot_character_t* ch, int index, float min, float max )
{
float value;
if( min > max )
{
gameLocal.Error( "cannot bound characteristic %d between %f and %f\n", index, min, max );
return 0;
}
value = Characteristic_Float( ch, index );
if( value < min )
{
return min;
}
if( value > max )
{
return max;
}
return value;
}