Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

be_ai_char.c (23168B)


      1 /*
      2 ===========================================================================
      3 Copyright (C) 1999-2005 Id Software, Inc.
      4 
      5 This file is part of Quake III Arena source code.
      6 
      7 Quake III Arena source code is free software; you can redistribute it
      8 and/or modify it under the terms of the GNU General Public License as
      9 published by the Free Software Foundation; either version 2 of the License,
     10 or (at your option) any later version.
     11 
     12 Quake III Arena source code is distributed in the hope that it will be
     13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
     14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15 GNU General Public License for more details.
     16 
     17 You should have received a copy of the GNU General Public License
     18 along with Foobar; if not, write to the Free Software
     19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     20 ===========================================================================
     21 */
     22 
     23 /*****************************************************************************
     24  * name:		be_ai_char.c
     25  *
     26  * desc:		bot characters
     27  *
     28  * $Archive: /MissionPack/code/botlib/be_ai_char.c $
     29  *
     30  *****************************************************************************/
     31 
     32 #include "../game/q_shared.h"
     33 #include "l_log.h"
     34 #include "l_memory.h"
     35 #include "l_utils.h"
     36 #include "l_script.h"
     37 #include "l_precomp.h"
     38 #include "l_struct.h"
     39 #include "l_libvar.h"
     40 #include "aasfile.h"
     41 #include "../game/botlib.h"
     42 #include "../game/be_aas.h"
     43 #include "be_aas_funcs.h"
     44 #include "be_interface.h"
     45 #include "../game/be_ai_char.h"
     46 
     47 #define MAX_CHARACTERISTICS		80
     48 
     49 #define CT_INTEGER				1
     50 #define CT_FLOAT				2
     51 #define CT_STRING				3
     52 
     53 #define DEFAULT_CHARACTER		"bots/default_c.c"
     54 
     55 //characteristic value
     56 union cvalue
     57 {
     58 	int integer;
     59 	float _float;
     60 	char *string;
     61 };
     62 //a characteristic
     63 typedef struct bot_characteristic_s
     64 {
     65 	char type;						//characteristic type
     66 	union cvalue value;				//characteristic value
     67 } bot_characteristic_t;
     68 
     69 //a bot character
     70 typedef struct bot_character_s
     71 {
     72 	char filename[MAX_QPATH];
     73 	float skill;
     74 	bot_characteristic_t c[1];		//variable sized
     75 } bot_character_t;
     76 
     77 bot_character_t *botcharacters[MAX_CLIENTS + 1];
     78 
     79 //========================================================================
     80 //
     81 // Parameter:			-
     82 // Returns:				-
     83 // Changes Globals:		-
     84 //========================================================================
     85 bot_character_t *BotCharacterFromHandle(int handle)
     86 {
     87 	if (handle <= 0 || handle > MAX_CLIENTS)
     88 	{
     89 		botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle);
     90 		return NULL;
     91 	} //end if
     92 	if (!botcharacters[handle])
     93 	{
     94 		botimport.Print(PRT_FATAL, "invalid character %d\n", handle);
     95 		return NULL;
     96 	} //end if
     97 	return botcharacters[handle];
     98 } //end of the function BotCharacterFromHandle
     99 //===========================================================================
    100 //
    101 // Parameter:			-
    102 // Returns:				-
    103 // Changes Globals:		-
    104 //===========================================================================
    105 void BotDumpCharacter(bot_character_t *ch)
    106 {
    107 	int i;
    108 
    109 	Log_Write("%s", ch->filename);
    110 	Log_Write("skill %d\n", ch->skill);
    111 	Log_Write("{\n");
    112 	for (i = 0; i < MAX_CHARACTERISTICS; i++)
    113 	{
    114 		switch(ch->c[i].type)
    115 		{
    116 			case CT_INTEGER: Log_Write(" %4d %d\n", i, ch->c[i].value.integer); break;
    117 			case CT_FLOAT: Log_Write(" %4d %f\n", i, ch->c[i].value._float); break;
    118 			case CT_STRING: Log_Write(" %4d %s\n", i, ch->c[i].value.string); break;
    119 		} //end case
    120 	} //end for
    121 	Log_Write("}\n");
    122 } //end of the function BotDumpCharacter
    123 //========================================================================
    124 //
    125 // Parameter:			-
    126 // Returns:				-
    127 // Changes Globals:		-
    128 //========================================================================
    129 void BotFreeCharacterStrings(bot_character_t *ch)
    130 {
    131 	int i;
    132 
    133 	for (i = 0; i < MAX_CHARACTERISTICS; i++)
    134 	{
    135 		if (ch->c[i].type == CT_STRING)
    136 		{
    137 			FreeMemory(ch->c[i].value.string);
    138 		} //end if
    139 	} //end for
    140 } //end of the function BotFreeCharacterStrings
    141 //========================================================================
    142 //
    143 // Parameter:			-
    144 // Returns:				-
    145 // Changes Globals:		-
    146 //========================================================================
    147 void BotFreeCharacter2(int handle)
    148 {
    149 	if (handle <= 0 || handle > MAX_CLIENTS)
    150 	{
    151 		botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle);
    152 		return;
    153 	} //end if
    154 	if (!botcharacters[handle])
    155 	{
    156 		botimport.Print(PRT_FATAL, "invalid character %d\n", handle);
    157 		return;
    158 	} //end if
    159 	BotFreeCharacterStrings(botcharacters[handle]);
    160 	FreeMemory(botcharacters[handle]);
    161 	botcharacters[handle] = NULL;
    162 } //end of the function BotFreeCharacter2
    163 //========================================================================
    164 //
    165 // Parameter:			-
    166 // Returns:				-
    167 // Changes Globals:		-
    168 //========================================================================
    169 void BotFreeCharacter(int handle)
    170 {
    171 	if (!LibVarGetValue("bot_reloadcharacters")) return;
    172 	BotFreeCharacter2(handle);
    173 } //end of the function BotFreeCharacter
    174 //===========================================================================
    175 //
    176 // Parameter:			-
    177 // Returns:				-
    178 // Changes Globals:		-
    179 //===========================================================================
    180 void BotDefaultCharacteristics(bot_character_t *ch, bot_character_t *defaultch)
    181 {
    182 	int i;
    183 
    184 	for (i = 0; i < MAX_CHARACTERISTICS; i++)
    185 	{
    186 		if (ch->c[i].type) continue;
    187 		//
    188 		if (defaultch->c[i].type == CT_FLOAT)
    189 		{
    190 			ch->c[i].type = CT_FLOAT;
    191 			ch->c[i].value._float = defaultch->c[i].value._float;
    192 		} //end if
    193 		else if (defaultch->c[i].type == CT_INTEGER)
    194 		{
    195 			ch->c[i].type = CT_INTEGER;
    196 			ch->c[i].value.integer = defaultch->c[i].value.integer;
    197 		} //end else if
    198 		else if (defaultch->c[i].type == CT_STRING)
    199 		{
    200 			ch->c[i].type = CT_STRING;
    201 			ch->c[i].value.string = (char *) GetMemory(strlen(defaultch->c[i].value.string)+1);
    202 			strcpy(ch->c[i].value.string, defaultch->c[i].value.string);
    203 		} //end else if
    204 	} //end for
    205 } //end of the function BotDefaultCharacteristics
    206 //===========================================================================
    207 //
    208 // Parameter:			-
    209 // Returns:				-
    210 // Changes Globals:		-
    211 //===========================================================================
    212 bot_character_t *BotLoadCharacterFromFile(char *charfile, int skill)
    213 {
    214 	int indent, index, foundcharacter;
    215 	bot_character_t *ch;
    216 	source_t *source;
    217 	token_t token;
    218 
    219 	foundcharacter = qfalse;
    220 	//a bot character is parsed in two phases
    221 	PC_SetBaseFolder(BOTFILESBASEFOLDER);
    222 	source = LoadSourceFile(charfile);
    223 	if (!source)
    224 	{
    225 		botimport.Print(PRT_ERROR, "counldn't load %s\n", charfile);
    226 		return NULL;
    227 	} //end if
    228 	ch = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) +
    229 					MAX_CHARACTERISTICS * sizeof(bot_characteristic_t));
    230 	strcpy(ch->filename, charfile);
    231 	while(PC_ReadToken(source, &token))
    232 	{
    233 		if (!strcmp(token.string, "skill"))
    234 		{
    235 			if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token))
    236 			{
    237 				FreeSource(source);
    238 				BotFreeCharacterStrings(ch);
    239 				FreeMemory(ch);
    240 				return NULL;
    241 			} //end if
    242 			if (!PC_ExpectTokenString(source, "{"))
    243 			{
    244 				FreeSource(source);
    245 				BotFreeCharacterStrings(ch);
    246 				FreeMemory(ch);
    247 				return NULL;
    248 			} //end if
    249 			//if it's the correct skill
    250 			if (skill < 0 || token.intvalue == skill)
    251 			{
    252 				foundcharacter = qtrue;
    253 				ch->skill = token.intvalue;
    254 				while(PC_ExpectAnyToken(source, &token))
    255 				{
    256 					if (!strcmp(token.string, "}")) break;
    257 					if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER))
    258 					{
    259 						SourceError(source, "expected integer index, found %s\n", token.string);
    260 						FreeSource(source);
    261 						BotFreeCharacterStrings(ch);
    262 						FreeMemory(ch);
    263 						return NULL;
    264 					} //end if
    265 					index = token.intvalue;
    266 					if (index < 0 || index > MAX_CHARACTERISTICS)
    267 					{
    268 						SourceError(source, "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS);
    269 						FreeSource(source);
    270 						BotFreeCharacterStrings(ch);
    271 						FreeMemory(ch);
    272 						return NULL;
    273 					} //end if
    274 					if (ch->c[index].type)
    275 					{
    276 						SourceError(source, "characteristic %d already initialized\n", index);
    277 						FreeSource(source);
    278 						BotFreeCharacterStrings(ch);
    279 						FreeMemory(ch);
    280 						return NULL;
    281 					} //end if
    282 					if (!PC_ExpectAnyToken(source, &token))
    283 					{
    284 						FreeSource(source);
    285 						BotFreeCharacterStrings(ch);
    286 						FreeMemory(ch);
    287 						return NULL;
    288 					} //end if
    289 					if (token.type == TT_NUMBER)
    290 					{
    291 						if (token.subtype & TT_FLOAT)
    292 						{
    293 							ch->c[index].value._float = token.floatvalue;
    294 							ch->c[index].type = CT_FLOAT;
    295 						} //end if
    296 						else
    297 						{
    298 							ch->c[index].value.integer = token.intvalue;
    299 							ch->c[index].type = CT_INTEGER;
    300 						} //end else
    301 					} //end if
    302 					else if (token.type == TT_STRING)
    303 					{
    304 						StripDoubleQuotes(token.string);
    305 						ch->c[index].value.string = GetMemory(strlen(token.string)+1);
    306 						strcpy(ch->c[index].value.string, token.string);
    307 						ch->c[index].type = CT_STRING;
    308 					} //end else if
    309 					else
    310 					{
    311 						SourceError(source, "expected integer, float or string, found %s\n", token.string);
    312 						FreeSource(source);
    313 						BotFreeCharacterStrings(ch);
    314 						FreeMemory(ch);
    315 						return NULL;
    316 					} //end else
    317 				} //end if
    318 				break;
    319 			} //end if
    320 			else
    321 			{
    322 				indent = 1;
    323 				while(indent)
    324 				{
    325 					if (!PC_ExpectAnyToken(source, &token))
    326 					{
    327 						FreeSource(source);
    328 						BotFreeCharacterStrings(ch);
    329 						FreeMemory(ch);
    330 						return NULL;
    331 					} //end if
    332 					if (!strcmp(token.string, "{")) indent++;
    333 					else if (!strcmp(token.string, "}")) indent--;
    334 				} //end while
    335 			} //end else
    336 		} //end if
    337 		else
    338 		{
    339 			SourceError(source, "unknown definition %s\n", token.string);
    340 			FreeSource(source);
    341 			BotFreeCharacterStrings(ch);
    342 			FreeMemory(ch);
    343 			return NULL;
    344 		} //end else
    345 	} //end while
    346 	FreeSource(source);
    347 	//
    348 	if (!foundcharacter)
    349 	{
    350 		BotFreeCharacterStrings(ch);
    351 		FreeMemory(ch);
    352 		return NULL;
    353 	} //end if
    354 	return ch;
    355 } //end of the function BotLoadCharacterFromFile
    356 //===========================================================================
    357 //
    358 // Parameter:			-
    359 // Returns:				-
    360 // Changes Globals:		-
    361 //===========================================================================
    362 int BotFindCachedCharacter(char *charfile, float skill)
    363 {
    364 	int handle;
    365 
    366 	for (handle = 1; handle <= MAX_CLIENTS; handle++)
    367 	{
    368 		if ( !botcharacters[handle] ) continue;
    369 		if ( strcmp( botcharacters[handle]->filename, charfile ) == 0 &&
    370 			(skill < 0 || fabs(botcharacters[handle]->skill - skill) < 0.01) )
    371 		{
    372 			return handle;
    373 		} //end if
    374 	} //end for
    375 	return 0;
    376 } //end of the function BotFindCachedCharacter
    377 //===========================================================================
    378 //
    379 // Parameter:			-
    380 // Returns:				-
    381 // Changes Globals:		-
    382 //===========================================================================
    383 int BotLoadCachedCharacter(char *charfile, float skill, int reload)
    384 {
    385 	int handle, cachedhandle, intskill;
    386 	bot_character_t *ch = NULL;
    387 #ifdef DEBUG
    388 	int starttime;
    389 
    390 	starttime = Sys_MilliSeconds();
    391 #endif //DEBUG
    392 
    393 	//find a free spot for a character
    394 	for (handle = 1; handle <= MAX_CLIENTS; handle++)
    395 	{
    396 		if (!botcharacters[handle]) break;
    397 	} //end for
    398 	if (handle > MAX_CLIENTS) return 0;
    399 	//try to load a cached character with the given skill
    400 	if (!reload)
    401 	{
    402 		cachedhandle = BotFindCachedCharacter(charfile, skill);
    403 		if (cachedhandle)
    404 		{
    405 			botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile);
    406 			return cachedhandle;
    407 		} //end if
    408 	} //end else
    409 	//
    410 	intskill = (int) (skill + 0.5);
    411 	//try to load the character with the given skill
    412 	ch = BotLoadCharacterFromFile(charfile, intskill);
    413 	if (ch)
    414 	{
    415 		botcharacters[handle] = ch;
    416 		//
    417 		botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", intskill, charfile);
    418 #ifdef DEBUG
    419 		if (bot_developer)
    420 		{
    421 			botimport.Print(PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", intskill, Sys_MilliSeconds() - starttime, charfile);
    422 		} //end if
    423 #endif //DEBUG
    424 		return handle;
    425 	} //end if
    426 	//
    427 	botimport.Print(PRT_WARNING, "couldn't find skill %d in %s\n", intskill, charfile);
    428 	//
    429 	if (!reload)
    430 	{
    431 		//try to load a cached default character with the given skill
    432 		cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, skill);
    433 		if (cachedhandle)
    434 		{
    435 			botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", intskill, charfile);
    436 			return cachedhandle;
    437 		} //end if
    438 	} //end if
    439 	//try to load the default character with the given skill
    440 	ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, intskill);
    441 	if (ch)
    442 	{
    443 		botcharacters[handle] = ch;
    444 		botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", intskill, charfile);
    445 		return handle;
    446 	} //end if
    447 	//
    448 	if (!reload)
    449 	{
    450 		//try to load a cached character with any skill
    451 		cachedhandle = BotFindCachedCharacter(charfile, -1);
    452 		if (cachedhandle)
    453 		{
    454 			botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile);
    455 			return cachedhandle;
    456 		} //end if
    457 	} //end if
    458 	//try to load a character with any skill
    459 	ch = BotLoadCharacterFromFile(charfile, -1);
    460 	if (ch)
    461 	{
    462 		botcharacters[handle] = ch;
    463 		botimport.Print(PRT_MESSAGE, "loaded skill %f from %s\n", ch->skill, charfile);
    464 		return handle;
    465 	} //end if
    466 	//
    467 	if (!reload)
    468 	{
    469 		//try to load a cached character with any skill
    470 		cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, -1);
    471 		if (cachedhandle)
    472 		{
    473 			botimport.Print(PRT_MESSAGE, "loaded cached default skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile);
    474 			return cachedhandle;
    475 		} //end if
    476 	} //end if
    477 	//try to load a character with any skill
    478 	ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, -1);
    479 	if (ch)
    480 	{
    481 		botcharacters[handle] = ch;
    482 		botimport.Print(PRT_MESSAGE, "loaded default skill %f from %s\n", ch->skill, charfile);
    483 		return handle;
    484 	} //end if
    485 	//
    486 	botimport.Print(PRT_WARNING, "couldn't load any skill from %s\n", charfile);
    487 	//couldn't load any character
    488 	return 0;
    489 } //end of the function BotLoadCachedCharacter
    490 //===========================================================================
    491 //
    492 // Parameter:			-
    493 // Returns:				-
    494 // Changes Globals:		-
    495 //===========================================================================
    496 int BotLoadCharacterSkill(char *charfile, float skill)
    497 {
    498 	int ch, defaultch;
    499 
    500 	defaultch = BotLoadCachedCharacter(DEFAULT_CHARACTER, skill, qfalse);
    501 	ch = BotLoadCachedCharacter(charfile, skill, LibVarGetValue("bot_reloadcharacters"));
    502 
    503 	if (defaultch && ch)
    504 	{
    505 		BotDefaultCharacteristics(botcharacters[ch], botcharacters[defaultch]);
    506 	} //end if
    507 
    508 	return ch;
    509 } //end of the function BotLoadCharacterSkill
    510 //===========================================================================
    511 //
    512 // Parameter:			-
    513 // Returns:				-
    514 // Changes Globals:		-
    515 //===========================================================================
    516 int BotInterpolateCharacters(int handle1, int handle2, float desiredskill)
    517 {
    518 	bot_character_t *ch1, *ch2, *out;
    519 	int i, handle;
    520 	float scale;
    521 
    522 	ch1 = BotCharacterFromHandle(handle1);
    523 	ch2 = BotCharacterFromHandle(handle2);
    524 	if (!ch1 || !ch2)
    525 		return 0;
    526 	//find a free spot for a character
    527 	for (handle = 1; handle <= MAX_CLIENTS; handle++)
    528 	{
    529 		if (!botcharacters[handle]) break;
    530 	} //end for
    531 	if (handle > MAX_CLIENTS) return 0;
    532 	out = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) +
    533 					MAX_CHARACTERISTICS * sizeof(bot_characteristic_t));
    534 	out->skill = desiredskill;
    535 	strcpy(out->filename, ch1->filename);
    536 	botcharacters[handle] = out;
    537 
    538 	scale = (float) (desiredskill - ch1->skill) / (ch2->skill - ch1->skill);
    539 	for (i = 0; i < MAX_CHARACTERISTICS; i++)
    540 	{
    541 		//
    542 		if (ch1->c[i].type == CT_FLOAT && ch2->c[i].type == CT_FLOAT)
    543 		{
    544 			out->c[i].type = CT_FLOAT;
    545 			out->c[i].value._float = ch1->c[i].value._float +
    546 								(ch2->c[i].value._float - ch1->c[i].value._float) * scale;
    547 		} //end if
    548 		else if (ch1->c[i].type == CT_INTEGER)
    549 		{
    550 			out->c[i].type = CT_INTEGER;
    551 			out->c[i].value.integer = ch1->c[i].value.integer;
    552 		} //end else if
    553 		else if (ch1->c[i].type == CT_STRING)
    554 		{
    555 			out->c[i].type = CT_STRING;
    556 			out->c[i].value.string = (char *) GetMemory(strlen(ch1->c[i].value.string)+1);
    557 			strcpy(out->c[i].value.string, ch1->c[i].value.string);
    558 		} //end else if
    559 	} //end for
    560 	return handle;
    561 } //end of the function BotInterpolateCharacters
    562 //===========================================================================
    563 //
    564 // Parameter:			-
    565 // Returns:				-
    566 // Changes Globals:		-
    567 //===========================================================================
    568 int BotLoadCharacter(char *charfile, float skill)
    569 {
    570 	int firstskill, secondskill, handle;
    571 
    572 	//make sure the skill is in the valid range
    573 	if (skill < 1.0) skill = 1.0;
    574 	else if (skill > 5.0) skill = 5.0;
    575 	//skill 1, 4 and 5 should be available in the character files
    576 	if (skill == 1.0 || skill == 4.0 || skill == 5.0)
    577 	{
    578 		return BotLoadCharacterSkill(charfile, skill);
    579 	} //end if
    580 	//check if there's a cached skill
    581 	handle = BotFindCachedCharacter(charfile, skill);
    582 	if (handle)
    583 	{
    584 		botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile);
    585 		return handle;
    586 	} //end if
    587 	if (skill < 4.0)
    588 	{
    589 		//load skill 1 and 4
    590 		firstskill = BotLoadCharacterSkill(charfile, 1);
    591 		if (!firstskill) return 0;
    592 		secondskill = BotLoadCharacterSkill(charfile, 4);
    593 		if (!secondskill) return firstskill;
    594 	} //end if
    595 	else
    596 	{
    597 		//load skill 4 and 5
    598 		firstskill = BotLoadCharacterSkill(charfile, 4);
    599 		if (!firstskill) return 0;
    600 		secondskill = BotLoadCharacterSkill(charfile, 5);
    601 		if (!secondskill) return firstskill;
    602 	} //end else
    603 	//interpolate between the two skills
    604 	handle = BotInterpolateCharacters(firstskill, secondskill, skill);
    605 	if (!handle) return 0;
    606 	//write the character to the log file
    607 	BotDumpCharacter(botcharacters[handle]);
    608 	//
    609 	return handle;
    610 } //end of the function BotLoadCharacter
    611 //===========================================================================
    612 //
    613 // Parameter:			-
    614 // Returns:				-
    615 // Changes Globals:		-
    616 //===========================================================================
    617 int CheckCharacteristicIndex(int character, int index)
    618 {
    619 	bot_character_t *ch;
    620 
    621 	ch = BotCharacterFromHandle(character);
    622 	if (!ch) return qfalse;
    623 	if (index < 0 || index >= MAX_CHARACTERISTICS)
    624 	{
    625 		botimport.Print(PRT_ERROR, "characteristic %d does not exist\n", index);
    626 		return qfalse;
    627 	} //end if
    628 	if (!ch->c[index].type)
    629 	{
    630 		botimport.Print(PRT_ERROR, "characteristic %d is not initialized\n", index);
    631 		return qfalse;
    632 	} //end if
    633 	return qtrue;
    634 } //end of the function CheckCharacteristicIndex
    635 //===========================================================================
    636 //
    637 // Parameter:			-
    638 // Returns:				-
    639 // Changes Globals:		-
    640 //===========================================================================
    641 float Characteristic_Float(int character, int index)
    642 {
    643 	bot_character_t *ch;
    644 
    645 	ch = BotCharacterFromHandle(character);
    646 	if (!ch) return 0;
    647 	//check if the index is in range
    648 	if (!CheckCharacteristicIndex(character, index)) return 0;
    649 	//an integer will be converted to a float
    650 	if (ch->c[index].type == CT_INTEGER)
    651 	{
    652 		return (float) ch->c[index].value.integer;
    653 	} //end if
    654 	//floats are just returned
    655 	else if (ch->c[index].type == CT_FLOAT)
    656 	{
    657 		return ch->c[index].value._float;
    658 	} //end else if
    659 	//cannot convert a string pointer to a float
    660 	else
    661 	{
    662 		botimport.Print(PRT_ERROR, "characteristic %d is not a float\n", index);
    663 		return 0;
    664 	} //end else if
    665 //	return 0;
    666 } //end of the function Characteristic_Float
    667 //===========================================================================
    668 //
    669 // Parameter:				-
    670 // Returns:					-
    671 // Changes Globals:		-
    672 //===========================================================================
    673 float Characteristic_BFloat(int character, int index, float min, float max)
    674 {
    675 	float value;
    676 	bot_character_t *ch;
    677 
    678 	ch = BotCharacterFromHandle(character);
    679 	if (!ch) return 0;
    680 	if (min > max)
    681 	{
    682 		botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %f and %f\n", index, min, max);
    683 		return 0;
    684 	} //end if
    685 	value = Characteristic_Float(character, index);
    686 	if (value < min) return min;
    687 	if (value > max) return max;
    688 	return value;
    689 } //end of the function Characteristic_BFloat
    690 //===========================================================================
    691 //
    692 // Parameter:			-
    693 // Returns:				-
    694 // Changes Globals:		-
    695 //===========================================================================
    696 int Characteristic_Integer(int character, int index)
    697 {
    698 	bot_character_t *ch;
    699 
    700 	ch = BotCharacterFromHandle(character);
    701 	if (!ch) return 0;
    702 	//check if the index is in range
    703 	if (!CheckCharacteristicIndex(character, index)) return 0;
    704 	//an integer will just be returned
    705 	if (ch->c[index].type == CT_INTEGER)
    706 	{
    707 		return ch->c[index].value.integer;
    708 	} //end if
    709 	//floats are casted to integers
    710 	else if (ch->c[index].type == CT_FLOAT)
    711 	{
    712 		return (int) ch->c[index].value._float;
    713 	} //end else if
    714 	else
    715 	{
    716 		botimport.Print(PRT_ERROR, "characteristic %d is not a integer\n", index);
    717 		return 0;
    718 	} //end else if
    719 //	return 0;
    720 } //end of the function Characteristic_Integer
    721 //===========================================================================
    722 //
    723 // Parameter:			-
    724 // Returns:				-
    725 // Changes Globals:		-
    726 //===========================================================================
    727 int Characteristic_BInteger(int character, int index, int min, int max)
    728 {
    729 	int value;
    730 	bot_character_t *ch;
    731 
    732 	ch = BotCharacterFromHandle(character);
    733 	if (!ch) return 0;
    734 	if (min > max)
    735 	{
    736 		botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max);
    737 		return 0;
    738 	} //end if
    739 	value = Characteristic_Integer(character, index);
    740 	if (value < min) return min;
    741 	if (value > max) return max;
    742 	return value;
    743 } //end of the function Characteristic_BInteger
    744 //===========================================================================
    745 //
    746 // Parameter:			-
    747 // Returns:				-
    748 // Changes Globals:		-
    749 //===========================================================================
    750 void Characteristic_String(int character, int index, char *buf, int size)
    751 {
    752 	bot_character_t *ch;
    753 
    754 	ch = BotCharacterFromHandle(character);
    755 	if (!ch) return;
    756 	//check if the index is in range
    757 	if (!CheckCharacteristicIndex(character, index)) return;
    758 	//an integer will be converted to a float
    759 	if (ch->c[index].type == CT_STRING)
    760 	{
    761 		strncpy(buf, ch->c[index].value.string, size-1);
    762 		buf[size-1] = '\0';
    763 		return;
    764 	} //end if
    765 	else
    766 	{
    767 		botimport.Print(PRT_ERROR, "characteristic %d is not a string\n", index);
    768 		return;
    769 	} //end else if
    770 	return;
    771 } //end of the function Characteristic_String
    772 //===========================================================================
    773 //
    774 // Parameter:			-
    775 // Returns:				-
    776 // Changes Globals:		-
    777 //===========================================================================
    778 void BotShutdownCharacters(void)
    779 {
    780 	int handle;
    781 
    782 	for (handle = 1; handle <= MAX_CLIENTS; handle++)
    783 	{
    784 		if (botcharacters[handle])
    785 		{
    786 			BotFreeCharacter2(handle);
    787 		} //end if
    788 	} //end for
    789 } //end of the function BotShutdownCharacters
    790