aboutsummaryrefslogtreecommitdiffstats
path: root/code/botlib/be_ai_char.c
diff options
context:
space:
mode:
Diffstat (limited to 'code/botlib/be_ai_char.c')
-rwxr-xr-xcode/botlib/be_ai_char.c790
1 files changed, 790 insertions, 0 deletions
diff --git a/code/botlib/be_ai_char.c b/code/botlib/be_ai_char.c
new file mode 100755
index 0000000..042adb4
--- /dev/null
+++ b/code/botlib/be_ai_char.c
@@ -0,0 +1,790 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+
+This file is part of Quake III Arena source code.
+
+Quake III Arena source code 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.
+
+Quake III Arena source code 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 Foobar; 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 "../game/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 "../game/botlib.h"
+#include "../game/be_aas.h"
+#include "be_aas_funcs.h"
+#include "be_interface.h"
+#include "../game/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
+