/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2006 Tim Angus This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Tremulous; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ai_char.c * * desc: bot characters * * $Archive: /MissionPack/code/botlib/be_ai_char.c $ * *****************************************************************************/ #include "../qcommon/q_shared.h" #include "l_log.h" #include "l_memory.h" #include "l_utils.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_libvar.h" #include "aasfile.h" #include "botlib.h" #include "be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_ai_char.h" #define MAX_CHARACTERISTICS 80 #define CT_INTEGER 1 #define CT_FLOAT 2 #define CT_STRING 3 #define DEFAULT_CHARACTER "bots/default_c.c" //characteristic value union cvalue { int integer; float _float; char *string; }; //a characteristic typedef struct bot_characteristic_s { char type; //characteristic type union cvalue value; //characteristic value } bot_characteristic_t; //a bot character typedef struct bot_character_s { char filename[MAX_QPATH]; float skill; bot_characteristic_t c[1]; //variable sized } bot_character_t; bot_character_t *botcharacters[MAX_CLIENTS + 1]; //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== bot_character_t *BotCharacterFromHandle(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); return NULL; } //end if if (!botcharacters[handle]) { botimport.Print(PRT_FATAL, "invalid character %d\n", handle); return NULL; } //end if return botcharacters[handle]; } //end of the function BotCharacterFromHandle //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDumpCharacter(bot_character_t *ch) { int i; Log_Write("%s", ch->filename); Log_Write("skill %d\n", ch->skill); Log_Write("{\n"); for (i = 0; i < MAX_CHARACTERISTICS; i++) { switch(ch->c[i].type) { case CT_INTEGER: Log_Write(" %4d %d\n", i, ch->c[i].value.integer); break; case CT_FLOAT: Log_Write(" %4d %f\n", i, ch->c[i].value._float); break; case CT_STRING: Log_Write(" %4d %s\n", i, ch->c[i].value.string); break; } //end case } //end for Log_Write("}\n"); } //end of the function BotDumpCharacter //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeCharacterStrings(bot_character_t *ch) { int i; for (i = 0; i < MAX_CHARACTERISTICS; i++) { if (ch->c[i].type == CT_STRING) { FreeMemory(ch->c[i].value.string); } //end if } //end for } //end of the function BotFreeCharacterStrings //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeCharacter2(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); return; } //end if if (!botcharacters[handle]) { botimport.Print(PRT_FATAL, "invalid character %d\n", handle); return; } //end if BotFreeCharacterStrings(botcharacters[handle]); FreeMemory(botcharacters[handle]); botcharacters[handle] = NULL; } //end of the function BotFreeCharacter2 //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeCharacter(int handle) { if (!LibVarGetValue("bot_reloadcharacters")) return; BotFreeCharacter2(handle); } //end of the function BotFreeCharacter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDefaultCharacteristics(bot_character_t *ch, bot_character_t *defaultch) { int i; for (i = 0; i < MAX_CHARACTERISTICS; i++) { if (ch->c[i].type) continue; // if (defaultch->c[i].type == CT_FLOAT) { ch->c[i].type = CT_FLOAT; ch->c[i].value._float = defaultch->c[i].value._float; } //end if else if (defaultch->c[i].type == CT_INTEGER) { ch->c[i].type = CT_INTEGER; ch->c[i].value.integer = defaultch->c[i].value.integer; } //end else if else if (defaultch->c[i].type == CT_STRING) { ch->c[i].type = CT_STRING; ch->c[i].value.string = (char *) GetMemory(strlen(defaultch->c[i].value.string)+1); strcpy(ch->c[i].value.string, defaultch->c[i].value.string); } //end else if } //end for } //end of the function BotDefaultCharacteristics //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_character_t *BotLoadCharacterFromFile(char *charfile, int skill) { int indent, index, foundcharacter; bot_character_t *ch; source_t *source; token_t token; foundcharacter = qfalse; //a bot character is parsed in two phases PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(charfile); if (!source) { botimport.Print(PRT_ERROR, "counldn't load %s\n", charfile); return NULL; } //end if ch = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); strcpy(ch->filename, charfile); while(PC_ReadToken(source, &token)) { if (!strcmp(token.string, "skill")) { if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) { FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if if (!PC_ExpectTokenString(source, "{")) { FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if //if it's the correct skill if (skill < 0 || token.intvalue == skill) { foundcharacter = qtrue; ch->skill = token.intvalue; while(PC_ExpectAnyToken(source, &token)) { if (!strcmp(token.string, "}")) break; if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) { SourceError(source, "expected integer index, found %s\n", token.string); FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if index = token.intvalue; if (index < 0 || index > MAX_CHARACTERISTICS) { SourceError(source, "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS); FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if if (ch->c[index].type) { SourceError(source, "characteristic %d already initialized\n", index); FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if if (!PC_ExpectAnyToken(source, &token)) { FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if if (token.type == TT_NUMBER) { if (token.subtype & TT_FLOAT) { ch->c[index].value._float = token.floatvalue; ch->c[index].type = CT_FLOAT; } //end if else { ch->c[index].value.integer = token.intvalue; ch->c[index].type = CT_INTEGER; } //end else } //end if else if (token.type == TT_STRING) { StripDoubleQuotes(token.string); ch->c[index].value.string = GetMemory(strlen(token.string)+1); strcpy(ch->c[index].value.string, token.string); ch->c[index].type = CT_STRING; } //end else if else { SourceError(source, "expected integer, float or string, found %s\n", token.string); FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end else } //end if break; } //end if else { indent = 1; while(indent) { if (!PC_ExpectAnyToken(source, &token)) { FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if if (!strcmp(token.string, "{")) indent++; else if (!strcmp(token.string, "}")) indent--; } //end while } //end else } //end if else { SourceError(source, "unknown definition %s\n", token.string); FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end else } //end while FreeSource(source); // if (!foundcharacter) { BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if return ch; } //end of the function BotLoadCharacterFromFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotFindCachedCharacter(char *charfile, float skill) { int handle; for (handle = 1; handle <= MAX_CLIENTS; handle++) { if ( !botcharacters[handle] ) continue; if ( strcmp( botcharacters[handle]->filename, charfile ) == 0 && (skill < 0 || fabs(botcharacters[handle]->skill - skill) < 0.01) ) { return handle; } //end if } //end for return 0; } //end of the function BotFindCachedCharacter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadCachedCharacter(char *charfile, float skill, int reload) { int handle, cachedhandle, intskill; bot_character_t *ch = NULL; #ifdef DEBUG int starttime; starttime = Sys_MilliSeconds(); #endif //DEBUG //find a free spot for a character for (handle = 1; handle <= MAX_CLIENTS; handle++) { if (!botcharacters[handle]) break; } //end for if (handle > MAX_CLIENTS) return 0; //try to load a cached character with the given skill if (!reload) { cachedhandle = BotFindCachedCharacter(charfile, skill); if (cachedhandle) { botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); return cachedhandle; } //end if } //end else // intskill = (int) (skill + 0.5); //try to load the character with the given skill ch = BotLoadCharacterFromFile(charfile, intskill); if (ch) { botcharacters[handle] = ch; // botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", intskill, charfile); #ifdef DEBUG if (bot_developer) { botimport.Print(PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", intskill, Sys_MilliSeconds() - starttime, charfile); } //end if #endif //DEBUG return handle; } //end if // botimport.Print(PRT_WARNING, "couldn't find skill %d in %s\n", intskill, charfile); // if (!reload) { //try to load a cached default character with the given skill cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, skill); if (cachedhandle) { botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", intskill, charfile); return cachedhandle; } //end if } //end if //try to load the default character with the given skill ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, intskill); if (ch) { botcharacters[handle] = ch; botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", intskill, charfile); return handle; } //end if // if (!reload) { //try to load a cached character with any skill cachedhandle = BotFindCachedCharacter(charfile, -1); if (cachedhandle) { botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); return cachedhandle; } //end if } //end if //try to load a character with any skill ch = BotLoadCharacterFromFile(charfile, -1); if (ch) { botcharacters[handle] = ch; botimport.Print(PRT_MESSAGE, "loaded skill %f from %s\n", ch->skill, charfile); return handle; } //end if // if (!reload) { //try to load a cached character with any skill cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, -1); if (cachedhandle) { botimport.Print(PRT_MESSAGE, "loaded cached default skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); return cachedhandle; } //end if } //end if //try to load a character with any skill ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, -1); if (ch) { botcharacters[handle] = ch; botimport.Print(PRT_MESSAGE, "loaded default skill %f from %s\n", ch->skill, charfile); return handle; } //end if // botimport.Print(PRT_WARNING, "couldn't load any skill from %s\n", charfile); //couldn't load any character return 0; } //end of the function BotLoadCachedCharacter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadCharacterSkill(char *charfile, float skill) { int ch, defaultch; defaultch = BotLoadCachedCharacter(DEFAULT_CHARACTER, skill, qfalse); ch = BotLoadCachedCharacter(charfile, skill, LibVarGetValue("bot_reloadcharacters")); if (defaultch && ch) { BotDefaultCharacteristics(botcharacters[ch], botcharacters[defaultch]); } //end if return ch; } //end of the function BotLoadCharacterSkill //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotInterpolateCharacters(int handle1, int handle2, float desiredskill) { bot_character_t *ch1, *ch2, *out; int i, handle; float scale; ch1 = BotCharacterFromHandle(handle1); ch2 = BotCharacterFromHandle(handle2); if (!ch1 || !ch2) return 0; //find a free spot for a character for (handle = 1; handle <= MAX_CLIENTS; handle++) { if (!botcharacters[handle]) break; } //end for if (handle > MAX_CLIENTS) return 0; out = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); out->skill = desiredskill; strcpy(out->filename, ch1->filename); botcharacters[handle] = out; scale = (float) (desiredskill - ch1->skill) / (ch2->skill - ch1->skill); for (i = 0; i < MAX_CHARACTERISTICS; i++) { // if (ch1->c[i].type == CT_FLOAT && ch2->c[i].type == CT_FLOAT) { out->c[i].type = CT_FLOAT; out->c[i].value._float = ch1->c[i].value._float + (ch2->c[i].value._float - ch1->c[i].value._float) * scale; } //end if else if (ch1->c[i].type == CT_INTEGER) { out->c[i].type = CT_INTEGER; out->c[i].value.integer = ch1->c[i].value.integer; } //end else if else if (ch1->c[i].type == CT_STRING) { out->c[i].type = CT_STRING; out->c[i].value.string = (char *) GetMemory(strlen(ch1->c[i].value.string)+1); strcpy(out->c[i].value.string, ch1->c[i].value.string); } //end else if } //end for return handle; } //end of the function BotInterpolateCharacters //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadCharacter(char *charfile, float skill) { int firstskill, secondskill, handle; //make sure the skill is in the valid range if (skill < 1.0) skill = 1.0; else if (skill > 5.0) skill = 5.0; //skill 1, 4 and 5 should be available in the character files if (skill == 1.0 || skill == 4.0 || skill == 5.0) { return BotLoadCharacterSkill(charfile, skill); } //end if //check if there's a cached skill handle = BotFindCachedCharacter(charfile, skill); if (handle) { botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); return handle; } //end if if (skill < 4.0) { //load skill 1 and 4 firstskill = BotLoadCharacterSkill(charfile, 1); if (!firstskill) return 0; secondskill = BotLoadCharacterSkill(charfile, 4); if (!secondskill) return firstskill; } //end if else { //load skill 4 and 5 firstskill = BotLoadCharacterSkill(charfile, 4); if (!firstskill) return 0; secondskill = BotLoadCharacterSkill(charfile, 5); if (!secondskill) return firstskill; } //end else //interpolate between the two skills handle = BotInterpolateCharacters(firstskill, secondskill, skill); if (!handle) return 0; //write the character to the log file BotDumpCharacter(botcharacters[handle]); // return handle; } //end of the function BotLoadCharacter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int CheckCharacteristicIndex(int character, int index) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return qfalse; if (index < 0 || index >= MAX_CHARACTERISTICS) { botimport.Print(PRT_ERROR, "characteristic %d does not exist\n", index); return qfalse; } //end if if (!ch->c[index].type) { botimport.Print(PRT_ERROR, "characteristic %d is not initialized\n", index); return qfalse; } //end if return qtrue; } //end of the function CheckCharacteristicIndex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float Characteristic_Float(int character, int index) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; //check if the index is in range if (!CheckCharacteristicIndex(character, index)) return 0; //an integer will be converted to a float if (ch->c[index].type == CT_INTEGER) { return (float) ch->c[index].value.integer; } //end if //floats are just returned else if (ch->c[index].type == CT_FLOAT) { return ch->c[index].value._float; } //end else if //cannot convert a string pointer to a float else { botimport.Print(PRT_ERROR, "characteristic %d is not a float\n", index); return 0; } //end else if // return 0; } //end of the function Characteristic_Float //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float Characteristic_BFloat(int character, int index, float min, float max) { float value; bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; if (min > max) { botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %f and %f\n", index, min, max); return 0; } //end if value = Characteristic_Float(character, index); if (value < min) return min; if (value > max) return max; return value; } //end of the function Characteristic_BFloat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Characteristic_Integer(int character, int index) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; //check if the index is in range if (!CheckCharacteristicIndex(character, index)) return 0; //an integer will just be returned if (ch->c[index].type == CT_INTEGER) { return ch->c[index].value.integer; } //end if //floats are casted to integers else if (ch->c[index].type == CT_FLOAT) { return (int) ch->c[index].value._float; } //end else if else { botimport.Print(PRT_ERROR, "characteristic %d is not a integer\n", index); return 0; } //end else if // return 0; } //end of the function Characteristic_Integer //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Characteristic_BInteger(int character, int index, int min, int max) { int value; bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; if (min > max) { botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max); return 0; } //end if value = Characteristic_Integer(character, index); if (value < min) return min; if (value > max) return max; return value; } //end of the function Characteristic_BInteger //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Characteristic_String(int character, int index, char *buf, int size) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return; //check if the index is in range if (!CheckCharacteristicIndex(character, index)) return; //an integer will be converted to a float if (ch->c[index].type == CT_STRING) { strncpy(buf, ch->c[index].value.string, size-1); buf[size-1] = '\0'; return; } //end if else { botimport.Print(PRT_ERROR, "characteristic %d is not a string\n", index); return; } //end else if return; } //end of the function Characteristic_String //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotShutdownCharacters(void) { int handle; for (handle = 1; handle <= MAX_CLIENTS; handle++) { if (botcharacters[handle]) { BotFreeCharacter2(handle); } //end if } //end for } //end of the function BotShutdownCharacters