Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

ai_main.c (45513B)


      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 /*****************************************************************************
     25  * name:		ai_main.c
     26  *
     27  * desc:		Quake3 bot AI
     28  *
     29  * $Archive: /MissionPack/code/game/ai_main.c $
     30  *
     31  *****************************************************************************/
     32 
     33 
     34 #include "g_local.h"
     35 #include "q_shared.h"
     36 #include "botlib.h"		//bot lib interface
     37 #include "be_aas.h"
     38 #include "be_ea.h"
     39 #include "be_ai_char.h"
     40 #include "be_ai_chat.h"
     41 #include "be_ai_gen.h"
     42 #include "be_ai_goal.h"
     43 #include "be_ai_move.h"
     44 #include "be_ai_weap.h"
     45 //
     46 #include "ai_main.h"
     47 #include "ai_dmq3.h"
     48 #include "ai_chat.h"
     49 #include "ai_cmd.h"
     50 #include "ai_dmnet.h"
     51 #include "ai_vcmd.h"
     52 
     53 //
     54 #include "chars.h"
     55 #include "inv.h"
     56 #include "syn.h"
     57 
     58 #define MAX_PATH		144
     59 
     60 
     61 //bot states
     62 bot_state_t	*botstates[MAX_CLIENTS];
     63 //number of bots
     64 int numbots;
     65 //floating point time
     66 float floattime;
     67 //time to do a regular update
     68 float regularupdate_time;
     69 //
     70 int bot_interbreed;
     71 int bot_interbreedmatchcount;
     72 //
     73 vmCvar_t bot_thinktime;
     74 vmCvar_t bot_memorydump;
     75 vmCvar_t bot_saveroutingcache;
     76 vmCvar_t bot_pause;
     77 vmCvar_t bot_report;
     78 vmCvar_t bot_testsolid;
     79 vmCvar_t bot_testclusters;
     80 vmCvar_t bot_developer;
     81 vmCvar_t bot_interbreedchar;
     82 vmCvar_t bot_interbreedbots;
     83 vmCvar_t bot_interbreedcycle;
     84 vmCvar_t bot_interbreedwrite;
     85 
     86 
     87 void ExitLevel( void );
     88 
     89 
     90 /*
     91 ==================
     92 BotAI_Print
     93 ==================
     94 */
     95 void QDECL BotAI_Print(int type, char *fmt, ...) {
     96 	char str[2048];
     97 	va_list ap;
     98 
     99 	va_start(ap, fmt);
    100 	vsprintf(str, fmt, ap);
    101 	va_end(ap);
    102 
    103 	switch(type) {
    104 		case PRT_MESSAGE: {
    105 			G_Printf("%s", str);
    106 			break;
    107 		}
    108 		case PRT_WARNING: {
    109 			G_Printf( S_COLOR_YELLOW "Warning: %s", str );
    110 			break;
    111 		}
    112 		case PRT_ERROR: {
    113 			G_Printf( S_COLOR_RED "Error: %s", str );
    114 			break;
    115 		}
    116 		case PRT_FATAL: {
    117 			G_Printf( S_COLOR_RED "Fatal: %s", str );
    118 			break;
    119 		}
    120 		case PRT_EXIT: {
    121 			G_Error( S_COLOR_RED "Exit: %s", str );
    122 			break;
    123 		}
    124 		default: {
    125 			G_Printf( "unknown print type\n" );
    126 			break;
    127 		}
    128 	}
    129 }
    130 
    131 
    132 /*
    133 ==================
    134 BotAI_Trace
    135 ==================
    136 */
    137 void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) {
    138 	trace_t trace;
    139 
    140 	trap_Trace(&trace, start, mins, maxs, end, passent, contentmask);
    141 	//copy the trace information
    142 	bsptrace->allsolid = trace.allsolid;
    143 	bsptrace->startsolid = trace.startsolid;
    144 	bsptrace->fraction = trace.fraction;
    145 	VectorCopy(trace.endpos, bsptrace->endpos);
    146 	bsptrace->plane.dist = trace.plane.dist;
    147 	VectorCopy(trace.plane.normal, bsptrace->plane.normal);
    148 	bsptrace->plane.signbits = trace.plane.signbits;
    149 	bsptrace->plane.type = trace.plane.type;
    150 	bsptrace->surface.value = trace.surfaceFlags;
    151 	bsptrace->ent = trace.entityNum;
    152 	bsptrace->exp_dist = 0;
    153 	bsptrace->sidenum = 0;
    154 	bsptrace->contents = 0;
    155 }
    156 
    157 /*
    158 ==================
    159 BotAI_GetClientState
    160 ==================
    161 */
    162 int BotAI_GetClientState( int clientNum, playerState_t *state ) {
    163 	gentity_t	*ent;
    164 
    165 	ent = &g_entities[clientNum];
    166 	if ( !ent->inuse ) {
    167 		return qfalse;
    168 	}
    169 	if ( !ent->client ) {
    170 		return qfalse;
    171 	}
    172 
    173 	memcpy( state, &ent->client->ps, sizeof(playerState_t) );
    174 	return qtrue;
    175 }
    176 
    177 /*
    178 ==================
    179 BotAI_GetEntityState
    180 ==================
    181 */
    182 int BotAI_GetEntityState( int entityNum, entityState_t *state ) {
    183 	gentity_t	*ent;
    184 
    185 	ent = &g_entities[entityNum];
    186 	memset( state, 0, sizeof(entityState_t) );
    187 	if (!ent->inuse) return qfalse;
    188 	if (!ent->r.linked) return qfalse;
    189 	if (ent->r.svFlags & SVF_NOCLIENT) return qfalse;
    190 	memcpy( state, &ent->s, sizeof(entityState_t) );
    191 	return qtrue;
    192 }
    193 
    194 /*
    195 ==================
    196 BotAI_GetSnapshotEntity
    197 ==================
    198 */
    199 int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) {
    200 	int		entNum;
    201 
    202 	entNum = trap_BotGetSnapshotEntity( clientNum, sequence );
    203 	if ( entNum == -1 ) {
    204 		memset(state, 0, sizeof(entityState_t));
    205 		return -1;
    206 	}
    207 
    208 	BotAI_GetEntityState( entNum, state );
    209 
    210 	return sequence + 1;
    211 }
    212 
    213 /*
    214 ==================
    215 BotAI_BotInitialChat
    216 ==================
    217 */
    218 void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) {
    219 	int		i, mcontext;
    220 	va_list	ap;
    221 	char	*p;
    222 	char	*vars[MAX_MATCHVARIABLES];
    223 
    224 	memset(vars, 0, sizeof(vars));
    225 	va_start(ap, type);
    226 	p = va_arg(ap, char *);
    227 	for (i = 0; i < MAX_MATCHVARIABLES; i++) {
    228 		if( !p ) {
    229 			break;
    230 		}
    231 		vars[i] = p;
    232 		p = va_arg(ap, char *);
    233 	}
    234 	va_end(ap);
    235 
    236 	mcontext = BotSynonymContext(bs);
    237 
    238 	trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] );
    239 }
    240 
    241 
    242 /*
    243 ==================
    244 BotTestAAS
    245 ==================
    246 */
    247 void BotTestAAS(vec3_t origin) {
    248 	int areanum;
    249 	aas_areainfo_t info;
    250 
    251 	trap_Cvar_Update(&bot_testsolid);
    252 	trap_Cvar_Update(&bot_testclusters);
    253 	if (bot_testsolid.integer) {
    254 		if (!trap_AAS_Initialized()) return;
    255 		areanum = BotPointAreaNum(origin);
    256 		if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area");
    257 		else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area");
    258 	}
    259 	else if (bot_testclusters.integer) {
    260 		if (!trap_AAS_Initialized()) return;
    261 		areanum = BotPointAreaNum(origin);
    262 		if (!areanum)
    263 			BotAI_Print(PRT_MESSAGE, "\r^1Solid!                              ");
    264 		else {
    265 			trap_AAS_AreaInfo(areanum, &info);
    266 			BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d       ", areanum, info.cluster);
    267 		}
    268 	}
    269 }
    270 
    271 /*
    272 ==================
    273 BotReportStatus
    274 ==================
    275 */
    276 void BotReportStatus(bot_state_t *bs) {
    277 	char goalname[MAX_MESSAGE_SIZE];
    278 	char netname[MAX_MESSAGE_SIZE];
    279 	char *leader, flagstatus[32];
    280 	//
    281 	ClientName(bs->client, netname, sizeof(netname));
    282 	if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L";
    283 	else leader = " ";
    284 
    285 	strcpy(flagstatus, "  ");
    286 	if (gametype == GT_CTF) {
    287 		if (BotCTFCarryingFlag(bs)) {
    288 			if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F ");
    289 			else strcpy(flagstatus, S_COLOR_BLUE"F ");
    290 		}
    291 	}
    292 #ifdef MISSIONPACK
    293 	else if (gametype == GT_1FCTF) {
    294 		if (Bot1FCTFCarryingFlag(bs)) {
    295 			if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F ");
    296 			else strcpy(flagstatus, S_COLOR_BLUE"F ");
    297 		}
    298 	}
    299 	else if (gametype == GT_HARVESTER) {
    300 		if (BotHarvesterCarryingCubes(bs)) {
    301 			if (BotTeam(bs) == TEAM_RED) Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_RED"%2d", bs->inventory[INVENTORY_REDCUBE]);
    302 			else Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_BLUE"%2d", bs->inventory[INVENTORY_BLUECUBE]);
    303 		}
    304 	}
    305 #endif
    306 
    307 	switch(bs->ltgtype) {
    308 		case LTG_TEAMHELP:
    309 		{
    310 			EasyClientName(bs->teammate, goalname, sizeof(goalname));
    311 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname);
    312 			break;
    313 		}
    314 		case LTG_TEAMACCOMPANY:
    315 		{
    316 			EasyClientName(bs->teammate, goalname, sizeof(goalname));
    317 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname);
    318 			break;
    319 		}
    320 		case LTG_DEFENDKEYAREA:
    321 		{
    322 			trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
    323 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname);
    324 			break;
    325 		}
    326 		case LTG_GETITEM:
    327 		{
    328 			trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
    329 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname);
    330 			break;
    331 		}
    332 		case LTG_KILL:
    333 		{
    334 			ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname));
    335 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname);
    336 			break;
    337 		}
    338 		case LTG_CAMP:
    339 		case LTG_CAMPORDER:
    340 		{
    341 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus);
    342 			break;
    343 		}
    344 		case LTG_PATROL:
    345 		{
    346 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus);
    347 			break;
    348 		}
    349 		case LTG_GETFLAG:
    350 		{
    351 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus);
    352 			break;
    353 		}
    354 		case LTG_RUSHBASE:
    355 		{
    356 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus);
    357 			break;
    358 		}
    359 		case LTG_RETURNFLAG:
    360 		{
    361 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus);
    362 			break;
    363 		}
    364 		case LTG_ATTACKENEMYBASE:
    365 		{
    366 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: attacking the enemy base\n", netname, leader, flagstatus);
    367 			break;
    368 		}
    369 		case LTG_HARVEST:
    370 		{
    371 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: harvesting\n", netname, leader, flagstatus);
    372 			break;
    373 		}
    374 		default:
    375 		{
    376 			BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus);
    377 			break;
    378 		}
    379 	}
    380 }
    381 
    382 /*
    383 ==================
    384 BotTeamplayReport
    385 ==================
    386 */
    387 void BotTeamplayReport(void) {
    388 	int i;
    389 	char buf[MAX_INFO_STRING];
    390 
    391 	BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n");
    392 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
    393 		//
    394 		if ( !botstates[i] || !botstates[i]->inuse ) continue;
    395 		//
    396 		trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
    397 		//if no config string or no name
    398 		if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
    399 		//skip spectators
    400 		if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) {
    401 			BotReportStatus(botstates[i]);
    402 		}
    403 	}
    404 	BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n");
    405 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
    406 		//
    407 		if ( !botstates[i] || !botstates[i]->inuse ) continue;
    408 		//
    409 		trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
    410 		//if no config string or no name
    411 		if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue;
    412 		//skip spectators
    413 		if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) {
    414 			BotReportStatus(botstates[i]);
    415 		}
    416 	}
    417 }
    418 
    419 /*
    420 ==================
    421 BotSetInfoConfigString
    422 ==================
    423 */
    424 void BotSetInfoConfigString(bot_state_t *bs) {
    425 	char goalname[MAX_MESSAGE_SIZE];
    426 	char netname[MAX_MESSAGE_SIZE];
    427 	char action[MAX_MESSAGE_SIZE];
    428 	char *leader, carrying[32], *cs;
    429 	bot_goal_t goal;
    430 	//
    431 	ClientName(bs->client, netname, sizeof(netname));
    432 	if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L";
    433 	else leader = " ";
    434 
    435 	strcpy(carrying, "  ");
    436 	if (gametype == GT_CTF) {
    437 		if (BotCTFCarryingFlag(bs)) {
    438 			strcpy(carrying, "F ");
    439 		}
    440 	}
    441 #ifdef MISSIONPACK
    442 	else if (gametype == GT_1FCTF) {
    443 		if (Bot1FCTFCarryingFlag(bs)) {
    444 			strcpy(carrying, "F ");
    445 		}
    446 	}
    447 	else if (gametype == GT_HARVESTER) {
    448 		if (BotHarvesterCarryingCubes(bs)) {
    449 			if (BotTeam(bs) == TEAM_RED) Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_REDCUBE]);
    450 			else Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_BLUECUBE]);
    451 		}
    452 	}
    453 #endif
    454 
    455 	switch(bs->ltgtype) {
    456 		case LTG_TEAMHELP:
    457 		{
    458 			EasyClientName(bs->teammate, goalname, sizeof(goalname));
    459 			Com_sprintf(action, sizeof(action), "helping %s", goalname);
    460 			break;
    461 		}
    462 		case LTG_TEAMACCOMPANY:
    463 		{
    464 			EasyClientName(bs->teammate, goalname, sizeof(goalname));
    465 			Com_sprintf(action, sizeof(action), "accompanying %s", goalname);
    466 			break;
    467 		}
    468 		case LTG_DEFENDKEYAREA:
    469 		{
    470 			trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
    471 			Com_sprintf(action, sizeof(action), "defending %s", goalname);
    472 			break;
    473 		}
    474 		case LTG_GETITEM:
    475 		{
    476 			trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname));
    477 			Com_sprintf(action, sizeof(action), "getting item %s", goalname);
    478 			break;
    479 		}
    480 		case LTG_KILL:
    481 		{
    482 			ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname));
    483 			Com_sprintf(action, sizeof(action), "killing %s", goalname);
    484 			break;
    485 		}
    486 		case LTG_CAMP:
    487 		case LTG_CAMPORDER:
    488 		{
    489 			Com_sprintf(action, sizeof(action), "camping");
    490 			break;
    491 		}
    492 		case LTG_PATROL:
    493 		{
    494 			Com_sprintf(action, sizeof(action), "patrolling");
    495 			break;
    496 		}
    497 		case LTG_GETFLAG:
    498 		{
    499 			Com_sprintf(action, sizeof(action), "capturing flag");
    500 			break;
    501 		}
    502 		case LTG_RUSHBASE:
    503 		{
    504 			Com_sprintf(action, sizeof(action), "rushing base");
    505 			break;
    506 		}
    507 		case LTG_RETURNFLAG:
    508 		{
    509 			Com_sprintf(action, sizeof(action), "returning flag");
    510 			break;
    511 		}
    512 		case LTG_ATTACKENEMYBASE:
    513 		{
    514 			Com_sprintf(action, sizeof(action), "attacking the enemy base");
    515 			break;
    516 		}
    517 		case LTG_HARVEST:
    518 		{
    519 			Com_sprintf(action, sizeof(action), "harvesting");
    520 			break;
    521 		}
    522 		default:
    523 		{
    524 			trap_BotGetTopGoal(bs->gs, &goal);
    525 			trap_BotGoalName(goal.number, goalname, sizeof(goalname));
    526 			Com_sprintf(action, sizeof(action), "roaming %s", goalname);
    527 			break;
    528 		}
    529 	}
    530   	cs = va("l\\%s\\c\\%s\\a\\%s",
    531 				leader,
    532 				carrying,
    533 				action);
    534   	trap_SetConfigstring (CS_BOTINFO + bs->client, cs);
    535 }
    536 
    537 /*
    538 ==============
    539 BotUpdateInfoConfigStrings
    540 ==============
    541 */
    542 void BotUpdateInfoConfigStrings(void) {
    543 	int i;
    544 	char buf[MAX_INFO_STRING];
    545 
    546 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
    547 		//
    548 		if ( !botstates[i] || !botstates[i]->inuse )
    549 			continue;
    550 		//
    551 		trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
    552 		//if no config string or no name
    553 		if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n")))
    554 			continue;
    555 		BotSetInfoConfigString(botstates[i]);
    556 	}
    557 }
    558 
    559 /*
    560 ==============
    561 BotInterbreedBots
    562 ==============
    563 */
    564 void BotInterbreedBots(void) {
    565 	float ranks[MAX_CLIENTS];
    566 	int parent1, parent2, child;
    567 	int i;
    568 
    569 	// get rankings for all the bots
    570 	for (i = 0; i < MAX_CLIENTS; i++) {
    571 		if ( botstates[i] && botstates[i]->inuse ) {
    572 			ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths;
    573 		}
    574 		else {
    575 			ranks[i] = -1;
    576 		}
    577 	}
    578 
    579 	if (trap_GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) {
    580 		trap_BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs);
    581 		trap_BotMutateGoalFuzzyLogic(botstates[child]->gs, 1);
    582 	}
    583 	// reset the kills and deaths
    584 	for (i = 0; i < MAX_CLIENTS; i++) {
    585 		if (botstates[i] && botstates[i]->inuse) {
    586 			botstates[i]->num_kills = 0;
    587 			botstates[i]->num_deaths = 0;
    588 		}
    589 	}
    590 }
    591 
    592 /*
    593 ==============
    594 BotWriteInterbreeded
    595 ==============
    596 */
    597 void BotWriteInterbreeded(char *filename) {
    598 	float rank, bestrank;
    599 	int i, bestbot;
    600 
    601 	bestrank = 0;
    602 	bestbot = -1;
    603 	// get the best bot
    604 	for (i = 0; i < MAX_CLIENTS; i++) {
    605 		if ( botstates[i] && botstates[i]->inuse ) {
    606 			rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths;
    607 		}
    608 		else {
    609 			rank = -1;
    610 		}
    611 		if (rank > bestrank) {
    612 			bestrank = rank;
    613 			bestbot = i;
    614 		}
    615 	}
    616 	if (bestbot >= 0) {
    617 		//write out the new goal fuzzy logic
    618 		trap_BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename);
    619 	}
    620 }
    621 
    622 /*
    623 ==============
    624 BotInterbreedEndMatch
    625 
    626 add link back into ExitLevel?
    627 ==============
    628 */
    629 void BotInterbreedEndMatch(void) {
    630 
    631 	if (!bot_interbreed) return;
    632 	bot_interbreedmatchcount++;
    633 	if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) {
    634 		bot_interbreedmatchcount = 0;
    635 		//
    636 		trap_Cvar_Update(&bot_interbreedwrite);
    637 		if (strlen(bot_interbreedwrite.string)) {
    638 			BotWriteInterbreeded(bot_interbreedwrite.string);
    639 			trap_Cvar_Set("bot_interbreedwrite", "");
    640 		}
    641 		BotInterbreedBots();
    642 	}
    643 }
    644 
    645 /*
    646 ==============
    647 BotInterbreeding
    648 ==============
    649 */
    650 void BotInterbreeding(void) {
    651 	int i;
    652 
    653 	trap_Cvar_Update(&bot_interbreedchar);
    654 	if (!strlen(bot_interbreedchar.string)) return;
    655 	//make sure we are in tournament mode
    656 	if (gametype != GT_TOURNAMENT) {
    657 		trap_Cvar_Set("g_gametype", va("%d", GT_TOURNAMENT));
    658 		ExitLevel();
    659 		return;
    660 	}
    661 	//shutdown all the bots
    662 	for (i = 0; i < MAX_CLIENTS; i++) {
    663 		if (botstates[i] && botstates[i]->inuse) {
    664 			BotAIShutdownClient(botstates[i]->client, qfalse);
    665 		}
    666 	}
    667 	//make sure all item weight configs are reloaded and Not shared
    668 	trap_BotLibVarSet("bot_reloadcharacters", "1");
    669 	//add a number of bots using the desired bot character
    670 	for (i = 0; i < bot_interbreedbots.integer; i++) {
    671 		trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s 4 free %i %s%d\n",
    672 						bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) );
    673 	}
    674 	//
    675 	trap_Cvar_Set("bot_interbreedchar", "");
    676 	bot_interbreed = qtrue;
    677 }
    678 
    679 /*
    680 ==============
    681 BotEntityInfo
    682 ==============
    683 */
    684 void BotEntityInfo(int entnum, aas_entityinfo_t *info) {
    685 	trap_AAS_EntityInfo(entnum, info);
    686 }
    687 
    688 /*
    689 ==============
    690 NumBots
    691 ==============
    692 */
    693 int NumBots(void) {
    694 	return numbots;
    695 }
    696 
    697 /*
    698 ==============
    699 BotTeamLeader
    700 ==============
    701 */
    702 int BotTeamLeader(bot_state_t *bs) {
    703 	int leader;
    704 
    705 	leader = ClientFromName(bs->teamleader);
    706 	if (leader < 0) return qfalse;
    707 	if (!botstates[leader] || !botstates[leader]->inuse) return qfalse;
    708 	return qtrue;
    709 }
    710 
    711 /*
    712 ==============
    713 AngleDifference
    714 ==============
    715 */
    716 float AngleDifference(float ang1, float ang2) {
    717 	float diff;
    718 
    719 	diff = ang1 - ang2;
    720 	if (ang1 > ang2) {
    721 		if (diff > 180.0) diff -= 360.0;
    722 	}
    723 	else {
    724 		if (diff < -180.0) diff += 360.0;
    725 	}
    726 	return diff;
    727 }
    728 
    729 /*
    730 ==============
    731 BotChangeViewAngle
    732 ==============
    733 */
    734 float BotChangeViewAngle(float angle, float ideal_angle, float speed) {
    735 	float move;
    736 
    737 	angle = AngleMod(angle);
    738 	ideal_angle = AngleMod(ideal_angle);
    739 	if (angle == ideal_angle) return angle;
    740 	move = ideal_angle - angle;
    741 	if (ideal_angle > angle) {
    742 		if (move > 180.0) move -= 360.0;
    743 	}
    744 	else {
    745 		if (move < -180.0) move += 360.0;
    746 	}
    747 	if (move > 0) {
    748 		if (move > speed) move = speed;
    749 	}
    750 	else {
    751 		if (move < -speed) move = -speed;
    752 	}
    753 	return AngleMod(angle + move);
    754 }
    755 
    756 /*
    757 ==============
    758 BotChangeViewAngles
    759 ==============
    760 */
    761 void BotChangeViewAngles(bot_state_t *bs, float thinktime) {
    762 	float diff, factor, maxchange, anglespeed, disired_speed;
    763 	int i;
    764 
    765 	if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
    766 	//
    767 	if (bs->enemy >= 0) {
    768 		factor = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01f, 1);
    769 		maxchange = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800);
    770 	}
    771 	else {
    772 		factor = 0.05f;
    773 		maxchange = 360;
    774 	}
    775 	if (maxchange < 240) maxchange = 240;
    776 	maxchange *= thinktime;
    777 	for (i = 0; i < 2; i++) {
    778 		//
    779 		if (bot_challenge.integer) {
    780 			//smooth slowdown view model
    781 			diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]));
    782 			anglespeed = diff * factor;
    783 			if (anglespeed > maxchange) anglespeed = maxchange;
    784 			bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i],
    785 											bs->ideal_viewangles[i], anglespeed);
    786 		}
    787 		else {
    788 			//over reaction view model
    789 			bs->viewangles[i] = AngleMod(bs->viewangles[i]);
    790 			bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]);
    791 			diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]);
    792 			disired_speed = diff * factor;
    793 			bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed);
    794 			if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange;
    795 			if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange;
    796 			anglespeed = bs->viewanglespeed[i];
    797 			if (anglespeed > maxchange) anglespeed = maxchange;
    798 			if (anglespeed < -maxchange) anglespeed = -maxchange;
    799 			bs->viewangles[i] += anglespeed;
    800 			bs->viewangles[i] = AngleMod(bs->viewangles[i]);
    801 			//demping
    802 			bs->viewanglespeed[i] *= 0.45 * (1 - factor);
    803 		}
    804 		//BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);`
    805 		//bs->viewangles[i] = bs->ideal_viewangles[i];
    806 	}
    807 	//bs->viewangles[PITCH] = 0;
    808 	if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360;
    809 	//elementary action: view
    810 	trap_EA_View(bs->client, bs->viewangles);
    811 }
    812 
    813 /*
    814 ==============
    815 BotInputToUserCommand
    816 ==============
    817 */
    818 void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) {
    819 	vec3_t angles, forward, right;
    820 	short temp;
    821 	int j;
    822 
    823 	//clear the whole structure
    824 	memset(ucmd, 0, sizeof(usercmd_t));
    825 	//
    826 	//Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed);
    827 	//the duration for the user command in milli seconds
    828 	ucmd->serverTime = time;
    829 	//
    830 	if (bi->actionflags & ACTION_DELAYEDJUMP) {
    831 		bi->actionflags |= ACTION_JUMP;
    832 		bi->actionflags &= ~ACTION_DELAYEDJUMP;
    833 	}
    834 	//set the buttons
    835 	if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK;
    836 	if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK;
    837 	if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK;
    838 	if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE;
    839 	if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE;
    840 	if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING;
    841 	if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE;
    842 	if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE;
    843 	if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG;
    844 	if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE;
    845 	if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL;
    846 	if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME;
    847 	//
    848 	ucmd->weapon = bi->weapon;
    849 	//set the view angles
    850 	//NOTE: the ucmd->angles are the angles WITHOUT the delta angles
    851 	ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]);
    852 	ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]);
    853 	ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]);
    854 	//subtract the delta angles
    855 	for (j = 0; j < 3; j++) {
    856 		temp = ucmd->angles[j] - delta_angles[j];
    857 		/*NOTE: disabled because temp should be mod first
    858 		if ( j == PITCH ) {
    859 			// don't let the player look up or down more than 90 degrees
    860 			if ( temp > 16000 ) temp = 16000;
    861 			else if ( temp < -16000 ) temp = -16000;
    862 		}
    863 		*/
    864 		ucmd->angles[j] = temp;
    865 	}
    866 	//NOTE: movement is relative to the REAL view angles
    867 	//get the horizontal forward and right vector
    868 	//get the pitch in the range [-180, 180]
    869 	if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH];
    870 	else angles[PITCH] = 0;
    871 	angles[YAW] = bi->viewangles[YAW];
    872 	angles[ROLL] = 0;
    873 	AngleVectors(angles, forward, right, NULL);
    874 	//bot input speed is in the range [0, 400]
    875 	bi->speed = bi->speed * 127 / 400;
    876 	//set the view independent movement
    877 	ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed;
    878 	ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed;
    879 	ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed;
    880 	//normal keyboard movement
    881 	if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127;
    882 	if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127;
    883 	if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127;
    884 	if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127;
    885 	//jump/moveup
    886 	if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127;
    887 	//crouch/movedown
    888 	if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127;
    889 	//
    890 	//Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove);
    891 	//Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime);
    892 }
    893 
    894 /*
    895 ==============
    896 BotUpdateInput
    897 ==============
    898 */
    899 void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) {
    900 	bot_input_t bi;
    901 	int j;
    902 
    903 	//add the delta angles to the bot's current view angles
    904 	for (j = 0; j < 3; j++) {
    905 		bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
    906 	}
    907 	//change the bot view angles
    908 	BotChangeViewAngles(bs, (float) elapsed_time / 1000);
    909 	//retrieve the bot input
    910 	trap_EA_GetInput(bs->client, (float) time / 1000, &bi);
    911 	//respawn hack
    912 	if (bi.actionflags & ACTION_RESPAWN) {
    913 		if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK);
    914 	}
    915 	//convert the bot input to a usercmd
    916 	BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time);
    917 	//subtract the delta angles
    918 	for (j = 0; j < 3; j++) {
    919 		bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
    920 	}
    921 }
    922 
    923 /*
    924 ==============
    925 BotAIRegularUpdate
    926 ==============
    927 */
    928 void BotAIRegularUpdate(void) {
    929 	if (regularupdate_time < FloatTime()) {
    930 		trap_BotUpdateEntityItems();
    931 		regularupdate_time = FloatTime() + 0.3;
    932 	}
    933 }
    934 
    935 /*
    936 ==============
    937 RemoveColorEscapeSequences
    938 ==============
    939 */
    940 void RemoveColorEscapeSequences( char *text ) {
    941 	int i, l;
    942 
    943 	l = 0;
    944 	for ( i = 0; text[i]; i++ ) {
    945 		if (Q_IsColorString(&text[i])) {
    946 			i++;
    947 			continue;
    948 		}
    949 		if (text[i] > 0x7E)
    950 			continue;
    951 		text[l++] = text[i];
    952 	}
    953 	text[l] = '\0';
    954 }
    955 
    956 /*
    957 ==============
    958 BotAI
    959 ==============
    960 */
    961 int BotAI(int client, float thinktime) {
    962 	bot_state_t *bs;
    963 	char buf[1024], *args;
    964 	int j;
    965 
    966 	trap_EA_ResetInput(client);
    967 	//
    968 	bs = botstates[client];
    969 	if (!bs || !bs->inuse) {
    970 		BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client);
    971 		return qfalse;
    972 	}
    973 
    974 	//retrieve the current client state
    975 	BotAI_GetClientState( client, &bs->cur_ps );
    976 
    977 	//retrieve any waiting server commands
    978 	while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) {
    979 		//have buf point to the command and args to the command arguments
    980 		args = strchr( buf, ' ');
    981 		if (!args) continue;
    982 		*args++ = '\0';
    983 
    984 		//remove color espace sequences from the arguments
    985 		RemoveColorEscapeSequences( args );
    986 
    987 		if (!Q_stricmp(buf, "cp "))
    988 			{ /*CenterPrintf*/ }
    989 		else if (!Q_stricmp(buf, "cs"))
    990 			{ /*ConfigStringModified*/ }
    991 		else if (!Q_stricmp(buf, "print")) {
    992 			//remove first and last quote from the chat message
    993 			memmove(args, args+1, strlen(args));
    994 			args[strlen(args)-1] = '\0';
    995 			trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args);
    996 		}
    997 		else if (!Q_stricmp(buf, "chat")) {
    998 			//remove first and last quote from the chat message
    999 			memmove(args, args+1, strlen(args));
   1000 			args[strlen(args)-1] = '\0';
   1001 			trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args);
   1002 		}
   1003 		else if (!Q_stricmp(buf, "tchat")) {
   1004 			//remove first and last quote from the chat message
   1005 			memmove(args, args+1, strlen(args));
   1006 			args[strlen(args)-1] = '\0';
   1007 			trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args);
   1008 		}
   1009 #ifdef MISSIONPACK
   1010 		else if (!Q_stricmp(buf, "vchat")) {
   1011 			BotVoiceChatCommand(bs, SAY_ALL, args);
   1012 		}
   1013 		else if (!Q_stricmp(buf, "vtchat")) {
   1014 			BotVoiceChatCommand(bs, SAY_TEAM, args);
   1015 		}
   1016 		else if (!Q_stricmp(buf, "vtell")) {
   1017 			BotVoiceChatCommand(bs, SAY_TELL, args);
   1018 		}
   1019 #endif
   1020 		else if (!Q_stricmp(buf, "scores"))
   1021 			{ /*FIXME: parse scores?*/ }
   1022 		else if (!Q_stricmp(buf, "clientLevelShot"))
   1023 			{ /*ignore*/ }
   1024 	}
   1025 	//add the delta angles to the bot's current view angles
   1026 	for (j = 0; j < 3; j++) {
   1027 		bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
   1028 	}
   1029 	//increase the local time of the bot
   1030 	bs->ltime += thinktime;
   1031 	//
   1032 	bs->thinktime = thinktime;
   1033 	//origin of the bot
   1034 	VectorCopy(bs->cur_ps.origin, bs->origin);
   1035 	//eye coordinates of the bot
   1036 	VectorCopy(bs->cur_ps.origin, bs->eye);
   1037 	bs->eye[2] += bs->cur_ps.viewheight;
   1038 	//get the area the bot is in
   1039 	bs->areanum = BotPointAreaNum(bs->origin);
   1040 	//the real AI
   1041 	BotDeathmatchAI(bs, thinktime);
   1042 	//set the weapon selection every AI frame
   1043 	trap_EA_SelectWeapon(bs->client, bs->weaponnum);
   1044 	//subtract the delta angles
   1045 	for (j = 0; j < 3; j++) {
   1046 		bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
   1047 	}
   1048 	//everything was ok
   1049 	return qtrue;
   1050 }
   1051 
   1052 /*
   1053 ==================
   1054 BotScheduleBotThink
   1055 ==================
   1056 */
   1057 void BotScheduleBotThink(void) {
   1058 	int i, botnum;
   1059 
   1060 	botnum = 0;
   1061 
   1062 	for( i = 0; i < MAX_CLIENTS; i++ ) {
   1063 		if( !botstates[i] || !botstates[i]->inuse ) {
   1064 			continue;
   1065 		}
   1066 		//initialize the bot think residual time
   1067 		botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots;
   1068 		botnum++;
   1069 	}
   1070 }
   1071 
   1072 /*
   1073 ==============
   1074 BotWriteSessionData
   1075 ==============
   1076 */
   1077 void BotWriteSessionData(bot_state_t *bs) {
   1078 	const char	*s;
   1079 	const char	*var;
   1080 
   1081 	s = va(
   1082 			"%i %i %i %i %i %i %i %i"
   1083 			" %f %f %f"
   1084 			" %f %f %f"
   1085 			" %f %f %f",
   1086 		bs->lastgoal_decisionmaker,
   1087 		bs->lastgoal_ltgtype,
   1088 		bs->lastgoal_teammate,
   1089 		bs->lastgoal_teamgoal.areanum,
   1090 		bs->lastgoal_teamgoal.entitynum,
   1091 		bs->lastgoal_teamgoal.flags,
   1092 		bs->lastgoal_teamgoal.iteminfo,
   1093 		bs->lastgoal_teamgoal.number,
   1094 		bs->lastgoal_teamgoal.origin[0],
   1095 		bs->lastgoal_teamgoal.origin[1],
   1096 		bs->lastgoal_teamgoal.origin[2],
   1097 		bs->lastgoal_teamgoal.mins[0],
   1098 		bs->lastgoal_teamgoal.mins[1],
   1099 		bs->lastgoal_teamgoal.mins[2],
   1100 		bs->lastgoal_teamgoal.maxs[0],
   1101 		bs->lastgoal_teamgoal.maxs[1],
   1102 		bs->lastgoal_teamgoal.maxs[2]
   1103 		);
   1104 
   1105 	var = va( "botsession%i", bs->client );
   1106 
   1107 	trap_Cvar_Set( var, s );
   1108 }
   1109 
   1110 /*
   1111 ==============
   1112 BotReadSessionData
   1113 ==============
   1114 */
   1115 void BotReadSessionData(bot_state_t *bs) {
   1116 	char	s[MAX_STRING_CHARS];
   1117 	const char	*var;
   1118 
   1119 	var = va( "botsession%i", bs->client );
   1120 	trap_Cvar_VariableStringBuffer( var, s, sizeof(s) );
   1121 
   1122 	sscanf(s,
   1123 			"%i %i %i %i %i %i %i %i"
   1124 			" %f %f %f"
   1125 			" %f %f %f"
   1126 			" %f %f %f",
   1127 		&bs->lastgoal_decisionmaker,
   1128 		&bs->lastgoal_ltgtype,
   1129 		&bs->lastgoal_teammate,
   1130 		&bs->lastgoal_teamgoal.areanum,
   1131 		&bs->lastgoal_teamgoal.entitynum,
   1132 		&bs->lastgoal_teamgoal.flags,
   1133 		&bs->lastgoal_teamgoal.iteminfo,
   1134 		&bs->lastgoal_teamgoal.number,
   1135 		&bs->lastgoal_teamgoal.origin[0],
   1136 		&bs->lastgoal_teamgoal.origin[1],
   1137 		&bs->lastgoal_teamgoal.origin[2],
   1138 		&bs->lastgoal_teamgoal.mins[0],
   1139 		&bs->lastgoal_teamgoal.mins[1],
   1140 		&bs->lastgoal_teamgoal.mins[2],
   1141 		&bs->lastgoal_teamgoal.maxs[0],
   1142 		&bs->lastgoal_teamgoal.maxs[1],
   1143 		&bs->lastgoal_teamgoal.maxs[2]
   1144 		);
   1145 }
   1146 
   1147 /*
   1148 ==============
   1149 BotAISetupClient
   1150 ==============
   1151 */
   1152 int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) {
   1153 	char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH];
   1154 	bot_state_t *bs;
   1155 	int errnum;
   1156 
   1157 	if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t));
   1158 	bs = botstates[client];
   1159 
   1160 	if (bs && bs->inuse) {
   1161 		BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client);
   1162 		return qfalse;
   1163 	}
   1164 
   1165 	if (!trap_AAS_Initialized()) {
   1166 		BotAI_Print(PRT_FATAL, "AAS not initialized\n");
   1167 		return qfalse;
   1168 	}
   1169 
   1170 	//load the bot character
   1171 	bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill);
   1172 	if (!bs->character) {
   1173 		BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile);
   1174 		return qfalse;
   1175 	}
   1176 	//copy the settings
   1177 	memcpy(&bs->settings, settings, sizeof(bot_settings_t));
   1178 	//allocate a goal state
   1179 	bs->gs = trap_BotAllocGoalState(client);
   1180 	//load the item weights
   1181 	trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH);
   1182 	errnum = trap_BotLoadItemWeights(bs->gs, filename);
   1183 	if (errnum != BLERR_NOERROR) {
   1184 		trap_BotFreeGoalState(bs->gs);
   1185 		return qfalse;
   1186 	}
   1187 	//allocate a weapon state
   1188 	bs->ws = trap_BotAllocWeaponState();
   1189 	//load the weapon weights
   1190 	trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH);
   1191 	errnum = trap_BotLoadWeaponWeights(bs->ws, filename);
   1192 	if (errnum != BLERR_NOERROR) {
   1193 		trap_BotFreeGoalState(bs->gs);
   1194 		trap_BotFreeWeaponState(bs->ws);
   1195 		return qfalse;
   1196 	}
   1197 	//allocate a chat state
   1198 	bs->cs = trap_BotAllocChatState();
   1199 	//load the chat file
   1200 	trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH);
   1201 	trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH);
   1202 	errnum = trap_BotLoadChatFile(bs->cs, filename, name);
   1203 	if (errnum != BLERR_NOERROR) {
   1204 		trap_BotFreeChatState(bs->cs);
   1205 		trap_BotFreeGoalState(bs->gs);
   1206 		trap_BotFreeWeaponState(bs->ws);
   1207 		return qfalse;
   1208 	}
   1209 	//get the gender characteristic
   1210 	trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH);
   1211 	//set the chat gender
   1212 	if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE);
   1213 	else if (*gender == 'm' || *gender == 'M') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE);
   1214 	else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS);
   1215 
   1216 	bs->inuse = qtrue;
   1217 	bs->client = client;
   1218 	bs->entitynum = client;
   1219 	bs->setupcount = 4;
   1220 	bs->entergame_time = FloatTime();
   1221 	bs->ms = trap_BotAllocMoveState();
   1222 	bs->walker = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WALKER, 0, 1);
   1223 	numbots++;
   1224 
   1225 	if (trap_Cvar_VariableIntegerValue("bot_testichat")) {
   1226 		trap_BotLibVarSet("bot_testichat", "1");
   1227 		BotChatTest(bs);
   1228 	}
   1229 	//NOTE: reschedule the bot thinking
   1230 	BotScheduleBotThink();
   1231 	//if interbreeding start with a mutation
   1232 	if (bot_interbreed) {
   1233 		trap_BotMutateGoalFuzzyLogic(bs->gs, 1);
   1234 	}
   1235 	// if we kept the bot client
   1236 	if (restart) {
   1237 		BotReadSessionData(bs);
   1238 	}
   1239 	//bot has been setup succesfully
   1240 	return qtrue;
   1241 }
   1242 
   1243 /*
   1244 ==============
   1245 BotAIShutdownClient
   1246 ==============
   1247 */
   1248 int BotAIShutdownClient(int client, qboolean restart) {
   1249 	bot_state_t *bs;
   1250 
   1251 	bs = botstates[client];
   1252 	if (!bs || !bs->inuse) {
   1253 		//BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client);
   1254 		return qfalse;
   1255 	}
   1256 
   1257 	if (restart) {
   1258 		BotWriteSessionData(bs);
   1259 	}
   1260 
   1261 	if (BotChat_ExitGame(bs)) {
   1262 		trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL);
   1263 	}
   1264 
   1265 	trap_BotFreeMoveState(bs->ms);
   1266 	//free the goal state`			
   1267 	trap_BotFreeGoalState(bs->gs);
   1268 	//free the chat file
   1269 	trap_BotFreeChatState(bs->cs);
   1270 	//free the weapon weights
   1271 	trap_BotFreeWeaponState(bs->ws);
   1272 	//free the bot character
   1273 	trap_BotFreeCharacter(bs->character);
   1274 	//
   1275 	BotFreeWaypoints(bs->checkpoints);
   1276 	BotFreeWaypoints(bs->patrolpoints);
   1277 	//clear activate goal stack
   1278 	BotClearActivateGoalStack(bs);
   1279 	//clear the bot state
   1280 	memset(bs, 0, sizeof(bot_state_t));
   1281 	//set the inuse flag to qfalse
   1282 	bs->inuse = qfalse;
   1283 	//there's one bot less
   1284 	numbots--;
   1285 	//everything went ok
   1286 	return qtrue;
   1287 }
   1288 
   1289 /*
   1290 ==============
   1291 BotResetState
   1292 
   1293 called when a bot enters the intermission or observer mode and
   1294 when the level is changed
   1295 ==============
   1296 */
   1297 void BotResetState(bot_state_t *bs) {
   1298 	int client, entitynum, inuse;
   1299 	int movestate, goalstate, chatstate, weaponstate;
   1300 	bot_settings_t settings;
   1301 	int character;
   1302 	playerState_t ps;							//current player state
   1303 	float entergame_time;
   1304 
   1305 	//save some things that should not be reset here
   1306 	memcpy(&settings, &bs->settings, sizeof(bot_settings_t));
   1307 	memcpy(&ps, &bs->cur_ps, sizeof(playerState_t));
   1308 	inuse = bs->inuse;
   1309 	client = bs->client;
   1310 	entitynum = bs->entitynum;
   1311 	character = bs->character;
   1312 	movestate = bs->ms;
   1313 	goalstate = bs->gs;
   1314 	chatstate = bs->cs;
   1315 	weaponstate = bs->ws;
   1316 	entergame_time = bs->entergame_time;
   1317 	//free checkpoints and patrol points
   1318 	BotFreeWaypoints(bs->checkpoints);
   1319 	BotFreeWaypoints(bs->patrolpoints);
   1320 	//reset the whole state
   1321 	memset(bs, 0, sizeof(bot_state_t));
   1322 	//copy back some state stuff that should not be reset
   1323 	bs->ms = movestate;
   1324 	bs->gs = goalstate;
   1325 	bs->cs = chatstate;
   1326 	bs->ws = weaponstate;
   1327 	memcpy(&bs->cur_ps, &ps, sizeof(playerState_t));
   1328 	memcpy(&bs->settings, &settings, sizeof(bot_settings_t));
   1329 	bs->inuse = inuse;
   1330 	bs->client = client;
   1331 	bs->entitynum = entitynum;
   1332 	bs->character = character;
   1333 	bs->entergame_time = entergame_time;
   1334 	//reset several states
   1335 	if (bs->ms) trap_BotResetMoveState(bs->ms);
   1336 	if (bs->gs) trap_BotResetGoalState(bs->gs);
   1337 	if (bs->ws) trap_BotResetWeaponState(bs->ws);
   1338 	if (bs->gs) trap_BotResetAvoidGoals(bs->gs);
   1339 	if (bs->ms) trap_BotResetAvoidReach(bs->ms);
   1340 }
   1341 
   1342 /*
   1343 ==============
   1344 BotAILoadMap
   1345 ==============
   1346 */
   1347 int BotAILoadMap( int restart ) {
   1348 	int			i;
   1349 	vmCvar_t	mapname;
   1350 
   1351 	if (!restart) {
   1352 		trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
   1353 		trap_BotLibLoadMap( mapname.string );
   1354 	}
   1355 
   1356 	for (i = 0; i < MAX_CLIENTS; i++) {
   1357 		if (botstates[i] && botstates[i]->inuse) {
   1358 			BotResetState( botstates[i] );
   1359 			botstates[i]->setupcount = 4;
   1360 		}
   1361 	}
   1362 
   1363 	BotSetupDeathmatchAI();
   1364 
   1365 	return qtrue;
   1366 }
   1367 
   1368 #ifdef MISSIONPACK
   1369 void ProximityMine_Trigger( gentity_t *trigger, gentity_t *other, trace_t *trace );
   1370 #endif
   1371 
   1372 /*
   1373 ==================
   1374 BotAIStartFrame
   1375 ==================
   1376 */
   1377 int BotAIStartFrame(int time) {
   1378 	int i;
   1379 	gentity_t	*ent;
   1380 	bot_entitystate_t state;
   1381 	int elapsed_time, thinktime;
   1382 	static int local_time;
   1383 	static int botlib_residual;
   1384 	static int lastbotthink_time;
   1385 
   1386 	G_CheckBotSpawn();
   1387 
   1388 	trap_Cvar_Update(&bot_rocketjump);
   1389 	trap_Cvar_Update(&bot_grapple);
   1390 	trap_Cvar_Update(&bot_fastchat);
   1391 	trap_Cvar_Update(&bot_nochat);
   1392 	trap_Cvar_Update(&bot_testrchat);
   1393 	trap_Cvar_Update(&bot_thinktime);
   1394 	trap_Cvar_Update(&bot_memorydump);
   1395 	trap_Cvar_Update(&bot_saveroutingcache);
   1396 	trap_Cvar_Update(&bot_pause);
   1397 	trap_Cvar_Update(&bot_report);
   1398 
   1399 	if (bot_report.integer) {
   1400 //		BotTeamplayReport();
   1401 //		trap_Cvar_Set("bot_report", "0");
   1402 		BotUpdateInfoConfigStrings();
   1403 	}
   1404 
   1405 	if (bot_pause.integer) {
   1406 		// execute bot user commands every frame
   1407 		for( i = 0; i < MAX_CLIENTS; i++ ) {
   1408 			if( !botstates[i] || !botstates[i]->inuse ) {
   1409 				continue;
   1410 			}
   1411 			if( g_entities[i].client->pers.connected != CON_CONNECTED ) {
   1412 				continue;
   1413 			}
   1414 			botstates[i]->lastucmd.forwardmove = 0;
   1415 			botstates[i]->lastucmd.rightmove = 0;
   1416 			botstates[i]->lastucmd.upmove = 0;
   1417 			botstates[i]->lastucmd.buttons = 0;
   1418 			botstates[i]->lastucmd.serverTime = time;
   1419 			trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd);
   1420 		}
   1421 		return qtrue;
   1422 	}
   1423 
   1424 	if (bot_memorydump.integer) {
   1425 		trap_BotLibVarSet("memorydump", "1");
   1426 		trap_Cvar_Set("bot_memorydump", "0");
   1427 	}
   1428 	if (bot_saveroutingcache.integer) {
   1429 		trap_BotLibVarSet("saveroutingcache", "1");
   1430 		trap_Cvar_Set("bot_saveroutingcache", "0");
   1431 	}
   1432 	//check if bot interbreeding is activated
   1433 	BotInterbreeding();
   1434 	//cap the bot think time
   1435 	if (bot_thinktime.integer > 200) {
   1436 		trap_Cvar_Set("bot_thinktime", "200");
   1437 	}
   1438 	//if the bot think time changed we should reschedule the bots
   1439 	if (bot_thinktime.integer != lastbotthink_time) {
   1440 		lastbotthink_time = bot_thinktime.integer;
   1441 		BotScheduleBotThink();
   1442 	}
   1443 
   1444 	elapsed_time = time - local_time;
   1445 	local_time = time;
   1446 
   1447 	botlib_residual += elapsed_time;
   1448 
   1449 	if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time;
   1450 	else thinktime = bot_thinktime.integer;
   1451 
   1452 	// update the bot library
   1453 	if ( botlib_residual >= thinktime ) {
   1454 		botlib_residual -= thinktime;
   1455 
   1456 		trap_BotLibStartFrame((float) time / 1000);
   1457 
   1458 		if (!trap_AAS_Initialized()) return qfalse;
   1459 
   1460 		//update entities in the botlib
   1461 		for (i = 0; i < MAX_GENTITIES; i++) {
   1462 			ent = &g_entities[i];
   1463 			if (!ent->inuse) {
   1464 				trap_BotLibUpdateEntity(i, NULL);
   1465 				continue;
   1466 			}
   1467 			if (!ent->r.linked) {
   1468 				trap_BotLibUpdateEntity(i, NULL);
   1469 				continue;
   1470 			}
   1471 			if (ent->r.svFlags & SVF_NOCLIENT) {
   1472 				trap_BotLibUpdateEntity(i, NULL);
   1473 				continue;
   1474 			}
   1475 			// do not update missiles
   1476 			if (ent->s.eType == ET_MISSILE && ent->s.weapon != WP_GRAPPLING_HOOK) {
   1477 				trap_BotLibUpdateEntity(i, NULL);
   1478 				continue;
   1479 			}
   1480 			// do not update event only entities
   1481 			if (ent->s.eType > ET_EVENTS) {
   1482 				trap_BotLibUpdateEntity(i, NULL);
   1483 				continue;
   1484 			}
   1485 #ifdef MISSIONPACK
   1486 			// never link prox mine triggers
   1487 			if (ent->r.contents == CONTENTS_TRIGGER) {
   1488 				if (ent->touch == ProximityMine_Trigger) {
   1489 					trap_BotLibUpdateEntity(i, NULL);
   1490 					continue;
   1491 				}
   1492 			}
   1493 #endif
   1494 			//
   1495 			memset(&state, 0, sizeof(bot_entitystate_t));
   1496 			//
   1497 			VectorCopy(ent->r.currentOrigin, state.origin);
   1498 			if (i < MAX_CLIENTS) {
   1499 				VectorCopy(ent->s.apos.trBase, state.angles);
   1500 			} else {
   1501 				VectorCopy(ent->r.currentAngles, state.angles);
   1502 			}
   1503 			VectorCopy(ent->s.origin2, state.old_origin);
   1504 			VectorCopy(ent->r.mins, state.mins);
   1505 			VectorCopy(ent->r.maxs, state.maxs);
   1506 			state.type = ent->s.eType;
   1507 			state.flags = ent->s.eFlags;
   1508 			if (ent->r.bmodel) state.solid = SOLID_BSP;
   1509 			else state.solid = SOLID_BBOX;
   1510 			state.groundent = ent->s.groundEntityNum;
   1511 			state.modelindex = ent->s.modelindex;
   1512 			state.modelindex2 = ent->s.modelindex2;
   1513 			state.frame = ent->s.frame;
   1514 			state.event = ent->s.event;
   1515 			state.eventParm = ent->s.eventParm;
   1516 			state.powerups = ent->s.powerups;
   1517 			state.legsAnim = ent->s.legsAnim;
   1518 			state.torsoAnim = ent->s.torsoAnim;
   1519 			state.weapon = ent->s.weapon;
   1520 			//
   1521 			trap_BotLibUpdateEntity(i, &state);
   1522 		}
   1523 
   1524 		BotAIRegularUpdate();
   1525 	}
   1526 
   1527 	floattime = trap_AAS_Time();
   1528 
   1529 	// execute scheduled bot AI
   1530 	for( i = 0; i < MAX_CLIENTS; i++ ) {
   1531 		if( !botstates[i] || !botstates[i]->inuse ) {
   1532 			continue;
   1533 		}
   1534 		//
   1535 		botstates[i]->botthink_residual += elapsed_time;
   1536 		//
   1537 		if ( botstates[i]->botthink_residual >= thinktime ) {
   1538 			botstates[i]->botthink_residual -= thinktime;
   1539 
   1540 			if (!trap_AAS_Initialized()) return qfalse;
   1541 
   1542 			if (g_entities[i].client->pers.connected == CON_CONNECTED) {
   1543 				BotAI(i, (float) thinktime / 1000);
   1544 			}
   1545 		}
   1546 	}
   1547 
   1548 
   1549 	// execute bot user commands every frame
   1550 	for( i = 0; i < MAX_CLIENTS; i++ ) {
   1551 		if( !botstates[i] || !botstates[i]->inuse ) {
   1552 			continue;
   1553 		}
   1554 		if( g_entities[i].client->pers.connected != CON_CONNECTED ) {
   1555 			continue;
   1556 		}
   1557 
   1558 		BotUpdateInput(botstates[i], time, elapsed_time);
   1559 		trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd);
   1560 	}
   1561 
   1562 	return qtrue;
   1563 }
   1564 
   1565 /*
   1566 ==============
   1567 BotInitLibrary
   1568 ==============
   1569 */
   1570 int BotInitLibrary(void) {
   1571 	char buf[144];
   1572 
   1573 	//set the maxclients and maxentities library variables before calling BotSetupLibrary
   1574 	trap_Cvar_VariableStringBuffer("sv_maxclients", buf, sizeof(buf));
   1575 	if (!strlen(buf)) strcpy(buf, "8");
   1576 	trap_BotLibVarSet("maxclients", buf);
   1577 	Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES);
   1578 	trap_BotLibVarSet("maxentities", buf);
   1579 	//bsp checksum
   1580 	trap_Cvar_VariableStringBuffer("sv_mapChecksum", buf, sizeof(buf));
   1581 	if (strlen(buf)) trap_BotLibVarSet("sv_mapChecksum", buf);
   1582 	//maximum number of aas links
   1583 	trap_Cvar_VariableStringBuffer("max_aaslinks", buf, sizeof(buf));
   1584 	if (strlen(buf)) trap_BotLibVarSet("max_aaslinks", buf);
   1585 	//maximum number of items in a level
   1586 	trap_Cvar_VariableStringBuffer("max_levelitems", buf, sizeof(buf));
   1587 	if (strlen(buf)) trap_BotLibVarSet("max_levelitems", buf);
   1588 	//game type
   1589 	trap_Cvar_VariableStringBuffer("g_gametype", buf, sizeof(buf));
   1590 	if (!strlen(buf)) strcpy(buf, "0");
   1591 	trap_BotLibVarSet("g_gametype", buf);
   1592 	//bot developer mode and log file
   1593 	trap_BotLibVarSet("bot_developer", bot_developer.string);
   1594 	trap_BotLibVarSet("log", buf);
   1595 	//no chatting
   1596 	trap_Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf));
   1597 	if (strlen(buf)) trap_BotLibVarSet("nochat", "0");
   1598 	//visualize jump pads
   1599 	trap_Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf));
   1600 	if (strlen(buf)) trap_BotLibVarSet("bot_visualizejumppads", buf);
   1601 	//forced clustering calculations
   1602 	trap_Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf));
   1603 	if (strlen(buf)) trap_BotLibVarSet("forceclustering", buf);
   1604 	//forced reachability calculations
   1605 	trap_Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf));
   1606 	if (strlen(buf)) trap_BotLibVarSet("forcereachability", buf);
   1607 	//force writing of AAS to file
   1608 	trap_Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf));
   1609 	if (strlen(buf)) trap_BotLibVarSet("forcewrite", buf);
   1610 	//no AAS optimization
   1611 	trap_Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf));
   1612 	if (strlen(buf)) trap_BotLibVarSet("aasoptimize", buf);
   1613 	//
   1614 	trap_Cvar_VariableStringBuffer("bot_saveroutingcache", buf, sizeof(buf));
   1615 	if (strlen(buf)) trap_BotLibVarSet("saveroutingcache", buf);
   1616 	//reload instead of cache bot character files
   1617 	trap_Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf));
   1618 	if (!strlen(buf)) strcpy(buf, "0");
   1619 	trap_BotLibVarSet("bot_reloadcharacters", buf);
   1620 	//base directory
   1621 	trap_Cvar_VariableStringBuffer("fs_basepath", buf, sizeof(buf));
   1622 	if (strlen(buf)) trap_BotLibVarSet("basedir", buf);
   1623 	//game directory
   1624 	trap_Cvar_VariableStringBuffer("fs_game", buf, sizeof(buf));
   1625 	if (strlen(buf)) trap_BotLibVarSet("gamedir", buf);
   1626 	//cd directory
   1627 	trap_Cvar_VariableStringBuffer("fs_cdpath", buf, sizeof(buf));
   1628 	if (strlen(buf)) trap_BotLibVarSet("cddir", buf);
   1629 	//
   1630 #ifdef MISSIONPACK
   1631 	trap_BotLibDefine("MISSIONPACK");
   1632 #endif
   1633 	//setup the bot library
   1634 	return trap_BotLibSetup();
   1635 }
   1636 
   1637 /*
   1638 ==============
   1639 BotAISetup
   1640 ==============
   1641 */
   1642 int BotAISetup( int restart ) {
   1643 	int			errnum;
   1644 
   1645 	trap_Cvar_Register(&bot_thinktime, "bot_thinktime", "100", CVAR_CHEAT);
   1646 	trap_Cvar_Register(&bot_memorydump, "bot_memorydump", "0", CVAR_CHEAT);
   1647 	trap_Cvar_Register(&bot_saveroutingcache, "bot_saveroutingcache", "0", CVAR_CHEAT);
   1648 	trap_Cvar_Register(&bot_pause, "bot_pause", "0", CVAR_CHEAT);
   1649 	trap_Cvar_Register(&bot_report, "bot_report", "0", CVAR_CHEAT);
   1650 	trap_Cvar_Register(&bot_testsolid, "bot_testsolid", "0", CVAR_CHEAT);
   1651 	trap_Cvar_Register(&bot_testclusters, "bot_testclusters", "0", CVAR_CHEAT);
   1652 	trap_Cvar_Register(&bot_developer, "bot_developer", "0", CVAR_CHEAT);
   1653 	trap_Cvar_Register(&bot_interbreedchar, "bot_interbreedchar", "", 0);
   1654 	trap_Cvar_Register(&bot_interbreedbots, "bot_interbreedbots", "10", 0);
   1655 	trap_Cvar_Register(&bot_interbreedcycle, "bot_interbreedcycle", "20", 0);
   1656 	trap_Cvar_Register(&bot_interbreedwrite, "bot_interbreedwrite", "", 0);
   1657 
   1658 	//if the game is restarted for a tournament
   1659 	if (restart) {
   1660 		return qtrue;
   1661 	}
   1662 
   1663 	//initialize the bot states
   1664 	memset( botstates, 0, sizeof(botstates) );
   1665 
   1666 	errnum = BotInitLibrary();
   1667 	if (errnum != BLERR_NOERROR) return qfalse;
   1668 	return qtrue;
   1669 }
   1670 
   1671 /*
   1672 ==============
   1673 BotAIShutdown
   1674 ==============
   1675 */
   1676 int BotAIShutdown( int restart ) {
   1677 
   1678 	int i;
   1679 
   1680 	//if the game is restarted for a tournament
   1681 	if ( restart ) {
   1682 		//shutdown all the bots in the botlib
   1683 		for (i = 0; i < MAX_CLIENTS; i++) {
   1684 			if (botstates[i] && botstates[i]->inuse) {
   1685 				BotAIShutdownClient(botstates[i]->client, restart);
   1686 			}
   1687 		}
   1688 		//don't shutdown the bot library
   1689 	}
   1690 	else {
   1691 		trap_BotLibShutdown();
   1692 	}
   1693 	return qtrue;
   1694 }
   1695