Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

ai_dmq3.c (158046B)


      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_dmq3.c
     26  *
     27  * desc:		Quake3 bot AI
     28  *
     29  * $Archive: /MissionPack/code/game/ai_dmq3.c $
     30  *
     31  *****************************************************************************/
     32 
     33 
     34 #include "g_local.h"
     35 #include "botlib.h"
     36 #include "be_aas.h"
     37 #include "be_ea.h"
     38 #include "be_ai_char.h"
     39 #include "be_ai_chat.h"
     40 #include "be_ai_gen.h"
     41 #include "be_ai_goal.h"
     42 #include "be_ai_move.h"
     43 #include "be_ai_weap.h"
     44 //
     45 #include "ai_main.h"
     46 #include "ai_dmq3.h"
     47 #include "ai_chat.h"
     48 #include "ai_cmd.h"
     49 #include "ai_dmnet.h"
     50 #include "ai_team.h"
     51 //
     52 #include "chars.h"				//characteristics
     53 #include "inv.h"				//indexes into the inventory
     54 #include "syn.h"				//synonyms
     55 #include "match.h"				//string matching types and vars
     56 
     57 // for the voice chats
     58 #include "../../ui/menudef.h" // sos001205 - for q3_ui also
     59 
     60 // from aasfile.h
     61 #define AREACONTENTS_MOVER				1024
     62 #define AREACONTENTS_MODELNUMSHIFT		24
     63 #define AREACONTENTS_MAXMODELNUM		0xFF
     64 #define AREACONTENTS_MODELNUM			(AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT)
     65 
     66 #define IDEAL_ATTACKDIST			140
     67 
     68 #define MAX_WAYPOINTS		128
     69 //
     70 bot_waypoint_t botai_waypoints[MAX_WAYPOINTS];
     71 bot_waypoint_t *botai_freewaypoints;
     72 
     73 //NOTE: not using a cvars which can be updated because the game should be reloaded anyway
     74 int gametype;		//game type
     75 int maxclients;		//maximum number of clients
     76 
     77 vmCvar_t bot_grapple;
     78 vmCvar_t bot_rocketjump;
     79 vmCvar_t bot_fastchat;
     80 vmCvar_t bot_nochat;
     81 vmCvar_t bot_testrchat;
     82 vmCvar_t bot_challenge;
     83 vmCvar_t bot_predictobstacles;
     84 vmCvar_t g_spSkill;
     85 
     86 extern vmCvar_t bot_developer;
     87 
     88 vec3_t lastteleport_origin;		//last teleport event origin
     89 float lastteleport_time;		//last teleport event time
     90 int max_bspmodelindex;			//maximum BSP model index
     91 
     92 //CTF flag goals
     93 bot_goal_t ctf_redflag;
     94 bot_goal_t ctf_blueflag;
     95 #ifdef MISSIONPACK
     96 bot_goal_t ctf_neutralflag;
     97 bot_goal_t redobelisk;
     98 bot_goal_t blueobelisk;
     99 bot_goal_t neutralobelisk;
    100 #endif
    101 
    102 #define MAX_ALTROUTEGOALS		32
    103 
    104 int altroutegoals_setup;
    105 aas_altroutegoal_t red_altroutegoals[MAX_ALTROUTEGOALS];
    106 int red_numaltroutegoals;
    107 aas_altroutegoal_t blue_altroutegoals[MAX_ALTROUTEGOALS];
    108 int blue_numaltroutegoals;
    109 
    110 
    111 /*
    112 ==================
    113 BotSetUserInfo
    114 ==================
    115 */
    116 void BotSetUserInfo(bot_state_t *bs, char *key, char *value) {
    117 	char userinfo[MAX_INFO_STRING];
    118 
    119 	trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo));
    120 	Info_SetValueForKey(userinfo, key, value);
    121 	trap_SetUserinfo(bs->client, userinfo);
    122 	ClientUserinfoChanged( bs->client );
    123 }
    124 
    125 /*
    126 ==================
    127 BotCTFCarryingFlag
    128 ==================
    129 */
    130 int BotCTFCarryingFlag(bot_state_t *bs) {
    131 	if (gametype != GT_CTF) return CTF_FLAG_NONE;
    132 
    133 	if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED;
    134 	else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE;
    135 	return CTF_FLAG_NONE;
    136 }
    137 
    138 /*
    139 ==================
    140 BotTeam
    141 ==================
    142 */
    143 int BotTeam(bot_state_t *bs) {
    144 	char info[1024];
    145 
    146 	if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
    147 		//BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n");
    148 		return qfalse;
    149 	}
    150 	trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info));
    151 	//
    152 	if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return TEAM_RED;
    153 	else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return TEAM_BLUE;
    154 	return TEAM_FREE;
    155 }
    156 
    157 /*
    158 ==================
    159 BotOppositeTeam
    160 ==================
    161 */
    162 int BotOppositeTeam(bot_state_t *bs) {
    163 	switch(BotTeam(bs)) {
    164 		case TEAM_RED: return TEAM_BLUE;
    165 		case TEAM_BLUE: return TEAM_RED;
    166 		default: return TEAM_FREE;
    167 	}
    168 }
    169 
    170 /*
    171 ==================
    172 BotEnemyFlag
    173 ==================
    174 */
    175 bot_goal_t *BotEnemyFlag(bot_state_t *bs) {
    176 	if (BotTeam(bs) == TEAM_RED) {
    177 		return &ctf_blueflag;
    178 	}
    179 	else {
    180 		return &ctf_redflag;
    181 	}
    182 }
    183 
    184 /*
    185 ==================
    186 BotTeamFlag
    187 ==================
    188 */
    189 bot_goal_t *BotTeamFlag(bot_state_t *bs) {
    190 	if (BotTeam(bs) == TEAM_RED) {
    191 		return &ctf_redflag;
    192 	}
    193 	else {
    194 		return &ctf_blueflag;
    195 	}
    196 }
    197 
    198 
    199 /*
    200 ==================
    201 EntityIsDead
    202 ==================
    203 */
    204 qboolean EntityIsDead(aas_entityinfo_t *entinfo) {
    205 	playerState_t ps;
    206 
    207 	if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) {
    208 		//retrieve the current client state
    209 		BotAI_GetClientState( entinfo->number, &ps );
    210 		if (ps.pm_type != PM_NORMAL) return qtrue;
    211 	}
    212 	return qfalse;
    213 }
    214 
    215 /*
    216 ==================
    217 EntityCarriesFlag
    218 ==================
    219 */
    220 qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) {
    221 	if ( entinfo->powerups & ( 1 << PW_REDFLAG ) )
    222 		return qtrue;
    223 	if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) )
    224 		return qtrue;
    225 #ifdef MISSIONPACK
    226 	if ( entinfo->powerups & ( 1 << PW_NEUTRALFLAG ) )
    227 		return qtrue;
    228 #endif
    229 	return qfalse;
    230 }
    231 
    232 /*
    233 ==================
    234 EntityIsInvisible
    235 ==================
    236 */
    237 qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) {
    238 	// the flag is always visible
    239 	if (EntityCarriesFlag(entinfo)) {
    240 		return qfalse;
    241 	}
    242 	if (entinfo->powerups & (1 << PW_INVIS)) {
    243 		return qtrue;
    244 	}
    245 	return qfalse;
    246 }
    247 
    248 /*
    249 ==================
    250 EntityIsShooting
    251 ==================
    252 */
    253 qboolean EntityIsShooting(aas_entityinfo_t *entinfo) {
    254 	if (entinfo->flags & EF_FIRING) {
    255 		return qtrue;
    256 	}
    257 	return qfalse;
    258 }
    259 
    260 /*
    261 ==================
    262 EntityIsChatting
    263 ==================
    264 */
    265 qboolean EntityIsChatting(aas_entityinfo_t *entinfo) {
    266 	if (entinfo->flags & EF_TALK) {
    267 		return qtrue;
    268 	}
    269 	return qfalse;
    270 }
    271 
    272 /*
    273 ==================
    274 EntityHasQuad
    275 ==================
    276 */
    277 qboolean EntityHasQuad(aas_entityinfo_t *entinfo) {
    278 	if (entinfo->powerups & (1 << PW_QUAD)) {
    279 		return qtrue;
    280 	}
    281 	return qfalse;
    282 }
    283 
    284 #ifdef MISSIONPACK
    285 /*
    286 ==================
    287 EntityHasKamikze
    288 ==================
    289 */
    290 qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo) {
    291 	if (entinfo->flags & EF_KAMIKAZE) {
    292 		return qtrue;
    293 	}
    294 	return qfalse;
    295 }
    296 
    297 /*
    298 ==================
    299 EntityCarriesCubes
    300 ==================
    301 */
    302 qboolean EntityCarriesCubes(aas_entityinfo_t *entinfo) {
    303 	entityState_t state;
    304 
    305 	if (gametype != GT_HARVESTER)
    306 		return qfalse;
    307 	//FIXME: get this info from the aas_entityinfo_t ?
    308 	BotAI_GetEntityState(entinfo->number, &state);
    309 	if (state.generic1 > 0)
    310 		return qtrue;
    311 	return qfalse;
    312 }
    313 
    314 /*
    315 ==================
    316 Bot1FCTFCarryingFlag
    317 ==================
    318 */
    319 int Bot1FCTFCarryingFlag(bot_state_t *bs) {
    320 	if (gametype != GT_1FCTF) return qfalse;
    321 
    322 	if (bs->inventory[INVENTORY_NEUTRALFLAG] > 0) return qtrue;
    323 	return qfalse;
    324 }
    325 
    326 /*
    327 ==================
    328 BotHarvesterCarryingCubes
    329 ==================
    330 */
    331 int BotHarvesterCarryingCubes(bot_state_t *bs) {
    332 	if (gametype != GT_HARVESTER) return qfalse;
    333 
    334 	if (bs->inventory[INVENTORY_REDCUBE] > 0) return qtrue;
    335 	if (bs->inventory[INVENTORY_BLUECUBE] > 0) return qtrue;
    336 	return qfalse;
    337 }
    338 #endif
    339 
    340 /*
    341 ==================
    342 BotRememberLastOrderedTask
    343 ==================
    344 */
    345 void BotRememberLastOrderedTask(bot_state_t *bs) {
    346 	if (!bs->ordered) {
    347 		return;
    348 	}
    349 	bs->lastgoal_decisionmaker = bs->decisionmaker;
    350 	bs->lastgoal_ltgtype = bs->ltgtype;
    351 	memcpy(&bs->lastgoal_teamgoal, &bs->teamgoal, sizeof(bot_goal_t));
    352 	bs->lastgoal_teammate = bs->teammate;
    353 }
    354 
    355 /*
    356 ==================
    357 BotSetTeamStatus
    358 ==================
    359 */
    360 void BotSetTeamStatus(bot_state_t *bs) {
    361 #ifdef MISSIONPACK
    362 	int teamtask;
    363 	aas_entityinfo_t entinfo;
    364 
    365 	teamtask = TEAMTASK_PATROL;
    366 
    367 	switch(bs->ltgtype) {
    368 		case LTG_TEAMHELP:
    369 			break;
    370 		case LTG_TEAMACCOMPANY:
    371 			BotEntityInfo(bs->teammate, &entinfo);
    372 			if ( ( (gametype == GT_CTF || gametype == GT_1FCTF) && EntityCarriesFlag(&entinfo))
    373 				|| ( gametype == GT_HARVESTER && EntityCarriesCubes(&entinfo)) ) {
    374 				teamtask = TEAMTASK_ESCORT;
    375 			}
    376 			else {
    377 				teamtask = TEAMTASK_FOLLOW;
    378 			}
    379 			break;
    380 		case LTG_DEFENDKEYAREA:
    381 			teamtask = TEAMTASK_DEFENSE;
    382 			break;
    383 		case LTG_GETFLAG:
    384 			teamtask = TEAMTASK_OFFENSE;
    385 			break;
    386 		case LTG_RUSHBASE:
    387 			teamtask = TEAMTASK_DEFENSE;
    388 			break;
    389 		case LTG_RETURNFLAG:
    390 			teamtask = TEAMTASK_RETRIEVE;
    391 			break;
    392 		case LTG_CAMP:
    393 		case LTG_CAMPORDER:
    394 			teamtask = TEAMTASK_CAMP;
    395 			break;
    396 		case LTG_PATROL:
    397 			teamtask = TEAMTASK_PATROL;
    398 			break;
    399 		case LTG_GETITEM:
    400 			teamtask = TEAMTASK_PATROL;
    401 			break;
    402 		case LTG_KILL:
    403 			teamtask = TEAMTASK_PATROL;
    404 			break;
    405 		case LTG_HARVEST:
    406 			teamtask = TEAMTASK_OFFENSE;
    407 			break;
    408 		case LTG_ATTACKENEMYBASE:
    409 			teamtask = TEAMTASK_OFFENSE;
    410 			break;
    411 		default:
    412 			teamtask = TEAMTASK_PATROL;
    413 			break;
    414 	}
    415 	BotSetUserInfo(bs, "teamtask", va("%d", teamtask));
    416 #endif
    417 }
    418 
    419 /*
    420 ==================
    421 BotSetLastOrderedTask
    422 ==================
    423 */
    424 int BotSetLastOrderedTask(bot_state_t *bs) {
    425 
    426 	if (gametype == GT_CTF) {
    427 		// don't go back to returning the flag if it's at the base
    428 		if ( bs->lastgoal_ltgtype == LTG_RETURNFLAG ) {
    429 			if ( BotTeam(bs) == TEAM_RED ) {
    430 				if ( bs->redflagstatus == 0 ) {
    431 					bs->lastgoal_ltgtype = 0;
    432 				}
    433 			}
    434 			else {
    435 				if ( bs->blueflagstatus == 0 ) {
    436 					bs->lastgoal_ltgtype = 0;
    437 				}
    438 			}
    439 		}
    440 	}
    441 
    442 	if ( bs->lastgoal_ltgtype ) {
    443 		bs->decisionmaker = bs->lastgoal_decisionmaker;
    444 		bs->ordered = qtrue;
    445 		bs->ltgtype = bs->lastgoal_ltgtype;
    446 		memcpy(&bs->teamgoal, &bs->lastgoal_teamgoal, sizeof(bot_goal_t));
    447 		bs->teammate = bs->lastgoal_teammate;
    448 		bs->teamgoal_time = FloatTime() + 300;
    449 		BotSetTeamStatus(bs);
    450 		//
    451 		if ( gametype == GT_CTF ) {
    452 			if ( bs->ltgtype == LTG_GETFLAG ) {
    453 				bot_goal_t *tb, *eb;
    454 				int tt, et;
    455 
    456 				tb = BotTeamFlag(bs);
    457 				eb = BotEnemyFlag(bs);
    458 				tt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, tb->areanum, TFL_DEFAULT);
    459 				et = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, eb->areanum, TFL_DEFAULT);
    460 				// if the travel time towards the enemy base is larger than towards our base
    461 				if (et > tt) {
    462 					//get an alternative route goal towards the enemy base
    463 					BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
    464 				}
    465 			}
    466 		}
    467 		return qtrue;
    468 	}
    469 	return qfalse;
    470 }
    471 
    472 /*
    473 ==================
    474 BotRefuseOrder
    475 ==================
    476 */
    477 void BotRefuseOrder(bot_state_t *bs) {
    478 	if (!bs->ordered)
    479 		return;
    480 	// if the bot was ordered to do something
    481 	if ( bs->order_time && bs->order_time > FloatTime() - 10 ) {
    482 		trap_EA_Action(bs->client, ACTION_NEGATIVE);
    483 		BotVoiceChat(bs, bs->decisionmaker, VOICECHAT_NO);
    484 		bs->order_time = 0;
    485 	}
    486 }
    487 
    488 /*
    489 ==================
    490 BotCTFSeekGoals
    491 ==================
    492 */
    493 void BotCTFSeekGoals(bot_state_t *bs) {
    494 	float rnd, l1, l2;
    495 	int flagstatus, c;
    496 	vec3_t dir;
    497 	aas_entityinfo_t entinfo;
    498 
    499 	//when carrying a flag in ctf the bot should rush to the base
    500 	if (BotCTFCarryingFlag(bs)) {
    501 		//if not already rushing to the base
    502 		if (bs->ltgtype != LTG_RUSHBASE) {
    503 			BotRefuseOrder(bs);
    504 			bs->ltgtype = LTG_RUSHBASE;
    505 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
    506 			bs->rushbaseaway_time = 0;
    507 			bs->decisionmaker = bs->client;
    508 			bs->ordered = qfalse;
    509 			//
    510 			switch(BotTeam(bs)) {
    511 				case TEAM_RED: VectorSubtract(bs->origin, ctf_blueflag.origin, dir); break;
    512 				case TEAM_BLUE: VectorSubtract(bs->origin, ctf_redflag.origin, dir); break;
    513 				default: VectorSet(dir, 999, 999, 999); break;
    514 			}
    515 			// if the bot picked up the flag very close to the enemy base
    516 			if ( VectorLength(dir) < 128 ) {
    517 				// get an alternative route goal through the enemy base
    518 				BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
    519 			} else {
    520 				// don't use any alt route goal, just get the hell out of the base
    521 				bs->altroutegoal.areanum = 0;
    522 			}
    523 			BotSetUserInfo(bs, "teamtask", va("%d", TEAMTASK_OFFENSE));
    524 			BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG);
    525 		}
    526 		else if (bs->rushbaseaway_time > FloatTime()) {
    527 			if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus;
    528 			else flagstatus = bs->blueflagstatus;
    529 			//if the flag is back
    530 			if (flagstatus == 0) {
    531 				bs->rushbaseaway_time = 0;
    532 			}
    533 		}
    534 		return;
    535 	}
    536 	// if the bot decided to follow someone
    537 	if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) {
    538 		// if the team mate being accompanied no longer carries the flag
    539 		BotEntityInfo(bs->teammate, &entinfo);
    540 		if (!EntityCarriesFlag(&entinfo)) {
    541 			bs->ltgtype = 0;
    542 		}
    543 	}
    544 	//
    545 	if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus;
    546 	else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus;
    547 	//if our team has the enemy flag and our flag is at the base
    548 	if (flagstatus == 1) {
    549 		//
    550 		if (bs->owndecision_time < FloatTime()) {
    551 			//if Not defending the base already
    552 			if (!(bs->ltgtype == LTG_DEFENDKEYAREA &&
    553 					(bs->teamgoal.number == ctf_redflag.number ||
    554 					bs->teamgoal.number == ctf_blueflag.number))) {
    555 				//if there is a visible team mate flag carrier
    556 				c = BotTeamFlagCarrierVisible(bs);
    557 				if (c >= 0 &&
    558 						// and not already following the team mate flag carrier
    559 						(bs->ltgtype != LTG_TEAMACCOMPANY || bs->teammate != c)) {
    560 					//
    561 					BotRefuseOrder(bs);
    562 					//follow the flag carrier
    563 					bs->decisionmaker = bs->client;
    564 					bs->ordered = qfalse;
    565 					//the team mate
    566 					bs->teammate = c;
    567 					//last time the team mate was visible
    568 					bs->teammatevisible_time = FloatTime();
    569 					//no message
    570 					bs->teammessage_time = 0;
    571 					//no arrive message
    572 					bs->arrive_time = 1;
    573 					//
    574 					BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW);
    575 					//get the team goal time
    576 					bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME;
    577 					bs->ltgtype = LTG_TEAMACCOMPANY;
    578 					bs->formation_dist = 3.5 * 32;		//3.5 meter
    579 					BotSetTeamStatus(bs);
    580 					bs->owndecision_time = FloatTime() + 5;
    581 				}
    582 			}
    583 		}
    584 		return;
    585 	}
    586 	//if the enemy has our flag
    587 	else if (flagstatus == 2) {
    588 		//
    589 		if (bs->owndecision_time < FloatTime()) {
    590 			//if enemy flag carrier is visible
    591 			c = BotEnemyFlagCarrierVisible(bs);
    592 			if (c >= 0) {
    593 				//FIXME: fight enemy flag carrier
    594 			}
    595 			//if not already doing something important
    596 			if (bs->ltgtype != LTG_GETFLAG &&
    597 				bs->ltgtype != LTG_RETURNFLAG &&
    598 				bs->ltgtype != LTG_TEAMHELP &&
    599 				bs->ltgtype != LTG_TEAMACCOMPANY &&
    600 				bs->ltgtype != LTG_CAMPORDER &&
    601 				bs->ltgtype != LTG_PATROL &&
    602 				bs->ltgtype != LTG_GETITEM) {
    603 
    604 				BotRefuseOrder(bs);
    605 				bs->decisionmaker = bs->client;
    606 				bs->ordered = qfalse;
    607 				//
    608 				if (random() < 0.5) {
    609 					//go for the enemy flag
    610 					bs->ltgtype = LTG_GETFLAG;
    611 				}
    612 				else {
    613 					bs->ltgtype = LTG_RETURNFLAG;
    614 				}
    615 				//no team message
    616 				bs->teammessage_time = 0;
    617 				//set the time the bot will stop getting the flag
    618 				bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME;
    619 				//get an alternative route goal towards the enemy base
    620 				BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
    621 				//
    622 				BotSetTeamStatus(bs);
    623 				bs->owndecision_time = FloatTime() + 5;
    624 			}
    625 		}
    626 		return;
    627 	}
    628 	//if both flags Not at their bases
    629 	else if (flagstatus == 3) {
    630 		//
    631 		if (bs->owndecision_time < FloatTime()) {
    632 			// if not trying to return the flag and not following the team flag carrier
    633 			if ( bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMACCOMPANY ) {
    634 				//
    635 				c = BotTeamFlagCarrierVisible(bs);
    636 				// if there is a visible team mate flag carrier
    637 				if (c >= 0) {
    638 					BotRefuseOrder(bs);
    639 					//follow the flag carrier
    640 					bs->decisionmaker = bs->client;
    641 					bs->ordered = qfalse;
    642 					//the team mate
    643 					bs->teammate = c;
    644 					//last time the team mate was visible
    645 					bs->teammatevisible_time = FloatTime();
    646 					//no message
    647 					bs->teammessage_time = 0;
    648 					//no arrive message
    649 					bs->arrive_time = 1;
    650 					//
    651 					BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW);
    652 					//get the team goal time
    653 					bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME;
    654 					bs->ltgtype = LTG_TEAMACCOMPANY;
    655 					bs->formation_dist = 3.5 * 32;		//3.5 meter
    656 					//
    657 					BotSetTeamStatus(bs);
    658 					bs->owndecision_time = FloatTime() + 5;
    659 				}
    660 				else {
    661 					BotRefuseOrder(bs);
    662 					bs->decisionmaker = bs->client;
    663 					bs->ordered = qfalse;
    664 					//get the enemy flag
    665 					bs->teammessage_time = FloatTime() + 2 * random();
    666 					//get the flag
    667 					bs->ltgtype = LTG_RETURNFLAG;
    668 					//set the time the bot will stop getting the flag
    669 					bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME;
    670 					//get an alternative route goal towards the enemy base
    671 					BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
    672 					//
    673 					BotSetTeamStatus(bs);
    674 					bs->owndecision_time = FloatTime() + 5;
    675 				}
    676 			}
    677 		}
    678 		return;
    679 	}
    680 	// don't just do something wait for the bot team leader to give orders
    681 	if (BotTeamLeader(bs)) {
    682 		return;
    683 	}
    684 	// if the bot is ordered to do something
    685 	if ( bs->lastgoal_ltgtype ) {
    686 		bs->teamgoal_time += 60;
    687 	}
    688 	// if the bot decided to do something on it's own and has a last ordered goal
    689 	if ( !bs->ordered && bs->lastgoal_ltgtype ) {
    690 		bs->ltgtype = 0;
    691 	}
    692 	//if already a CTF or team goal
    693 	if (bs->ltgtype == LTG_TEAMHELP ||
    694 			bs->ltgtype == LTG_TEAMACCOMPANY ||
    695 			bs->ltgtype == LTG_DEFENDKEYAREA ||
    696 			bs->ltgtype == LTG_GETFLAG ||
    697 			bs->ltgtype == LTG_RUSHBASE ||
    698 			bs->ltgtype == LTG_RETURNFLAG ||
    699 			bs->ltgtype == LTG_CAMPORDER ||
    700 			bs->ltgtype == LTG_PATROL ||
    701 			bs->ltgtype == LTG_GETITEM ||
    702 			bs->ltgtype == LTG_MAKELOVE_UNDER ||
    703 			bs->ltgtype == LTG_MAKELOVE_ONTOP) {
    704 		return;
    705 	}
    706 	//
    707 	if (BotSetLastOrderedTask(bs))
    708 		return;
    709 	//
    710 	if (bs->owndecision_time > FloatTime())
    711 		return;;
    712 	//if the bot is roaming
    713 	if (bs->ctfroam_time > FloatTime())
    714 		return;
    715 	//if the bot has anough aggression to decide what to do
    716 	if (BotAggression(bs) < 50)
    717 		return;
    718 	//set the time to send a message to the team mates
    719 	bs->teammessage_time = FloatTime() + 2 * random();
    720 	//
    721 	if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) {
    722 		if (bs->teamtaskpreference & TEAMTP_ATTACKER) {
    723 			l1 = 0.7f;
    724 		}
    725 		else {
    726 			l1 = 0.2f;
    727 		}
    728 		l2 = 0.9f;
    729 	}
    730 	else {
    731 		l1 = 0.4f;
    732 		l2 = 0.7f;
    733 	}
    734 	//get the flag or defend the base
    735 	rnd = random();
    736 	if (rnd < l1 && ctf_redflag.areanum && ctf_blueflag.areanum) {
    737 		bs->decisionmaker = bs->client;
    738 		bs->ordered = qfalse;
    739 		bs->ltgtype = LTG_GETFLAG;
    740 		//set the time the bot will stop getting the flag
    741 		bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME;
    742 		//get an alternative route goal towards the enemy base
    743 		BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
    744 		BotSetTeamStatus(bs);
    745 	}
    746 	else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) {
    747 		bs->decisionmaker = bs->client;
    748 		bs->ordered = qfalse;
    749 		//
    750 		if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
    751 		else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
    752 		//set the ltg type
    753 		bs->ltgtype = LTG_DEFENDKEYAREA;
    754 		//set the time the bot stops defending the base
    755 		bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
    756 		bs->defendaway_time = 0;
    757 		BotSetTeamStatus(bs);
    758 	}
    759 	else {
    760 		bs->ltgtype = 0;
    761 		//set the time the bot will stop roaming
    762 		bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME;
    763 		BotSetTeamStatus(bs);
    764 	}
    765 	bs->owndecision_time = FloatTime() + 5;
    766 #ifdef DEBUG
    767 	BotPrintTeamGoal(bs);
    768 #endif //DEBUG
    769 }
    770 
    771 /*
    772 ==================
    773 BotCTFRetreatGoals
    774 ==================
    775 */
    776 void BotCTFRetreatGoals(bot_state_t *bs) {
    777 	//when carrying a flag in ctf the bot should rush to the base
    778 	if (BotCTFCarryingFlag(bs)) {
    779 		//if not already rushing to the base
    780 		if (bs->ltgtype != LTG_RUSHBASE) {
    781 			BotRefuseOrder(bs);
    782 			bs->ltgtype = LTG_RUSHBASE;
    783 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
    784 			bs->rushbaseaway_time = 0;
    785 			bs->decisionmaker = bs->client;
    786 			bs->ordered = qfalse;
    787 			BotSetTeamStatus(bs);
    788 		}
    789 	}
    790 }
    791 
    792 #ifdef MISSIONPACK
    793 /*
    794 ==================
    795 Bot1FCTFSeekGoals
    796 ==================
    797 */
    798 void Bot1FCTFSeekGoals(bot_state_t *bs) {
    799 	aas_entityinfo_t entinfo;
    800 	float rnd, l1, l2;
    801 	int c;
    802 
    803 	//when carrying a flag in ctf the bot should rush to the base
    804 	if (Bot1FCTFCarryingFlag(bs)) {
    805 		//if not already rushing to the base
    806 		if (bs->ltgtype != LTG_RUSHBASE) {
    807 			BotRefuseOrder(bs);
    808 			bs->ltgtype = LTG_RUSHBASE;
    809 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
    810 			bs->rushbaseaway_time = 0;
    811 			bs->decisionmaker = bs->client;
    812 			bs->ordered = qfalse;
    813 			//get an alternative route goal towards the enemy base
    814 			BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
    815 			//
    816 			BotSetTeamStatus(bs);
    817 			BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG);
    818 		}
    819 		return;
    820 	}
    821 	// if the bot decided to follow someone
    822 	if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) {
    823 		// if the team mate being accompanied no longer carries the flag
    824 		BotEntityInfo(bs->teammate, &entinfo);
    825 		if (!EntityCarriesFlag(&entinfo)) {
    826 			bs->ltgtype = 0;
    827 		}
    828 	}
    829 	//our team has the flag
    830 	if (bs->neutralflagstatus == 1) {
    831 		if (bs->owndecision_time < FloatTime()) {
    832 			// if not already following someone
    833 			if (bs->ltgtype != LTG_TEAMACCOMPANY) {
    834 				//if there is a visible team mate flag carrier
    835 				c = BotTeamFlagCarrierVisible(bs);
    836 				if (c >= 0) {
    837 					BotRefuseOrder(bs);
    838 					//follow the flag carrier
    839 					bs->decisionmaker = bs->client;
    840 					bs->ordered = qfalse;
    841 					//the team mate
    842 					bs->teammate = c;
    843 					//last time the team mate was visible
    844 					bs->teammatevisible_time = FloatTime();
    845 					//no message
    846 					bs->teammessage_time = 0;
    847 					//no arrive message
    848 					bs->arrive_time = 1;
    849 					//
    850 					BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW);
    851 					//get the team goal time
    852 					bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME;
    853 					bs->ltgtype = LTG_TEAMACCOMPANY;
    854 					bs->formation_dist = 3.5 * 32;		//3.5 meter
    855 					BotSetTeamStatus(bs);
    856 					bs->owndecision_time = FloatTime() + 5;
    857 					return;
    858 				}
    859 			}
    860 			//if already a CTF or team goal
    861 			if (bs->ltgtype == LTG_TEAMHELP ||
    862 					bs->ltgtype == LTG_TEAMACCOMPANY ||
    863 					bs->ltgtype == LTG_DEFENDKEYAREA ||
    864 					bs->ltgtype == LTG_GETFLAG ||
    865 					bs->ltgtype == LTG_RUSHBASE ||
    866 					bs->ltgtype == LTG_CAMPORDER ||
    867 					bs->ltgtype == LTG_PATROL ||
    868 					bs->ltgtype == LTG_ATTACKENEMYBASE ||
    869 					bs->ltgtype == LTG_GETITEM ||
    870 					bs->ltgtype == LTG_MAKELOVE_UNDER ||
    871 					bs->ltgtype == LTG_MAKELOVE_ONTOP) {
    872 				return;
    873 			}
    874 			//if not already attacking the enemy base
    875 			if (bs->ltgtype != LTG_ATTACKENEMYBASE) {
    876 				BotRefuseOrder(bs);
    877 				bs->decisionmaker = bs->client;
    878 				bs->ordered = qfalse;
    879 				//
    880 				if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
    881 				else memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
    882 				//set the ltg type
    883 				bs->ltgtype = LTG_ATTACKENEMYBASE;
    884 				//set the time the bot will stop getting the flag
    885 				bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME;
    886 				BotSetTeamStatus(bs);
    887 				bs->owndecision_time = FloatTime() + 5;
    888 			}
    889 		}
    890 		return;
    891 	}
    892 	//enemy team has the flag
    893 	else if (bs->neutralflagstatus == 2) {
    894 		if (bs->owndecision_time < FloatTime()) {
    895 			c = BotEnemyFlagCarrierVisible(bs);
    896 			if (c >= 0) {
    897 				//FIXME: attack enemy flag carrier
    898 			}
    899 			//if already a CTF or team goal
    900 			if (bs->ltgtype == LTG_TEAMHELP ||
    901 					bs->ltgtype == LTG_TEAMACCOMPANY ||
    902 					bs->ltgtype == LTG_CAMPORDER ||
    903 					bs->ltgtype == LTG_PATROL ||
    904 					bs->ltgtype == LTG_GETITEM) {
    905 				return;
    906 			}
    907 			// if not already defending the base
    908 			if (bs->ltgtype != LTG_DEFENDKEYAREA) {
    909 				BotRefuseOrder(bs);
    910 				bs->decisionmaker = bs->client;
    911 				bs->ordered = qfalse;
    912 				//
    913 				if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
    914 				else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
    915 				//set the ltg type
    916 				bs->ltgtype = LTG_DEFENDKEYAREA;
    917 				//set the time the bot stops defending the base
    918 				bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
    919 				bs->defendaway_time = 0;
    920 				BotSetTeamStatus(bs);
    921 				bs->owndecision_time = FloatTime() + 5;
    922 			}
    923 		}
    924 		return;
    925 	}
    926 	// don't just do something wait for the bot team leader to give orders
    927 	if (BotTeamLeader(bs)) {
    928 		return;
    929 	}
    930 	// if the bot is ordered to do something
    931 	if ( bs->lastgoal_ltgtype ) {
    932 		bs->teamgoal_time += 60;
    933 	}
    934 	// if the bot decided to do something on it's own and has a last ordered goal
    935 	if ( !bs->ordered && bs->lastgoal_ltgtype ) {
    936 		bs->ltgtype = 0;
    937 	}
    938 	//if already a CTF or team goal
    939 	if (bs->ltgtype == LTG_TEAMHELP ||
    940 			bs->ltgtype == LTG_TEAMACCOMPANY ||
    941 			bs->ltgtype == LTG_DEFENDKEYAREA ||
    942 			bs->ltgtype == LTG_GETFLAG ||
    943 			bs->ltgtype == LTG_RUSHBASE ||
    944 			bs->ltgtype == LTG_RETURNFLAG ||
    945 			bs->ltgtype == LTG_CAMPORDER ||
    946 			bs->ltgtype == LTG_PATROL ||
    947 			bs->ltgtype == LTG_ATTACKENEMYBASE ||
    948 			bs->ltgtype == LTG_GETITEM ||
    949 			bs->ltgtype == LTG_MAKELOVE_UNDER ||
    950 			bs->ltgtype == LTG_MAKELOVE_ONTOP) {
    951 		return;
    952 	}
    953 	//
    954 	if (BotSetLastOrderedTask(bs))
    955 		return;
    956 	//
    957 	if (bs->owndecision_time > FloatTime())
    958 		return;;
    959 	//if the bot is roaming
    960 	if (bs->ctfroam_time > FloatTime())
    961 		return;
    962 	//if the bot has anough aggression to decide what to do
    963 	if (BotAggression(bs) < 50)
    964 		return;
    965 	//set the time to send a message to the team mates
    966 	bs->teammessage_time = FloatTime() + 2 * random();
    967 	//
    968 	if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) {
    969 		if (bs->teamtaskpreference & TEAMTP_ATTACKER) {
    970 			l1 = 0.7f;
    971 		}
    972 		else {
    973 			l1 = 0.2f;
    974 		}
    975 		l2 = 0.9f;
    976 	}
    977 	else {
    978 		l1 = 0.4f;
    979 		l2 = 0.7f;
    980 	}
    981 	//get the flag or defend the base
    982 	rnd = random();
    983 	if (rnd < l1 && ctf_neutralflag.areanum) {
    984 		bs->decisionmaker = bs->client;
    985 		bs->ordered = qfalse;
    986 		bs->ltgtype = LTG_GETFLAG;
    987 		//set the time the bot will stop getting the flag
    988 		bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME;
    989 		BotSetTeamStatus(bs);
    990 	}
    991 	else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) {
    992 		bs->decisionmaker = bs->client;
    993 		bs->ordered = qfalse;
    994 		//
    995 		if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
    996 		else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
    997 		//set the ltg type
    998 		bs->ltgtype = LTG_DEFENDKEYAREA;
    999 		//set the time the bot stops defending the base
   1000 		bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
   1001 		bs->defendaway_time = 0;
   1002 		BotSetTeamStatus(bs);
   1003 	}
   1004 	else {
   1005 		bs->ltgtype = 0;
   1006 		//set the time the bot will stop roaming
   1007 		bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME;
   1008 		BotSetTeamStatus(bs);
   1009 	}
   1010 	bs->owndecision_time = FloatTime() + 5;
   1011 #ifdef DEBUG
   1012 	BotPrintTeamGoal(bs);
   1013 #endif //DEBUG
   1014 }
   1015 
   1016 /*
   1017 ==================
   1018 Bot1FCTFRetreatGoals
   1019 ==================
   1020 */
   1021 void Bot1FCTFRetreatGoals(bot_state_t *bs) {
   1022 	//when carrying a flag in ctf the bot should rush to the enemy base
   1023 	if (Bot1FCTFCarryingFlag(bs)) {
   1024 		//if not already rushing to the base
   1025 		if (bs->ltgtype != LTG_RUSHBASE) {
   1026 			BotRefuseOrder(bs);
   1027 			bs->ltgtype = LTG_RUSHBASE;
   1028 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
   1029 			bs->rushbaseaway_time = 0;
   1030 			bs->decisionmaker = bs->client;
   1031 			bs->ordered = qfalse;
   1032 			//get an alternative route goal towards the enemy base
   1033 			BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
   1034 			BotSetTeamStatus(bs);
   1035 		}
   1036 	}
   1037 }
   1038 
   1039 /*
   1040 ==================
   1041 BotObeliskSeekGoals
   1042 ==================
   1043 */
   1044 void BotObeliskSeekGoals(bot_state_t *bs) {
   1045 	float rnd, l1, l2;
   1046 
   1047 	// don't just do something wait for the bot team leader to give orders
   1048 	if (BotTeamLeader(bs)) {
   1049 		return;
   1050 	}
   1051 	// if the bot is ordered to do something
   1052 	if ( bs->lastgoal_ltgtype ) {
   1053 		bs->teamgoal_time += 60;
   1054 	}
   1055 	//if already a team goal
   1056 	if (bs->ltgtype == LTG_TEAMHELP ||
   1057 			bs->ltgtype == LTG_TEAMACCOMPANY ||
   1058 			bs->ltgtype == LTG_DEFENDKEYAREA ||
   1059 			bs->ltgtype == LTG_GETFLAG ||
   1060 			bs->ltgtype == LTG_RUSHBASE ||
   1061 			bs->ltgtype == LTG_RETURNFLAG ||
   1062 			bs->ltgtype == LTG_CAMPORDER ||
   1063 			bs->ltgtype == LTG_PATROL ||
   1064 			bs->ltgtype == LTG_ATTACKENEMYBASE ||
   1065 			bs->ltgtype == LTG_GETITEM ||
   1066 			bs->ltgtype == LTG_MAKELOVE_UNDER ||
   1067 			bs->ltgtype == LTG_MAKELOVE_ONTOP) {
   1068 		return;
   1069 	}
   1070 	//
   1071 	if (BotSetLastOrderedTask(bs))
   1072 		return;
   1073 	//if the bot is roaming
   1074 	if (bs->ctfroam_time > FloatTime())
   1075 		return;
   1076 	//if the bot has anough aggression to decide what to do
   1077 	if (BotAggression(bs) < 50)
   1078 		return;
   1079 	//set the time to send a message to the team mates
   1080 	bs->teammessage_time = FloatTime() + 2 * random();
   1081 	//
   1082 	if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) {
   1083 		if (bs->teamtaskpreference & TEAMTP_ATTACKER) {
   1084 			l1 = 0.7f;
   1085 		}
   1086 		else {
   1087 			l1 = 0.2f;
   1088 		}
   1089 		l2 = 0.9f;
   1090 	}
   1091 	else {
   1092 		l1 = 0.4f;
   1093 		l2 = 0.7f;
   1094 	}
   1095 	//get the flag or defend the base
   1096 	rnd = random();
   1097 	if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) {
   1098 		bs->decisionmaker = bs->client;
   1099 		bs->ordered = qfalse;
   1100 		//
   1101 		if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t));
   1102 		else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t));
   1103 		//set the ltg type
   1104 		bs->ltgtype = LTG_ATTACKENEMYBASE;
   1105 		//set the time the bot will stop attacking the enemy base
   1106 		bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME;
   1107 		//get an alternate route goal towards the enemy base
   1108 		BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
   1109 		BotSetTeamStatus(bs);
   1110 	}
   1111 	else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) {
   1112 		bs->decisionmaker = bs->client;
   1113 		bs->ordered = qfalse;
   1114 		//
   1115 		if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t));
   1116 		else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t));
   1117 		//set the ltg type
   1118 		bs->ltgtype = LTG_DEFENDKEYAREA;
   1119 		//set the time the bot stops defending the base
   1120 		bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
   1121 		bs->defendaway_time = 0;
   1122 		BotSetTeamStatus(bs);
   1123 	}
   1124 	else {
   1125 		bs->ltgtype = 0;
   1126 		//set the time the bot will stop roaming
   1127 		bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME;
   1128 		BotSetTeamStatus(bs);
   1129 	}
   1130 }
   1131 
   1132 /*
   1133 ==================
   1134 BotGoHarvest
   1135 ==================
   1136 */
   1137 void BotGoHarvest(bot_state_t *bs) {
   1138 	//
   1139 	if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t));
   1140 	else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t));
   1141 	//set the ltg type
   1142 	bs->ltgtype = LTG_HARVEST;
   1143 	//set the time the bot will stop harvesting
   1144 	bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME;
   1145 	bs->harvestaway_time = 0;
   1146 	BotSetTeamStatus(bs);
   1147 }
   1148 
   1149 /*
   1150 ==================
   1151 BotObeliskRetreatGoals
   1152 ==================
   1153 */
   1154 void BotObeliskRetreatGoals(bot_state_t *bs) {
   1155 	//nothing special
   1156 }
   1157 
   1158 /*
   1159 ==================
   1160 BotHarvesterSeekGoals
   1161 ==================
   1162 */
   1163 void BotHarvesterSeekGoals(bot_state_t *bs) {
   1164 	aas_entityinfo_t entinfo;
   1165 	float rnd, l1, l2;
   1166 	int c;
   1167 
   1168 	//when carrying cubes in harvester the bot should rush to the base
   1169 	if (BotHarvesterCarryingCubes(bs)) {
   1170 		//if not already rushing to the base
   1171 		if (bs->ltgtype != LTG_RUSHBASE) {
   1172 			BotRefuseOrder(bs);
   1173 			bs->ltgtype = LTG_RUSHBASE;
   1174 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
   1175 			bs->rushbaseaway_time = 0;
   1176 			bs->decisionmaker = bs->client;
   1177 			bs->ordered = qfalse;
   1178 			//get an alternative route goal towards the enemy base
   1179 			BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
   1180 			//
   1181 			BotSetTeamStatus(bs);
   1182 		}
   1183 		return;
   1184 	}
   1185 	// don't just do something wait for the bot team leader to give orders
   1186 	if (BotTeamLeader(bs)) {
   1187 		return;
   1188 	}
   1189 	// if the bot decided to follow someone
   1190 	if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) {
   1191 		// if the team mate being accompanied no longer carries the flag
   1192 		BotEntityInfo(bs->teammate, &entinfo);
   1193 		if (!EntityCarriesCubes(&entinfo)) {
   1194 			bs->ltgtype = 0;
   1195 		}
   1196 	}
   1197 	// if the bot is ordered to do something
   1198 	if ( bs->lastgoal_ltgtype ) {
   1199 		bs->teamgoal_time += 60;
   1200 	}
   1201 	//if not yet doing something
   1202 	if (bs->ltgtype == LTG_TEAMHELP ||
   1203 			bs->ltgtype == LTG_TEAMACCOMPANY ||
   1204 			bs->ltgtype == LTG_DEFENDKEYAREA ||
   1205 			bs->ltgtype == LTG_GETFLAG ||
   1206 			bs->ltgtype == LTG_CAMPORDER ||
   1207 			bs->ltgtype == LTG_PATROL ||
   1208 			bs->ltgtype == LTG_ATTACKENEMYBASE ||
   1209 			bs->ltgtype == LTG_HARVEST ||
   1210 			bs->ltgtype == LTG_GETITEM ||
   1211 			bs->ltgtype == LTG_MAKELOVE_UNDER ||
   1212 			bs->ltgtype == LTG_MAKELOVE_ONTOP) {
   1213 		return;
   1214 	}
   1215 	//
   1216 	if (BotSetLastOrderedTask(bs))
   1217 		return;
   1218 	//if the bot is roaming
   1219 	if (bs->ctfroam_time > FloatTime())
   1220 		return;
   1221 	//if the bot has anough aggression to decide what to do
   1222 	if (BotAggression(bs) < 50)
   1223 		return;
   1224 	//set the time to send a message to the team mates
   1225 	bs->teammessage_time = FloatTime() + 2 * random();
   1226 	//
   1227 	c = BotEnemyCubeCarrierVisible(bs);
   1228 	if (c >= 0) {
   1229 		//FIXME: attack enemy cube carrier
   1230 	}
   1231 	if (bs->ltgtype != LTG_TEAMACCOMPANY) {
   1232 		//if there is a visible team mate carrying cubes
   1233 		c = BotTeamCubeCarrierVisible(bs);
   1234 		if (c >= 0) {
   1235 			//follow the team mate carrying cubes
   1236 			bs->decisionmaker = bs->client;
   1237 			bs->ordered = qfalse;
   1238 			//the team mate
   1239 			bs->teammate = c;
   1240 			//last time the team mate was visible
   1241 			bs->teammatevisible_time = FloatTime();
   1242 			//no message
   1243 			bs->teammessage_time = 0;
   1244 			//no arrive message
   1245 			bs->arrive_time = 1;
   1246 			//
   1247 			BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW);
   1248 			//get the team goal time
   1249 			bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME;
   1250 			bs->ltgtype = LTG_TEAMACCOMPANY;
   1251 			bs->formation_dist = 3.5 * 32;		//3.5 meter
   1252 			BotSetTeamStatus(bs);
   1253 			return;
   1254 		}
   1255 	}
   1256 	//
   1257 	if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) {
   1258 		if (bs->teamtaskpreference & TEAMTP_ATTACKER) {
   1259 			l1 = 0.7f;
   1260 		}
   1261 		else {
   1262 			l1 = 0.2f;
   1263 		}
   1264 		l2 = 0.9f;
   1265 	}
   1266 	else {
   1267 		l1 = 0.4f;
   1268 		l2 = 0.7f;
   1269 	}
   1270 	//
   1271 	rnd = random();
   1272 	if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) {
   1273 		bs->decisionmaker = bs->client;
   1274 		bs->ordered = qfalse;
   1275 		BotGoHarvest(bs);
   1276 	}
   1277 	else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) {
   1278 		bs->decisionmaker = bs->client;
   1279 		bs->ordered = qfalse;
   1280 		//
   1281 		if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t));
   1282 		else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t));
   1283 		//set the ltg type
   1284 		bs->ltgtype = LTG_DEFENDKEYAREA;
   1285 		//set the time the bot stops defending the base
   1286 		bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
   1287 		bs->defendaway_time = 0;
   1288 		BotSetTeamStatus(bs);
   1289 	}
   1290 	else {
   1291 		bs->ltgtype = 0;
   1292 		//set the time the bot will stop roaming
   1293 		bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME;
   1294 		BotSetTeamStatus(bs);
   1295 	}
   1296 }
   1297 
   1298 /*
   1299 ==================
   1300 BotHarvesterRetreatGoals
   1301 ==================
   1302 */
   1303 void BotHarvesterRetreatGoals(bot_state_t *bs) {
   1304 	//when carrying cubes in harvester the bot should rush to the base
   1305 	if (BotHarvesterCarryingCubes(bs)) {
   1306 		//if not already rushing to the base
   1307 		if (bs->ltgtype != LTG_RUSHBASE) {
   1308 			BotRefuseOrder(bs);
   1309 			bs->ltgtype = LTG_RUSHBASE;
   1310 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
   1311 			bs->rushbaseaway_time = 0;
   1312 			bs->decisionmaker = bs->client;
   1313 			bs->ordered = qfalse;
   1314 			BotSetTeamStatus(bs);
   1315 		}
   1316 		return;
   1317 	}
   1318 }
   1319 #endif
   1320 
   1321 /*
   1322 ==================
   1323 BotTeamGoals
   1324 ==================
   1325 */
   1326 void BotTeamGoals(bot_state_t *bs, int retreat) {
   1327 
   1328 	if ( retreat ) {
   1329 		if (gametype == GT_CTF) {
   1330 			BotCTFRetreatGoals(bs);
   1331 		}
   1332 #ifdef MISSIONPACK
   1333 		else if (gametype == GT_1FCTF) {
   1334 			Bot1FCTFRetreatGoals(bs);
   1335 		}
   1336 		else if (gametype == GT_OBELISK) {
   1337 			BotObeliskRetreatGoals(bs);
   1338 		}
   1339 		else if (gametype == GT_HARVESTER) {
   1340 			BotHarvesterRetreatGoals(bs);
   1341 		}
   1342 #endif
   1343 	}
   1344 	else {
   1345 		if (gametype == GT_CTF) {
   1346 			//decide what to do in CTF mode
   1347 			BotCTFSeekGoals(bs);
   1348 		}
   1349 #ifdef MISSIONPACK
   1350 		else if (gametype == GT_1FCTF) {
   1351 			Bot1FCTFSeekGoals(bs);
   1352 		}
   1353 		else if (gametype == GT_OBELISK) {
   1354 			BotObeliskSeekGoals(bs);
   1355 		}
   1356 		else if (gametype == GT_HARVESTER) {
   1357 			BotHarvesterSeekGoals(bs);
   1358 		}
   1359 #endif
   1360 	}
   1361 	// reset the order time which is used to see if
   1362 	// we decided to refuse an order
   1363 	bs->order_time = 0;
   1364 }
   1365 
   1366 /*
   1367 ==================
   1368 BotPointAreaNum
   1369 ==================
   1370 */
   1371 int BotPointAreaNum(vec3_t origin) {
   1372 	int areanum, numareas, areas[10];
   1373 	vec3_t end;
   1374 
   1375 	areanum = trap_AAS_PointAreaNum(origin);
   1376 	if (areanum) return areanum;
   1377 	VectorCopy(origin, end);
   1378 	end[2] += 10;
   1379 	numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10);
   1380 	if (numareas > 0) return areas[0];
   1381 	return 0;
   1382 }
   1383 
   1384 /*
   1385 ==================
   1386 ClientName
   1387 ==================
   1388 */
   1389 char *ClientName(int client, char *name, int size) {
   1390 	char buf[MAX_INFO_STRING];
   1391 
   1392 	if (client < 0 || client >= MAX_CLIENTS) {
   1393 		BotAI_Print(PRT_ERROR, "ClientName: client out of range\n");
   1394 		return "[client out of range]";
   1395 	}
   1396 	trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
   1397 	strncpy(name, Info_ValueForKey(buf, "n"), size-1);
   1398 	name[size-1] = '\0';
   1399 	Q_CleanStr( name );
   1400 	return name;
   1401 }
   1402 
   1403 /*
   1404 ==================
   1405 ClientSkin
   1406 ==================
   1407 */
   1408 char *ClientSkin(int client, char *skin, int size) {
   1409 	char buf[MAX_INFO_STRING];
   1410 
   1411 	if (client < 0 || client >= MAX_CLIENTS) {
   1412 		BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n");
   1413 		return "[client out of range]";
   1414 	}
   1415 	trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
   1416 	strncpy(skin, Info_ValueForKey(buf, "model"), size-1);
   1417 	skin[size-1] = '\0';
   1418 	return skin;
   1419 }
   1420 
   1421 /*
   1422 ==================
   1423 ClientFromName
   1424 ==================
   1425 */
   1426 int ClientFromName(char *name) {
   1427 	int i;
   1428 	char buf[MAX_INFO_STRING];
   1429 	static int maxclients;
   1430 
   1431 	if (!maxclients)
   1432 		maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
   1433 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
   1434 		trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
   1435 		Q_CleanStr( buf );
   1436 		if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i;
   1437 	}
   1438 	return -1;
   1439 }
   1440 
   1441 /*
   1442 ==================
   1443 ClientOnSameTeamFromName
   1444 ==================
   1445 */
   1446 int ClientOnSameTeamFromName(bot_state_t *bs, char *name) {
   1447 	int i;
   1448 	char buf[MAX_INFO_STRING];
   1449 	static int maxclients;
   1450 
   1451 	if (!maxclients)
   1452 		maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
   1453 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
   1454 		if (!BotSameTeam(bs, i))
   1455 			continue;
   1456 		trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
   1457 		Q_CleanStr( buf );
   1458 		if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i;
   1459 	}
   1460 	return -1;
   1461 }
   1462 
   1463 /*
   1464 ==================
   1465 stristr
   1466 ==================
   1467 */
   1468 char *stristr(char *str, char *charset) {
   1469 	int i;
   1470 
   1471 	while(*str) {
   1472 		for (i = 0; charset[i] && str[i]; i++) {
   1473 			if (toupper(charset[i]) != toupper(str[i])) break;
   1474 		}
   1475 		if (!charset[i]) return str;
   1476 		str++;
   1477 	}
   1478 	return NULL;
   1479 }
   1480 
   1481 /*
   1482 ==================
   1483 EasyClientName
   1484 ==================
   1485 */
   1486 char *EasyClientName(int client, char *buf, int size) {
   1487 	int i;
   1488 	char *str1, *str2, *ptr, c;
   1489 	char name[128];
   1490 
   1491 	strcpy(name, ClientName(client, name, sizeof(name)));
   1492 	for (i = 0; name[i]; i++) name[i] &= 127;
   1493 	//remove all spaces
   1494 	for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) {
   1495 		memmove(ptr, ptr+1, strlen(ptr+1)+1);
   1496 	}
   1497 	//check for [x] and ]x[ clan names
   1498 	str1 = strstr(name, "[");
   1499 	str2 = strstr(name, "]");
   1500 	if (str1 && str2) {
   1501 		if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1);
   1502 		else memmove(str2, str1+1, strlen(str1+1)+1);
   1503 	}
   1504 	//remove Mr prefix
   1505 	if ((name[0] == 'm' || name[0] == 'M') &&
   1506 			(name[1] == 'r' || name[1] == 'R')) {
   1507 		memmove(name, name+2, strlen(name+2)+1);
   1508 	}
   1509 	//only allow lower case alphabet characters
   1510 	ptr = name;
   1511 	while(*ptr) {
   1512 		c = *ptr;
   1513 		if ((c >= 'a' && c <= 'z') ||
   1514 				(c >= '0' && c <= '9') || c == '_') {
   1515 			ptr++;
   1516 		}
   1517 		else if (c >= 'A' && c <= 'Z') {
   1518 			*ptr += 'a' - 'A';
   1519 			ptr++;
   1520 		}
   1521 		else {
   1522 			memmove(ptr, ptr+1, strlen(ptr + 1)+1);
   1523 		}
   1524 	}
   1525 	strncpy(buf, name, size-1);
   1526 	buf[size-1] = '\0';
   1527 	return buf;
   1528 }
   1529 
   1530 /*
   1531 ==================
   1532 BotSynonymContext
   1533 ==================
   1534 */
   1535 int BotSynonymContext(bot_state_t *bs) {
   1536 	int context;
   1537 
   1538 	context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES;
   1539 	//
   1540 	if (gametype == GT_CTF
   1541 #ifdef MISSIONPACK
   1542 		|| gametype == GT_1FCTF
   1543 #endif
   1544 		) {
   1545 		if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_CTFREDTEAM;
   1546 		else context |= CONTEXT_CTFBLUETEAM;
   1547 	}
   1548 #ifdef MISSIONPACK
   1549 	else if (gametype == GT_OBELISK) {
   1550 		if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_OBELISKREDTEAM;
   1551 		else context |= CONTEXT_OBELISKBLUETEAM;
   1552 	}
   1553 	else if (gametype == GT_HARVESTER) {
   1554 		if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_HARVESTERREDTEAM;
   1555 		else context |= CONTEXT_HARVESTERBLUETEAM;
   1556 	}
   1557 #endif
   1558 	return context;
   1559 }
   1560 
   1561 /*
   1562 ==================
   1563 BotChooseWeapon
   1564 ==================
   1565 */
   1566 void BotChooseWeapon(bot_state_t *bs) {
   1567 	int newweaponnum;
   1568 
   1569 	if (bs->cur_ps.weaponstate == WEAPON_RAISING ||
   1570 			bs->cur_ps.weaponstate == WEAPON_DROPPING) {
   1571 		trap_EA_SelectWeapon(bs->client, bs->weaponnum);
   1572 	}
   1573 	else {
   1574 		newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory);
   1575 		if (bs->weaponnum != newweaponnum) bs->weaponchange_time = FloatTime();
   1576 		bs->weaponnum = newweaponnum;
   1577 		//BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum);
   1578 		trap_EA_SelectWeapon(bs->client, bs->weaponnum);
   1579 	}
   1580 }
   1581 
   1582 /*
   1583 ==================
   1584 BotSetupForMovement
   1585 ==================
   1586 */
   1587 void BotSetupForMovement(bot_state_t *bs) {
   1588 	bot_initmove_t initmove;
   1589 
   1590 	memset(&initmove, 0, sizeof(bot_initmove_t));
   1591 	VectorCopy(bs->cur_ps.origin, initmove.origin);
   1592 	VectorCopy(bs->cur_ps.velocity, initmove.velocity);
   1593 	VectorClear(initmove.viewoffset);
   1594 	initmove.viewoffset[2] += bs->cur_ps.viewheight;
   1595 	initmove.entitynum = bs->entitynum;
   1596 	initmove.client = bs->client;
   1597 	initmove.thinktime = bs->thinktime;
   1598 	//set the onground flag
   1599 	if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND;
   1600 	//set the teleported flag
   1601 	if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) {
   1602 		initmove.or_moveflags |= MFL_TELEPORTED;
   1603 	}
   1604 	//set the waterjump flag
   1605 	if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) {
   1606 		initmove.or_moveflags |= MFL_WATERJUMP;
   1607 	}
   1608 	//set presence type
   1609 	if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH;
   1610 	else initmove.presencetype = PRESENCE_NORMAL;
   1611 	//
   1612 	if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK;
   1613 	//
   1614 	VectorCopy(bs->viewangles, initmove.viewangles);
   1615 	//
   1616 	trap_BotInitMoveState(bs->ms, &initmove);
   1617 }
   1618 
   1619 /*
   1620 ==================
   1621 BotCheckItemPickup
   1622 ==================
   1623 */
   1624 void BotCheckItemPickup(bot_state_t *bs, int *oldinventory) {
   1625 #ifdef MISSIONPACK
   1626 	int offence, leader;
   1627 
   1628 	if (gametype <= GT_TEAM)
   1629 		return;
   1630 
   1631 	offence = -1;
   1632 	// go into offence if picked up the kamikaze or invulnerability
   1633 	if (!oldinventory[INVENTORY_KAMIKAZE] && bs->inventory[INVENTORY_KAMIKAZE] >= 1) {
   1634 		offence = qtrue;
   1635 	}
   1636 	if (!oldinventory[INVENTORY_INVULNERABILITY] && bs->inventory[INVENTORY_INVULNERABILITY] >= 1) {
   1637 		offence = qtrue;
   1638 	}
   1639 	// if not already wearing the kamikaze or invulnerability
   1640 	if (!bs->inventory[INVENTORY_KAMIKAZE] && !bs->inventory[INVENTORY_INVULNERABILITY]) {
   1641 		if (!oldinventory[INVENTORY_SCOUT] && bs->inventory[INVENTORY_SCOUT] >= 1) {
   1642 			offence = qtrue;
   1643 		}
   1644 		if (!oldinventory[INVENTORY_GUARD] && bs->inventory[INVENTORY_GUARD] >= 1) {
   1645 			offence = qtrue;
   1646 		}
   1647 		if (!oldinventory[INVENTORY_DOUBLER] && bs->inventory[INVENTORY_DOUBLER] >= 1) {
   1648 			offence = qfalse;
   1649 		}
   1650 		if (!oldinventory[INVENTORY_AMMOREGEN] && bs->inventory[INVENTORY_AMMOREGEN] >= 1) {
   1651 			offence = qfalse;
   1652 		}
   1653 	}
   1654 
   1655 	if (offence >= 0) {
   1656 		leader = ClientFromName(bs->teamleader);
   1657 		if (offence) {
   1658 			if (!(bs->teamtaskpreference & TEAMTP_ATTACKER)) {
   1659 				// if we have a bot team leader
   1660 				if (BotTeamLeader(bs)) {
   1661 					// tell the leader we want to be on offence
   1662 					BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE);
   1663 					//BotAI_BotInitialChat(bs, "wantoffence", NULL);
   1664 					//trap_BotEnterChat(bs->cs, leader, CHAT_TELL);
   1665 				}
   1666 				else if (g_spSkill.integer <= 3) {
   1667 					if ( bs->ltgtype != LTG_GETFLAG &&
   1668 						 bs->ltgtype != LTG_ATTACKENEMYBASE &&
   1669 						 bs->ltgtype != LTG_HARVEST ) {
   1670 						//
   1671 						if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) &&
   1672 							(gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) {
   1673 							// tell the leader we want to be on offence
   1674 							BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE);
   1675 							//BotAI_BotInitialChat(bs, "wantoffence", NULL);
   1676 							//trap_BotEnterChat(bs->cs, leader, CHAT_TELL);
   1677 						}
   1678 					}
   1679 					bs->teamtaskpreference |= TEAMTP_ATTACKER;
   1680 				}
   1681 			}
   1682 			bs->teamtaskpreference &= ~TEAMTP_DEFENDER;
   1683 		}
   1684 		else {
   1685 			if (!(bs->teamtaskpreference & TEAMTP_DEFENDER)) {
   1686 				// if we have a bot team leader
   1687 				if (BotTeamLeader(bs)) {
   1688 					// tell the leader we want to be on defense
   1689 					BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE);
   1690 					//BotAI_BotInitialChat(bs, "wantdefence", NULL);
   1691 					//trap_BotEnterChat(bs->cs, leader, CHAT_TELL);
   1692 				}
   1693 				else if (g_spSkill.integer <= 3) {
   1694 					if ( bs->ltgtype != LTG_DEFENDKEYAREA ) {
   1695 						//
   1696 						if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) &&
   1697 							(gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) {
   1698 							// tell the leader we want to be on defense
   1699 							BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE);
   1700 							//BotAI_BotInitialChat(bs, "wantdefence", NULL);
   1701 							//trap_BotEnterChat(bs->cs, leader, CHAT_TELL);
   1702 						}
   1703 					}
   1704 				}
   1705 				bs->teamtaskpreference |= TEAMTP_DEFENDER;
   1706 			}
   1707 			bs->teamtaskpreference &= ~TEAMTP_ATTACKER;
   1708 		}
   1709 	}
   1710 #endif
   1711 }
   1712 
   1713 /*
   1714 ==================
   1715 BotUpdateInventory
   1716 ==================
   1717 */
   1718 void BotUpdateInventory(bot_state_t *bs) {
   1719 	int oldinventory[MAX_ITEMS];
   1720 
   1721 	memcpy(oldinventory, bs->inventory, sizeof(oldinventory));
   1722 	//armor
   1723 	bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR];
   1724 	//weapons
   1725 	bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0;
   1726 	bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0;
   1727 	bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0;
   1728 	bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0;
   1729 	bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0;
   1730 	bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0;
   1731 	bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0;
   1732 	bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0;
   1733 	bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0;
   1734 	bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0;
   1735 #ifdef MISSIONPACK
   1736 	bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;;
   1737 	bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;;
   1738 	bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;;
   1739 #endif
   1740 	//ammo
   1741 	bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN];
   1742 	bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN];
   1743 	bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER];
   1744 	bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN];
   1745 	bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING];
   1746 	bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER];
   1747 	bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN];
   1748 	bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG];
   1749 #ifdef MISSIONPACK
   1750 	bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN];
   1751 	bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER];
   1752 	bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN];
   1753 #endif
   1754 	//powerups
   1755 	bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH];
   1756 	bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER;
   1757 	bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT;
   1758 #ifdef MISSIONPACK
   1759 	bs->inventory[INVENTORY_KAMIKAZE] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_KAMIKAZE;
   1760 	bs->inventory[INVENTORY_PORTAL] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_PORTAL;
   1761 	bs->inventory[INVENTORY_INVULNERABILITY] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_INVULNERABILITY;
   1762 #endif
   1763 	bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0;
   1764 	bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0;
   1765 	bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0;
   1766 	bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0;
   1767 	bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0;
   1768 	bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0;
   1769 #ifdef MISSIONPACK
   1770 	bs->inventory[INVENTORY_SCOUT] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_SCOUT;
   1771 	bs->inventory[INVENTORY_GUARD] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_GUARD;
   1772 	bs->inventory[INVENTORY_DOUBLER] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_DOUBLER;
   1773 	bs->inventory[INVENTORY_AMMOREGEN] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_AMMOREGEN;
   1774 #endif
   1775 	bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0;
   1776 	bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0;
   1777 #ifdef MISSIONPACK
   1778 	bs->inventory[INVENTORY_NEUTRALFLAG] = bs->cur_ps.powerups[PW_NEUTRALFLAG] != 0;
   1779 	if (BotTeam(bs) == TEAM_RED) {
   1780 		bs->inventory[INVENTORY_REDCUBE] = bs->cur_ps.generic1;
   1781 		bs->inventory[INVENTORY_BLUECUBE] = 0;
   1782 	}
   1783 	else {
   1784 		bs->inventory[INVENTORY_REDCUBE] = 0;
   1785 		bs->inventory[INVENTORY_BLUECUBE] = bs->cur_ps.generic1;
   1786 	}
   1787 #endif
   1788 	BotCheckItemPickup(bs, oldinventory);
   1789 }
   1790 
   1791 /*
   1792 ==================
   1793 BotUpdateBattleInventory
   1794 ==================
   1795 */
   1796 void BotUpdateBattleInventory(bot_state_t *bs, int enemy) {
   1797 	vec3_t dir;
   1798 	aas_entityinfo_t entinfo;
   1799 
   1800 	BotEntityInfo(enemy, &entinfo);
   1801 	VectorSubtract(entinfo.origin, bs->origin, dir);
   1802 	bs->inventory[ENEMY_HEIGHT] = (int) dir[2];
   1803 	dir[2] = 0;
   1804 	bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir);
   1805 	//FIXME: add num visible enemies and num visible team mates to the inventory
   1806 }
   1807 
   1808 #ifdef MISSIONPACK
   1809 /*
   1810 ==================
   1811 BotUseKamikaze
   1812 ==================
   1813 */
   1814 #define KAMIKAZE_DIST		1024
   1815 
   1816 void BotUseKamikaze(bot_state_t *bs) {
   1817 	int c, teammates, enemies;
   1818 	aas_entityinfo_t entinfo;
   1819 	vec3_t dir, target;
   1820 	bot_goal_t *goal;
   1821 	bsp_trace_t trace;
   1822 
   1823 	//if the bot has no kamikaze
   1824 	if (bs->inventory[INVENTORY_KAMIKAZE] <= 0)
   1825 		return;
   1826 	if (bs->kamikaze_time > FloatTime())
   1827 		return;
   1828 	bs->kamikaze_time = FloatTime() + 0.2;
   1829 	if (gametype == GT_CTF) {
   1830 		//never use kamikaze if the team flag carrier is visible
   1831 		if (BotCTFCarryingFlag(bs))
   1832 			return;
   1833 		c = BotTeamFlagCarrierVisible(bs);
   1834 		if (c >= 0) {
   1835 			BotEntityInfo(c, &entinfo);
   1836 			VectorSubtract(entinfo.origin, bs->origin, dir);
   1837 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST))
   1838 				return;
   1839 		}
   1840 		c = BotEnemyFlagCarrierVisible(bs);
   1841 		if (c >= 0) {
   1842 			BotEntityInfo(c, &entinfo);
   1843 			VectorSubtract(entinfo.origin, bs->origin, dir);
   1844 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) {
   1845 				trap_EA_Use(bs->client);
   1846 				return;
   1847 			}
   1848 		}
   1849 	}
   1850 	else if (gametype == GT_1FCTF) {
   1851 		//never use kamikaze if the team flag carrier is visible
   1852 		if (Bot1FCTFCarryingFlag(bs))
   1853 			return;
   1854 		c = BotTeamFlagCarrierVisible(bs);
   1855 		if (c >= 0) {
   1856 			BotEntityInfo(c, &entinfo);
   1857 			VectorSubtract(entinfo.origin, bs->origin, dir);
   1858 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST))
   1859 				return;
   1860 		}
   1861 		c = BotEnemyFlagCarrierVisible(bs);
   1862 		if (c >= 0) {
   1863 			BotEntityInfo(c, &entinfo);
   1864 			VectorSubtract(entinfo.origin, bs->origin, dir);
   1865 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) {
   1866 				trap_EA_Use(bs->client);
   1867 				return;
   1868 			}
   1869 		}
   1870 	}
   1871 	else if (gametype == GT_OBELISK) {
   1872 		switch(BotTeam(bs)) {
   1873 			case TEAM_RED: goal = &blueobelisk; break;
   1874 			default: goal = &redobelisk; break;
   1875 		}
   1876 		//if the obelisk is visible
   1877 		VectorCopy(goal->origin, target);
   1878 		target[2] += 1;
   1879 		VectorSubtract(bs->origin, target, dir);
   1880 		if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST * 0.9)) {
   1881 			BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
   1882 			if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
   1883 				trap_EA_Use(bs->client);
   1884 				return;
   1885 			}
   1886 		}
   1887 	}
   1888 	else if (gametype == GT_HARVESTER) {
   1889 		//
   1890 		if (BotHarvesterCarryingCubes(bs))
   1891 			return;
   1892 		//never use kamikaze if a team mate carrying cubes is visible
   1893 		c = BotTeamCubeCarrierVisible(bs);
   1894 		if (c >= 0) {
   1895 			BotEntityInfo(c, &entinfo);
   1896 			VectorSubtract(entinfo.origin, bs->origin, dir);
   1897 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST))
   1898 				return;
   1899 		}
   1900 		c = BotEnemyCubeCarrierVisible(bs);
   1901 		if (c >= 0) {
   1902 			BotEntityInfo(c, &entinfo);
   1903 			VectorSubtract(entinfo.origin, bs->origin, dir);
   1904 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) {
   1905 				trap_EA_Use(bs->client);
   1906 				return;
   1907 			}
   1908 		}
   1909 	}
   1910 	//
   1911 	BotVisibleTeamMatesAndEnemies(bs, &teammates, &enemies, KAMIKAZE_DIST);
   1912 	//
   1913 	if (enemies > 2 && enemies > teammates+1) {
   1914 		trap_EA_Use(bs->client);
   1915 		return;
   1916 	}
   1917 }
   1918 
   1919 /*
   1920 ==================
   1921 BotUseInvulnerability
   1922 ==================
   1923 */
   1924 void BotUseInvulnerability(bot_state_t *bs) {
   1925 	int c;
   1926 	vec3_t dir, target;
   1927 	bot_goal_t *goal;
   1928 	bsp_trace_t trace;
   1929 
   1930 	//if the bot has no invulnerability
   1931 	if (bs->inventory[INVENTORY_INVULNERABILITY] <= 0)
   1932 		return;
   1933 	if (bs->invulnerability_time > FloatTime())
   1934 		return;
   1935 	bs->invulnerability_time = FloatTime() + 0.2;
   1936 	if (gametype == GT_CTF) {
   1937 		//never use kamikaze if the team flag carrier is visible
   1938 		if (BotCTFCarryingFlag(bs))
   1939 			return;
   1940 		c = BotEnemyFlagCarrierVisible(bs);
   1941 		if (c >= 0)
   1942 			return;
   1943 		//if near enemy flag and the flag is visible
   1944 		switch(BotTeam(bs)) {
   1945 			case TEAM_RED: goal = &ctf_blueflag; break;
   1946 			default: goal = &ctf_redflag; break;
   1947 		}
   1948 		//if the obelisk is visible
   1949 		VectorCopy(goal->origin, target);
   1950 		target[2] += 1;
   1951 		VectorSubtract(bs->origin, target, dir);
   1952 		if (VectorLengthSquared(dir) < Square(200)) {
   1953 			BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
   1954 			if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
   1955 				trap_EA_Use(bs->client);
   1956 				return;
   1957 			}
   1958 		}
   1959 	}
   1960 	else if (gametype == GT_1FCTF) {
   1961 		//never use kamikaze if the team flag carrier is visible
   1962 		if (Bot1FCTFCarryingFlag(bs))
   1963 			return;
   1964 		c = BotEnemyFlagCarrierVisible(bs);
   1965 		if (c >= 0)
   1966 			return;
   1967 		//if near enemy flag and the flag is visible
   1968 		switch(BotTeam(bs)) {
   1969 			case TEAM_RED: goal = &ctf_blueflag; break;
   1970 			default: goal = &ctf_redflag; break;
   1971 		}
   1972 		//if the obelisk is visible
   1973 		VectorCopy(goal->origin, target);
   1974 		target[2] += 1;
   1975 		VectorSubtract(bs->origin, target, dir);
   1976 		if (VectorLengthSquared(dir) < Square(200)) {
   1977 			BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
   1978 			if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
   1979 				trap_EA_Use(bs->client);
   1980 				return;
   1981 			}
   1982 		}
   1983 	}
   1984 	else if (gametype == GT_OBELISK) {
   1985 		switch(BotTeam(bs)) {
   1986 			case TEAM_RED: goal = &blueobelisk; break;
   1987 			default: goal = &redobelisk; break;
   1988 		}
   1989 		//if the obelisk is visible
   1990 		VectorCopy(goal->origin, target);
   1991 		target[2] += 1;
   1992 		VectorSubtract(bs->origin, target, dir);
   1993 		if (VectorLengthSquared(dir) < Square(300)) {
   1994 			BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
   1995 			if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
   1996 				trap_EA_Use(bs->client);
   1997 				return;
   1998 			}
   1999 		}
   2000 	}
   2001 	else if (gametype == GT_HARVESTER) {
   2002 		//
   2003 		if (BotHarvesterCarryingCubes(bs))
   2004 			return;
   2005 		c = BotEnemyCubeCarrierVisible(bs);
   2006 		if (c >= 0)
   2007 			return;
   2008 		//if near enemy base and enemy base is visible
   2009 		switch(BotTeam(bs)) {
   2010 			case TEAM_RED: goal = &blueobelisk; break;
   2011 			default: goal = &redobelisk; break;
   2012 		}
   2013 		//if the obelisk is visible
   2014 		VectorCopy(goal->origin, target);
   2015 		target[2] += 1;
   2016 		VectorSubtract(bs->origin, target, dir);
   2017 		if (VectorLengthSquared(dir) < Square(200)) {
   2018 			BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
   2019 			if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
   2020 				trap_EA_Use(bs->client);
   2021 				return;
   2022 			}
   2023 		}
   2024 	}
   2025 }
   2026 #endif
   2027 
   2028 /*
   2029 ==================
   2030 BotBattleUseItems
   2031 ==================
   2032 */
   2033 void BotBattleUseItems(bot_state_t *bs) {
   2034 	if (bs->inventory[INVENTORY_HEALTH] < 40) {
   2035 		if (bs->inventory[INVENTORY_TELEPORTER] > 0) {
   2036 			if (!BotCTFCarryingFlag(bs)
   2037 #ifdef MISSIONPACK
   2038 				&& !Bot1FCTFCarryingFlag(bs)
   2039 				&& !BotHarvesterCarryingCubes(bs)
   2040 #endif
   2041 				) {
   2042 				trap_EA_Use(bs->client);
   2043 			}
   2044 		}
   2045 	}
   2046 	if (bs->inventory[INVENTORY_HEALTH] < 60) {
   2047 		if (bs->inventory[INVENTORY_MEDKIT] > 0) {
   2048 			trap_EA_Use(bs->client);
   2049 		}
   2050 	}
   2051 #ifdef MISSIONPACK
   2052 	BotUseKamikaze(bs);
   2053 	BotUseInvulnerability(bs);
   2054 #endif
   2055 }
   2056 
   2057 /*
   2058 ==================
   2059 BotSetTeleportTime
   2060 ==================
   2061 */
   2062 void BotSetTeleportTime(bot_state_t *bs) {
   2063 	if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) {
   2064 		bs->teleport_time = FloatTime();
   2065 	}
   2066 	bs->last_eFlags = bs->cur_ps.eFlags;
   2067 }
   2068 
   2069 /*
   2070 ==================
   2071 BotIsDead
   2072 ==================
   2073 */
   2074 qboolean BotIsDead(bot_state_t *bs) {
   2075 	return (bs->cur_ps.pm_type == PM_DEAD);
   2076 }
   2077 
   2078 /*
   2079 ==================
   2080 BotIsObserver
   2081 ==================
   2082 */
   2083 qboolean BotIsObserver(bot_state_t *bs) {
   2084 	char buf[MAX_INFO_STRING];
   2085 	if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue;
   2086 	trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf));
   2087 	if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue;
   2088 	return qfalse;
   2089 }
   2090 
   2091 /*
   2092 ==================
   2093 BotIntermission
   2094 ==================
   2095 */
   2096 qboolean BotIntermission(bot_state_t *bs) {
   2097 	//NOTE: we shouldn't be looking at the game code...
   2098 	if (level.intermissiontime) return qtrue;
   2099 	return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION);
   2100 }
   2101 
   2102 /*
   2103 ==================
   2104 BotInLavaOrSlime
   2105 ==================
   2106 */
   2107 qboolean BotInLavaOrSlime(bot_state_t *bs) {
   2108 	vec3_t feet;
   2109 
   2110 	VectorCopy(bs->origin, feet);
   2111 	feet[2] -= 23;
   2112 	return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME));
   2113 }
   2114 
   2115 /*
   2116 ==================
   2117 BotCreateWayPoint
   2118 ==================
   2119 */
   2120 bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) {
   2121 	bot_waypoint_t *wp;
   2122 	vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8};
   2123 
   2124 	wp = botai_freewaypoints;
   2125 	if ( !wp ) {
   2126 		BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" );
   2127 		return NULL;
   2128 	}
   2129 	botai_freewaypoints = botai_freewaypoints->next;
   2130 
   2131 	Q_strncpyz( wp->name, name, sizeof(wp->name) );
   2132 	VectorCopy(origin, wp->goal.origin);
   2133 	VectorCopy(waypointmins, wp->goal.mins);
   2134 	VectorCopy(waypointmaxs, wp->goal.maxs);
   2135 	wp->goal.areanum = areanum;
   2136 	wp->next = NULL;
   2137 	wp->prev = NULL;
   2138 	return wp;
   2139 }
   2140 
   2141 /*
   2142 ==================
   2143 BotFindWayPoint
   2144 ==================
   2145 */
   2146 bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) {
   2147 	bot_waypoint_t *wp;
   2148 
   2149 	for (wp = waypoints; wp; wp = wp->next) {
   2150 		if (!Q_stricmp(wp->name, name)) return wp;
   2151 	}
   2152 	return NULL;
   2153 }
   2154 
   2155 /*
   2156 ==================
   2157 BotFreeWaypoints
   2158 ==================
   2159 */
   2160 void BotFreeWaypoints(bot_waypoint_t *wp) {
   2161 	bot_waypoint_t *nextwp;
   2162 
   2163 	for (; wp; wp = nextwp) {
   2164 		nextwp = wp->next;
   2165 		wp->next = botai_freewaypoints;
   2166 		botai_freewaypoints = wp;
   2167 	}
   2168 }
   2169 
   2170 /*
   2171 ==================
   2172 BotInitWaypoints
   2173 ==================
   2174 */
   2175 void BotInitWaypoints(void) {
   2176 	int i;
   2177 
   2178 	botai_freewaypoints = NULL;
   2179 	for (i = 0; i < MAX_WAYPOINTS; i++) {
   2180 		botai_waypoints[i].next = botai_freewaypoints;
   2181 		botai_freewaypoints = &botai_waypoints[i];
   2182 	}
   2183 }
   2184 
   2185 /*
   2186 ==================
   2187 TeamPlayIsOn
   2188 ==================
   2189 */
   2190 int TeamPlayIsOn(void) {
   2191 	return ( gametype >= GT_TEAM );
   2192 }
   2193 
   2194 /*
   2195 ==================
   2196 BotAggression
   2197 ==================
   2198 */
   2199 float BotAggression(bot_state_t *bs) {
   2200 	//if the bot has quad
   2201 	if (bs->inventory[INVENTORY_QUAD]) {
   2202 		//if the bot is not holding the gauntlet or the enemy is really nearby
   2203 		if (bs->weaponnum != WP_GAUNTLET ||
   2204 			bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) {
   2205 			return 70;
   2206 		}
   2207 	}
   2208 	//if the enemy is located way higher than the bot
   2209 	if (bs->inventory[ENEMY_HEIGHT] > 200) return 0;
   2210 	//if the bot is very low on health
   2211 	if (bs->inventory[INVENTORY_HEALTH] < 60) return 0;
   2212 	//if the bot is low on health
   2213 	if (bs->inventory[INVENTORY_HEALTH] < 80) {
   2214 		//if the bot has insufficient armor
   2215 		if (bs->inventory[INVENTORY_ARMOR] < 40) return 0;
   2216 	}
   2217 	//if the bot can use the bfg
   2218 	if (bs->inventory[INVENTORY_BFG10K] > 0 &&
   2219 			bs->inventory[INVENTORY_BFGAMMO] > 7) return 100;
   2220 	//if the bot can use the railgun
   2221 	if (bs->inventory[INVENTORY_RAILGUN] > 0 &&
   2222 			bs->inventory[INVENTORY_SLUGS] > 5) return 95;
   2223 	//if the bot can use the lightning gun
   2224 	if (bs->inventory[INVENTORY_LIGHTNING] > 0 &&
   2225 			bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 90;
   2226 	//if the bot can use the rocketlauncher
   2227 	if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 &&
   2228 			bs->inventory[INVENTORY_ROCKETS] > 5) return 90;
   2229 	//if the bot can use the plasmagun
   2230 	if (bs->inventory[INVENTORY_PLASMAGUN] > 0 &&
   2231 			bs->inventory[INVENTORY_CELLS] > 40) return 85;
   2232 	//if the bot can use the grenade launcher
   2233 	if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 &&
   2234 			bs->inventory[INVENTORY_GRENADES] > 10) return 80;
   2235 	//if the bot can use the shotgun
   2236 	if (bs->inventory[INVENTORY_SHOTGUN] > 0 &&
   2237 			bs->inventory[INVENTORY_SHELLS] > 10) return 50;
   2238 	//otherwise the bot is not feeling too good
   2239 	return 0;
   2240 }
   2241 
   2242 /*
   2243 ==================
   2244 BotFeelingBad
   2245 ==================
   2246 */
   2247 float BotFeelingBad(bot_state_t *bs) {
   2248 	if (bs->weaponnum == WP_GAUNTLET) {
   2249 		return 100;
   2250 	}
   2251 	if (bs->inventory[INVENTORY_HEALTH] < 40) {
   2252 		return 100;
   2253 	}
   2254 	if (bs->weaponnum == WP_MACHINEGUN) {
   2255 		return 90;
   2256 	}
   2257 	if (bs->inventory[INVENTORY_HEALTH] < 60) {
   2258 		return 80;
   2259 	}
   2260 	return 0;
   2261 }
   2262 
   2263 /*
   2264 ==================
   2265 BotWantsToRetreat
   2266 ==================
   2267 */
   2268 int BotWantsToRetreat(bot_state_t *bs) {
   2269 	aas_entityinfo_t entinfo;
   2270 
   2271 	if (gametype == GT_CTF) {
   2272 		//always retreat when carrying a CTF flag
   2273 		if (BotCTFCarryingFlag(bs))
   2274 			return qtrue;
   2275 	}
   2276 #ifdef MISSIONPACK
   2277 	else if (gametype == GT_1FCTF) {
   2278 		//if carrying the flag then always retreat
   2279 		if (Bot1FCTFCarryingFlag(bs))
   2280 			return qtrue;
   2281 	}
   2282 	else if (gametype == GT_OBELISK) {
   2283 		//the bots should be dedicated to attacking the enemy obelisk
   2284 		if (bs->ltgtype == LTG_ATTACKENEMYBASE) {
   2285 			if (bs->enemy != redobelisk.entitynum ||
   2286 						bs->enemy != blueobelisk.entitynum) {
   2287 				return qtrue;
   2288 			}
   2289 		}
   2290 		if (BotFeelingBad(bs) > 50) {
   2291 			return qtrue;
   2292 		}
   2293 		return qfalse;
   2294 	}
   2295 	else if (gametype == GT_HARVESTER) {
   2296 		//if carrying cubes then always retreat
   2297 		if (BotHarvesterCarryingCubes(bs)) return qtrue;
   2298 	}
   2299 #endif
   2300 	//
   2301 	if (bs->enemy >= 0) {
   2302 		//if the enemy is carrying a flag
   2303 		BotEntityInfo(bs->enemy, &entinfo);
   2304 		if (EntityCarriesFlag(&entinfo))
   2305 			return qfalse;
   2306 	}
   2307 	//if the bot is getting the flag
   2308 	if (bs->ltgtype == LTG_GETFLAG)
   2309 		return qtrue;
   2310 	//
   2311 	if (BotAggression(bs) < 50)
   2312 		return qtrue;
   2313 	return qfalse;
   2314 }
   2315 
   2316 /*
   2317 ==================
   2318 BotWantsToChase
   2319 ==================
   2320 */
   2321 int BotWantsToChase(bot_state_t *bs) {
   2322 	aas_entityinfo_t entinfo;
   2323 
   2324 	if (gametype == GT_CTF) {
   2325 		//never chase when carrying a CTF flag
   2326 		if (BotCTFCarryingFlag(bs))
   2327 			return qfalse;
   2328 		//always chase if the enemy is carrying a flag
   2329 		BotEntityInfo(bs->enemy, &entinfo);
   2330 		if (EntityCarriesFlag(&entinfo))
   2331 			return qtrue;
   2332 	}
   2333 #ifdef MISSIONPACK
   2334 	else if (gametype == GT_1FCTF) {
   2335 		//never chase if carrying the flag
   2336 		if (Bot1FCTFCarryingFlag(bs))
   2337 			return qfalse;
   2338 		//always chase if the enemy is carrying a flag
   2339 		BotEntityInfo(bs->enemy, &entinfo);
   2340 		if (EntityCarriesFlag(&entinfo))
   2341 			return qtrue;
   2342 	}
   2343 	else if (gametype == GT_OBELISK) {
   2344 		//the bots should be dedicated to attacking the enemy obelisk
   2345 		if (bs->ltgtype == LTG_ATTACKENEMYBASE) {
   2346 			if (bs->enemy != redobelisk.entitynum ||
   2347 						bs->enemy != blueobelisk.entitynum) {
   2348 				return qfalse;
   2349 			}
   2350 		}
   2351 	}
   2352 	else if (gametype == GT_HARVESTER) {
   2353 		//never chase if carrying cubes
   2354 		if (BotHarvesterCarryingCubes(bs))
   2355 			return qfalse;
   2356 	}
   2357 #endif
   2358 	//if the bot is getting the flag
   2359 	if (bs->ltgtype == LTG_GETFLAG)
   2360 		return qfalse;
   2361 	//
   2362 	if (BotAggression(bs) > 50)
   2363 		return qtrue;
   2364 	return qfalse;
   2365 }
   2366 
   2367 /*
   2368 ==================
   2369 BotWantsToHelp
   2370 ==================
   2371 */
   2372 int BotWantsToHelp(bot_state_t *bs) {
   2373 	return qtrue;
   2374 }
   2375 
   2376 /*
   2377 ==================
   2378 BotCanAndWantsToRocketJump
   2379 ==================
   2380 */
   2381 int BotCanAndWantsToRocketJump(bot_state_t *bs) {
   2382 	float rocketjumper;
   2383 
   2384 	//if rocket jumping is disabled
   2385 	if (!bot_rocketjump.integer) return qfalse;
   2386 	//if no rocket launcher
   2387 	if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse;
   2388 	//if low on rockets
   2389 	if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse;
   2390 	//never rocket jump with the Quad
   2391 	if (bs->inventory[INVENTORY_QUAD]) return qfalse;
   2392 	//if low on health
   2393 	if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse;
   2394 	//if not full health
   2395 	if (bs->inventory[INVENTORY_HEALTH] < 90) {
   2396 		//if the bot has insufficient armor
   2397 		if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse;
   2398 	}
   2399 	rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1);
   2400 	if (rocketjumper < 0.5) return qfalse;
   2401 	return qtrue;
   2402 }
   2403 
   2404 /*
   2405 ==================
   2406 BotHasPersistantPowerupAndWeapon
   2407 ==================
   2408 */
   2409 int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) {
   2410 #ifdef MISSIONPACK
   2411 	// if the bot does not have a persistant powerup
   2412 	if (!bs->inventory[INVENTORY_SCOUT] &&
   2413 		!bs->inventory[INVENTORY_GUARD] &&
   2414 		!bs->inventory[INVENTORY_DOUBLER] &&
   2415 		!bs->inventory[INVENTORY_AMMOREGEN] ) {
   2416 		return qfalse;
   2417 	}
   2418 #endif
   2419 	//if the bot is very low on health
   2420 	if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse;
   2421 	//if the bot is low on health
   2422 	if (bs->inventory[INVENTORY_HEALTH] < 80) {
   2423 		//if the bot has insufficient armor
   2424 		if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse;
   2425 	}
   2426 	//if the bot can use the bfg
   2427 	if (bs->inventory[INVENTORY_BFG10K] > 0 &&
   2428 			bs->inventory[INVENTORY_BFGAMMO] > 7) return qtrue;
   2429 	//if the bot can use the railgun
   2430 	if (bs->inventory[INVENTORY_RAILGUN] > 0 &&
   2431 			bs->inventory[INVENTORY_SLUGS] > 5) return qtrue;
   2432 	//if the bot can use the lightning gun
   2433 	if (bs->inventory[INVENTORY_LIGHTNING] > 0 &&
   2434 			bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return qtrue;
   2435 	//if the bot can use the rocketlauncher
   2436 	if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 &&
   2437 			bs->inventory[INVENTORY_ROCKETS] > 5) return qtrue;
   2438 	//
   2439 	if (bs->inventory[INVENTORY_NAILGUN] > 0 &&
   2440 			bs->inventory[INVENTORY_NAILS] > 5) return qtrue;
   2441 	//
   2442 	if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 &&
   2443 			bs->inventory[INVENTORY_MINES] > 5) return qtrue;
   2444 	//
   2445 	if (bs->inventory[INVENTORY_CHAINGUN] > 0 &&
   2446 			bs->inventory[INVENTORY_BELT] > 40) return qtrue;
   2447 	//if the bot can use the plasmagun
   2448 	if (bs->inventory[INVENTORY_PLASMAGUN] > 0 &&
   2449 			bs->inventory[INVENTORY_CELLS] > 20) return qtrue;
   2450 	return qfalse;
   2451 }
   2452 
   2453 /*
   2454 ==================
   2455 BotGoCamp
   2456 ==================
   2457 */
   2458 void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) {
   2459 	float camper;
   2460 
   2461 	bs->decisionmaker = bs->client;
   2462 	//set message time to zero so bot will NOT show any message
   2463 	bs->teammessage_time = 0;
   2464 	//set the ltg type
   2465 	bs->ltgtype = LTG_CAMP;
   2466 	//set the team goal
   2467 	memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t));
   2468 	//get the team goal time
   2469 	camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1);
   2470 	if (camper > 0.99) bs->teamgoal_time = FloatTime() + 99999;
   2471 	else bs->teamgoal_time = FloatTime() + 120 + 180 * camper + random() * 15;
   2472 	//set the last time the bot started camping
   2473 	bs->camp_time = FloatTime();
   2474 	//the teammate that requested the camping
   2475 	bs->teammate = 0;
   2476 	//do NOT type arrive message
   2477 	bs->arrive_time = 1;
   2478 }
   2479 
   2480 /*
   2481 ==================
   2482 BotWantsToCamp
   2483 ==================
   2484 */
   2485 int BotWantsToCamp(bot_state_t *bs) {
   2486 	float camper;
   2487 	int cs, traveltime, besttraveltime;
   2488 	bot_goal_t goal, bestgoal;
   2489 
   2490 	camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1);
   2491 	if (camper < 0.1) return qfalse;
   2492 	//if the bot has a team goal
   2493 	if (bs->ltgtype == LTG_TEAMHELP ||
   2494 			bs->ltgtype == LTG_TEAMACCOMPANY ||
   2495 			bs->ltgtype == LTG_DEFENDKEYAREA ||
   2496 			bs->ltgtype == LTG_GETFLAG ||
   2497 			bs->ltgtype == LTG_RUSHBASE ||
   2498 			bs->ltgtype == LTG_CAMP ||
   2499 			bs->ltgtype == LTG_CAMPORDER ||
   2500 			bs->ltgtype == LTG_PATROL) {
   2501 		return qfalse;
   2502 	}
   2503 	//if camped recently
   2504 	if (bs->camp_time > FloatTime() - 60 + 300 * (1-camper)) return qfalse;
   2505 	//
   2506 	if (random() > camper) {
   2507 		bs->camp_time = FloatTime();
   2508 		return qfalse;
   2509 	}
   2510 	//if the bot isn't healthy anough
   2511 	if (BotAggression(bs) < 50) return qfalse;
   2512 	//the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo
   2513 	if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) &&
   2514 		(bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) &&
   2515 		(bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) {
   2516 		return qfalse;
   2517 	}
   2518 	//find the closest camp spot
   2519 	besttraveltime = 99999;
   2520 	for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) {
   2521 		traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT);
   2522 		if (traveltime && traveltime < besttraveltime) {
   2523 			besttraveltime = traveltime;
   2524 			memcpy(&bestgoal, &goal, sizeof(bot_goal_t));
   2525 		}
   2526 	}
   2527 	if (besttraveltime > 150) return qfalse;
   2528 	//ok found a camp spot, go camp there
   2529 	BotGoCamp(bs, &bestgoal);
   2530 	bs->ordered = qfalse;
   2531 	//
   2532 	return qtrue;
   2533 }
   2534 
   2535 /*
   2536 ==================
   2537 BotDontAvoid
   2538 ==================
   2539 */
   2540 void BotDontAvoid(bot_state_t *bs, char *itemname) {
   2541 	bot_goal_t goal;
   2542 	int num;
   2543 
   2544 	num = trap_BotGetLevelItemGoal(-1, itemname, &goal);
   2545 	while(num >= 0) {
   2546 		trap_BotRemoveFromAvoidGoals(bs->gs, goal.number);
   2547 		num = trap_BotGetLevelItemGoal(num, itemname, &goal);
   2548 	}
   2549 }
   2550 
   2551 /*
   2552 ==================
   2553 BotGoForPowerups
   2554 ==================
   2555 */
   2556 void BotGoForPowerups(bot_state_t *bs) {
   2557 
   2558 	//don't avoid any of the powerups anymore
   2559 	BotDontAvoid(bs, "Quad Damage");
   2560 	BotDontAvoid(bs, "Regeneration");
   2561 	BotDontAvoid(bs, "Battle Suit");
   2562 	BotDontAvoid(bs, "Speed");
   2563 	BotDontAvoid(bs, "Invisibility");
   2564 	//BotDontAvoid(bs, "Flight");
   2565 	//reset the long term goal time so the bot will go for the powerup
   2566 	//NOTE: the long term goal type doesn't change
   2567 	bs->ltg_time = 0;
   2568 }
   2569 
   2570 /*
   2571 ==================
   2572 BotRoamGoal
   2573 ==================
   2574 */
   2575 void BotRoamGoal(bot_state_t *bs, vec3_t goal) {
   2576 	int pc, i;
   2577 	float len, rnd;
   2578 	vec3_t dir, bestorg, belowbestorg;
   2579 	bsp_trace_t trace;
   2580 
   2581 	for (i = 0; i < 10; i++) {
   2582 		//start at the bot origin
   2583 		VectorCopy(bs->origin, bestorg);
   2584 		rnd = random();
   2585 		if (rnd > 0.25) {
   2586 			//add a random value to the x-coordinate
   2587 			if (random() < 0.5) bestorg[0] -= 800 * random() + 100;
   2588 			else bestorg[0] += 800 * random() + 100;
   2589 		}
   2590 		if (rnd < 0.75) {
   2591 			//add a random value to the y-coordinate
   2592 			if (random() < 0.5) bestorg[1] -= 800 * random() + 100;
   2593 			else bestorg[1] += 800 * random() + 100;
   2594 		}
   2595 		//add a random value to the z-coordinate (NOTE: 48 = maxjump?)
   2596 		bestorg[2] += 2 * 48 * crandom();
   2597 		//trace a line from the origin to the roam target
   2598 		BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID);
   2599 		//direction and length towards the roam target
   2600 		VectorSubtract(trace.endpos, bs->origin, dir);
   2601 		len = VectorNormalize(dir);
   2602 		//if the roam target is far away anough
   2603 		if (len > 200) {
   2604 			//the roam target is in the given direction before walls
   2605 			VectorScale(dir, len * trace.fraction - 40, dir);
   2606 			VectorAdd(bs->origin, dir, bestorg);
   2607 			//get the coordinates of the floor below the roam target
   2608 			belowbestorg[0] = bestorg[0];
   2609 			belowbestorg[1] = bestorg[1];
   2610 			belowbestorg[2] = bestorg[2] - 800;
   2611 			BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID);
   2612 			//
   2613 			if (!trace.startsolid) {
   2614 				trace.endpos[2]++;
   2615 				pc = trap_PointContents(trace.endpos, bs->entitynum);
   2616 				if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) {
   2617 					VectorCopy(bestorg, goal);
   2618 					return;
   2619 				}
   2620 			}
   2621 		}
   2622 	}
   2623 	VectorCopy(bestorg, goal);
   2624 }
   2625 
   2626 /*
   2627 ==================
   2628 BotAttackMove
   2629 ==================
   2630 */
   2631 bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) {
   2632 	int movetype, i, attackentity;
   2633 	float attack_skill, jumper, croucher, dist, strafechange_time;
   2634 	float attack_dist, attack_range;
   2635 	vec3_t forward, backward, sideward, hordir, up = {0, 0, 1};
   2636 	aas_entityinfo_t entinfo;
   2637 	bot_moveresult_t moveresult;
   2638 	bot_goal_t goal;
   2639 
   2640 	attackentity = bs->enemy;
   2641 	//
   2642 	if (bs->attackchase_time > FloatTime()) {
   2643 		//create the chase goal
   2644 		goal.entitynum = attackentity;
   2645 		goal.areanum = bs->lastenemyareanum;
   2646 		VectorCopy(bs->lastenemyorigin, goal.origin);
   2647 		VectorSet(goal.mins, -8, -8, -8);
   2648 		VectorSet(goal.maxs, 8, 8, 8);
   2649 		//initialize the movement state
   2650 		BotSetupForMovement(bs);
   2651 		//move towards the goal
   2652 		trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl);
   2653 		return moveresult;
   2654 	}
   2655 	//
   2656 	memset(&moveresult, 0, sizeof(bot_moveresult_t));
   2657 	//
   2658 	attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
   2659 	jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1);
   2660 	croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1);
   2661 	//if the bot is really stupid
   2662 	if (attack_skill < 0.2) return moveresult;
   2663 	//initialize the movement state
   2664 	BotSetupForMovement(bs);
   2665 	//get the enemy entity info
   2666 	BotEntityInfo(attackentity, &entinfo);
   2667 	//direction towards the enemy
   2668 	VectorSubtract(entinfo.origin, bs->origin, forward);
   2669 	//the distance towards the enemy
   2670 	dist = VectorNormalize(forward);
   2671 	VectorNegate(forward, backward);
   2672 	//walk, crouch or jump
   2673 	movetype = MOVE_WALK;
   2674 	//
   2675 	if (bs->attackcrouch_time < FloatTime() - 1) {
   2676 		if (random() < jumper) {
   2677 			movetype = MOVE_JUMP;
   2678 		}
   2679 		//wait at least one second before crouching again
   2680 		else if (bs->attackcrouch_time < FloatTime() - 1 && random() < croucher) {
   2681 			bs->attackcrouch_time = FloatTime() + croucher * 5;
   2682 		}
   2683 	}
   2684 	if (bs->attackcrouch_time > FloatTime()) movetype = MOVE_CROUCH;
   2685 	//if the bot should jump
   2686 	if (movetype == MOVE_JUMP) {
   2687 		//if jumped last frame
   2688 		if (bs->attackjump_time > FloatTime()) {
   2689 			movetype = MOVE_WALK;
   2690 		}
   2691 		else {
   2692 			bs->attackjump_time = FloatTime() + 1;
   2693 		}
   2694 	}
   2695 	if (bs->cur_ps.weapon == WP_GAUNTLET) {
   2696 		attack_dist = 0;
   2697 		attack_range = 0;
   2698 	}
   2699 	else {
   2700 		attack_dist = IDEAL_ATTACKDIST;
   2701 		attack_range = 40;
   2702 	}
   2703 	//if the bot is stupid
   2704 	if (attack_skill <= 0.4) {
   2705 		//just walk to or away from the enemy
   2706 		if (dist > attack_dist + attack_range) {
   2707 			if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult;
   2708 		}
   2709 		if (dist < attack_dist - attack_range) {
   2710 			if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult;
   2711 		}
   2712 		return moveresult;
   2713 	}
   2714 	//increase the strafe time
   2715 	bs->attackstrafe_time += bs->thinktime;
   2716 	//get the strafe change time
   2717 	strafechange_time = 0.4 + (1 - attack_skill) * 0.2;
   2718 	if (attack_skill > 0.7) strafechange_time += crandom() * 0.2;
   2719 	//if the strafe direction should be changed
   2720 	if (bs->attackstrafe_time > strafechange_time) {
   2721 		//some magic number :)
   2722 		if (random() > 0.935) {
   2723 			//flip the strafe direction
   2724 			bs->flags ^= BFL_STRAFERIGHT;
   2725 			bs->attackstrafe_time = 0;
   2726 		}
   2727 	}
   2728 	//
   2729 	for (i = 0; i < 2; i++) {
   2730 		hordir[0] = forward[0];
   2731 		hordir[1] = forward[1];
   2732 		hordir[2] = 0;
   2733 		VectorNormalize(hordir);
   2734 		//get the sideward vector
   2735 		CrossProduct(hordir, up, sideward);
   2736 		//reverse the vector depending on the strafe direction
   2737 		if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward);
   2738 		//randomly go back a little
   2739 		if (random() > 0.9) {
   2740 			VectorAdd(sideward, backward, sideward);
   2741 		}
   2742 		else {
   2743 			//walk forward or backward to get at the ideal attack distance
   2744 			if (dist > attack_dist + attack_range) {
   2745 				VectorAdd(sideward, forward, sideward);
   2746 			}
   2747 			else if (dist < attack_dist - attack_range) {
   2748 				VectorAdd(sideward, backward, sideward);
   2749 			}
   2750 		}
   2751 		//perform the movement
   2752 		if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype))
   2753 			return moveresult;
   2754 		//movement failed, flip the strafe direction
   2755 		bs->flags ^= BFL_STRAFERIGHT;
   2756 		bs->attackstrafe_time = 0;
   2757 	}
   2758 	//bot couldn't do any usefull movement
   2759 //	bs->attackchase_time = AAS_Time() + 6;
   2760 	return moveresult;
   2761 }
   2762 
   2763 /*
   2764 ==================
   2765 BotSameTeam
   2766 ==================
   2767 */
   2768 int BotSameTeam(bot_state_t *bs, int entnum) {
   2769 	char info1[1024], info2[1024];
   2770 
   2771 	if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
   2772 		//BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
   2773 		return qfalse;
   2774 	}
   2775 	if (entnum < 0 || entnum >= MAX_CLIENTS) {
   2776 		//BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
   2777 		return qfalse;
   2778 	}
   2779 	if ( gametype >= GT_TEAM ) {
   2780 		trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1));
   2781 		trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2));
   2782 		//
   2783 		if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue;
   2784 	}
   2785 	return qfalse;
   2786 }
   2787 
   2788 /*
   2789 ==================
   2790 InFieldOfVision
   2791 ==================
   2792 */
   2793 qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles)
   2794 {
   2795 	int i;
   2796 	float diff, angle;
   2797 
   2798 	for (i = 0; i < 2; i++) {
   2799 		angle = AngleMod(viewangles[i]);
   2800 		angles[i] = AngleMod(angles[i]);
   2801 		diff = angles[i] - angle;
   2802 		if (angles[i] > angle) {
   2803 			if (diff > 180.0) diff -= 360.0;
   2804 		}
   2805 		else {
   2806 			if (diff < -180.0) diff += 360.0;
   2807 		}
   2808 		if (diff > 0) {
   2809 			if (diff > fov * 0.5) return qfalse;
   2810 		}
   2811 		else {
   2812 			if (diff < -fov * 0.5) return qfalse;
   2813 		}
   2814 	}
   2815 	return qtrue;
   2816 }
   2817 
   2818 /*
   2819 ==================
   2820 BotEntityVisible
   2821 
   2822 returns visibility in the range [0, 1] taking fog and water surfaces into account
   2823 ==================
   2824 */
   2825 float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) {
   2826 	int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc;
   2827 	float squaredfogdist, waterfactor, vis, bestvis;
   2828 	bsp_trace_t trace;
   2829 	aas_entityinfo_t entinfo;
   2830 	vec3_t dir, entangles, start, end, middle;
   2831 
   2832 	//calculate middle of bounding box
   2833 	BotEntityInfo(ent, &entinfo);
   2834 	VectorAdd(entinfo.mins, entinfo.maxs, middle);
   2835 	VectorScale(middle, 0.5, middle);
   2836 	VectorAdd(entinfo.origin, middle, middle);
   2837 	//check if entity is within field of vision
   2838 	VectorSubtract(middle, eye, dir);
   2839 	vectoangles(dir, entangles);
   2840 	if (!InFieldOfVision(viewangles, fov, entangles)) return 0;
   2841 	//
   2842 	pc = trap_AAS_PointContents(eye);
   2843 	infog = (pc & CONTENTS_FOG);
   2844 	inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER));
   2845 	//
   2846 	bestvis = 0;
   2847 	for (i = 0; i < 3; i++) {
   2848 		//if the point is not in potential visible sight
   2849 		//if (!AAS_inPVS(eye, middle)) continue;
   2850 		//
   2851 		contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP;
   2852 		passent = viewer;
   2853 		hitent = ent;
   2854 		VectorCopy(eye, start);
   2855 		VectorCopy(middle, end);
   2856 		//if the entity is in water, lava or slime
   2857 		if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
   2858 			contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
   2859 		}
   2860 		//if eye is in water, lava or slime
   2861 		if (inwater) {
   2862 			if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) {
   2863 				passent = ent;
   2864 				hitent = viewer;
   2865 				VectorCopy(middle, start);
   2866 				VectorCopy(eye, end);
   2867 			}
   2868 			contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
   2869 		}
   2870 		//trace from start to end
   2871 		BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask);
   2872 		//if water was hit
   2873 		waterfactor = 1.0;
   2874 		if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
   2875 			//if the water surface is translucent
   2876 			if (1) {
   2877 				//trace through the water
   2878 				contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
   2879 				BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask);
   2880 				waterfactor = 0.5;
   2881 			}
   2882 		}
   2883 		//if a full trace or the hitent was hit
   2884 		if (trace.fraction >= 1 || trace.ent == hitent) {
   2885 			//check for fog, assuming there's only one fog brush where
   2886 			//either the viewer or the entity is in or both are in
   2887 			otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG);
   2888 			if (infog && otherinfog) {
   2889 				VectorSubtract(trace.endpos, eye, dir);
   2890 				squaredfogdist = VectorLengthSquared(dir);
   2891 			}
   2892 			else if (infog) {
   2893 				VectorCopy(trace.endpos, start);
   2894 				BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG);
   2895 				VectorSubtract(eye, trace.endpos, dir);
   2896 				squaredfogdist = VectorLengthSquared(dir);
   2897 			}
   2898 			else if (otherinfog) {
   2899 				VectorCopy(trace.endpos, end);
   2900 				BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG);
   2901 				VectorSubtract(end, trace.endpos, dir);
   2902 				squaredfogdist = VectorLengthSquared(dir);
   2903 			}
   2904 			else {
   2905 				//if the entity and the viewer are not in fog assume there's no fog in between
   2906 				squaredfogdist = 0;
   2907 			}
   2908 			//decrease visibility with the view distance through fog
   2909 			vis = 1 / ((squaredfogdist * 0.001) < 1 ? 1 : (squaredfogdist * 0.001));
   2910 			//if entering water visibility is reduced
   2911 			vis *= waterfactor;
   2912 			//
   2913 			if (vis > bestvis) bestvis = vis;
   2914 			//if pretty much no fog
   2915 			if (bestvis >= 0.95) return bestvis;
   2916 		}
   2917 		//check bottom and top of bounding box as well
   2918 		if (i == 0) middle[2] += entinfo.mins[2];
   2919 		else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2];
   2920 	}
   2921 	return bestvis;
   2922 }
   2923 
   2924 /*
   2925 ==================
   2926 BotFindEnemy
   2927 ==================
   2928 */
   2929 int BotFindEnemy(bot_state_t *bs, int curenemy) {
   2930 	int i, healthdecrease;
   2931 	float f, alertness, easyfragger, vis;
   2932 	float squaredist, cursquaredist;
   2933 	aas_entityinfo_t entinfo, curenemyinfo;
   2934 	vec3_t dir, angles;
   2935 
   2936 	alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1);
   2937 	easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1);
   2938 	//check if the health decreased
   2939 	healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH];
   2940 	//remember the current health value
   2941 	bs->lasthealth = bs->inventory[INVENTORY_HEALTH];
   2942 	//
   2943 	if (curenemy >= 0) {
   2944 		BotEntityInfo(curenemy, &curenemyinfo);
   2945 		if (EntityCarriesFlag(&curenemyinfo)) return qfalse;
   2946 		VectorSubtract(curenemyinfo.origin, bs->origin, dir);
   2947 		cursquaredist = VectorLengthSquared(dir);
   2948 	}
   2949 	else {
   2950 		cursquaredist = 0;
   2951 	}
   2952 #ifdef MISSIONPACK
   2953 	if (gametype == GT_OBELISK) {
   2954 		vec3_t target;
   2955 		bot_goal_t *goal;
   2956 		bsp_trace_t trace;
   2957 
   2958 		if (BotTeam(bs) == TEAM_RED)
   2959 			goal = &blueobelisk;
   2960 		else
   2961 			goal = &redobelisk;
   2962 		//if the obelisk is visible
   2963 		VectorCopy(goal->origin, target);
   2964 		target[2] += 1;
   2965 		BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
   2966 		if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
   2967 			if (goal->entitynum == bs->enemy) {
   2968 				return qfalse;
   2969 			}
   2970 			bs->enemy = goal->entitynum;
   2971 			bs->enemysight_time = FloatTime();
   2972 			bs->enemysuicide = qfalse;
   2973 			bs->enemydeath_time = 0;
   2974 			bs->enemyvisible_time = FloatTime();
   2975 			return qtrue;
   2976 		}
   2977 	}
   2978 #endif
   2979 	//
   2980 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
   2981 
   2982 		if (i == bs->client) continue;
   2983 		//if it's the current enemy
   2984 		if (i == curenemy) continue;
   2985 		//
   2986 		BotEntityInfo(i, &entinfo);
   2987 		//
   2988 		if (!entinfo.valid) continue;
   2989 		//if the enemy isn't dead and the enemy isn't the bot self
   2990 		if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
   2991 		//if the enemy is invisible and not shooting
   2992 		if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
   2993 			continue;
   2994 		}
   2995 		//if not an easy fragger don't shoot at chatting players
   2996 		if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue;
   2997 		//
   2998 		if (lastteleport_time > FloatTime() - 3) {
   2999 			VectorSubtract(entinfo.origin, lastteleport_origin, dir);
   3000 			if (VectorLengthSquared(dir) < Square(70)) continue;
   3001 		}
   3002 		//calculate the distance towards the enemy
   3003 		VectorSubtract(entinfo.origin, bs->origin, dir);
   3004 		squaredist = VectorLengthSquared(dir);
   3005 		//if this entity is not carrying a flag
   3006 		if (!EntityCarriesFlag(&entinfo))
   3007 		{
   3008 			//if this enemy is further away than the current one
   3009 			if (curenemy >= 0 && squaredist > cursquaredist) continue;
   3010 		} //end if
   3011 		//if the bot has no
   3012 		if (squaredist > Square(900.0 + alertness * 4000.0)) continue;
   3013 		//if on the same team
   3014 		if (BotSameTeam(bs, i)) continue;
   3015 		//if the bot's health decreased or the enemy is shooting
   3016 		if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo)))
   3017 			f = 360;
   3018 		else
   3019 			f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9));
   3020 		//check if the enemy is visible
   3021 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i);
   3022 		if (vis <= 0) continue;
   3023 		//if the enemy is quite far away, not shooting and the bot is not damaged
   3024 		if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo))
   3025 		{
   3026 			//check if we can avoid this enemy
   3027 			VectorSubtract(bs->origin, entinfo.origin, dir);
   3028 			vectoangles(dir, angles);
   3029 			//if the bot isn't in the fov of the enemy
   3030 			if (!InFieldOfVision(entinfo.angles, 90, angles)) {
   3031 				//update some stuff for this enemy
   3032 				BotUpdateBattleInventory(bs, i);
   3033 				//if the bot doesn't really want to fight
   3034 				if (BotWantsToRetreat(bs)) continue;
   3035 			}
   3036 		}
   3037 		//found an enemy
   3038 		bs->enemy = entinfo.number;
   3039 		if (curenemy >= 0) bs->enemysight_time = FloatTime() - 2;
   3040 		else bs->enemysight_time = FloatTime();
   3041 		bs->enemysuicide = qfalse;
   3042 		bs->enemydeath_time = 0;
   3043 		bs->enemyvisible_time = FloatTime();
   3044 		return qtrue;
   3045 	}
   3046 	return qfalse;
   3047 }
   3048 
   3049 /*
   3050 ==================
   3051 BotTeamFlagCarrierVisible
   3052 ==================
   3053 */
   3054 int BotTeamFlagCarrierVisible(bot_state_t *bs) {
   3055 	int i;
   3056 	float vis;
   3057 	aas_entityinfo_t entinfo;
   3058 
   3059 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
   3060 		if (i == bs->client)
   3061 			continue;
   3062 		//
   3063 		BotEntityInfo(i, &entinfo);
   3064 		//if this player is active
   3065 		if (!entinfo.valid)
   3066 			continue;
   3067 		//if this player is carrying a flag
   3068 		if (!EntityCarriesFlag(&entinfo))
   3069 			continue;
   3070 		//if the flag carrier is not on the same team
   3071 		if (!BotSameTeam(bs, i))
   3072 			continue;
   3073 		//if the flag carrier is not visible
   3074 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
   3075 		if (vis <= 0)
   3076 			continue;
   3077 		//
   3078 		return i;
   3079 	}
   3080 	return -1;
   3081 }
   3082 
   3083 /*
   3084 ==================
   3085 BotTeamFlagCarrier
   3086 ==================
   3087 */
   3088 int BotTeamFlagCarrier(bot_state_t *bs) {
   3089 	int i;
   3090 	aas_entityinfo_t entinfo;
   3091 
   3092 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
   3093 		if (i == bs->client)
   3094 			continue;
   3095 		//
   3096 		BotEntityInfo(i, &entinfo);
   3097 		//if this player is active
   3098 		if (!entinfo.valid)
   3099 			continue;
   3100 		//if this player is carrying a flag
   3101 		if (!EntityCarriesFlag(&entinfo))
   3102 			continue;
   3103 		//if the flag carrier is not on the same team
   3104 		if (!BotSameTeam(bs, i))
   3105 			continue;
   3106 		//
   3107 		return i;
   3108 	}
   3109 	return -1;
   3110 }
   3111 
   3112 /*
   3113 ==================
   3114 BotEnemyFlagCarrierVisible
   3115 ==================
   3116 */
   3117 int BotEnemyFlagCarrierVisible(bot_state_t *bs) {
   3118 	int i;
   3119 	float vis;
   3120 	aas_entityinfo_t entinfo;
   3121 
   3122 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
   3123 		if (i == bs->client)
   3124 			continue;
   3125 		//
   3126 		BotEntityInfo(i, &entinfo);
   3127 		//if this player is active
   3128 		if (!entinfo.valid)
   3129 			continue;
   3130 		//if this player is carrying a flag
   3131 		if (!EntityCarriesFlag(&entinfo))
   3132 			continue;
   3133 		//if the flag carrier is on the same team
   3134 		if (BotSameTeam(bs, i))
   3135 			continue;
   3136 		//if the flag carrier is not visible
   3137 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
   3138 		if (vis <= 0)
   3139 			continue;
   3140 		//
   3141 		return i;
   3142 	}
   3143 	return -1;
   3144 }
   3145 
   3146 /*
   3147 ==================
   3148 BotVisibleTeamMatesAndEnemies
   3149 ==================
   3150 */
   3151 void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range) {
   3152 	int i;
   3153 	float vis;
   3154 	aas_entityinfo_t entinfo;
   3155 	vec3_t dir;
   3156 
   3157 	if (teammates)
   3158 		*teammates = 0;
   3159 	if (enemies)
   3160 		*enemies = 0;
   3161 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
   3162 		if (i == bs->client)
   3163 			continue;
   3164 		//
   3165 		BotEntityInfo(i, &entinfo);
   3166 		//if this player is active
   3167 		if (!entinfo.valid)
   3168 			continue;
   3169 		//if this player is carrying a flag
   3170 		if (!EntityCarriesFlag(&entinfo))
   3171 			continue;
   3172 		//if not within range
   3173 		VectorSubtract(entinfo.origin, bs->origin, dir);
   3174 		if (VectorLengthSquared(dir) > Square(range))
   3175 			continue;
   3176 		//if the flag carrier is not visible
   3177 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
   3178 		if (vis <= 0)
   3179 			continue;
   3180 		//if the flag carrier is on the same team
   3181 		if (BotSameTeam(bs, i)) {
   3182 			if (teammates)
   3183 				(*teammates)++;
   3184 		}
   3185 		else {
   3186 			if (enemies)
   3187 				(*enemies)++;
   3188 		}
   3189 	}
   3190 }
   3191 
   3192 #ifdef MISSIONPACK
   3193 /*
   3194 ==================
   3195 BotTeamCubeCarrierVisible
   3196 ==================
   3197 */
   3198 int BotTeamCubeCarrierVisible(bot_state_t *bs) {
   3199 	int i;
   3200 	float vis;
   3201 	aas_entityinfo_t entinfo;
   3202 
   3203 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
   3204 		if (i == bs->client) continue;
   3205 		//
   3206 		BotEntityInfo(i, &entinfo);
   3207 		//if this player is active
   3208 		if (!entinfo.valid) continue;
   3209 		//if this player is carrying a flag
   3210 		if (!EntityCarriesCubes(&entinfo)) continue;
   3211 		//if the flag carrier is not on the same team
   3212 		if (!BotSameTeam(bs, i)) continue;
   3213 		//if the flag carrier is not visible
   3214 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
   3215 		if (vis <= 0) continue;
   3216 		//
   3217 		return i;
   3218 	}
   3219 	return -1;
   3220 }
   3221 
   3222 /*
   3223 ==================
   3224 BotEnemyCubeCarrierVisible
   3225 ==================
   3226 */
   3227 int BotEnemyCubeCarrierVisible(bot_state_t *bs) {
   3228 	int i;
   3229 	float vis;
   3230 	aas_entityinfo_t entinfo;
   3231 
   3232 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
   3233 		if (i == bs->client)
   3234 			continue;
   3235 		//
   3236 		BotEntityInfo(i, &entinfo);
   3237 		//if this player is active
   3238 		if (!entinfo.valid)
   3239 			continue;
   3240 		//if this player is carrying a flag
   3241 		if (!EntityCarriesCubes(&entinfo)) continue;
   3242 		//if the flag carrier is on the same team
   3243 		if (BotSameTeam(bs, i))
   3244 			continue;
   3245 		//if the flag carrier is not visible
   3246 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
   3247 		if (vis <= 0)
   3248 			continue;
   3249 		//
   3250 		return i;
   3251 	}
   3252 	return -1;
   3253 }
   3254 #endif
   3255 
   3256 /*
   3257 ==================
   3258 BotAimAtEnemy
   3259 ==================
   3260 */
   3261 void BotAimAtEnemy(bot_state_t *bs) {
   3262 	int i, enemyvisible;
   3263 	float dist, f, aim_skill, aim_accuracy, speed, reactiontime;
   3264 	vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity;
   3265 	vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4};
   3266 	weaponinfo_t wi;
   3267 	aas_entityinfo_t entinfo;
   3268 	bot_goal_t goal;
   3269 	bsp_trace_t trace;
   3270 	vec3_t target;
   3271 
   3272 	//if the bot has no enemy
   3273 	if (bs->enemy < 0) {
   3274 		return;
   3275 	}
   3276 	//get the enemy entity information
   3277 	BotEntityInfo(bs->enemy, &entinfo);
   3278 	//if this is not a player (should be an obelisk)
   3279 	if (bs->enemy >= MAX_CLIENTS) {
   3280 		//if the obelisk is visible
   3281 		VectorCopy(entinfo.origin, target);
   3282 #ifdef MISSIONPACK
   3283 		// if attacking an obelisk
   3284 		if ( bs->enemy == redobelisk.entitynum ||
   3285 			bs->enemy == blueobelisk.entitynum ) {
   3286 			target[2] += 32;
   3287 		}
   3288 #endif
   3289 		//aim at the obelisk
   3290 		VectorSubtract(target, bs->eye, dir);
   3291 		vectoangles(dir, bs->ideal_viewangles);
   3292 		//set the aim target before trying to attack
   3293 		VectorCopy(target, bs->aimtarget);
   3294 		return;
   3295 	}
   3296 	//
   3297 	//BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy);
   3298 	//
   3299 	aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1);
   3300 	aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1);
   3301 	//
   3302 	if (aim_skill > 0.95) {
   3303 		//don't aim too early
   3304 		reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1);
   3305 		if (bs->enemysight_time > FloatTime() - reactiontime) return;
   3306 		if (bs->teleport_time > FloatTime() - reactiontime) return;
   3307 	}
   3308 
   3309 	//get the weapon information
   3310 	trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
   3311 	//get the weapon specific aim accuracy and or aim skill
   3312 	if (wi.number == WP_MACHINEGUN) {
   3313 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1);
   3314 	}
   3315 	else if (wi.number == WP_SHOTGUN) {
   3316 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1);
   3317 	}
   3318 	else if (wi.number == WP_GRENADE_LAUNCHER) {
   3319 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1);
   3320 		aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1);
   3321 	}
   3322 	else if (wi.number == WP_ROCKET_LAUNCHER) {
   3323 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1);
   3324 		aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1);
   3325 	}
   3326 	else if (wi.number == WP_LIGHTNING) {
   3327 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1);
   3328 	}
   3329 	else if (wi.number == WP_RAILGUN) {
   3330 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1);
   3331 	}
   3332 	else if (wi.number == WP_PLASMAGUN) {
   3333 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1);
   3334 		aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1);
   3335 	}
   3336 	else if (wi.number == WP_BFG) {
   3337 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1);
   3338 		aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1);
   3339 	}
   3340 	//
   3341 	if (aim_accuracy <= 0) aim_accuracy = 0.0001f;
   3342 	//get the enemy entity information
   3343 	BotEntityInfo(bs->enemy, &entinfo);
   3344 	//if the enemy is invisible then shoot crappy most of the time
   3345 	if (EntityIsInvisible(&entinfo)) {
   3346 		if (random() > 0.1) aim_accuracy *= 0.4f;
   3347 	}
   3348 	//
   3349 	VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity);
   3350 	VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity);
   3351 	//enemy origin and velocity is remembered every 0.5 seconds
   3352 	if (bs->enemyposition_time < FloatTime()) {
   3353 		//
   3354 		bs->enemyposition_time = FloatTime() + 0.5;
   3355 		VectorCopy(enemyvelocity, bs->enemyvelocity);
   3356 		VectorCopy(entinfo.origin, bs->enemyorigin);
   3357 	}
   3358 	//if not extremely skilled
   3359 	if (aim_skill < 0.9) {
   3360 		VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
   3361 		//if the enemy moved a bit
   3362 		if (VectorLengthSquared(dir) > Square(48)) {
   3363 			//if the enemy changed direction
   3364 			if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) {
   3365 				//aim accuracy should be worse now
   3366 				aim_accuracy *= 0.7f;
   3367 			}
   3368 		}
   3369 	}
   3370 	//check visibility of enemy
   3371 	enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy);
   3372 	//if the enemy is visible
   3373 	if (enemyvisible) {
   3374 		//
   3375 		VectorCopy(entinfo.origin, bestorigin);
   3376 		bestorigin[2] += 8;
   3377 		//get the start point shooting from
   3378 		//NOTE: the x and y projectile start offsets are ignored
   3379 		VectorCopy(bs->origin, start);
   3380 		start[2] += bs->cur_ps.viewheight;
   3381 		start[2] += wi.offset[2];
   3382 		//
   3383 		BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT);
   3384 		//if the enemy is NOT hit
   3385 		if (trace.fraction <= 1 && trace.ent != entinfo.number) {
   3386 			bestorigin[2] += 16;
   3387 		}
   3388 		//if it is not an instant hit weapon the bot might want to predict the enemy
   3389 		if (wi.speed) {
   3390 			//
   3391 			VectorSubtract(bestorigin, bs->origin, dir);
   3392 			dist = VectorLength(dir);
   3393 			VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
   3394 			//if the enemy is NOT pretty far away and strafing just small steps left and right
   3395 			if (!(dist > 100 && VectorLengthSquared(dir) < Square(32))) {
   3396 				//if skilled anough do exact prediction
   3397 				if (aim_skill > 0.8 &&
   3398 						//if the weapon is ready to fire
   3399 						bs->cur_ps.weaponstate == WEAPON_READY) {
   3400 					aas_clientmove_t move;
   3401 					vec3_t origin;
   3402 
   3403 					VectorSubtract(entinfo.origin, bs->origin, dir);
   3404 					//distance towards the enemy
   3405 					dist = VectorLength(dir);
   3406 					//direction the enemy is moving in
   3407 					VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
   3408 					//
   3409 					VectorScale(dir, 1 / entinfo.update_time, dir);
   3410 					//
   3411 					VectorCopy(entinfo.origin, origin);
   3412 					origin[2] += 1;
   3413 					//
   3414 					VectorClear(cmdmove);
   3415 					//AAS_ClearShownDebugLines();
   3416 					trap_AAS_PredictClientMovement(&move, bs->enemy, origin,
   3417 														PRESENCE_CROUCH, qfalse,
   3418 														dir, cmdmove, 0,
   3419 														dist * 10 / wi.speed, 0.1f, 0, 0, qfalse);
   3420 					VectorCopy(move.endpos, bestorigin);
   3421 					//BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", FloatTime(), VectorLength(dir), dist * 10 / wi.speed);
   3422 				}
   3423 				//if not that skilled do linear prediction
   3424 				else if (aim_skill > 0.4) {
   3425 					VectorSubtract(entinfo.origin, bs->origin, dir);
   3426 					//distance towards the enemy
   3427 					dist = VectorLength(dir);
   3428 					//direction the enemy is moving in
   3429 					VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
   3430 					dir[2] = 0;
   3431 					//
   3432 					speed = VectorNormalize(dir) / entinfo.update_time;
   3433 					//botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed);
   3434 					//best spot to aim at
   3435 					VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin);
   3436 				}
   3437 			}
   3438 		}
   3439 		//if the projectile does radial damage
   3440 		if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) {
   3441 			//if the enemy isn't standing significantly higher than the bot
   3442 			if (entinfo.origin[2] < bs->origin[2] + 16) {
   3443 				//try to aim at the ground in front of the enemy
   3444 				VectorCopy(entinfo.origin, end);
   3445 				end[2] -= 64;
   3446 				BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT);
   3447 				//
   3448 				VectorCopy(bestorigin, groundtarget);
   3449 				if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16;
   3450 				else groundtarget[2] = trace.endpos[2] - 8;
   3451 				//trace a line from projectile start to ground target
   3452 				BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT);
   3453 				//if hitpoint is not vertically too far from the ground target
   3454 				if (fabs(trace.endpos[2] - groundtarget[2]) < 50) {
   3455 					VectorSubtract(trace.endpos, groundtarget, dir);
   3456 					//if the hitpoint is near anough the ground target
   3457 					if (VectorLengthSquared(dir) < Square(60)) {
   3458 						VectorSubtract(trace.endpos, start, dir);
   3459 						//if the hitpoint is far anough from the bot
   3460 						if (VectorLengthSquared(dir) > Square(100)) {
   3461 							//check if the bot is visible from the ground target
   3462 							trace.endpos[2] += 1;
   3463 							BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT);
   3464 							if (trace.fraction >= 1) {
   3465 								//botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time());
   3466 								VectorCopy(groundtarget, bestorigin);
   3467 							}
   3468 						}
   3469 					}
   3470 				}
   3471 			}
   3472 		}
   3473 		bestorigin[0] += 20 * crandom() * (1 - aim_accuracy);
   3474 		bestorigin[1] += 20 * crandom() * (1 - aim_accuracy);
   3475 		bestorigin[2] += 10 * crandom() * (1 - aim_accuracy);
   3476 	}
   3477 	else {
   3478 		//
   3479 		VectorCopy(bs->lastenemyorigin, bestorigin);
   3480 		bestorigin[2] += 8;
   3481 		//if the bot is skilled anough
   3482 		if (aim_skill > 0.5) {
   3483 			//do prediction shots around corners
   3484 			if (wi.number == WP_BFG ||
   3485 				wi.number == WP_ROCKET_LAUNCHER ||
   3486 				wi.number == WP_GRENADE_LAUNCHER) {
   3487 				//create the chase goal
   3488 				goal.entitynum = bs->client;
   3489 				goal.areanum = bs->areanum;
   3490 				VectorCopy(bs->eye, goal.origin);
   3491 				VectorSet(goal.mins, -8, -8, -8);
   3492 				VectorSet(goal.maxs, 8, 8, 8);
   3493 				//
   3494 				if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) {
   3495 					VectorSubtract(target, bs->eye, dir);
   3496 					if (VectorLengthSquared(dir) > Square(80)) {
   3497 						VectorCopy(target, bestorigin);
   3498 						bestorigin[2] -= 20;
   3499 					}
   3500 				}
   3501 				aim_accuracy = 1;
   3502 			}
   3503 		}
   3504 	}
   3505 	//
   3506 	if (enemyvisible) {
   3507 		BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT);
   3508 		VectorCopy(trace.endpos, bs->aimtarget);
   3509 	}
   3510 	else {
   3511 		VectorCopy(bestorigin, bs->aimtarget);
   3512 	}
   3513 	//get aim direction
   3514 	VectorSubtract(bestorigin, bs->eye, dir);
   3515 	//
   3516 	if (wi.number == WP_MACHINEGUN ||
   3517 		wi.number == WP_SHOTGUN ||
   3518 		wi.number == WP_LIGHTNING ||
   3519 		wi.number == WP_RAILGUN) {
   3520 		//distance towards the enemy
   3521 		dist = VectorLength(dir);
   3522 		if (dist > 150) dist = 150;
   3523 		f = 0.6 + dist / 150 * 0.4;
   3524 		aim_accuracy *= f;
   3525 	}
   3526 	//add some random stuff to the aim direction depending on the aim accuracy
   3527 	if (aim_accuracy < 0.8) {
   3528 		VectorNormalize(dir);
   3529 		for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy);
   3530 	}
   3531 	//set the ideal view angles
   3532 	vectoangles(dir, bs->ideal_viewangles);
   3533 	//take the weapon spread into account for lower skilled bots
   3534 	bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy);
   3535 	bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
   3536 	bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy);
   3537 	bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
   3538 	//if the bots should be really challenging
   3539 	if (bot_challenge.integer) {
   3540 		//if the bot is really accurate and has the enemy in view for some time
   3541 		if (aim_accuracy > 0.9 && bs->enemysight_time < FloatTime() - 1) {
   3542 			//set the view angles directly
   3543 			if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
   3544 			VectorCopy(bs->ideal_viewangles, bs->viewangles);
   3545 			trap_EA_View(bs->client, bs->viewangles);
   3546 		}
   3547 	}
   3548 }
   3549 
   3550 /*
   3551 ==================
   3552 BotCheckAttack
   3553 ==================
   3554 */
   3555 void BotCheckAttack(bot_state_t *bs) {
   3556 	float points, reactiontime, fov, firethrottle;
   3557 	int attackentity;
   3558 	bsp_trace_t bsptrace;
   3559 	//float selfpreservation;
   3560 	vec3_t forward, right, start, end, dir, angles;
   3561 	weaponinfo_t wi;
   3562 	bsp_trace_t trace;
   3563 	aas_entityinfo_t entinfo;
   3564 	vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8};
   3565 
   3566 	attackentity = bs->enemy;
   3567 	//
   3568 	BotEntityInfo(attackentity, &entinfo);
   3569 	// if not attacking a player
   3570 	if (attackentity >= MAX_CLIENTS) {
   3571 #ifdef MISSIONPACK
   3572 		// if attacking an obelisk
   3573 		if ( entinfo.number == redobelisk.entitynum ||
   3574 			entinfo.number == blueobelisk.entitynum ) {
   3575 			// if obelisk is respawning return
   3576 			if ( g_entities[entinfo.number].activator &&
   3577 				g_entities[entinfo.number].activator->s.frame == 2 ) {
   3578 				return;
   3579 			}
   3580 		}
   3581 #endif
   3582 	}
   3583 	//
   3584 	reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1);
   3585 	if (bs->enemysight_time > FloatTime() - reactiontime) return;
   3586 	if (bs->teleport_time > FloatTime() - reactiontime) return;
   3587 	//if changing weapons
   3588 	if (bs->weaponchange_time > FloatTime() - 0.1) return;
   3589 	//check fire throttle characteristic
   3590 	if (bs->firethrottlewait_time > FloatTime()) return;
   3591 	firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1);
   3592 	if (bs->firethrottleshoot_time < FloatTime()) {
   3593 		if (random() > firethrottle) {
   3594 			bs->firethrottlewait_time = FloatTime() + firethrottle;
   3595 			bs->firethrottleshoot_time = 0;
   3596 		}
   3597 		else {
   3598 			bs->firethrottleshoot_time = FloatTime() + 1 - firethrottle;
   3599 			bs->firethrottlewait_time = 0;
   3600 		}
   3601 	}
   3602 	//
   3603 	//
   3604 	VectorSubtract(bs->aimtarget, bs->eye, dir);
   3605 	//
   3606 	if (bs->weaponnum == WP_GAUNTLET) {
   3607 		if (VectorLengthSquared(dir) > Square(60)) {
   3608 			return;
   3609 		}
   3610 	}
   3611 	if (VectorLengthSquared(dir) < Square(100))
   3612 		fov = 120;
   3613 	else
   3614 		fov = 50;
   3615 	//
   3616 	vectoangles(dir, angles);
   3617 	if (!InFieldOfVision(bs->viewangles, fov, angles))
   3618 		return;
   3619 	BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
   3620 	if (bsptrace.fraction < 1 && bsptrace.ent != attackentity)
   3621 		return;
   3622 
   3623 	//get the weapon info
   3624 	trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
   3625 	//get the start point shooting from
   3626 	VectorCopy(bs->origin, start);
   3627 	start[2] += bs->cur_ps.viewheight;
   3628 	AngleVectors(bs->viewangles, forward, right, NULL);
   3629 	start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1];
   3630 	start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1];
   3631 	start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2];
   3632 	//end point aiming at
   3633 	VectorMA(start, 1000, forward, end);
   3634 	//a little back to make sure not inside a very close enemy
   3635 	VectorMA(start, -12, forward, start);
   3636 	BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT);
   3637 	//if the entity is a client
   3638 	if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) {
   3639 		if (trace.ent != attackentity) {
   3640 			//if a teammate is hit
   3641 			if (BotSameTeam(bs, trace.ent))
   3642 				return;
   3643 		}
   3644 	}
   3645 	//if won't hit the enemy or not attacking a player (obelisk)
   3646 	if (trace.ent != attackentity || attackentity >= MAX_CLIENTS) {
   3647 		//if the projectile does radial damage
   3648 		if (wi.proj.damagetype & DAMAGETYPE_RADIAL) {
   3649 			if (trace.fraction * 1000 < wi.proj.radius) {
   3650 				points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5;
   3651 				if (points > 0) {
   3652 					return;
   3653 				}
   3654 			}
   3655 			//FIXME: check if a teammate gets radial damage
   3656 		}
   3657 	}
   3658 	//if fire has to be release to activate weapon
   3659 	if (wi.flags & WFL_FIRERELEASED) {
   3660 		if (bs->flags & BFL_ATTACKED) {
   3661 			trap_EA_Attack(bs->client);
   3662 		}
   3663 	}
   3664 	else {
   3665 		trap_EA_Attack(bs->client);
   3666 	}
   3667 	bs->flags ^= BFL_ATTACKED;
   3668 }
   3669 
   3670 /*
   3671 ==================
   3672 BotMapScripts
   3673 ==================
   3674 */
   3675 void BotMapScripts(bot_state_t *bs) {
   3676 	char info[1024];
   3677 	char mapname[128];
   3678 	int i, shootbutton;
   3679 	float aim_accuracy;
   3680 	aas_entityinfo_t entinfo;
   3681 	vec3_t dir;
   3682 
   3683 	trap_GetServerinfo(info, sizeof(info));
   3684 
   3685 	strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1);
   3686 	mapname[sizeof(mapname)-1] = '\0';
   3687 
   3688 	if (!Q_stricmp(mapname, "q3tourney6")) {
   3689 		vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680};
   3690 		vec3_t buttonorg = {304, 352, 920};
   3691 		//NOTE: NEVER use the func_bobbing in q3tourney6
   3692 		bs->tfl &= ~TFL_FUNCBOB;
   3693 		//if the bot is below the bounding box
   3694 		if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) {
   3695 			if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) {
   3696 				if (bs->origin[2] < mins[2]) {
   3697 					return;
   3698 				}
   3699 			}
   3700 		}
   3701 		shootbutton = qfalse;
   3702 		//if an enemy is below this bounding box then shoot the button
   3703 		for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
   3704 
   3705 			if (i == bs->client) continue;
   3706 			//
   3707 			BotEntityInfo(i, &entinfo);
   3708 			//
   3709 			if (!entinfo.valid) continue;
   3710 			//if the enemy isn't dead and the enemy isn't the bot self
   3711 			if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
   3712 			//
   3713 			if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) {
   3714 				if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) {
   3715 					if (entinfo.origin[2] < mins[2]) {
   3716 						//if there's a team mate below the crusher
   3717 						if (BotSameTeam(bs, i)) {
   3718 							shootbutton = qfalse;
   3719 							break;
   3720 						}
   3721 						else {
   3722 							shootbutton = qtrue;
   3723 						}
   3724 					}
   3725 				}
   3726 			}
   3727 		}
   3728 		if (shootbutton) {
   3729 			bs->flags |= BFL_IDEALVIEWSET;
   3730 			VectorSubtract(buttonorg, bs->eye, dir);
   3731 			vectoangles(dir, bs->ideal_viewangles);
   3732 			aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1);
   3733 			bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy);
   3734 			bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
   3735 			bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy);
   3736 			bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
   3737 			//
   3738 			if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) {
   3739 				trap_EA_Attack(bs->client);
   3740 			}
   3741 		}
   3742 	}
   3743 	else if (!Q_stricmp(mapname, "mpq3tourney6")) {
   3744 		//NOTE: NEVER use the func_bobbing in mpq3tourney6
   3745 		bs->tfl &= ~TFL_FUNCBOB;
   3746 	}
   3747 }
   3748 
   3749 /*
   3750 ==================
   3751 BotSetMovedir
   3752 ==================
   3753 */
   3754 // bk001205 - made these static
   3755 static vec3_t VEC_UP		= {0, -1,  0};
   3756 static vec3_t MOVEDIR_UP	= {0,  0,  1};
   3757 static vec3_t VEC_DOWN		= {0, -2,  0};
   3758 static vec3_t MOVEDIR_DOWN	= {0,  0, -1};
   3759 
   3760 void BotSetMovedir(vec3_t angles, vec3_t movedir) {
   3761 	if (VectorCompare(angles, VEC_UP)) {
   3762 		VectorCopy(MOVEDIR_UP, movedir);
   3763 	}
   3764 	else if (VectorCompare(angles, VEC_DOWN)) {
   3765 		VectorCopy(MOVEDIR_DOWN, movedir);
   3766 	}
   3767 	else {
   3768 		AngleVectors(angles, movedir, NULL, NULL);
   3769 	}
   3770 }
   3771 
   3772 /*
   3773 ==================
   3774 BotModelMinsMaxs
   3775 
   3776 this is ugly
   3777 ==================
   3778 */
   3779 int BotModelMinsMaxs(int modelindex, int eType, int contents, vec3_t mins, vec3_t maxs) {
   3780 	gentity_t *ent;
   3781 	int i;
   3782 
   3783 	ent = &g_entities[0];
   3784 	for (i = 0; i < level.num_entities; i++, ent++) {
   3785 		if ( !ent->inuse ) {
   3786 			continue;
   3787 		}
   3788 		if ( eType && ent->s.eType != eType) {
   3789 			continue;
   3790 		}
   3791 		if ( contents && ent->r.contents != contents) {
   3792 			continue;
   3793 		}
   3794 		if (ent->s.modelindex == modelindex) {
   3795 			if (mins)
   3796 				VectorAdd(ent->r.currentOrigin, ent->r.mins, mins);
   3797 			if (maxs)
   3798 				VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs);
   3799 			return i;
   3800 		}
   3801 	}
   3802 	if (mins)
   3803 		VectorClear(mins);
   3804 	if (maxs)
   3805 		VectorClear(maxs);
   3806 	return 0;
   3807 }
   3808 
   3809 /*
   3810 ==================
   3811 BotFuncButtonGoal
   3812 ==================
   3813 */
   3814 int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) {
   3815 	int i, areas[10], numareas, modelindex, entitynum;
   3816 	char model[128];
   3817 	float lip, dist, health, angle;
   3818 	vec3_t size, start, end, mins, maxs, angles, points[10];
   3819 	vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs;
   3820 	vec3_t extramins = {1, 1, 1}, extramaxs = {-1, -1, -1};
   3821 	bsp_trace_t bsptrace;
   3822 
   3823 	activategoal->shoot = qfalse;
   3824 	VectorClear(activategoal->target);
   3825 	//create a bot goal towards the button
   3826 	trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model));
   3827 	if (!*model)
   3828 		return qfalse;
   3829 	modelindex = atoi(model+1);
   3830 	if (!modelindex)
   3831 		return qfalse;
   3832 	VectorClear(angles);
   3833 	entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs);
   3834 	//get the lip of the button
   3835 	trap_AAS_FloatForBSPEpairKey(bspent, "lip", &lip);
   3836 	if (!lip) lip = 4;
   3837 	//get the move direction from the angle
   3838 	trap_AAS_FloatForBSPEpairKey(bspent, "angle", &angle);
   3839 	VectorSet(angles, 0, angle, 0);
   3840 	BotSetMovedir(angles, movedir);
   3841 	//button size
   3842 	VectorSubtract(maxs, mins, size);
   3843 	//button origin
   3844 	VectorAdd(mins, maxs, origin);
   3845 	VectorScale(origin, 0.5, origin);
   3846 	//touch distance of the button
   3847 	dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2];
   3848 	dist *= 0.5;
   3849 	//
   3850 	trap_AAS_FloatForBSPEpairKey(bspent, "health", &health);
   3851 	//if the button is shootable
   3852 	if (health) {
   3853 		//calculate the shoot target
   3854 		VectorMA(origin, -dist, movedir, goalorigin);
   3855 		//
   3856 		VectorCopy(goalorigin, activategoal->target);
   3857 		activategoal->shoot = qtrue;
   3858 		//
   3859 		BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, goalorigin, bs->entitynum, MASK_SHOT);
   3860 		// if the button is visible from the current position
   3861 		if (bsptrace.fraction >= 1.0 || bsptrace.ent == entitynum) {
   3862 			//
   3863 			activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable button
   3864 			activategoal->goal.number = 0;
   3865 			activategoal->goal.flags = 0;
   3866 			VectorCopy(bs->origin, activategoal->goal.origin);
   3867 			activategoal->goal.areanum = bs->areanum;
   3868 			VectorSet(activategoal->goal.mins, -8, -8, -8);
   3869 			VectorSet(activategoal->goal.maxs, 8, 8, 8);
   3870 			//
   3871 			return qtrue;
   3872 		}
   3873 		else {
   3874 			//create a goal from where the button is visible and shoot at the button from there
   3875 			//add bounding box size to the dist
   3876 			trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs);
   3877 			for (i = 0; i < 3; i++) {
   3878 				if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]);
   3879 				else dist += fabs(movedir[i]) * fabs(bboxmins[i]);
   3880 			}
   3881 			//calculate the goal origin
   3882 			VectorMA(origin, -dist, movedir, goalorigin);
   3883 			//
   3884 			VectorCopy(goalorigin, start);
   3885 			start[2] += 24;
   3886 			VectorCopy(start, end);
   3887 			end[2] -= 512;
   3888 			numareas = trap_AAS_TraceAreas(start, end, areas, points, 10);
   3889 			//
   3890 			for (i = numareas-1; i >= 0; i--) {
   3891 				if (trap_AAS_AreaReachability(areas[i])) {
   3892 					break;
   3893 				}
   3894 			}
   3895 			if (i < 0) {
   3896 				// FIXME: trace forward and maybe in other directions to find a valid area
   3897 			}
   3898 			if (i >= 0) {
   3899 				//
   3900 				VectorCopy(points[i], activategoal->goal.origin);
   3901 				activategoal->goal.areanum = areas[i];
   3902 				VectorSet(activategoal->goal.mins, 8, 8, 8);
   3903 				VectorSet(activategoal->goal.maxs, -8, -8, -8);
   3904 				//
   3905 				for (i = 0; i < 3; i++)
   3906 				{
   3907 					if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]);
   3908 					else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]);
   3909 				} //end for
   3910 				//
   3911 				activategoal->goal.entitynum = entitynum;
   3912 				activategoal->goal.number = 0;
   3913 				activategoal->goal.flags = 0;
   3914 				return qtrue;
   3915 			}
   3916 		}
   3917 		return qfalse;
   3918 	}
   3919 	else {
   3920 		//add bounding box size to the dist
   3921 		trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs);
   3922 		for (i = 0; i < 3; i++) {
   3923 			if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]);
   3924 			else dist += fabs(movedir[i]) * fabs(bboxmins[i]);
   3925 		}
   3926 		//calculate the goal origin
   3927 		VectorMA(origin, -dist, movedir, goalorigin);
   3928 		//
   3929 		VectorCopy(goalorigin, start);
   3930 		start[2] += 24;
   3931 		VectorCopy(start, end);
   3932 		end[2] -= 100;
   3933 		numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10);
   3934 		//
   3935 		for (i = 0; i < numareas; i++) {
   3936 			if (trap_AAS_AreaReachability(areas[i])) {
   3937 				break;
   3938 			}
   3939 		}
   3940 		if (i < numareas) {
   3941 			//
   3942 			VectorCopy(origin, activategoal->goal.origin);
   3943 			activategoal->goal.areanum = areas[i];
   3944 			VectorSubtract(mins, origin, activategoal->goal.mins);
   3945 			VectorSubtract(maxs, origin, activategoal->goal.maxs);
   3946 			//
   3947 			for (i = 0; i < 3; i++)
   3948 			{
   3949 				if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]);
   3950 				else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]);
   3951 			} //end for
   3952 			//
   3953 			activategoal->goal.entitynum = entitynum;
   3954 			activategoal->goal.number = 0;
   3955 			activategoal->goal.flags = 0;
   3956 			return qtrue;
   3957 		}
   3958 	}
   3959 	return qfalse;
   3960 }
   3961 
   3962 /*
   3963 ==================
   3964 BotFuncDoorGoal
   3965 ==================
   3966 */
   3967 int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) {
   3968 	int modelindex, entitynum;
   3969 	char model[MAX_INFO_STRING];
   3970 	vec3_t mins, maxs, origin, angles;
   3971 
   3972 	//shoot at the shootable door
   3973 	trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model));
   3974 	if (!*model)
   3975 		return qfalse;
   3976 	modelindex = atoi(model+1);
   3977 	if (!modelindex)
   3978 		return qfalse;
   3979 	VectorClear(angles);
   3980 	entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs);
   3981 	//door origin
   3982 	VectorAdd(mins, maxs, origin);
   3983 	VectorScale(origin, 0.5, origin);
   3984 	VectorCopy(origin, activategoal->target);
   3985 	activategoal->shoot = qtrue;
   3986 	//
   3987 	activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable door
   3988 	activategoal->goal.number = 0;
   3989 	activategoal->goal.flags = 0;
   3990 	VectorCopy(bs->origin, activategoal->goal.origin);
   3991 	activategoal->goal.areanum = bs->areanum;
   3992 	VectorSet(activategoal->goal.mins, -8, -8, -8);
   3993 	VectorSet(activategoal->goal.maxs, 8, 8, 8);
   3994 	return qtrue;
   3995 }
   3996 
   3997 /*
   3998 ==================
   3999 BotTriggerMultipleGoal
   4000 ==================
   4001 */
   4002 int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) {
   4003 	int i, areas[10], numareas, modelindex, entitynum;
   4004 	char model[128];
   4005 	vec3_t start, end, mins, maxs, angles;
   4006 	vec3_t origin, goalorigin;
   4007 
   4008 	activategoal->shoot = qfalse;
   4009 	VectorClear(activategoal->target);
   4010 	//create a bot goal towards the trigger
   4011 	trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model));
   4012 	if (!*model)
   4013 		return qfalse;
   4014 	modelindex = atoi(model+1);
   4015 	if (!modelindex)
   4016 		return qfalse;
   4017 	VectorClear(angles);
   4018 	entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs);
   4019 	//trigger origin
   4020 	VectorAdd(mins, maxs, origin);
   4021 	VectorScale(origin, 0.5, origin);
   4022 	VectorCopy(origin, goalorigin);
   4023 	//
   4024 	VectorCopy(goalorigin, start);
   4025 	start[2] += 24;
   4026 	VectorCopy(start, end);
   4027 	end[2] -= 100;
   4028 	numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10);
   4029 	//
   4030 	for (i = 0; i < numareas; i++) {
   4031 		if (trap_AAS_AreaReachability(areas[i])) {
   4032 			break;
   4033 		}
   4034 	}
   4035 	if (i < numareas) {
   4036 		VectorCopy(origin, activategoal->goal.origin);
   4037 		activategoal->goal.areanum = areas[i];
   4038 		VectorSubtract(mins, origin, activategoal->goal.mins);
   4039 		VectorSubtract(maxs, origin, activategoal->goal.maxs);
   4040 		//
   4041 		activategoal->goal.entitynum = entitynum;
   4042 		activategoal->goal.number = 0;
   4043 		activategoal->goal.flags = 0;
   4044 		return qtrue;
   4045 	}
   4046 	return qfalse;
   4047 }
   4048 
   4049 /*
   4050 ==================
   4051 BotPopFromActivateGoalStack
   4052 ==================
   4053 */
   4054 int BotPopFromActivateGoalStack(bot_state_t *bs) {
   4055 	if (!bs->activatestack)
   4056 		return qfalse;
   4057 	BotEnableActivateGoalAreas(bs->activatestack, qtrue);
   4058 	bs->activatestack->inuse = qfalse;
   4059 	bs->activatestack->justused_time = FloatTime();
   4060 	bs->activatestack = bs->activatestack->next;
   4061 	return qtrue;
   4062 }
   4063 
   4064 /*
   4065 ==================
   4066 BotPushOntoActivateGoalStack
   4067 ==================
   4068 */
   4069 int BotPushOntoActivateGoalStack(bot_state_t *bs, bot_activategoal_t *activategoal) {
   4070 	int i, best;
   4071 	float besttime;
   4072 
   4073 	best = -1;
   4074 	besttime = FloatTime() + 9999;
   4075 	//
   4076 	for (i = 0; i < MAX_ACTIVATESTACK; i++) {
   4077 		if (!bs->activategoalheap[i].inuse) {
   4078 			if (bs->activategoalheap[i].justused_time < besttime) {
   4079 				besttime = bs->activategoalheap[i].justused_time;
   4080 				best = i;
   4081 			}
   4082 		}
   4083 	}
   4084 	if (best != -1) {
   4085 		memcpy(&bs->activategoalheap[best], activategoal, sizeof(bot_activategoal_t));
   4086 		bs->activategoalheap[best].inuse = qtrue;
   4087 		bs->activategoalheap[best].next = bs->activatestack;
   4088 		bs->activatestack = &bs->activategoalheap[best];
   4089 		return qtrue;
   4090 	}
   4091 	return qfalse;
   4092 }
   4093 
   4094 /*
   4095 ==================
   4096 BotClearActivateGoalStack
   4097 ==================
   4098 */
   4099 void BotClearActivateGoalStack(bot_state_t *bs) {
   4100 	while(bs->activatestack)
   4101 		BotPopFromActivateGoalStack(bs);
   4102 }
   4103 
   4104 /*
   4105 ==================
   4106 BotEnableActivateGoalAreas
   4107 ==================
   4108 */
   4109 void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable) {
   4110 	int i;
   4111 
   4112 	if (activategoal->areasdisabled == !enable)
   4113 		return;
   4114 	for (i = 0; i < activategoal->numareas; i++)
   4115 		trap_AAS_EnableRoutingArea( activategoal->areas[i], enable );
   4116 	activategoal->areasdisabled = !enable;
   4117 }
   4118 
   4119 /*
   4120 ==================
   4121 BotIsGoingToActivateEntity
   4122 ==================
   4123 */
   4124 int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) {
   4125 	bot_activategoal_t *a;
   4126 	int i;
   4127 
   4128 	for (a = bs->activatestack; a; a = a->next) {
   4129 		if (a->time < FloatTime())
   4130 			continue;
   4131 		if (a->goal.entitynum == entitynum)
   4132 			return qtrue;
   4133 	}
   4134 	for (i = 0; i < MAX_ACTIVATESTACK; i++) {
   4135 		if (bs->activategoalheap[i].inuse)
   4136 			continue;
   4137 		//
   4138 		if (bs->activategoalheap[i].goal.entitynum == entitynum) {
   4139 			// if the bot went for this goal less than 2 seconds ago
   4140 			if (bs->activategoalheap[i].justused_time > FloatTime() - 2)
   4141 				return qtrue;
   4142 		}
   4143 	}
   4144 	return qfalse;
   4145 }
   4146 
   4147 /*
   4148 ==================
   4149 BotGetActivateGoal
   4150 
   4151   returns the number of the bsp entity to activate
   4152   goal->entitynum will be set to the game entity to activate
   4153 ==================
   4154 */
   4155 //#define OBSTACLEDEBUG
   4156 
   4157 int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activategoal) {
   4158 	int i, ent, cur_entities[10], spawnflags, modelindex, areas[MAX_ACTIVATEAREAS*2], numareas, t;
   4159 	char model[MAX_INFO_STRING], tmpmodel[128];
   4160 	char target[128], classname[128];
   4161 	float health;
   4162 	char targetname[10][128];
   4163 	aas_entityinfo_t entinfo;
   4164 	aas_areainfo_t areainfo;
   4165 	vec3_t origin, angles, absmins, absmaxs;
   4166 
   4167 	memset(activategoal, 0, sizeof(bot_activategoal_t));
   4168 	BotEntityInfo(entitynum, &entinfo);
   4169 	Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex);
   4170 	for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
   4171 		if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue;
   4172 		if (!strcmp(model, tmpmodel)) break;
   4173 	}
   4174 	if (!ent) {
   4175 		BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity found with model %s\n", model);
   4176 		return 0;
   4177 	}
   4178 	trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname));
   4179 	if (!classname) {
   4180 		BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model);
   4181 		return 0;
   4182 	}
   4183 	//if it is a door
   4184 	if (!strcmp(classname, "func_door")) {
   4185 		if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) {
   4186 			//if the door has health then the door must be shot to open
   4187 			if (health) {
   4188 				BotFuncDoorActivateGoal(bs, ent, activategoal);
   4189 				return ent;
   4190 			}
   4191 		}
   4192 		//
   4193 		trap_AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags);
   4194 		// if the door starts open then just wait for the door to return
   4195 		if ( spawnflags & 1 )
   4196 			return 0;
   4197 		//get the door origin
   4198 		if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) {
   4199 			VectorClear(origin);
   4200 		}
   4201 		//if the door is open or opening already
   4202 		if (!VectorCompare(origin, entinfo.origin))
   4203 			return 0;
   4204 		// store all the areas the door is in
   4205 		trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model));
   4206 		if (*model) {
   4207 			modelindex = atoi(model+1);
   4208 			if (modelindex) {
   4209 				VectorClear(angles);
   4210 				BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs);
   4211 				//
   4212 				numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2);
   4213 				// store the areas with reachabilities first
   4214 				for (i = 0; i < numareas; i++) {
   4215 					if (activategoal->numareas >= MAX_ACTIVATEAREAS)
   4216 						break;
   4217 					if ( !trap_AAS_AreaReachability(areas[i]) ) {
   4218 						continue;
   4219 					}
   4220 					trap_AAS_AreaInfo(areas[i], &areainfo);
   4221 					if (areainfo.contents & AREACONTENTS_MOVER) {
   4222 						activategoal->areas[activategoal->numareas++] = areas[i];
   4223 					}
   4224 				}
   4225 				// store any remaining areas
   4226 				for (i = 0; i < numareas; i++) {
   4227 					if (activategoal->numareas >= MAX_ACTIVATEAREAS)
   4228 						break;
   4229 					if ( trap_AAS_AreaReachability(areas[i]) ) {
   4230 						continue;
   4231 					}
   4232 					trap_AAS_AreaInfo(areas[i], &areainfo);
   4233 					if (areainfo.contents & AREACONTENTS_MOVER) {
   4234 						activategoal->areas[activategoal->numareas++] = areas[i];
   4235 					}
   4236 				}
   4237 			}
   4238 		}
   4239 	}
   4240 	// if the bot is blocked by or standing on top of a button
   4241 	if (!strcmp(classname, "func_button")) {
   4242 		return 0;
   4243 	}
   4244 	// get the targetname so we can find an entity with a matching target
   4245 	if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) {
   4246 		if (bot_developer.integer) {
   4247 			BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model \"%s\" has no targetname\n", model);
   4248 		}
   4249 		return 0;
   4250 	}
   4251 	// allow tree-like activation
   4252 	cur_entities[0] = trap_AAS_NextBSPEntity(0);
   4253 	for (i = 0; i >= 0 && i < 10;) {
   4254 		for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) {
   4255 			if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue;
   4256 			if (!strcmp(targetname[i], target)) {
   4257 				cur_entities[i] = trap_AAS_NextBSPEntity(ent);
   4258 				break;
   4259 			}
   4260 		}
   4261 		if (!ent) {
   4262 			if (bot_developer.integer) {
   4263 				BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity with target \"%s\"\n", targetname[i]);
   4264 			}
   4265 			i--;
   4266 			continue;
   4267 		}
   4268 		if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) {
   4269 			if (bot_developer.integer) {
   4270 				BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with target \"%s\" has no classname\n", targetname[i]);
   4271 			}
   4272 			continue;
   4273 		}
   4274 		// BSP button model
   4275 		if (!strcmp(classname, "func_button")) {
   4276 			//
   4277 			if (!BotFuncButtonActivateGoal(bs, ent, activategoal))
   4278 				continue;
   4279 			// if the bot tries to activate this button already
   4280 			if ( bs->activatestack && bs->activatestack->inuse &&
   4281 				 bs->activatestack->goal.entitynum == activategoal->goal.entitynum &&
   4282 				 bs->activatestack->time > FloatTime() &&
   4283 				 bs->activatestack->start_time < FloatTime() - 2)
   4284 				continue;
   4285 			// if the bot is in a reachability area
   4286 			if ( trap_AAS_AreaReachability(bs->areanum) ) {
   4287 				// disable all areas the blocking entity is in
   4288 				BotEnableActivateGoalAreas( activategoal, qfalse );
   4289 				//
   4290 				t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl);
   4291 				// if the button is not reachable
   4292 				if (!t) {
   4293 					continue;
   4294 				}
   4295 				activategoal->time = FloatTime() + t * 0.01 + 5;
   4296 			}
   4297 			return ent;
   4298 		}
   4299 		// invisible trigger multiple box
   4300 		else if (!strcmp(classname, "trigger_multiple")) {
   4301 			//
   4302 			if (!BotTriggerMultipleActivateGoal(bs, ent, activategoal))
   4303 				continue;
   4304 			// if the bot tries to activate this trigger already
   4305 			if ( bs->activatestack && bs->activatestack->inuse &&
   4306 				 bs->activatestack->goal.entitynum == activategoal->goal.entitynum &&
   4307 				 bs->activatestack->time > FloatTime() &&
   4308 				 bs->activatestack->start_time < FloatTime() - 2)
   4309 				continue;
   4310 			// if the bot is in a reachability area
   4311 			if ( trap_AAS_AreaReachability(bs->areanum) ) {
   4312 				// disable all areas the blocking entity is in
   4313 				BotEnableActivateGoalAreas( activategoal, qfalse );
   4314 				//
   4315 				t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl);
   4316 				// if the trigger is not reachable
   4317 				if (!t) {
   4318 					continue;
   4319 				}
   4320 				activategoal->time = FloatTime() + t * 0.01 + 5;
   4321 			}
   4322 			return ent;
   4323 		}
   4324 		else if (!strcmp(classname, "func_timer")) {
   4325 			// just skip the func_timer
   4326 			continue;
   4327 		}
   4328 		// the actual button or trigger might be linked through a target_relay or target_delay
   4329 		else if (!strcmp(classname, "target_relay") || !strcmp(classname, "target_delay")) {
   4330 			if (trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[i+1], sizeof(targetname[0]))) {
   4331 				i++;
   4332 				cur_entities[i] = trap_AAS_NextBSPEntity(0);
   4333 			}
   4334 		}
   4335 	}
   4336 #ifdef OBSTACLEDEBUG
   4337 	BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no valid activator for entity with target \"%s\"\n", targetname[0]);
   4338 #endif
   4339 	return 0;
   4340 }
   4341 
   4342 /*
   4343 ==================
   4344 BotGoForActivateGoal
   4345 ==================
   4346 */
   4347 int BotGoForActivateGoal(bot_state_t *bs, bot_activategoal_t *activategoal) {
   4348 	aas_entityinfo_t activateinfo;
   4349 
   4350 	activategoal->inuse = qtrue;
   4351 	if (!activategoal->time)
   4352 		activategoal->time = FloatTime() + 10;
   4353 	activategoal->start_time = FloatTime();
   4354 	BotEntityInfo(activategoal->goal.entitynum, &activateinfo);
   4355 	VectorCopy(activateinfo.origin, activategoal->origin);
   4356 	//
   4357 	if (BotPushOntoActivateGoalStack(bs, activategoal)) {
   4358 		// enter the activate entity AI node
   4359 		AIEnter_Seek_ActivateEntity(bs, "BotGoForActivateGoal");
   4360 		return qtrue;
   4361 	}
   4362 	else {
   4363 		// enable any routing areas that were disabled
   4364 		BotEnableActivateGoalAreas(activategoal, qtrue);
   4365 		return qfalse;
   4366 	}
   4367 }
   4368 
   4369 /*
   4370 ==================
   4371 BotPrintActivateGoalInfo
   4372 ==================
   4373 */
   4374 void BotPrintActivateGoalInfo(bot_state_t *bs, bot_activategoal_t *activategoal, int bspent) {
   4375 	char netname[MAX_NETNAME];
   4376 	char classname[128];
   4377 	char buf[128];
   4378 
   4379 	ClientName(bs->client, netname, sizeof(netname));
   4380 	trap_AAS_ValueForBSPEpairKey(bspent, "classname", classname, sizeof(classname));
   4381 	if (activategoal->shoot) {
   4382 		Com_sprintf(buf, sizeof(buf), "%s: I have to shoot at a %s from %1.1f %1.1f %1.1f in area %d\n",
   4383 						netname, classname,
   4384 						activategoal->goal.origin[0],
   4385 						activategoal->goal.origin[1],
   4386 						activategoal->goal.origin[2],
   4387 						activategoal->goal.areanum);
   4388 	}
   4389 	else {
   4390 		Com_sprintf(buf, sizeof(buf), "%s: I have to activate a %s at %1.1f %1.1f %1.1f in area %d\n",
   4391 						netname, classname,
   4392 						activategoal->goal.origin[0],
   4393 						activategoal->goal.origin[1],
   4394 						activategoal->goal.origin[2],
   4395 						activategoal->goal.areanum);
   4396 	}
   4397 	trap_EA_Say(bs->client, buf);
   4398 }
   4399 
   4400 /*
   4401 ==================
   4402 BotRandomMove
   4403 ==================
   4404 */
   4405 void BotRandomMove(bot_state_t *bs, bot_moveresult_t *moveresult) {
   4406 	vec3_t dir, angles;
   4407 
   4408 	angles[0] = 0;
   4409 	angles[1] = random() * 360;
   4410 	angles[2] = 0;
   4411 	AngleVectors(angles, dir, NULL, NULL);
   4412 
   4413 	trap_BotMoveInDirection(bs->ms, dir, 400, MOVE_WALK);
   4414 
   4415 	moveresult->failure = qfalse;
   4416 	VectorCopy(dir, moveresult->movedir);
   4417 }
   4418 
   4419 /*
   4420 ==================
   4421 BotAIBlocked
   4422 
   4423 Very basic handling of bots being blocked by other entities.
   4424 Check what kind of entity is blocking the bot and try to activate
   4425 it. If that's not an option then try to walk around or over the entity.
   4426 Before the bot ends in this part of the AI it should predict which doors to
   4427 open, which buttons to activate etc.
   4428 ==================
   4429 */
   4430 void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) {
   4431 	int movetype, bspent;
   4432 	vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1};
   4433 	aas_entityinfo_t entinfo;
   4434 	bot_activategoal_t activategoal;
   4435 
   4436 	// if the bot is not blocked by anything
   4437 	if (!moveresult->blocked) {
   4438 		bs->notblocked_time = FloatTime();
   4439 		return;
   4440 	}
   4441 	// if stuck in a solid area
   4442 	if ( moveresult->type == RESULTTYPE_INSOLIDAREA ) {
   4443 		// move in a random direction in the hope to get out
   4444 		BotRandomMove(bs, moveresult);
   4445 		//
   4446 		return;
   4447 	}
   4448 	// get info for the entity that is blocking the bot
   4449 	BotEntityInfo(moveresult->blockentity, &entinfo);
   4450 #ifdef OBSTACLEDEBUG
   4451 	ClientName(bs->client, netname, sizeof(netname));
   4452 	BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex);
   4453 #endif // OBSTACLEDEBUG
   4454 	// if blocked by a bsp model and the bot wants to activate it
   4455 	if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) {
   4456 		// find the bsp entity which should be activated in order to get the blocking entity out of the way
   4457 		bspent = BotGetActivateGoal(bs, entinfo.number, &activategoal);
   4458 		if (bspent) {
   4459 			//
   4460 			if (bs->activatestack && !bs->activatestack->inuse)
   4461 				bs->activatestack = NULL;
   4462 			// if not already trying to activate this entity
   4463 			if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) {
   4464 				//
   4465 				BotGoForActivateGoal(bs, &activategoal);
   4466 			}
   4467 			// if ontop of an obstacle or
   4468 			// if the bot is not in a reachability area it'll still
   4469 			// need some dynamic obstacle avoidance, otherwise return
   4470 			if (!(moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) &&
   4471 				trap_AAS_AreaReachability(bs->areanum))
   4472 				return;
   4473 		}
   4474 		else {
   4475 			// enable any routing areas that were disabled
   4476 			BotEnableActivateGoalAreas(&activategoal, qtrue);
   4477 		}
   4478 	}
   4479 	// just some basic dynamic obstacle avoidance code
   4480 	hordir[0] = moveresult->movedir[0];
   4481 	hordir[1] = moveresult->movedir[1];
   4482 	hordir[2] = 0;
   4483 	// if no direction just take a random direction
   4484 	if (VectorNormalize(hordir) < 0.1) {
   4485 		VectorSet(angles, 0, 360 * random(), 0);
   4486 		AngleVectors(angles, hordir, NULL, NULL);
   4487 	}
   4488 	//
   4489 	//if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP;
   4490 	//else
   4491 	movetype = MOVE_WALK;
   4492 	// if there's an obstacle at the bot's feet and head then
   4493 	// the bot might be able to crouch through
   4494 	VectorCopy(bs->origin, start);
   4495 	start[2] += 18;
   4496 	VectorMA(start, 5, hordir, end);
   4497 	VectorSet(mins, -16, -16, -24);
   4498 	VectorSet(maxs, 16, 16, 4);
   4499 	//
   4500 	//bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID);
   4501 	//if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH;
   4502 	// get the sideward vector
   4503 	CrossProduct(hordir, up, sideward);
   4504 	//
   4505 	if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward);
   4506 	// try to crouch straight forward?
   4507 	if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) {
   4508 		// perform the movement
   4509 		if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) {
   4510 			// flip the avoid direction flag
   4511 			bs->flags ^= BFL_AVOIDRIGHT;
   4512 			// flip the direction
   4513 			// VectorNegate(sideward, sideward);
   4514 			VectorMA(sideward, -1, hordir, sideward);
   4515 			// move in the other direction
   4516 			trap_BotMoveInDirection(bs->ms, sideward, 400, movetype);
   4517 		}
   4518 	}
   4519 	//
   4520 	if (bs->notblocked_time < FloatTime() - 0.4) {
   4521 		// just reset goals and hope the bot will go into another direction?
   4522 		// is this still needed??
   4523 		if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
   4524 		else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
   4525 	}
   4526 }
   4527 
   4528 /*
   4529 ==================
   4530 BotAIPredictObstacles
   4531 
   4532 Predict the route towards the goal and check if the bot
   4533 will be blocked by certain obstacles. When the bot has obstacles
   4534 on it's path the bot should figure out if they can be removed
   4535 by activating certain entities.
   4536 ==================
   4537 */
   4538 int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) {
   4539 	int modelnum, entitynum, bspent;
   4540 	bot_activategoal_t activategoal;
   4541 	aas_predictroute_t route;
   4542 
   4543 	if (!bot_predictobstacles.integer)
   4544 		return qfalse;
   4545 
   4546 	// always predict when the goal change or at regular intervals
   4547 	if (bs->predictobstacles_goalareanum == goal->areanum &&
   4548 		bs->predictobstacles_time > FloatTime() - 6) {
   4549 		return qfalse;
   4550 	}
   4551 	bs->predictobstacles_goalareanum = goal->areanum;
   4552 	bs->predictobstacles_time = FloatTime();
   4553 
   4554 	// predict at most 100 areas or 10 seconds ahead
   4555 	trap_AAS_PredictRoute(&route, bs->areanum, bs->origin,
   4556 							goal->areanum, bs->tfl, 100, 1000,
   4557 							RSE_USETRAVELTYPE|RSE_ENTERCONTENTS,
   4558 							AREACONTENTS_MOVER, TFL_BRIDGE, 0);
   4559 	// if bot has to travel through an area with a mover
   4560 	if (route.stopevent & RSE_ENTERCONTENTS) {
   4561 		// if the bot will run into a mover
   4562 		if (route.endcontents & AREACONTENTS_MOVER) {
   4563 			//NOTE: this only works with bspc 2.1 or higher
   4564 			modelnum = (route.endcontents & AREACONTENTS_MODELNUM) >> AREACONTENTS_MODELNUMSHIFT;
   4565 			if (modelnum) {
   4566 				//
   4567 				entitynum = BotModelMinsMaxs(modelnum, ET_MOVER, 0, NULL, NULL);
   4568 				if (entitynum) {
   4569 					//NOTE: BotGetActivateGoal already checks if the door is open or not
   4570 					bspent = BotGetActivateGoal(bs, entitynum, &activategoal);
   4571 					if (bspent) {
   4572 						//
   4573 						if (bs->activatestack && !bs->activatestack->inuse)
   4574 							bs->activatestack = NULL;
   4575 						// if not already trying to activate this entity
   4576 						if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) {
   4577 							//
   4578 							//BotAI_Print(PRT_MESSAGE, "blocked by mover model %d, entity %d ?\n", modelnum, entitynum);
   4579 							//
   4580 							BotGoForActivateGoal(bs, &activategoal);
   4581 							return qtrue;
   4582 						}
   4583 						else {
   4584 							// enable any routing areas that were disabled
   4585 							BotEnableActivateGoalAreas(&activategoal, qtrue);
   4586 						}
   4587 					}
   4588 				}
   4589 			}
   4590 		}
   4591 	}
   4592 	else if (route.stopevent & RSE_USETRAVELTYPE) {
   4593 		if (route.endtravelflags & TFL_BRIDGE) {
   4594 			//FIXME: check if the bridge is available to travel over
   4595 		}
   4596 	}
   4597 	return qfalse;
   4598 }
   4599 
   4600 /*
   4601 ==================
   4602 BotCheckConsoleMessages
   4603 ==================
   4604 */
   4605 void BotCheckConsoleMessages(bot_state_t *bs) {
   4606 	char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr;
   4607 	float chat_reply;
   4608 	int context, handle;
   4609 	bot_consolemessage_t m;
   4610 	bot_match_t match;
   4611 
   4612 	//the name of this bot
   4613 	ClientName(bs->client, botname, sizeof(botname));
   4614 	//
   4615 	while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) {
   4616 		//if the chat state is flooded with messages the bot will read them quickly
   4617 		if (trap_BotNumConsoleMessages(bs->cs) < 10) {
   4618 			//if it is a chat message the bot needs some time to read it
   4619 			if (m.type == CMS_CHAT && m.time > FloatTime() - (1 + random())) break;
   4620 		}
   4621 		//
   4622 		ptr = m.message;
   4623 		//if it is a chat message then don't unify white spaces and don't
   4624 		//replace synonyms in the netname
   4625 		if (m.type == CMS_CHAT) {
   4626 			//
   4627 			if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
   4628 				ptr = m.message + match.variables[MESSAGE].offset;
   4629 			}
   4630 		}
   4631 		//unify the white spaces in the message
   4632 		trap_UnifyWhiteSpaces(ptr);
   4633 		//replace synonyms in the right context
   4634 		context = BotSynonymContext(bs);
   4635 		trap_BotReplaceSynonyms(ptr, context);
   4636 		//if there's no match
   4637 		if (!BotMatchMessage(bs, m.message)) {
   4638 			//if it is a chat message
   4639 			if (m.type == CMS_CHAT && !bot_nochat.integer) {
   4640 				//
   4641 				if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
   4642 					trap_BotRemoveConsoleMessage(bs->cs, handle);
   4643 					continue;
   4644 				}
   4645 				//don't use eliza chats with team messages
   4646 				if (match.subtype & ST_TEAM) {
   4647 					trap_BotRemoveConsoleMessage(bs->cs, handle);
   4648 					continue;
   4649 				}
   4650 				//
   4651 				trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname));
   4652 				trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message));
   4653 				//if this is a message from the bot self
   4654 				if (bs->client == ClientFromName(netname)) {
   4655 					trap_BotRemoveConsoleMessage(bs->cs, handle);
   4656 					continue;
   4657 				}
   4658 				//unify the message
   4659 				trap_UnifyWhiteSpaces(message);
   4660 				//
   4661 				trap_Cvar_Update(&bot_testrchat);
   4662 				if (bot_testrchat.integer) {
   4663 					//
   4664 					trap_BotLibVarSet("bot_testrchat", "1");
   4665 					//if bot replies with a chat message
   4666 					if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
   4667 															NULL, NULL,
   4668 															NULL, NULL,
   4669 															NULL, NULL,
   4670 															botname, netname)) {
   4671 						BotAI_Print(PRT_MESSAGE, "------------------------\n");
   4672 					}
   4673 					else {
   4674 						BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n");
   4675 					}
   4676 				}
   4677 				//if at a valid chat position and not chatting already and not in teamplay
   4678 				else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs) && !TeamPlayIsOn()) {
   4679 					chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1);
   4680 					if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) {
   4681 						//if bot replies with a chat message
   4682 						if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
   4683 																NULL, NULL,
   4684 																NULL, NULL,
   4685 																NULL, NULL,
   4686 																botname, netname)) {
   4687 							//remove the console message
   4688 							trap_BotRemoveConsoleMessage(bs->cs, handle);
   4689 							bs->stand_time = FloatTime() + BotChatTime(bs);
   4690 							AIEnter_Stand(bs, "BotCheckConsoleMessages: reply chat");
   4691 							//EA_Say(bs->client, bs->cs.chatmessage);
   4692 							break;
   4693 						}
   4694 					}
   4695 				}
   4696 			}
   4697 		}
   4698 		//remove the console message
   4699 		trap_BotRemoveConsoleMessage(bs->cs, handle);
   4700 	}
   4701 }
   4702 
   4703 /*
   4704 ==================
   4705 BotCheckEvents
   4706 ==================
   4707 */
   4708 void BotCheckForGrenades(bot_state_t *bs, entityState_t *state) {
   4709 	// if this is not a grenade
   4710 	if (state->eType != ET_MISSILE || state->weapon != WP_GRENADE_LAUNCHER)
   4711 		return;
   4712 	// try to avoid the grenade
   4713 	trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS);
   4714 }
   4715 
   4716 #ifdef MISSIONPACK
   4717 /*
   4718 ==================
   4719 BotCheckForProxMines
   4720 ==================
   4721 */
   4722 void BotCheckForProxMines(bot_state_t *bs, entityState_t *state) {
   4723 	// if this is not a prox mine
   4724 	if (state->eType != ET_MISSILE || state->weapon != WP_PROX_LAUNCHER)
   4725 		return;
   4726 	// if this prox mine is from someone on our own team
   4727 	if (state->generic1 == BotTeam(bs))
   4728 		return;
   4729 	// if the bot doesn't have a weapon to deactivate the mine
   4730 	if (!(bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) &&
   4731 		!(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) &&
   4732 		!(bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) ) {
   4733 		return;
   4734 	}
   4735 	// try to avoid the prox mine
   4736 	trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS);
   4737 	//
   4738 	if (bs->numproxmines >= MAX_PROXMINES)
   4739 		return;
   4740 	bs->proxmines[bs->numproxmines] = state->number;
   4741 	bs->numproxmines++;
   4742 }
   4743 
   4744 /*
   4745 ==================
   4746 BotCheckForKamikazeBody
   4747 ==================
   4748 */
   4749 void BotCheckForKamikazeBody(bot_state_t *bs, entityState_t *state) {
   4750 	// if this entity is not wearing the kamikaze
   4751 	if (!(state->eFlags & EF_KAMIKAZE))
   4752 		return;
   4753 	// if this entity isn't dead
   4754 	if (!(state->eFlags & EF_DEAD))
   4755 		return;
   4756 	//remember this kamikaze body
   4757 	bs->kamikazebody = state->number;
   4758 }
   4759 #endif
   4760 
   4761 /*
   4762 ==================
   4763 BotCheckEvents
   4764 ==================
   4765 */
   4766 void BotCheckEvents(bot_state_t *bs, entityState_t *state) {
   4767 	int event;
   4768 	char buf[128];
   4769 #ifdef MISSIONPACK
   4770 	aas_entityinfo_t entinfo;
   4771 #endif
   4772 
   4773 	//NOTE: this sucks, we're accessing the gentity_t directly
   4774 	//but there's no other fast way to do it right now
   4775 	if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) {
   4776 		return;
   4777 	}
   4778 	bs->entityeventTime[state->number] = g_entities[state->number].eventTime;
   4779 	//if it's an event only entity
   4780 	if (state->eType > ET_EVENTS) {
   4781 		event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS;
   4782 	}
   4783 	else {
   4784 		event = state->event & ~EV_EVENT_BITS;
   4785 	}
   4786 	//
   4787 	switch(event) {
   4788 		//client obituary event
   4789 		case EV_OBITUARY:
   4790 		{
   4791 			int target, attacker, mod;
   4792 
   4793 			target = state->otherEntityNum;
   4794 			attacker = state->otherEntityNum2;
   4795 			mod = state->eventParm;
   4796 			//
   4797 			if (target == bs->client) {
   4798 				bs->botdeathtype = mod;
   4799 				bs->lastkilledby = attacker;
   4800 				//
   4801 				if (target == attacker ||
   4802 					target == ENTITYNUM_NONE ||
   4803 					target == ENTITYNUM_WORLD) bs->botsuicide = qtrue;
   4804 				else bs->botsuicide = qfalse;
   4805 				//
   4806 				bs->num_deaths++;
   4807 			}
   4808 			//else if this client was killed by the bot
   4809 			else if (attacker == bs->client) {
   4810 				bs->enemydeathtype = mod;
   4811 				bs->lastkilledplayer = target;
   4812 				bs->killedenemy_time = FloatTime();
   4813 				//
   4814 				bs->num_kills++;
   4815 			}
   4816 			else if (attacker == bs->enemy && target == attacker) {
   4817 				bs->enemysuicide = qtrue;
   4818 			}
   4819 			//
   4820 #ifdef MISSIONPACK			
   4821 			if (gametype == GT_1FCTF) {
   4822 				//
   4823 				BotEntityInfo(target, &entinfo);
   4824 				if ( entinfo.powerups & ( 1 << PW_NEUTRALFLAG ) ) {
   4825 					if (!BotSameTeam(bs, target)) {
   4826 						bs->neutralflagstatus = 3;	//enemy dropped the flag
   4827 						bs->flagstatuschanged = qtrue;
   4828 					}
   4829 				}
   4830 			}
   4831 #endif
   4832 			break;
   4833 		}
   4834 		case EV_GLOBAL_SOUND:
   4835 		{
   4836 			if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
   4837 				BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
   4838 				break;
   4839 			}
   4840 			trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
   4841 			/*
   4842 			if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) {
   4843 				//red flag is returned
   4844 				bs->redflagstatus = 0;
   4845 				bs->flagstatuschanged = qtrue;
   4846 			}
   4847 			else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) {
   4848 				//blue flag is returned
   4849 				bs->blueflagstatus = 0;
   4850 				bs->flagstatuschanged = qtrue;
   4851 			}
   4852 			else*/
   4853 #ifdef MISSIONPACK
   4854 			if (!strcmp(buf, "sound/items/kamikazerespawn.wav" )) {
   4855 				//the kamikaze respawned so dont avoid it
   4856 				BotDontAvoid(bs, "Kamikaze");
   4857 			}
   4858 			else
   4859 #endif
   4860 				if (!strcmp(buf, "sound/items/poweruprespawn.wav")) {
   4861 				//powerup respawned... go get it
   4862 				BotGoForPowerups(bs);
   4863 			}
   4864 			break;
   4865 		}
   4866 		case EV_GLOBAL_TEAM_SOUND:
   4867 		{
   4868 			if (gametype == GT_CTF) {
   4869 				switch(state->eventParm) {
   4870 					case GTS_RED_CAPTURE:
   4871 						bs->blueflagstatus = 0;
   4872 						bs->redflagstatus = 0;
   4873 						bs->flagstatuschanged = qtrue;
   4874 						break; //see BotMatch_CTF
   4875 					case GTS_BLUE_CAPTURE:
   4876 						bs->blueflagstatus = 0;
   4877 						bs->redflagstatus = 0;
   4878 						bs->flagstatuschanged = qtrue;
   4879 						break; //see BotMatch_CTF
   4880 					case GTS_RED_RETURN:
   4881 						//blue flag is returned
   4882 						bs->blueflagstatus = 0;
   4883 						bs->flagstatuschanged = qtrue;
   4884 						break;
   4885 					case GTS_BLUE_RETURN:
   4886 						//red flag is returned
   4887 						bs->redflagstatus = 0;
   4888 						bs->flagstatuschanged = qtrue;
   4889 						break;
   4890 					case GTS_RED_TAKEN:
   4891 						//blue flag is taken
   4892 						bs->blueflagstatus = 1;
   4893 						bs->flagstatuschanged = qtrue;
   4894 						break; //see BotMatch_CTF
   4895 					case GTS_BLUE_TAKEN:
   4896 						//red flag is taken
   4897 						bs->redflagstatus = 1;
   4898 						bs->flagstatuschanged = qtrue;
   4899 						break; //see BotMatch_CTF
   4900 				}
   4901 			}
   4902 #ifdef MISSIONPACK
   4903 			else if (gametype == GT_1FCTF) {
   4904 				switch(state->eventParm) {
   4905 					case GTS_RED_CAPTURE:
   4906 						bs->neutralflagstatus = 0;
   4907 						bs->flagstatuschanged = qtrue;
   4908 						break;
   4909 					case GTS_BLUE_CAPTURE:
   4910 						bs->neutralflagstatus = 0;
   4911 						bs->flagstatuschanged = qtrue;
   4912 						break;
   4913 					case GTS_RED_RETURN:
   4914 						//flag has returned
   4915 						bs->neutralflagstatus = 0;
   4916 						bs->flagstatuschanged = qtrue;
   4917 						break;
   4918 					case GTS_BLUE_RETURN:
   4919 						//flag has returned
   4920 						bs->neutralflagstatus = 0;
   4921 						bs->flagstatuschanged = qtrue;
   4922 						break;
   4923 					case GTS_RED_TAKEN:
   4924 						bs->neutralflagstatus = BotTeam(bs) == TEAM_RED ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c
   4925 						bs->flagstatuschanged = qtrue;
   4926 						break;
   4927 					case GTS_BLUE_TAKEN:
   4928 						bs->neutralflagstatus = BotTeam(bs) == TEAM_BLUE ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c
   4929 						bs->flagstatuschanged = qtrue;
   4930 						break;
   4931 				}
   4932 			}
   4933 #endif
   4934 			break;
   4935 		}
   4936 		case EV_PLAYER_TELEPORT_IN:
   4937 		{
   4938 			VectorCopy(state->origin, lastteleport_origin);
   4939 			lastteleport_time = FloatTime();
   4940 			break;
   4941 		}
   4942 		case EV_GENERAL_SOUND:
   4943 		{
   4944 			//if this sound is played on the bot
   4945 			if (state->number == bs->client) {
   4946 				if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
   4947 					BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
   4948 					break;
   4949 				}
   4950 				//check out the sound
   4951 				trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
   4952 				//if falling into a death pit
   4953 				if (!strcmp(buf, "*falling1.wav")) {
   4954 					//if the bot has a personal teleporter
   4955 					if (bs->inventory[INVENTORY_TELEPORTER] > 0) {
   4956 						//use the holdable item
   4957 						trap_EA_Use(bs->client);
   4958 					}
   4959 				}
   4960 			}
   4961 			break;
   4962 		}
   4963 		case EV_FOOTSTEP:
   4964 		case EV_FOOTSTEP_METAL:
   4965 		case EV_FOOTSPLASH:
   4966 		case EV_FOOTWADE:
   4967 		case EV_SWIM:
   4968 		case EV_FALL_SHORT:
   4969 		case EV_FALL_MEDIUM:
   4970 		case EV_FALL_FAR:
   4971 		case EV_STEP_4:
   4972 		case EV_STEP_8:
   4973 		case EV_STEP_12:
   4974 		case EV_STEP_16:
   4975 		case EV_JUMP_PAD:
   4976 		case EV_JUMP:
   4977 		case EV_TAUNT:
   4978 		case EV_WATER_TOUCH:
   4979 		case EV_WATER_LEAVE:
   4980 		case EV_WATER_UNDER:
   4981 		case EV_WATER_CLEAR:
   4982 		case EV_ITEM_PICKUP:
   4983 		case EV_GLOBAL_ITEM_PICKUP:
   4984 		case EV_NOAMMO:
   4985 		case EV_CHANGE_WEAPON:
   4986 		case EV_FIRE_WEAPON:
   4987 			//FIXME: either add to sound queue or mark player as someone making noise
   4988 			break;
   4989 		case EV_USE_ITEM0:
   4990 		case EV_USE_ITEM1:
   4991 		case EV_USE_ITEM2:
   4992 		case EV_USE_ITEM3:
   4993 		case EV_USE_ITEM4:
   4994 		case EV_USE_ITEM5:
   4995 		case EV_USE_ITEM6:
   4996 		case EV_USE_ITEM7:
   4997 		case EV_USE_ITEM8:
   4998 		case EV_USE_ITEM9:
   4999 		case EV_USE_ITEM10:
   5000 		case EV_USE_ITEM11:
   5001 		case EV_USE_ITEM12:
   5002 		case EV_USE_ITEM13:
   5003 		case EV_USE_ITEM14:
   5004 			break;
   5005 	}
   5006 }
   5007 
   5008 /*
   5009 ==================
   5010 BotCheckSnapshot
   5011 ==================
   5012 */
   5013 void BotCheckSnapshot(bot_state_t *bs) {
   5014 	int ent;
   5015 	entityState_t state;
   5016 
   5017 	//remove all avoid spots
   5018 	trap_BotAddAvoidSpot(bs->ms, vec3_origin, 0, AVOID_CLEAR);
   5019 	//reset kamikaze body
   5020 	bs->kamikazebody = 0;
   5021 	//reset number of proxmines
   5022 	bs->numproxmines = 0;
   5023 	//
   5024 	ent = 0;
   5025 	while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) {
   5026 		//check the entity state for events
   5027 		BotCheckEvents(bs, &state);
   5028 		//check for grenades the bot should avoid
   5029 		BotCheckForGrenades(bs, &state);
   5030 		//
   5031 #ifdef MISSIONPACK
   5032 		//check for proximity mines which the bot should deactivate
   5033 		BotCheckForProxMines(bs, &state);
   5034 		//check for dead bodies with the kamikaze effect which should be gibbed
   5035 		BotCheckForKamikazeBody(bs, &state);
   5036 #endif
   5037 	}
   5038 	//check the player state for events
   5039 	BotAI_GetEntityState(bs->client, &state);
   5040 	//copy the player state events to the entity state
   5041 	state.event = bs->cur_ps.externalEvent;
   5042 	state.eventParm = bs->cur_ps.externalEventParm;
   5043 	//
   5044 	BotCheckEvents(bs, &state);
   5045 }
   5046 
   5047 /*
   5048 ==================
   5049 BotCheckAir
   5050 ==================
   5051 */
   5052 void BotCheckAir(bot_state_t *bs) {
   5053 	if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) {
   5054 		if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) {
   5055 			return;
   5056 		}
   5057 	}
   5058 	bs->lastair_time = FloatTime();
   5059 }
   5060 
   5061 /*
   5062 ==================
   5063 BotAlternateRoute
   5064 ==================
   5065 */
   5066 bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal) {
   5067 	int t;
   5068 
   5069 	// if the bot has an alternative route goal
   5070 	if (bs->altroutegoal.areanum) {
   5071 		//
   5072 		if (bs->reachedaltroutegoal_time)
   5073 			return goal;
   5074 		// travel time towards alternative route goal
   5075 		t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->altroutegoal.areanum, bs->tfl);
   5076 		if (t && t < 20) {
   5077 			//BotAI_Print(PRT_MESSAGE, "reached alternate route goal\n");
   5078 			bs->reachedaltroutegoal_time = FloatTime();
   5079 		}
   5080 		memcpy(goal, &bs->altroutegoal, sizeof(bot_goal_t));
   5081 		return &bs->altroutegoal;
   5082 	}
   5083 	return goal;
   5084 }
   5085 
   5086 /*
   5087 ==================
   5088 BotGetAlternateRouteGoal
   5089 ==================
   5090 */
   5091 int BotGetAlternateRouteGoal(bot_state_t *bs, int base) {
   5092 	aas_altroutegoal_t *altroutegoals;
   5093 	bot_goal_t *goal;
   5094 	int numaltroutegoals, rnd;
   5095 
   5096 	if (base == TEAM_RED) {
   5097 		altroutegoals = red_altroutegoals;
   5098 		numaltroutegoals = red_numaltroutegoals;
   5099 	}
   5100 	else {
   5101 		altroutegoals = blue_altroutegoals;
   5102 		numaltroutegoals = blue_numaltroutegoals;
   5103 	}
   5104 	if (!numaltroutegoals)
   5105 		return qfalse;
   5106 	rnd = (float) random() * numaltroutegoals;
   5107 	if (rnd >= numaltroutegoals)
   5108 		rnd = numaltroutegoals-1;
   5109 	goal = &bs->altroutegoal;
   5110 	goal->areanum = altroutegoals[rnd].areanum;
   5111 	VectorCopy(altroutegoals[rnd].origin, goal->origin);
   5112 	VectorSet(goal->mins, -8, -8, -8);
   5113 	VectorSet(goal->maxs, 8, 8, 8);
   5114 	goal->entitynum = 0;
   5115 	goal->iteminfo = 0;
   5116 	goal->number = 0;
   5117 	goal->flags = 0;
   5118 	//
   5119 	bs->reachedaltroutegoal_time = 0;
   5120 	return qtrue;
   5121 }
   5122 
   5123 /*
   5124 ==================
   5125 BotSetupAlternateRouteGoals
   5126 ==================
   5127 */
   5128 void BotSetupAlternativeRouteGoals(void) {
   5129 
   5130 	if (altroutegoals_setup)
   5131 		return;
   5132 #ifdef MISSIONPACK
   5133 	if (gametype == GT_CTF) {
   5134 		if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0)
   5135 			BotAI_Print(PRT_WARNING, "no alt routes without Neutral Flag\n");
   5136 		if (ctf_neutralflag.areanum) {
   5137 			//
   5138 			red_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
   5139 										ctf_neutralflag.origin, ctf_neutralflag.areanum,
   5140 										ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT,
   5141 										red_altroutegoals, MAX_ALTROUTEGOALS,
   5142 										ALTROUTEGOAL_CLUSTERPORTALS|
   5143 										ALTROUTEGOAL_VIEWPORTALS);
   5144 			blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
   5145 										ctf_neutralflag.origin, ctf_neutralflag.areanum,
   5146 										ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT,
   5147 										blue_altroutegoals, MAX_ALTROUTEGOALS,
   5148 										ALTROUTEGOAL_CLUSTERPORTALS|
   5149 										ALTROUTEGOAL_VIEWPORTALS);
   5150 		}
   5151 	}
   5152 	else if (gametype == GT_1FCTF) {
   5153 		//
   5154 		red_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
   5155 									ctf_neutralflag.origin, ctf_neutralflag.areanum,
   5156 									ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT,
   5157 									red_altroutegoals, MAX_ALTROUTEGOALS,
   5158 									ALTROUTEGOAL_CLUSTERPORTALS|
   5159 									ALTROUTEGOAL_VIEWPORTALS);
   5160 		blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
   5161 									ctf_neutralflag.origin, ctf_neutralflag.areanum,
   5162 									ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT,
   5163 									blue_altroutegoals, MAX_ALTROUTEGOALS,
   5164 									ALTROUTEGOAL_CLUSTERPORTALS|
   5165 									ALTROUTEGOAL_VIEWPORTALS);
   5166 	}
   5167 	else if (gametype == GT_OBELISK) {
   5168 		if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0)
   5169 			BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n");
   5170 		//
   5171 		red_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
   5172 									neutralobelisk.origin, neutralobelisk.areanum,
   5173 									redobelisk.origin, redobelisk.areanum, TFL_DEFAULT,
   5174 									red_altroutegoals, MAX_ALTROUTEGOALS,
   5175 									ALTROUTEGOAL_CLUSTERPORTALS|
   5176 									ALTROUTEGOAL_VIEWPORTALS);
   5177 		blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
   5178 									neutralobelisk.origin, neutralobelisk.areanum,
   5179 									blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT,
   5180 									blue_altroutegoals, MAX_ALTROUTEGOALS,
   5181 									ALTROUTEGOAL_CLUSTERPORTALS|
   5182 									ALTROUTEGOAL_VIEWPORTALS);
   5183 	}
   5184 	else if (gametype == GT_HARVESTER) {
   5185 		//
   5186 		red_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
   5187 									neutralobelisk.origin, neutralobelisk.areanum,
   5188 									redobelisk.origin, redobelisk.areanum, TFL_DEFAULT,
   5189 									red_altroutegoals, MAX_ALTROUTEGOALS,
   5190 									ALTROUTEGOAL_CLUSTERPORTALS|
   5191 									ALTROUTEGOAL_VIEWPORTALS);
   5192 		blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
   5193 									neutralobelisk.origin, neutralobelisk.areanum,
   5194 									blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT,
   5195 									blue_altroutegoals, MAX_ALTROUTEGOALS,
   5196 									ALTROUTEGOAL_CLUSTERPORTALS|
   5197 									ALTROUTEGOAL_VIEWPORTALS);
   5198 	}
   5199 #endif
   5200 	altroutegoals_setup = qtrue;
   5201 }
   5202 
   5203 /*
   5204 ==================
   5205 BotDeathmatchAI
   5206 ==================
   5207 */
   5208 void BotDeathmatchAI(bot_state_t *bs, float thinktime) {
   5209 	char gender[144], name[144], buf[144];
   5210 	char userinfo[MAX_INFO_STRING];
   5211 	int i;
   5212 
   5213 	//if the bot has just been setup
   5214 	if (bs->setupcount > 0) {
   5215 		bs->setupcount--;
   5216 		if (bs->setupcount > 0) return;
   5217 		//get the gender characteristic
   5218 		trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender));
   5219 		//set the bot gender
   5220 		trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo));
   5221 		Info_SetValueForKey(userinfo, "sex", gender);
   5222 		trap_SetUserinfo(bs->client, userinfo);
   5223 		//set the team
   5224 		if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) {
   5225 			Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team);
   5226 			trap_EA_Command(bs->client, buf);
   5227 		}
   5228 		//set the chat gender
   5229 		if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE);
   5230 		else if (gender[0] == 'f')  trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE);
   5231 		else  trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS);
   5232 		//set the chat name
   5233 		ClientName(bs->client, name, sizeof(name));
   5234 		trap_BotSetChatName(bs->cs, name, bs->client);
   5235 		//
   5236 		bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
   5237 		bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
   5238 		//
   5239 		bs->setupcount = 0;
   5240 		//
   5241 		BotSetupAlternativeRouteGoals();
   5242 	}
   5243 	//no ideal view set
   5244 	bs->flags &= ~BFL_IDEALVIEWSET;
   5245 	//
   5246 	if (!BotIntermission(bs)) {
   5247 		//set the teleport time
   5248 		BotSetTeleportTime(bs);
   5249 		//update some inventory values
   5250 		BotUpdateInventory(bs);
   5251 		//check out the snapshot
   5252 		BotCheckSnapshot(bs);
   5253 		//check for air
   5254 		BotCheckAir(bs);
   5255 	}
   5256 	//check the console messages
   5257 	BotCheckConsoleMessages(bs);
   5258 	//if not in the intermission and not in observer mode
   5259 	if (!BotIntermission(bs) && !BotIsObserver(bs)) {
   5260 		//do team AI
   5261 		BotTeamAI(bs);
   5262 	}
   5263 	//if the bot has no ai node
   5264 	if (!bs->ainode) {
   5265 		AIEnter_Seek_LTG(bs, "BotDeathmatchAI: no ai node");
   5266 	}
   5267 	//if the bot entered the game less than 8 seconds ago
   5268 	if (!bs->entergamechat && bs->entergame_time > FloatTime() - 8) {
   5269 		if (BotChat_EnterGame(bs)) {
   5270 			bs->stand_time = FloatTime() + BotChatTime(bs);
   5271 			AIEnter_Stand(bs, "BotDeathmatchAI: chat enter game");
   5272 		}
   5273 		bs->entergamechat = qtrue;
   5274 	}
   5275 	//reset the node switches from the previous frame
   5276 	BotResetNodeSwitches();
   5277 	//execute AI nodes
   5278 	for (i = 0; i < MAX_NODESWITCHES; i++) {
   5279 		if (bs->ainode(bs)) break;
   5280 	}
   5281 	//if the bot removed itself :)
   5282 	if (!bs->inuse) return;
   5283 	//if the bot executed too many AI nodes
   5284 	if (i >= MAX_NODESWITCHES) {
   5285 		trap_BotDumpGoalStack(bs->gs);
   5286 		trap_BotDumpAvoidGoals(bs->gs);
   5287 		BotDumpNodeSwitches(bs);
   5288 		ClientName(bs->client, name, sizeof(name));
   5289 		BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, FloatTime(), MAX_NODESWITCHES);
   5290 	}
   5291 	//
   5292 	bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
   5293 	bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
   5294 }
   5295 
   5296 /*
   5297 ==================
   5298 BotSetEntityNumForGoalWithModel
   5299 ==================
   5300 */
   5301 void BotSetEntityNumForGoalWithModel(bot_goal_t *goal, int eType, char *modelname) {
   5302 	gentity_t *ent;
   5303 	int i, modelindex;
   5304 	vec3_t dir;
   5305 
   5306 	modelindex = G_ModelIndex( modelname );
   5307 	ent = &g_entities[0];
   5308 	for (i = 0; i < level.num_entities; i++, ent++) {
   5309 		if ( !ent->inuse ) {
   5310 			continue;
   5311 		}
   5312 		if ( eType && ent->s.eType != eType) {
   5313 			continue;
   5314 		}
   5315 		if (ent->s.modelindex != modelindex) {
   5316 			continue;
   5317 		}
   5318 		VectorSubtract(goal->origin, ent->s.origin, dir);
   5319 		if (VectorLengthSquared(dir) < Square(10)) {
   5320 			goal->entitynum = i;
   5321 			return;
   5322 		}
   5323 	}
   5324 }
   5325 
   5326 /*
   5327 ==================
   5328 BotSetEntityNumForGoal
   5329 ==================
   5330 */
   5331 void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname) {
   5332 	gentity_t *ent;
   5333 	int i;
   5334 	vec3_t dir;
   5335 
   5336 	ent = &g_entities[0];
   5337 	for (i = 0; i < level.num_entities; i++, ent++) {
   5338 		if ( !ent->inuse ) {
   5339 			continue;
   5340 		}
   5341 		if ( !Q_stricmp(ent->classname, classname) ) {
   5342 			continue;
   5343 		}
   5344 		VectorSubtract(goal->origin, ent->s.origin, dir);
   5345 		if (VectorLengthSquared(dir) < Square(10)) {
   5346 			goal->entitynum = i;
   5347 			return;
   5348 		}
   5349 	}
   5350 }
   5351 
   5352 /*
   5353 ==================
   5354 BotGoalForBSPEntity
   5355 ==================
   5356 */
   5357 int BotGoalForBSPEntity( char *classname, bot_goal_t *goal ) {
   5358 	char value[MAX_INFO_STRING];
   5359 	vec3_t origin, start, end;
   5360 	int ent, numareas, areas[10];
   5361 
   5362 	memset(goal, 0, sizeof(bot_goal_t));
   5363 	for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
   5364 		if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", value, sizeof(value)))
   5365 			continue;
   5366 		if (!strcmp(value, classname)) {
   5367 			if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin))
   5368 				return qfalse;
   5369 			VectorCopy(origin, goal->origin);
   5370 			VectorCopy(origin, start);
   5371 			start[2] -= 32;
   5372 			VectorCopy(origin, end);
   5373 			end[2] += 32;
   5374 			numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10);
   5375 			if (!numareas)
   5376 				return qfalse;
   5377 			goal->areanum = areas[0];
   5378 			return qtrue;
   5379 		}
   5380 	}
   5381 	return qfalse;
   5382 }
   5383 
   5384 /*
   5385 ==================
   5386 BotSetupDeathmatchAI
   5387 ==================
   5388 */
   5389 void BotSetupDeathmatchAI(void) {
   5390 	int ent, modelnum;
   5391 	char model[128];
   5392 
   5393 	gametype = trap_Cvar_VariableIntegerValue("g_gametype");
   5394 	maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
   5395 
   5396 	trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0);
   5397 	trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0);
   5398 	trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0);
   5399 	trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0);
   5400 	trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0);
   5401 	trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0);
   5402 	trap_Cvar_Register(&bot_predictobstacles, "bot_predictobstacles", "1", 0);
   5403 	trap_Cvar_Register(&g_spSkill, "g_spSkill", "2", 0);
   5404 	//
   5405 	if (gametype == GT_CTF) {
   5406 		if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0)
   5407 			BotAI_Print(PRT_WARNING, "CTF without Red Flag\n");
   5408 		if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0)
   5409 			BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n");
   5410 	}
   5411 #ifdef MISSIONPACK
   5412 	else if (gametype == GT_1FCTF) {
   5413 		if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0)
   5414 			BotAI_Print(PRT_WARNING, "One Flag CTF without Neutral Flag\n");
   5415 		if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0)
   5416 			BotAI_Print(PRT_WARNING, "CTF without Red Flag\n");
   5417 		if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0)
   5418 			BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n");
   5419 	}
   5420 	else if (gametype == GT_OBELISK) {
   5421 		if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0)
   5422 			BotAI_Print(PRT_WARNING, "Obelisk without red obelisk\n");
   5423 		BotSetEntityNumForGoal(&redobelisk, "team_redobelisk");
   5424 		if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0)
   5425 			BotAI_Print(PRT_WARNING, "Obelisk without blue obelisk\n");
   5426 		BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk");
   5427 	}
   5428 	else if (gametype == GT_HARVESTER) {
   5429 		if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0)
   5430 			BotAI_Print(PRT_WARNING, "Harvester without red obelisk\n");
   5431 		BotSetEntityNumForGoal(&redobelisk, "team_redobelisk");
   5432 		if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0)
   5433 			BotAI_Print(PRT_WARNING, "Harvester without blue obelisk\n");
   5434 		BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk");
   5435 		if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0)
   5436 			BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n");
   5437 		BotSetEntityNumForGoal(&neutralobelisk, "team_neutralobelisk");
   5438 	}
   5439 #endif
   5440 
   5441 	max_bspmodelindex = 0;
   5442 	for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
   5443 		if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue;
   5444 		if (model[0] == '*') {
   5445 			modelnum = atoi(model+1);
   5446 			if (modelnum > max_bspmodelindex)
   5447 				max_bspmodelindex = modelnum;
   5448 		}
   5449 	}
   5450 	//initialize the waypoint heap
   5451 	BotInitWaypoints();
   5452 }
   5453 
   5454 /*
   5455 ==================
   5456 BotShutdownDeathmatchAI
   5457 ==================
   5458 */
   5459 void BotShutdownDeathmatchAI(void) {
   5460 	altroutegoals_setup = qfalse;
   5461 }