home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2000 March / maximum-cd-2000-03.iso / Quake3 Game Source / Q3AGameSource.exe / Main / ai_dmq3.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-01-18  |  81.2 KB  |  2,646 lines

  1. // Copyright (C) 1999-2000 Id Software, Inc.
  2. //
  3. /*****************************************************************************
  4.  * name:        ai_dmq3.c
  5.  *
  6.  * desc:        Quake3 bot AI
  7.  *
  8.  * $Archive: /source/code/game/ai_dmq3.c $
  9.  * $Author: Raduffy $ 
  10.  * $Revision: 7 $
  11.  * $Modtime: 1/14/00 5:28p $
  12.  * $Date: 1/14/00 5:35p $
  13.  *
  14.  *****************************************************************************/
  15.  
  16.  
  17. #include "g_local.h"
  18. #include "botlib.h"
  19. #include "be_aas.h"
  20. #include "be_ea.h"
  21. #include "be_ai_char.h"
  22. #include "be_ai_chat.h"
  23. #include "be_ai_gen.h"
  24. #include "be_ai_goal.h"
  25. #include "be_ai_move.h"
  26. #include "be_ai_weap.h"
  27. //
  28. #include "ai_main.h"
  29. #include "ai_dmq3.h"
  30. #include "ai_chat.h"
  31. #include "ai_cmd.h"
  32. #include "ai_dmnet.h"
  33. #include "ai_team.h"
  34. //
  35. #include "chars.h"                //characteristics
  36. #include "inv.h"                //indexes into the inventory
  37. #include "syn.h"                //synonyms
  38. #include "match.h"                //string matching types and vars
  39.  
  40. #define IDEAL_ATTACKDIST            140
  41. #define WEAPONINDEX_MACHINEGUN        2
  42.  
  43. #define MAX_WAYPOINTS        128
  44. //
  45. bot_waypoint_t botai_waypoints[MAX_WAYPOINTS];
  46. bot_waypoint_t *botai_freewaypoints;
  47.  
  48. //NOTE: not using a cvars which can be updated because the game should be reloaded anyway
  49. int gametype;        //game type
  50. int maxclients;        //maximum number of clients
  51.  
  52. vmCvar_t bot_grapple;
  53. vmCvar_t bot_rocketjump;
  54. vmCvar_t bot_fastchat;
  55. vmCvar_t bot_nochat;
  56. vmCvar_t bot_testrchat;
  57. vmCvar_t bot_challenge;
  58.  
  59. vec3_t lastteleport_origin;        //last teleport event origin
  60. float lastteleport_time;        //last teleport event time
  61. int max_bspmodelindex;            //maximum BSP model index
  62.  
  63. //CTF flag goals
  64. bot_goal_t ctf_redflag;
  65. bot_goal_t ctf_blueflag;
  66.  
  67. #ifdef CTF
  68. /*
  69. ==================
  70. BotCTFCarryingFlag
  71. ==================
  72. */
  73. int BotCTFCarryingFlag(bot_state_t *bs) {
  74.     if (gametype != GT_CTF) return CTF_FLAG_NONE;
  75.  
  76.     if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED;
  77.     else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE;
  78.     return CTF_FLAG_NONE;
  79. }
  80.  
  81. /*
  82. ==================
  83. BotCTFTeam
  84. ==================
  85. */
  86. int BotCTFTeam(bot_state_t *bs) {
  87.     char info[1024];
  88.  
  89.     if (gametype != GT_CTF) return CTF_TEAM_NONE;
  90.     if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
  91.         //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n");
  92.         return qfalse;
  93.     }
  94.     trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info));
  95.     //
  96.     if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return CTF_TEAM_RED;
  97.     else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return CTF_TEAM_BLUE;
  98.     return CTF_TEAM_NONE;
  99. }
  100.  
  101. /*
  102. ==================
  103. BotCTFRetreatGoals
  104. ==================
  105. */
  106. void BotCTFRetreatGoals(bot_state_t *bs) {
  107.     //when carrying a flag in ctf the bot should rush to the base
  108.     if (BotCTFCarryingFlag(bs)) {
  109.         //if not already rushing to the base
  110.         if (bs->ltgtype != LTG_RUSHBASE) {
  111.             bs->ltgtype = LTG_RUSHBASE;
  112.             bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
  113.             bs->rushbaseaway_time = 0;
  114.         }
  115.     }
  116. }
  117.  
  118. /*
  119. ==================
  120. EntityIsDead
  121. ==================
  122. */
  123. qboolean EntityIsDead(aas_entityinfo_t *entinfo) {
  124.     playerState_t ps;
  125.  
  126.     if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) {
  127.         //retrieve the current client state
  128.         BotAI_GetClientState( entinfo->number, &ps );
  129.         if (ps.pm_type != PM_NORMAL) return qtrue;
  130.     }
  131.     return qfalse;
  132. }
  133.  
  134. /*
  135. ==================
  136. EntityIsInvisible
  137. ==================
  138. */
  139. qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) {
  140.     if (entinfo->powerups & (1 << PW_INVIS)) {
  141.         return qtrue;
  142.     }
  143.     return qfalse;
  144. }
  145.  
  146. /*
  147. ==================
  148. EntityCarriesFlag
  149. ==================
  150. */
  151. qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) {
  152.     if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) return qtrue;
  153.     if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) return qtrue;
  154.     return qfalse;
  155. }
  156.  
  157. /*
  158. ==================
  159. EntityIsShooting
  160. ==================
  161. */
  162. qboolean EntityIsShooting(aas_entityinfo_t *entinfo) {
  163.     if (entinfo->flags & EF_FIRING) {
  164.         return qtrue;
  165.     }
  166.     return qfalse;
  167. }
  168.  
  169. /*
  170. ==================
  171. EntityIsChatting
  172. ==================
  173. */
  174. qboolean EntityIsChatting(aas_entityinfo_t *entinfo) {
  175.     if (entinfo->flags & EF_TALK) {
  176.         return qtrue;
  177.     }
  178.     return qfalse;
  179. }
  180.  
  181. /*
  182. ==================
  183. EntityHasQuad
  184. ==================
  185. */
  186. qboolean EntityHasQuad(aas_entityinfo_t *entinfo) {
  187.     if (entinfo->powerups & (1 << PW_QUAD)) {
  188.         return qtrue;
  189.     }
  190.     return qfalse;
  191. }
  192.  
  193. /*
  194. ==================
  195. BotCTFSeekGoals
  196. ==================
  197. */
  198. void BotCTFSeekGoals(bot_state_t *bs) {
  199.     float rnd;
  200.     int flagstatus, c;
  201.  
  202.     //when carrying a flag in ctf the bot should rush to the base
  203.     if (BotCTFCarryingFlag(bs)) {
  204.         //if not already rushing to the base
  205.         if (bs->ltgtype != LTG_RUSHBASE) {
  206.             bs->ltgtype = LTG_RUSHBASE;
  207.             bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
  208.             bs->rushbaseaway_time = 0;
  209.         }
  210.         else if (bs->rushbaseaway_time > trap_AAS_Time()) {
  211.             if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus;
  212.             else flagstatus = bs->blueflagstatus;
  213.             //if the flag is back
  214.             if (flagstatus == 0) {
  215.                 bs->rushbaseaway_time = 0;
  216.             }
  217.         }
  218.         return;
  219.     }
  220.     //
  221.     if (BotCTFTeam(bs) == CTF_TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus;
  222.     else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus;
  223.     //if the enemy flag is not at it's base
  224.     if (flagstatus == 1) {
  225.         //if Not defending the base
  226.         if (!(bs->ltgtype == LTG_DEFENDKEYAREA &&
  227.             (bs->teamgoal.number == ctf_redflag.number ||
  228.             bs->teamgoal.number == ctf_blueflag.number))) {
  229.             //if not already accompanying someone
  230.             if (bs->ltgtype != LTG_TEAMACCOMPANY) {
  231.                 //if there is avisible team mate flag carrier
  232.                 c = BotTeamFlagCarrierVisible(bs);
  233.                 if (c >= 0) {
  234.                     //follow the flag carrier
  235.                     //the team mate
  236.                     bs->teammate = c;
  237.                     //last time the team mate was visible
  238.                     bs->teammatevisible_time = trap_AAS_Time();
  239.                     //set the time to send a message to the team mates
  240.                     bs->teammessage_time = trap_AAS_Time() + 2 * random();
  241.                     //get the team goal time
  242.                     bs->teamgoal_time = trap_AAS_Time() + TEAM_ACCOMPANY_TIME;
  243.                     bs->ltgtype = LTG_TEAMACCOMPANY;
  244.                     bs->formation_dist = 3.5 * 32;        //3.5 meter
  245.                     bs->arrive_time = 0;
  246.                     return;
  247.                 }
  248.             }
  249.         }
  250.     }
  251.     //if the base flag is stolen
  252.     else if (flagstatus == 2) {
  253.         //if not already going for the enemy flag
  254.         if (bs->ltgtype != LTG_GETFLAG) {
  255.             //if there's no bot team leader
  256.             if (!BotTeamLeader(bs)) {
  257.                 //go for the enemy flag
  258.                 bs->ltgtype = LTG_GETFLAG;
  259.                 //no team message
  260.                 bs->teammessage_time = 1;
  261.                 //set the time the bot will stop getting the flag
  262.                 bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME;
  263.                 return;
  264.             }
  265.         }
  266.     }
  267.     //if both flags not at their bases
  268.     else if (flagstatus == 3) {
  269.         //
  270.         if (bs->ltgtype != LTG_GETFLAG &&
  271.             bs->ltgtype != LTG_TEAMACCOMPANY) {
  272.             //if there is avisible team mate flag carrier
  273.             c = BotTeamFlagCarrierVisible(bs);
  274.             if (c >= 0) {
  275.                 //follow the flag carrier
  276.                 return;
  277.             }
  278.             else {
  279.                 //otherwise attack the enemy base
  280.             }
  281.             return;
  282.         }
  283.     }
  284.     //if the bot is roaming
  285.     if (bs->ctfroam_time > trap_AAS_Time()) return;
  286.     //if already a CTF or team goal
  287.     if (bs->ltgtype == LTG_TEAMHELP ||
  288.             bs->ltgtype == LTG_TEAMACCOMPANY ||
  289.             bs->ltgtype == LTG_DEFENDKEYAREA ||
  290.             bs->ltgtype == LTG_GETFLAG ||
  291.             bs->ltgtype == LTG_RUSHBASE ||
  292.             bs->ltgtype == LTG_RETURNFLAG ||
  293.             bs->ltgtype == LTG_CAMPORDER ||
  294.             bs->ltgtype == LTG_PATROL) {
  295.         return;
  296.     }
  297.     //if the bot has anough aggression to decide what to do
  298.     if (BotAggression(bs) < 50) return;
  299.     //set the time to send a message to the team mates
  300.     bs->teammessage_time = trap_AAS_Time() + 2 * random();
  301.     //get the flag or defend the base
  302.     rnd = random();
  303.     if (rnd < 0.33 && ctf_redflag.areanum && ctf_blueflag.areanum) {
  304.         bs->ltgtype = LTG_GETFLAG;
  305.         //set the time the bot will stop getting the flag
  306.         bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME;
  307.     }
  308.     else if (rnd < 0.66 && ctf_redflag.areanum && ctf_blueflag.areanum) {
  309.         //
  310.         if (BotCTFTeam(bs) == CTF_TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
  311.         else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
  312.         //set the ltg type
  313.         bs->ltgtype = LTG_DEFENDKEYAREA;
  314.         //set the time the bot stops defending the base
  315.         bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME;
  316.         bs->defendaway_time = 0;
  317.     }
  318.     else {
  319.         bs->ltgtype = 0;
  320.         //set the time the bot will stop roaming
  321.         bs->ctfroam_time = trap_AAS_Time() + CTF_ROAM_TIME;
  322.     }
  323. #ifdef DEBUG
  324.     BotPrintTeamGoal(bs);
  325. #endif //DEBUG
  326. }
  327.  
  328. #endif //CTF
  329.  
  330. /*
  331. ==================
  332. BotPointAreaNum
  333. ==================
  334. */
  335. int BotPointAreaNum(vec3_t origin) {
  336.     int areanum, numareas, areas[10];
  337.     vec3_t end;
  338.  
  339.     areanum = trap_AAS_PointAreaNum(origin);
  340.     if (areanum) return areanum;
  341.     VectorCopy(origin, end);
  342.     end[2] += 10;
  343.     numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10);
  344.     if (numareas > 0) return areas[0];
  345.     return 0;
  346. }
  347.  
  348. /*
  349. ==================
  350. ClientName
  351. ==================
  352. */
  353. char *ClientName(int client, char *name, int size) {
  354.     char buf[MAX_INFO_STRING];
  355.  
  356.     if (client < 0 || client >= MAX_CLIENTS) {
  357.         BotAI_Print(PRT_ERROR, "ClientName: client out of range\n");
  358.         return "[client out of range]";
  359.     }
  360.     trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
  361.     strncpy(name, Info_ValueForKey(buf, "n"), size-1);
  362.     name[size-1] = '\0';
  363.     Q_CleanStr( name );
  364.     return name;
  365. }
  366.  
  367. /*
  368. ==================
  369. ClientSkin
  370. ==================
  371. */
  372. char *ClientSkin(int client, char *skin, int size) {
  373.     char buf[MAX_INFO_STRING];
  374.  
  375.     if (client < 0 || client >= MAX_CLIENTS) {
  376.         BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n");
  377.         return "[client out of range]";
  378.     }
  379.     trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
  380.     strncpy(skin, Info_ValueForKey(buf, "model"), size-1);
  381.     skin[size-1] = '\0';
  382.     return skin;
  383. }
  384.  
  385. /*
  386. ==================
  387. ClientFromName
  388. ==================
  389. */
  390. int ClientFromName(char *name) {
  391.     int i;
  392.     char buf[MAX_INFO_STRING];
  393.     static int maxclients;
  394.  
  395.     if (!maxclients)
  396.         maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
  397.     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
  398.         trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
  399.         Q_CleanStr( buf );
  400.         if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i;
  401.     }
  402.     return -1;
  403. }
  404.  
  405. /*
  406. ==================
  407. stristr
  408. ==================
  409. */
  410. char *stristr(char *str, char *charset) {
  411.     int i;
  412.  
  413.     while(*str) {
  414.         for (i = 0; charset[i] && str[i]; i++) {
  415.             if (toupper(charset[i]) != toupper(str[i])) break;
  416.         }
  417.         if (!charset[i]) return str;
  418.         str++;
  419.     }
  420.     return NULL;
  421. }
  422.  
  423. /*
  424. ==================
  425. EasyClientName
  426. ==================
  427. */
  428. char *EasyClientName(int client, char *buf, int size) {
  429.     int i;
  430.     char *str1, *str2, *ptr, c;
  431.     char name[128];
  432.  
  433.     strcpy(name, ClientName(client, name, sizeof(name)));
  434.     for (i = 0; name[i]; i++) name[i] &= 127;
  435.     //remove all spaces
  436.     for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) {
  437.         memmove(ptr, ptr+1, strlen(ptr+1)+1);
  438.     }
  439.     //check for [x] and ]x[ clan names
  440.     str1 = strstr(name, "[");
  441.     str2 = strstr(name, "]");
  442.     if (str1 && str2) {
  443.         if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1);
  444.         else memmove(str2, str1+1, strlen(str1+1)+1);
  445.     }
  446.     //remove Mr prefix
  447.     if ((name[0] == 'm' || name[0] == 'M') &&
  448.             (name[1] == 'r' || name[1] == 'R')) {
  449.         memmove(name, name+2, strlen(name+2)+1);
  450.     }
  451.     //only allow lower case alphabet characters
  452.     ptr = name;
  453.     while(*ptr) {
  454.         c = *ptr;
  455.         if ((c >= 'a' && c <= 'z') ||
  456.                 (c >= '0' && c <= '9') || c == '_') {
  457.             ptr++;
  458.         }
  459.         else if (c >= 'A' && c <= 'Z') {
  460.             *ptr += 'a' - 'A';
  461.             ptr++;
  462.         }
  463.         else {
  464.             memmove(ptr, ptr+1, strlen(ptr + 1)+1);
  465.         }
  466.     }
  467.     strncpy(buf, name, size-1);
  468.     buf[size-1] = '\0';
  469.     return buf;
  470. }
  471.  
  472. /*
  473. ==================
  474. BotChooseWeapon
  475. ==================
  476. */
  477. void BotChooseWeapon(bot_state_t *bs) {
  478.     int newweaponnum;
  479.  
  480.     if (bs->cur_ps.weaponstate == WEAPON_RAISING ||
  481.             bs->cur_ps.weaponstate == WEAPON_DROPPING) {
  482.         trap_EA_SelectWeapon(bs->client, bs->weaponnum);
  483.     }
  484.     else {
  485.         newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory);
  486.         if (bs->weaponnum != newweaponnum) bs->weaponchange_time = trap_AAS_Time();
  487.         bs->weaponnum = newweaponnum;
  488.         //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum);
  489.         trap_EA_SelectWeapon(bs->client, bs->weaponnum);
  490.     }
  491. }
  492.  
  493. /*
  494. ==================
  495. BotSetupForMovement
  496. ==================
  497. */
  498. void BotSetupForMovement(bot_state_t *bs) {
  499.     bot_initmove_t initmove;
  500.  
  501.     memset(&initmove, 0, sizeof(bot_initmove_t));
  502.     VectorCopy(bs->cur_ps.origin, initmove.origin);
  503.     VectorCopy(bs->cur_ps.velocity, initmove.velocity);
  504.     VectorCopy(bs->cur_ps.origin, initmove.viewoffset);
  505.     initmove.viewoffset[2] += bs->cur_ps.viewheight;
  506.     initmove.entitynum = bs->entitynum;
  507.     initmove.client = bs->client;
  508.     initmove.thinktime = bs->thinktime;
  509.     //set the onground flag
  510.     if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND;
  511.     //set the teleported flag
  512.     if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) {
  513.         initmove.or_moveflags |= MFL_TELEPORTED;
  514.     }
  515.     //set the waterjump flag
  516.     if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) {
  517.         initmove.or_moveflags |= MFL_WATERJUMP;
  518.     }
  519.     //set presence type
  520.     if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH;
  521.     else initmove.presencetype = PRESENCE_NORMAL;
  522.     //
  523.     if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK;
  524.     //
  525.     VectorCopy(bs->viewangles, initmove.viewangles);
  526.     //
  527.     trap_BotInitMoveState(bs->ms, &initmove);
  528. }
  529.  
  530. /*
  531. ==================
  532. BotUpdateInventory
  533. ==================
  534. */
  535. void BotUpdateInventory(bot_state_t *bs) {
  536.     //armor
  537.     bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR];
  538.     //weapons
  539.     bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0;
  540.     bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0;
  541.     bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0;
  542.     bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0;
  543.     bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0;
  544.     bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0;
  545.     bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0;
  546.     bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0;
  547.     bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0;
  548.     bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0;
  549.     //ammo
  550.     bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN];
  551.     bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN];
  552.     bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER];
  553.     bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN];
  554.     bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING];
  555.     bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER];
  556.     bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN];
  557.     bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG];
  558.     //powerups
  559.     bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH];
  560.     bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER;
  561.     bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT;
  562.     bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0;
  563.     bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0;
  564.     bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0;
  565.     bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0;
  566.     bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0;
  567.     bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0;
  568.     bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0;
  569.     bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0;
  570.     //
  571. }
  572.  
  573. /*
  574. ==================
  575. BotUpdateBattleInventory
  576. ==================
  577. */
  578. void BotUpdateBattleInventory(bot_state_t *bs, int enemy) {
  579.     vec3_t dir;
  580.     aas_entityinfo_t entinfo;
  581.  
  582.     BotEntityInfo(enemy, &entinfo);
  583.     VectorSubtract(entinfo.origin, bs->origin, dir);
  584.     bs->inventory[ENEMY_HEIGHT] = (int) dir[2];
  585.     dir[2] = 0;
  586.     bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir);
  587.     //FIXME: add num visible enemies and num visible team mates to the inventory
  588. }
  589.  
  590. /*
  591. ==================
  592. BotBattleUseItems
  593. ==================
  594. */
  595. void BotBattleUseItems(bot_state_t *bs) {
  596.     if (bs->inventory[INVENTORY_HEALTH] < 40) {
  597.         if (bs->inventory[INVENTORY_TELEPORTER] > 0) {
  598.             if (!BotCTFCarryingFlag(bs)) {
  599.                 trap_EA_Use(bs->client);
  600.             }
  601.         }
  602.         if (bs->inventory[INVENTORY_MEDKIT] > 0) {
  603.             trap_EA_Use(bs->client);
  604.         }
  605.     }
  606. }
  607.  
  608. /*
  609. ==================
  610. BotSetTeleportTime
  611. ==================
  612. */
  613. void BotSetTeleportTime(bot_state_t *bs) {
  614.     if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) {
  615.         bs->teleport_time = trap_AAS_Time();
  616.     }
  617.     bs->last_eFlags = bs->cur_ps.eFlags;
  618. }
  619.  
  620. /*
  621. ==================
  622. BotIsDead
  623. ==================
  624. */
  625. qboolean BotIsDead(bot_state_t *bs) {
  626.     return (bs->cur_ps.pm_type == PM_DEAD);
  627. }
  628.  
  629. /*
  630. ==================
  631. BotIsObserver
  632. ==================
  633. */
  634. qboolean BotIsObserver(bot_state_t *bs) {
  635.     char buf[MAX_INFO_STRING];
  636.     if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue;
  637.     trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf));
  638.     if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue;
  639.     return qfalse;
  640. }
  641.  
  642. /*
  643. ==================
  644. BotIntermission
  645. ==================
  646. */
  647. qboolean BotIntermission(bot_state_t *bs) {
  648.     //NOTE: we shouldn't be looking at the game code...
  649.     if (level.intermissiontime) return qtrue;
  650.     return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION);
  651. }
  652.  
  653. /*
  654. ==================
  655. BotInLavaOrSlime
  656. ==================
  657. */
  658. qboolean BotInLavaOrSlime(bot_state_t *bs) {
  659.     vec3_t feet;
  660.  
  661.     VectorCopy(bs->origin, feet);
  662.     feet[2] -= 23;
  663.     return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME));
  664. }
  665.  
  666. /*
  667. ==================
  668. BotCreateWayPoint
  669. ==================
  670. */
  671. bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) {
  672.     bot_waypoint_t *wp;
  673.     vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8};
  674.  
  675.     wp = botai_freewaypoints;
  676.     if ( !wp ) {
  677.         BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" );
  678.         return NULL;
  679.     }
  680.     botai_freewaypoints = botai_freewaypoints->next;
  681.  
  682.     Q_strncpyz( wp->name, name, sizeof(wp->name) );
  683.     VectorCopy(origin, wp->goal.origin);
  684.     VectorCopy(waypointmins, wp->goal.mins);
  685.     VectorCopy(waypointmaxs, wp->goal.maxs);
  686.     wp->goal.areanum = areanum;
  687.     wp->next = NULL;
  688.     wp->prev = NULL;
  689.     return wp;
  690. }
  691.  
  692. /*
  693. ==================
  694. BotFindWayPoint
  695. ==================
  696. */
  697. bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) {
  698.     bot_waypoint_t *wp;
  699.  
  700.     for (wp = waypoints; wp; wp = wp->next) {
  701.         if (!Q_stricmp(wp->name, name)) return wp;
  702.     }
  703.     return NULL;
  704. }
  705.  
  706. /*
  707. ==================
  708. BotFreeWaypoints
  709. ==================
  710. */
  711. void BotFreeWaypoints(bot_waypoint_t *wp) {
  712.     bot_waypoint_t *nextwp;
  713.  
  714.     for (; wp; wp = nextwp) {
  715.         nextwp = wp->next;
  716.         wp->next = botai_freewaypoints;
  717.         botai_freewaypoints = wp;
  718.     }
  719. }
  720.  
  721. /*
  722. ==================
  723. BotInitWaypoints
  724. ==================
  725. */
  726. void BotInitWaypoints(void) {
  727.     int i;
  728.  
  729.     botai_freewaypoints = NULL;
  730.     for (i = 0; i < MAX_WAYPOINTS; i++) {
  731.         botai_waypoints[i].next = botai_freewaypoints;
  732.         botai_freewaypoints = &botai_waypoints[i];
  733.     }
  734. }
  735.  
  736. /*
  737. ==================
  738. TeamPlayIsOn
  739. ==================
  740. */
  741. int TeamPlayIsOn(void) {
  742.     return ( gametype == GT_TEAM || gametype == GT_CTF );
  743. }
  744.  
  745. /*
  746. ==================
  747. BotAggression
  748. ==================
  749. */
  750. float BotAggression(bot_state_t *bs) {
  751.     //if the bot has quad
  752.     if (bs->inventory[INVENTORY_QUAD]) {
  753.         //if the bot is not holding the gauntlet or the enemy is really nearby
  754.         if (bs->weaponnum != WP_GAUNTLET ||
  755.             bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) {
  756.             return 70;
  757.         }
  758.     }
  759.     //if the enemy is located way higher than the bot
  760.     if (bs->inventory[ENEMY_HEIGHT] > 200) return 0;
  761.     //if the bot is very low on health
  762.     if (bs->inventory[INVENTORY_HEALTH] < 60) return 0;
  763.     //if the bot is low on health
  764.     if (bs->inventory[INVENTORY_HEALTH] < 80) {
  765.         //if the bot has insufficient armor
  766.         if (bs->inventory[INVENTORY_ARMOR] < 40) return 0;
  767.     }
  768.     //if the bot can use the bfg
  769.     if (bs->inventory[INVENTORY_BFG10K] > 0 &&
  770.             bs->inventory[INVENTORY_BFGAMMO] > 7) return 100;
  771.     //if the bot can use the railgun
  772.     if (bs->inventory[INVENTORY_RAILGUN] > 0 &&
  773.             bs->inventory[INVENTORY_SLUGS] > 5) return 95;
  774.     //if the bot can use the lightning gun
  775.     if (bs->inventory[INVENTORY_LIGHTNING] > 0 &&
  776.             bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 90;
  777.     //if the bot can use the rocketlauncher
  778.     if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 &&
  779.             bs->inventory[INVENTORY_ROCKETS] > 5) return 90;
  780.     //if the bot can use the plasmagun
  781.     if (bs->inventory[INVENTORY_PLASMAGUN] > 0 &&
  782.             bs->inventory[INVENTORY_CELLS] > 40) return 85;
  783.     //if the bot can use the grenade launcher
  784.     if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 &&
  785.             bs->inventory[INVENTORY_GRENADES] > 10) return 80;
  786.     //if the bot can use the shotgun
  787.     if (bs->inventory[INVENTORY_SHOTGUN] > 0 &&
  788.             bs->inventory[INVENTORY_SHELLS] > 10) return 50;
  789.     //otherwise the bot is not feeling too good
  790.     return 0;
  791. }
  792.  
  793. /*
  794. ==================
  795. BotWantsToRetreat
  796. ==================
  797. */
  798. int BotWantsToRetreat(bot_state_t *bs) {
  799.     aas_entityinfo_t entinfo;
  800.  
  801.     //always retreat when carrying a CTF flag
  802.     if (BotCTFCarryingFlag(bs)) return qtrue;
  803.     //
  804.     if (bs->enemy >= 0) {
  805.         //if the enemy is carrying a flag
  806.         BotEntityInfo(bs->enemy, &entinfo);
  807.         if (EntityCarriesFlag(&entinfo)) return qfalse;
  808.     }
  809.     //if the bot is getting the flag
  810.     if (bs->ltgtype == LTG_GETFLAG) return qtrue;
  811.     //
  812.     if (BotAggression(bs) < 50) return qtrue;
  813.     return qfalse;
  814. }
  815.  
  816. /*
  817. ==================
  818. BotWantsToChase
  819. ==================
  820. */
  821. int BotWantsToChase(bot_state_t *bs) {
  822.     aas_entityinfo_t entinfo;
  823.  
  824.     //always retreat when carrying a CTF flag
  825.     if (BotCTFCarryingFlag(bs)) return qfalse;
  826.     //if the enemy is carrying a flag
  827.     BotEntityInfo(bs->enemy, &entinfo);
  828.     if (EntityCarriesFlag(&entinfo)) return qtrue;
  829.     //if the bot is getting the flag
  830.     if (bs->ltgtype == LTG_GETFLAG) return qfalse;
  831.     //
  832.     if (BotAggression(bs) > 50) return qtrue;
  833.     return qfalse;
  834. }
  835.  
  836. /*
  837. ==================
  838. BotWantsToHelp
  839. ==================
  840. */
  841. int BotWantsToHelp(bot_state_t *bs) {
  842.     return qtrue;
  843. }
  844.  
  845. /*
  846. ==================
  847. BotCanAndWantsToRocketJump
  848. ==================
  849. */
  850. int BotCanAndWantsToRocketJump(bot_state_t *bs) {
  851.     float rocketjumper;
  852.  
  853.     //if rocket jumping is disabled
  854.     if (!bot_rocketjump.integer) return qfalse;
  855.     //if no rocket launcher
  856.     if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse;
  857.     //if low on rockets
  858.     if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse;
  859.     //never rocket jump with the Quad
  860.     if (bs->inventory[INVENTORY_QUAD]) return qfalse;
  861.     //if low on health
  862.     if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse;
  863.     //if not full health
  864.     if (bs->inventory[INVENTORY_HEALTH] < 90) {
  865.         //if the bot has insufficient armor
  866.         if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse;
  867.     }
  868.     rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1);
  869.     if (rocketjumper < 0.5) return qfalse;
  870.     return qtrue;
  871. }
  872.  
  873. /*
  874. ==================
  875. BotGoCamp
  876. ==================
  877. */
  878. void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) {
  879.     float camper;
  880.  
  881.     //set message time to zero so bot will NOT show any message
  882.     bs->teammessage_time = 0;
  883.     //set the ltg type
  884.     bs->ltgtype = LTG_CAMP;
  885.     //set the team goal
  886.     memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t));
  887.     //get the team goal time
  888.     camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1);
  889.     if (camper > 0.99) bs->teamgoal_time = 99999;
  890.     else bs->teamgoal_time = 120 + 180 * camper + random() * 15;
  891.     //set the last time the bot started camping
  892.     bs->camp_time = trap_AAS_Time();
  893.     //the teammate that requested the camping
  894.     bs->teammate = 0;
  895.     //do NOT type arrive message
  896.     bs->arrive_time = 1;
  897. }
  898.  
  899. /*
  900. ==================
  901. BotWantsToCamp
  902. ==================
  903. */
  904. int BotWantsToCamp(bot_state_t *bs) {
  905.     float camper;
  906.     int cs, traveltime, besttraveltime;
  907.     bot_goal_t goal, bestgoal;
  908.  
  909.     camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1);
  910.     if (camper < 0.1) return qfalse;
  911.     //if the bot has a team goal
  912.     if (bs->ltgtype == LTG_TEAMHELP ||
  913.             bs->ltgtype == LTG_TEAMACCOMPANY ||
  914.             bs->ltgtype == LTG_DEFENDKEYAREA ||
  915.             bs->ltgtype == LTG_GETFLAG ||
  916.             bs->ltgtype == LTG_RUSHBASE ||
  917.             bs->ltgtype == LTG_CAMP ||
  918.             bs->ltgtype == LTG_CAMPORDER ||
  919.             bs->ltgtype == LTG_PATROL) {
  920.         return qfalse;
  921.     }
  922.     //if camped recently
  923.     if (bs->camp_time > trap_AAS_Time() - 60 + 300 * (1-camper)) return qfalse;
  924.     //
  925.     if (random() > camper) {
  926.         bs->camp_time = trap_AAS_Time();
  927.         return qfalse;
  928.     }
  929.     //if the bot isn't healthy anough
  930.     if (BotAggression(bs) < 50) return qfalse;
  931.     //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo
  932.     if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) &&
  933.         (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) &&
  934.         (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) {
  935.         return qfalse;
  936.     }
  937.     //find the closest camp spot
  938.     besttraveltime = 99999;
  939.     for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) {
  940.         traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT);
  941.         if (traveltime && traveltime < besttraveltime) {
  942.             besttraveltime = traveltime;
  943.             memcpy(&bestgoal, &goal, sizeof(bot_goal_t));
  944.         }
  945.     }
  946.     if (besttraveltime > 150) return qfalse;
  947.     //ok found a camp spot, go camp there
  948.     BotGoCamp(bs, &bestgoal);
  949.     //
  950.     return qtrue;
  951. }
  952.  
  953. /*
  954. ==================
  955. BotDontAvoid
  956. ==================
  957. */
  958. void BotDontAvoid(bot_state_t *bs, char *itemname) {
  959.     bot_goal_t goal;
  960.     int num;
  961.  
  962.     num = trap_BotGetLevelItemGoal(-1, itemname, &goal);
  963.     while(num >= 0) {
  964.         trap_BotRemoveFromAvoidGoals(bs->gs, goal.number);
  965.         num = trap_BotGetLevelItemGoal(num, itemname, &goal);
  966.     }
  967. }
  968.  
  969. /*
  970. ==================
  971. BotGoForPowerups
  972. ==================
  973. */
  974. void BotGoForPowerups(bot_state_t *bs) {
  975.  
  976.     //don't avoid any of the powerups anymore
  977.     BotDontAvoid(bs, "Quad Damage");
  978.     BotDontAvoid(bs, "Regeneration");
  979.     BotDontAvoid(bs, "Battle Suit");
  980.     BotDontAvoid(bs, "Speed");
  981.     BotDontAvoid(bs, "Invisibility");
  982.     //BotDontAvoid(bs, "Flight");
  983.     //reset the long term goal time so the bot will go for the powerup
  984.     //NOTE: the long term goal type doesn't change
  985.     bs->ltg_time = 0;
  986. }
  987.  
  988. /*
  989. ==================
  990. BotRoamGoal
  991. ==================
  992. */
  993. void BotRoamGoal(bot_state_t *bs, vec3_t goal) {
  994.     int pc, i;
  995.     float len, rnd;
  996.     vec3_t dir, bestorg, belowbestorg;
  997.     bsp_trace_t trace;
  998.  
  999.     for (i = 0; i < 10; i++) {
  1000.         //start at the bot origin
  1001.         VectorCopy(bs->origin, bestorg);
  1002.         rnd = random();
  1003.         if (rnd > 0.25) {
  1004.             //add a random value to the x-coordinate
  1005.             if (random() < 0.5) bestorg[0] -= 800 * random() + 100;
  1006.             else bestorg[0] += 800 * random() + 100;
  1007.         }
  1008.         if (rnd < 0.75) {
  1009.             //add a random value to the y-coordinate
  1010.             if (random() < 0.5) bestorg[1] -= 800 * random() + 100;
  1011.             else bestorg[1] += 800 * random() + 100;
  1012.         }
  1013.         //add a random value to the z-coordinate (NOTE: 48 = maxjump?)
  1014.         bestorg[2] += 2 * 48 * crandom();
  1015.         //trace a line from the origin to the roam target
  1016.         BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID);
  1017.         //direction and length towards the roam target
  1018.         VectorSubtract(trace.endpos, bs->origin, dir);
  1019.         len = VectorNormalize(dir);
  1020.         //if the roam target is far away anough
  1021.         if (len > 200) {
  1022.             //the roam target is in the given direction before walls
  1023.             VectorScale(dir, len * trace.fraction - 40, dir);
  1024.             VectorAdd(bs->origin, dir, bestorg);
  1025.             //get the coordinates of the floor below the roam target
  1026.             belowbestorg[0] = bestorg[0];
  1027.             belowbestorg[1] = bestorg[1];
  1028.             belowbestorg[2] = bestorg[2] - 800;
  1029.             BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID);
  1030.             //
  1031.             if (!trace.startsolid) {
  1032.                 trace.endpos[2]++;
  1033.                 pc = trap_PointContents(trace.endpos, bs->entitynum);
  1034.                 if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) {
  1035.                     VectorCopy(bestorg, goal);
  1036.                     return;
  1037.                 }
  1038.             }
  1039.         }
  1040.     }
  1041.     VectorCopy(bestorg, goal);
  1042. }
  1043.  
  1044. /*
  1045. ==================
  1046. BotAttackMove
  1047. ==================
  1048. */
  1049. bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) {
  1050.     int movetype, i;
  1051.     float attack_skill, jumper, croucher, dist, strafechange_time;
  1052.     float attack_dist, attack_range;
  1053.     vec3_t forward, backward, sideward, hordir, up = {0, 0, 1};
  1054.     aas_entityinfo_t entinfo;
  1055.     bot_moveresult_t moveresult;
  1056.     bot_goal_t goal;
  1057.  
  1058.     if (bs->attackchase_time > trap_AAS_Time()) {
  1059.         //create the chase goal
  1060.         goal.entitynum = bs->enemy;
  1061.         goal.areanum = bs->lastenemyareanum;
  1062.         VectorCopy(bs->lastenemyorigin, goal.origin);
  1063.         VectorSet(goal.mins, -8, -8, -8);
  1064.         VectorSet(goal.maxs, 8, 8, 8);
  1065.         //initialize the movement state
  1066.         BotSetupForMovement(bs);
  1067.         //move towards the goal
  1068.         trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl);
  1069.         return moveresult;
  1070.     }
  1071.     //
  1072.     memset(&moveresult, 0, sizeof(bot_moveresult_t));
  1073.     //
  1074.     attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
  1075.     jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1);
  1076.     croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1);
  1077.     //if the bot is really stupid
  1078.     if (attack_skill < 0.2) return moveresult;
  1079.     //initialize the movement state
  1080.     BotSetupForMovement(bs);
  1081.     //get the enemy entity info
  1082.     BotEntityInfo(bs->enemy, &entinfo);
  1083.     //direction towards the enemy
  1084.     VectorSubtract(entinfo.origin, bs->origin, forward);
  1085.     //the distance towards the enemy
  1086.     dist = VectorNormalize(forward);
  1087.     VectorNegate(forward, backward);
  1088.     //walk, crouch or jump
  1089.     movetype = MOVE_WALK;
  1090.     //
  1091.     if (bs->attackcrouch_time < trap_AAS_Time() - 1) {
  1092.         if (random() < jumper) {
  1093.             movetype = MOVE_JUMP;
  1094.         }
  1095.         //wait at least one second before crouching again
  1096.         else if (bs->attackcrouch_time < trap_AAS_Time() - 1 && random() < croucher) {
  1097.             bs->attackcrouch_time = trap_AAS_Time() + croucher * 5;
  1098.         }
  1099.     }
  1100.     if (bs->attackcrouch_time > trap_AAS_Time()) movetype = MOVE_CROUCH;
  1101.     //if the bot should jump
  1102.     if (movetype == MOVE_JUMP) {
  1103.         //if jumped last frame
  1104.         if (bs->attackjump_time > trap_AAS_Time()) {
  1105.             movetype = MOVE_WALK;
  1106.         }
  1107.         else {
  1108.             bs->attackjump_time = trap_AAS_Time() + 1;
  1109.         }
  1110.     }
  1111.     if (bs->cur_ps.weapon == WP_GAUNTLET) {
  1112.         attack_dist = 0;
  1113.         attack_range = 0;
  1114.     }
  1115.     else {
  1116.         attack_dist = IDEAL_ATTACKDIST;
  1117.         attack_range = 40;
  1118.     }
  1119.     //if the bot is stupid
  1120.     if (attack_skill <= 0.4) {
  1121.         //just walk to or away from the enemy
  1122.         if (dist > attack_dist + attack_range) {
  1123.             if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult;
  1124.         }
  1125.         if (dist < attack_dist - attack_range) {
  1126.             if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult;
  1127.         }
  1128.         return moveresult;
  1129.     }
  1130.     //increase the strafe time
  1131.     bs->attackstrafe_time += bs->thinktime;
  1132.     //get the strafe change time
  1133.     strafechange_time = 0.4 + (1 - attack_skill) * 0.2;
  1134.     if (attack_skill > 0.7) strafechange_time += crandom() * 0.2;
  1135.     //if the strafe direction should be changed
  1136.     if (bs->attackstrafe_time > strafechange_time) {
  1137.         //some magic number :)
  1138.         if (random() > 0.935) {
  1139.             //flip the strafe direction
  1140.             bs->flags ^= BFL_STRAFERIGHT;
  1141.             bs->attackstrafe_time = 0;
  1142.         }
  1143.     }
  1144.     //
  1145.     for (i = 0; i < 2; i++) {
  1146.         hordir[0] = forward[0];
  1147.         hordir[1] = forward[1];
  1148.         hordir[2] = 0;
  1149.         VectorNormalize(hordir);
  1150.         //get the sideward vector
  1151.         CrossProduct(hordir, up, sideward);
  1152.         //reverse the vector depending on the strafe direction
  1153.         if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward);
  1154.         //randomly go back a little
  1155.         if (random() > 0.9) {
  1156.             VectorAdd(sideward, backward, sideward);
  1157.         }
  1158.         else {
  1159.             //walk forward or backward to get at the ideal attack distance
  1160.             if (dist > attack_dist + attack_range) VectorAdd(sideward, forward, sideward);
  1161.             else if (dist < attack_dist - attack_range) VectorAdd(sideward, backward, sideward);
  1162.         }
  1163.         //perform the movement
  1164.         if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) return moveresult;
  1165.         //movement failed, flip the strafe direction
  1166.         bs->flags ^= BFL_STRAFERIGHT;
  1167.         bs->attackstrafe_time = 0;
  1168.     }
  1169.     //bot couldn't do any usefull movement
  1170. //    bs->attackchase_time = AAS_Time() + 6;
  1171.     return moveresult;
  1172. }
  1173.  
  1174. /*
  1175. ==================
  1176. BotSameTeam
  1177. ==================
  1178. */
  1179. int BotSameTeam(bot_state_t *bs, int entnum) {
  1180.     char info1[1024], info2[1024];
  1181.  
  1182.     if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
  1183.         //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
  1184.         return qfalse;
  1185.     }
  1186.     if (entnum < 0 || entnum >= MAX_CLIENTS) {
  1187.         //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
  1188.         return qfalse;
  1189.     }
  1190.     if (gametype == GT_TEAM || gametype == GT_CTF) {
  1191.         trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1));
  1192.         trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2));
  1193.         //
  1194.         if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue;
  1195.     }
  1196.     return qfalse;
  1197. }
  1198.  
  1199. /*
  1200. ==================
  1201. InFieldOfVision
  1202. ==================
  1203. */
  1204. qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles)
  1205. {
  1206.     int i;
  1207.     float diff, angle;
  1208.  
  1209.     for (i = 0; i < 2; i++) {
  1210.         angle = AngleMod(viewangles[i]);
  1211.         angles[i] = AngleMod(angles[i]);
  1212.         diff = angles[i] - angle;
  1213.         if (angles[i] > angle) {
  1214.             if (diff > 180.0) diff -= 360.0;
  1215.         }
  1216.         else {
  1217.             if (diff < -180.0) diff += 360.0;
  1218.         }
  1219.         if (diff > 0) {
  1220.             if (diff > fov * 0.5) return qfalse;
  1221.         }
  1222.         else {
  1223.             if (diff < -fov * 0.5) return qfalse;
  1224.         }
  1225.     }
  1226.     return qtrue;
  1227. }
  1228.  
  1229. /*
  1230. ==================
  1231. BotEntityVisible
  1232.  
  1233. returns visibility in the range [0, 1] taking fog and water surfaces into account
  1234. ==================
  1235. */
  1236. float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) {
  1237.     int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc;
  1238.     float fogdist, waterfactor, vis, bestvis;
  1239.     bsp_trace_t trace;
  1240.     aas_entityinfo_t entinfo;
  1241.     vec3_t dir, entangles, start, end, middle;
  1242.  
  1243.     //calculate middle of bounding box
  1244.     BotEntityInfo(ent, &entinfo);
  1245.     VectorAdd(entinfo.mins, entinfo.maxs, middle);
  1246.     VectorScale(middle, 0.5, middle);
  1247.     VectorAdd(entinfo.origin, middle, middle);
  1248.     //check if entity is within field of vision
  1249.     VectorSubtract(middle, eye, dir);
  1250.     vectoangles(dir, entangles);
  1251.     if (!InFieldOfVision(viewangles, fov, entangles)) return 0;
  1252.     //
  1253.     pc = trap_AAS_PointContents(eye);
  1254.     infog = (pc & CONTENTS_SOLID);
  1255.     inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER));
  1256.     //
  1257.     bestvis = 0;
  1258.     for (i = 0; i < 3; i++) {
  1259.         //if the point is not in potential visible sight
  1260.         //if (!AAS_inPVS(eye, middle)) continue;
  1261.         //
  1262.         contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP;
  1263.         passent = viewer;
  1264.         hitent = ent;
  1265.         VectorCopy(eye, start);
  1266.         VectorCopy(middle, end);
  1267.         //if the entity is in water, lava or slime
  1268.         if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
  1269.             contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
  1270.         }
  1271.         //if eye is in water, lava or slime
  1272.         if (inwater) {
  1273.             if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) {
  1274.                 passent = ent;
  1275.                 hitent = viewer;
  1276.                 VectorCopy(middle, start);
  1277.                 VectorCopy(eye, end);
  1278.             }
  1279.             contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
  1280.         }
  1281.         //trace from start to end
  1282.         BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask);
  1283.         //if water was hit
  1284.         waterfactor = 1.0;
  1285.         if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
  1286.             //if the water surface is translucent
  1287.             if (1) {
  1288.                 //trace through the water
  1289.                 contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
  1290.                 BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask);
  1291.                 waterfactor = 0.5;
  1292.             }
  1293.         }
  1294.         //if a full trace or the hitent was hit
  1295.         if (trace.fraction >= 1 || trace.ent == hitent) {
  1296.             //check for fog, assuming there's only one fog brush where
  1297.             //either the viewer or the entity is in or both are in
  1298.             otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG);
  1299.             if (infog && otherinfog) {
  1300.                 VectorSubtract(trace.endpos, eye, dir);
  1301.                 fogdist = VectorLength(dir);
  1302.             }
  1303.             else if (infog) {
  1304.                 VectorCopy(trace.endpos, start);
  1305.                 BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG);
  1306.                 VectorSubtract(eye, trace.endpos, dir);
  1307.                 fogdist = VectorLength(dir);
  1308.             }
  1309.             else if (otherinfog) {
  1310.                 VectorCopy(trace.endpos, end);
  1311.                 BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG);
  1312.                 VectorSubtract(end, trace.endpos, dir);
  1313.                 fogdist = VectorLength(dir);
  1314.             }
  1315.             else {
  1316.                 //if the entity and the viewer are not in fog assume there's no fog in between
  1317.                 fogdist = 0;
  1318.             }
  1319.             //decrease visibility with the view distance through fog
  1320.             vis = 1 / ((fogdist * fogdist * 0.001) < 1 ? 1 : (fogdist * fogdist * 0.001));
  1321.             //if entering water visibility is reduced
  1322.             vis *= waterfactor;
  1323.             //
  1324.             if (vis > bestvis) bestvis = vis;
  1325.             //if pretty much no fog
  1326.             if (bestvis >= 0.95) return bestvis;
  1327.         }
  1328.         //check bottom and top of bounding box as well
  1329.         if (i == 0) middle[2] += entinfo.mins[2];
  1330.         else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2];
  1331.     }
  1332.     return bestvis;
  1333. }
  1334.  
  1335. /*
  1336. ==================
  1337. BotFindEnemy
  1338. ==================
  1339. */
  1340. int BotFindEnemy(bot_state_t *bs, int curenemy) {
  1341.     int i, healthdecrease;
  1342.     float f, dist, curdist, alertness, easyfragger, vis;
  1343.     aas_entityinfo_t entinfo, curenemyinfo;
  1344.     vec3_t dir, angles;
  1345.  
  1346.     alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1);
  1347.     easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1);
  1348.     //check if the health decreased
  1349.     healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH];
  1350.     //remember the current health value
  1351.     bs->lasthealth = bs->inventory[INVENTORY_HEALTH];
  1352.     //
  1353.     if (curenemy >= 0) {
  1354.         BotEntityInfo(curenemy, &curenemyinfo);
  1355.         if (EntityCarriesFlag(&curenemyinfo)) return qfalse;
  1356.         VectorSubtract(curenemyinfo.origin, bs->origin, dir);
  1357.         curdist = VectorLength(dir);
  1358.     }
  1359.     else {
  1360.         curdist = 0;
  1361.     }
  1362.     //
  1363.     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
  1364.  
  1365.         if (i == bs->client) continue;
  1366.         //if it's the current enemy
  1367.         if (i == curenemy) continue;
  1368.         //
  1369.         BotEntityInfo(i, &entinfo);
  1370.         //
  1371.         if (!entinfo.valid) continue;
  1372.         //if the enemy isn't dead and the enemy isn't the bot self
  1373.         if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
  1374.         //if the enemy is invisible and not shooting
  1375.         if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
  1376.             continue;
  1377.         }
  1378.         //if not an easy fragger don't shoot at chatting players
  1379.         if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue;
  1380.         //
  1381.         if (lastteleport_time > trap_AAS_Time() - 3) {
  1382.             VectorSubtract(entinfo.origin, lastteleport_origin, dir);
  1383.             if (VectorLength(dir) < 70) continue;
  1384.         }
  1385.         //calculate the distance towards the enemy
  1386.         VectorSubtract(entinfo.origin, bs->origin, dir);
  1387.         dist = VectorLength(dir);
  1388.         //if this entity is not carrying a flag
  1389.         if (!EntityCarriesFlag(&entinfo))
  1390.         {
  1391.             //if this enemy is further away than the current one
  1392.             if (curenemy >= 0 && dist > curdist) continue;
  1393.         } //end if
  1394.         //if the bot has no
  1395.         if (dist > 900 + alertness * 4000) continue;
  1396.         //if on the same team
  1397.         if (BotSameTeam(bs, i)) continue;
  1398.         //if the bot's health decreased or the enemy is shooting
  1399.         if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) f = 360;
  1400.         else f = 90 + 90 - (90 - (dist > 810 ? 810 : dist) / 9);
  1401.         //check if the enemy is visible
  1402.         vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i);
  1403.         if (vis <= 0) continue;
  1404.         //if the enemy is quite far away, not shooting and the bot is not damaged
  1405.         if (curenemy < 0 && dist > 200 && !healthdecrease && !EntityIsShooting(&entinfo))
  1406.         {
  1407.             //check if we can avoid this enemy
  1408.             VectorSubtract(bs->origin, entinfo.origin, dir);
  1409.             vectoangles(dir, angles);
  1410.             //if the bot isn't in the fov of the enemy
  1411.             if (!InFieldOfVision(entinfo.angles, 120, angles)) {
  1412.                 //update some stuff for this enemy
  1413.                 BotUpdateBattleInventory(bs, i);
  1414.                 //if the bot doesn't really want to fight
  1415.                 if (BotWantsToRetreat(bs)) continue;
  1416.             }
  1417.         }
  1418.         //found an enemy
  1419.         bs->enemy = entinfo.number;
  1420.         if (curenemy >= 0) bs->enemysight_time = trap_AAS_Time() - 2;
  1421.         else bs->enemysight_time = trap_AAS_Time();
  1422.         bs->enemysuicide = qfalse;
  1423.         bs->enemydeath_time = 0;
  1424.         return qtrue;
  1425.     }
  1426.     return qfalse;
  1427. }
  1428.  
  1429. /*
  1430. ==================
  1431. BotTeamFlagCarrierVisible
  1432. ==================
  1433. */
  1434. int BotTeamFlagCarrierVisible(bot_state_t *bs) {
  1435.     int i;
  1436.     float vis;
  1437.     aas_entityinfo_t entinfo;
  1438.  
  1439.     for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
  1440.         if (i == bs->client) continue;
  1441.         //
  1442.         BotEntityInfo(i, &entinfo);
  1443.         //if this player is active
  1444.         if (!entinfo.valid) continue;
  1445.         //if this player is carrying a flag
  1446.         if (!EntityCarriesFlag(&entinfo)) continue;
  1447.         //if the flag carrier is not on the same team
  1448.         if (!BotSameTeam(bs, i)) continue;
  1449.         //if the flag carrier is not visible
  1450.         vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
  1451.         if (vis <= 0) continue;
  1452.         //
  1453.         return i;
  1454.     }
  1455.     return -1;
  1456. }
  1457.  
  1458. /*
  1459. ==================
  1460. BotAimAtEnemy
  1461. ==================
  1462. */
  1463. void BotAimAtEnemy(bot_state_t *bs) {
  1464.     int i, enemyvisible;
  1465.     float dist, f, aim_skill, aim_accuracy, speed, reactiontime;
  1466.     vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity;
  1467.     vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4};
  1468.     weaponinfo_t wi;
  1469.     aas_entityinfo_t entinfo;
  1470.     bot_goal_t goal;
  1471.     bsp_trace_t trace;
  1472.     vec3_t target;
  1473.  
  1474.     //if the bot has no enemy
  1475.     if (bs->enemy < 0) return;
  1476.     //
  1477.     //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy);
  1478.     //
  1479.     aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1);
  1480.     aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1);
  1481.     //
  1482.     if (aim_skill > 0.95) {
  1483.         //don't aim too early
  1484.         reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1);
  1485.         if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return;
  1486.         if (bs->teleport_time > trap_AAS_Time() - reactiontime) return;
  1487.     }
  1488.  
  1489.     //get the weapon information
  1490.     trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
  1491.     //get the weapon specific aim accuracy and or aim skill
  1492.     if (wi.number == WP_MACHINEGUN) {
  1493.         aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1);
  1494.     }
  1495.     if (wi.number == WP_SHOTGUN) {
  1496.         aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1);
  1497.     }
  1498.     if (wi.number == WP_GRENADE_LAUNCHER) {
  1499.         aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1);
  1500.         aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1);
  1501.     }
  1502.     if (wi.number == WP_ROCKET_LAUNCHER) {
  1503.         aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1);
  1504.         aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1);
  1505.     }
  1506.     if (wi.number == WP_LIGHTNING) {
  1507.         aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1);
  1508.     }
  1509.     if (wi.number == WP_RAILGUN) {
  1510.         aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1);
  1511.     }
  1512.     if (wi.number == WP_PLASMAGUN) {
  1513.         aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1);
  1514.         aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1);
  1515.     }
  1516.     if (wi.number == WP_BFG) {
  1517.         aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1);
  1518.         aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1);
  1519.     }
  1520.     //
  1521.     if (aim_accuracy <= 0) aim_accuracy = 0.0001;
  1522.     //get the enemy entity information
  1523.     BotEntityInfo(bs->enemy, &entinfo);
  1524.     //if the enemy is invisible then shoot crappy most of the time
  1525.     if (EntityIsInvisible(&entinfo)) {
  1526.         if (random() > 0.1) aim_accuracy *= 0.4;
  1527.     }
  1528.     //
  1529.     VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity);
  1530.     VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity);
  1531.     //enemy origin and velocity is remembered every 0.5 seconds
  1532.     if (bs->enemyposition_time < trap_AAS_Time()) {
  1533.         //
  1534.         bs->enemyposition_time = trap_AAS_Time() + 0.5;
  1535.         VectorCopy(enemyvelocity, bs->enemyvelocity);
  1536.         VectorCopy(entinfo.origin, bs->enemyorigin);
  1537.     }
  1538.     //if not extremely skilled
  1539.     if (aim_skill < 0.9) {
  1540.         VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
  1541.         //if the enemy moved a bit
  1542.         if (VectorLength(dir) > 48) {
  1543.             //if the enemy changed direction
  1544.             if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) {
  1545.                 //aim accuracy should be worse now
  1546.                 aim_accuracy *= 0.7;
  1547.             }
  1548.         }
  1549.     }
  1550.     //check visibility of enemy
  1551.     enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy);
  1552.     //if the enemy is visible
  1553.     if (enemyvisible) {
  1554.         //
  1555.         VectorCopy(entinfo.origin, bestorigin);
  1556.         bestorigin[2] += 8;
  1557.         //get the start point shooting from
  1558.         //NOTE: the x and y projectile start offsets are ignored
  1559.         VectorCopy(bs->origin, start);
  1560.         start[2] += bs->cur_ps.viewheight;
  1561.         start[2] += wi.offset[2];
  1562.         //
  1563.         BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT);
  1564.         //if the enemy is NOT hit
  1565.         if (trace.fraction <= 1 && trace.ent != entinfo.number) {
  1566.             bestorigin[2] += 16;
  1567.         }
  1568.         //if it is not an instant hit weapon the bot might want to predict the enemy
  1569.         if (wi.speed) {
  1570.             //
  1571.             VectorSubtract(bestorigin, bs->origin, dir);
  1572.             dist = VectorLength(dir);
  1573.             VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
  1574.             //if the enemy is NOT pretty far away and strafing just small steps left and right
  1575.             if (!(dist > 100 && VectorLength(dir) < 32)) {
  1576.                 //if skilled anough do exact prediction
  1577.                 if (aim_skill > 0.8 &&
  1578.                         //if the weapon is ready to fire
  1579.                         bs->cur_ps.weaponstate == WEAPON_READY) {
  1580.                     aas_clientmove_t move;
  1581.                     vec3_t origin;
  1582.  
  1583.                     VectorSubtract(entinfo.origin, bs->origin, dir);
  1584.                     //distance towards the enemy
  1585.                     dist = VectorLength(dir);
  1586.                     //direction the enemy is moving in
  1587.                     VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
  1588.                     //
  1589.                     VectorScale(dir, 1 / entinfo.update_time, dir);
  1590.                     //
  1591.                     VectorCopy(entinfo.origin, origin);
  1592.                     origin[2] += 1;
  1593.                     //
  1594.                     VectorClear(cmdmove);
  1595.                     //AAS_ClearShownDebugLines();
  1596.                     trap_AAS_PredictClientMovement(&move, bs->enemy, origin,
  1597.                                                         PRESENCE_CROUCH, qfalse,
  1598.                                                         dir, cmdmove, 0,
  1599.                                                         dist * 10 / wi.speed, 0.1, 0, 0, qfalse);
  1600.                     VectorCopy(move.endpos, bestorigin);
  1601.                     //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", trap_AAS_Time(), VectorLength(dir), dist * 10 / wi.speed);
  1602.                 }
  1603.                 //if not that skilled do linear prediction
  1604.                 else if (aim_skill > 0.4) {
  1605.                     VectorSubtract(entinfo.origin, bs->origin, dir);
  1606.                     //distance towards the enemy
  1607.                     dist = VectorLength(dir);
  1608.                     //direction the enemy is moving in
  1609.                     VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
  1610.                     dir[2] = 0;
  1611.                     //
  1612.                     speed = VectorNormalize(dir) / entinfo.update_time;
  1613.                     //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed);
  1614.                     //best spot to aim at
  1615.                     VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin);
  1616.                 }
  1617.             }
  1618.         }
  1619.         //if the projectile does radial damage
  1620.         if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) {
  1621.             //if the enemy isn't standing significantly higher than the bot
  1622.             if (entinfo.origin[2] < bs->origin[2] + 16) {
  1623.                 //try to aim at the ground in front of the enemy
  1624.                 VectorCopy(entinfo.origin, end);
  1625.                 end[2] -= 64;
  1626.                 BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT);
  1627.                 //
  1628.                 VectorCopy(bestorigin, groundtarget);
  1629.                 if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16;
  1630.                 else groundtarget[2] = trace.endpos[2] - 8;
  1631.                 //trace a line from projectile start to ground target
  1632.                 BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT);
  1633.                 //if hitpoint is not vertically too far from the ground target
  1634.                 if (fabs(trace.endpos[2] - groundtarget[2]) < 50) {
  1635.                     VectorSubtract(trace.endpos, groundtarget, dir);
  1636.                     //if the hitpoint is near anough the ground target
  1637.                     if (VectorLength(dir) < 60) {
  1638.                         VectorSubtract(trace.endpos, start, dir);
  1639.                         //if the hitpoint is far anough from the bot
  1640.                         if (VectorLength(dir) > 100) {
  1641.                             //check if the bot is visible from the ground target
  1642.                             trace.endpos[2] += 1;
  1643.                             BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT);
  1644.                             if (trace.fraction >= 1) {
  1645.                                 //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time());
  1646.                                 VectorCopy(groundtarget, bestorigin);
  1647.                             }
  1648.                         }
  1649.                     }
  1650.                 }
  1651.             }
  1652.         }
  1653.         bestorigin[0] += 20 * crandom() * (1 - aim_accuracy);
  1654.         bestorigin[1] += 20 * crandom() * (1 - aim_accuracy);
  1655.         bestorigin[2] += 10 * crandom() * (1 - aim_accuracy);
  1656.     }
  1657.     else {
  1658.         //
  1659.         VectorCopy(bs->lastenemyorigin, bestorigin);
  1660.         bestorigin[2] += 8;
  1661.         //if the bot is skilled anough
  1662.         if (aim_skill > 0.5) {
  1663.             //do prediction shots around corners
  1664.             if (wi.number == WP_BFG ||
  1665.                 wi.number == WP_ROCKET_LAUNCHER ||
  1666.                 wi.number == WP_GRENADE_LAUNCHER) {
  1667.                 //create the chase goal
  1668.                 goal.entitynum = bs->client;
  1669.                 goal.areanum = bs->areanum;
  1670.                 VectorCopy(bs->eye, goal.origin);
  1671.                 VectorSet(goal.mins, -8, -8, -8);
  1672.                 VectorSet(goal.maxs, 8, 8, 8);
  1673.                 //
  1674.                 if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) {
  1675.                     VectorSubtract(target, bs->eye, dir);
  1676.                     if (VectorLength(dir) > 80) {
  1677.                         VectorCopy(target, bestorigin);
  1678.                         bestorigin[2] -= 20;
  1679.                     }
  1680.                 }
  1681.                 aim_accuracy = 1;
  1682.             }
  1683.         }
  1684.     }
  1685.     //
  1686.     if (enemyvisible) {
  1687.         BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT);
  1688.         VectorCopy(trace.endpos, bs->aimtarget);
  1689.     }
  1690.     else {
  1691.         VectorCopy(bestorigin, bs->aimtarget);
  1692.     }
  1693.     //get aim direction
  1694.     VectorSubtract(bestorigin, bs->eye, dir);
  1695.     //
  1696.     if (wi.number == WP_MACHINEGUN ||
  1697.         wi.number == WP_SHOTGUN ||
  1698.         wi.number == WP_LIGHTNING ||
  1699.         wi.number == WP_RAILGUN) {
  1700.         //distance towards the enemy
  1701.         dist = VectorLength(dir);
  1702.         if (dist > 150) dist = 150;
  1703.         f = 0.6 + dist / 150 * 0.4;
  1704.         aim_accuracy *= f;
  1705.     }
  1706.     //add some random stuff to the aim direction depending on the aim accuracy
  1707.     if (aim_accuracy < 0.8) {
  1708.         VectorNormalize(dir);
  1709.         for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy);
  1710.     }
  1711.     //set the ideal view angles
  1712.     vectoangles(dir, bs->ideal_viewangles);
  1713.     //take the weapon spread into account for lower skilled bots
  1714.     bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy);
  1715.     bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
  1716.     bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy);
  1717.     bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
  1718.     //if the bots should be really challenging
  1719.     if (bot_challenge.integer) {
  1720.         //if the bot is really accurate and has the enemy in view for some time
  1721.         if (aim_accuracy > 0.9 && bs->enemysight_time < trap_AAS_Time() - 1) {
  1722.             //set the view angles directly
  1723.             if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
  1724.             VectorCopy(bs->ideal_viewangles, bs->viewangles);
  1725.             trap_EA_View(bs->client, bs->viewangles);
  1726.         }
  1727.     }
  1728. }
  1729.  
  1730. /*
  1731. ==================
  1732. BotCheckAttack
  1733. ==================
  1734. */
  1735. void BotCheckAttack(bot_state_t *bs) {
  1736.     float points, reactiontime, fov, firethrottle;
  1737.     bsp_trace_t bsptrace;
  1738.     //float selfpreservation;
  1739.     vec3_t forward, right, start, end, dir, angles;
  1740.     weaponinfo_t wi;
  1741.     bsp_trace_t trace;
  1742.     aas_entityinfo_t entinfo;
  1743.     vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8};
  1744.  
  1745.     if (bs->enemy < 0) return;
  1746.     //
  1747.     reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1);
  1748.     if (bs->enemysight_time > trap_AAS_Time() - reactiontime) return;
  1749.     if (bs->teleport_time > trap_AAS_Time() - reactiontime) return;
  1750.     //if changing weapons
  1751.     if (bs->weaponchange_time > trap_AAS_Time() - 0.1) return;
  1752.     //check fire throttle characteristic
  1753.     if (bs->firethrottlewait_time > trap_AAS_Time()) return;
  1754.     firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1);
  1755.     if (bs->firethrottleshoot_time < trap_AAS_Time()) {
  1756.         if (random() > firethrottle) {
  1757.             bs->firethrottlewait_time = trap_AAS_Time() + firethrottle;
  1758.             bs->firethrottleshoot_time = 0;
  1759.         }
  1760.         else {
  1761.             bs->firethrottleshoot_time = trap_AAS_Time() + 1 - firethrottle;
  1762.             bs->firethrottlewait_time = 0;
  1763.         }
  1764.     }
  1765.     //
  1766.     BotEntityInfo(bs->enemy, &entinfo);
  1767.     VectorSubtract(entinfo.origin, bs->eye, dir);
  1768.     //
  1769.     if (VectorLength(dir) < 100) fov = 120;
  1770.     else fov = 50;
  1771.     /*
  1772.     //if the enemy isn't visible
  1773.     if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, fov, bs->enemy)) {
  1774.         //botimport.Print(PRT_MESSAGE, "enemy not visible\n");
  1775.         return;
  1776.     }*/
  1777.     vectoangles(dir, angles);
  1778.     if (!InFieldOfVision(bs->viewangles, fov, angles)) return;
  1779.     BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
  1780.     if (bsptrace.fraction < 1 && bsptrace.ent != bs->enemy) return;
  1781.  
  1782.     //get the weapon info
  1783.     trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
  1784.     //get the start point shooting from
  1785.     VectorCopy(bs->origin, start);
  1786.     start[2] += bs->cur_ps.viewheight;
  1787.     AngleVectors(bs->viewangles, forward, right, NULL);
  1788.     start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1];
  1789.     start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1];
  1790.     start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2];
  1791.     //end point aiming at
  1792.     VectorMA(start, 1000, forward, end);
  1793.     //a little back to make sure not inside a very close enemy
  1794.     VectorMA(start, -12, forward, start);
  1795.     BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT);
  1796.     //if won't hit the enemy
  1797.     if (trace.ent != bs->enemy) {
  1798.         //if the entity is a client
  1799.         if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) {
  1800.             //if a teammate is hit
  1801.             if (BotSameTeam(bs, trace.ent)) return;
  1802.         }
  1803.         //if the projectile does a radial damage
  1804.         if (wi.proj.damagetype & DAMAGETYPE_RADIAL) {
  1805.             if (trace.fraction * 1000 < wi.proj.radius) {
  1806.                 points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5;
  1807.                 if (points > 0) {
  1808. //                    selfpreservation = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_SELFPRESERVATION, 0, 1);
  1809. //                    if (random() < selfpreservation) return;
  1810.                     return;
  1811.                 }
  1812.             }
  1813.             //FIXME: check if a teammate gets radial damage
  1814.         }
  1815.     }
  1816.     //if fire has to be release to activate weapon
  1817.     if (wi.flags & WFL_FIRERELEASED) {
  1818.         if (bs->flags & BFL_ATTACKED) {
  1819.             trap_EA_Attack(bs->client);
  1820.         }
  1821.     }
  1822.     else {
  1823.         trap_EA_Attack(bs->client);
  1824.     }
  1825.     bs->flags ^= BFL_ATTACKED;
  1826. }
  1827.  
  1828. /*
  1829. ==================
  1830. BotMapScripts
  1831. ==================
  1832. */
  1833. void BotMapScripts(bot_state_t *bs) {
  1834.     char info[1024];
  1835.     char mapname[128];
  1836.     int i, shootbutton;
  1837.     float aim_accuracy;
  1838.     aas_entityinfo_t entinfo;
  1839.     vec3_t dir;
  1840.  
  1841.     trap_GetServerinfo(info, sizeof(info));
  1842.  
  1843.     strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1);
  1844.     mapname[sizeof(mapname)-1] = '\0';
  1845.  
  1846.     if (!Q_stricmp(mapname, "q3tourney6")) {
  1847.         vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680};
  1848.         vec3_t buttonorg = {304, 352, 920};
  1849.         //NOTE: NEVER use the func_bobbing in q3tourney6
  1850.         bs->tfl &= ~TFL_FUNCBOB;
  1851.         //if the bot is below the bounding box
  1852.         if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) {
  1853.             if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) {
  1854.                 if (bs->origin[2] < mins[2]) {
  1855.                     return;
  1856.                 }
  1857.             }
  1858.         }
  1859.         shootbutton = qfalse;
  1860.         //if an enemy is below this bounding box then shoot the button
  1861.         for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
  1862.  
  1863.             if (i == bs->client) continue;
  1864.             //
  1865.             BotEntityInfo(i, &entinfo);
  1866.             //
  1867.             if (!entinfo.valid) continue;
  1868.             //if the enemy isn't dead and the enemy isn't the bot self
  1869.             if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
  1870.             //
  1871.             if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) {
  1872.                 if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) {
  1873.                     if (entinfo.origin[2] < mins[2]) {
  1874.                         //if there's a team mate below the crusher
  1875.                         if (BotSameTeam(bs, i)) {
  1876.                             shootbutton = qfalse;
  1877.                             break;
  1878.                         }
  1879.                         else {
  1880.                             shootbutton = qtrue;
  1881.                         }
  1882.                     }
  1883.                 }
  1884.             }
  1885.         }
  1886.         if (shootbutton) {
  1887.             bs->flags |= BFL_IDEALVIEWSET;
  1888.             VectorSubtract(buttonorg, bs->eye, dir);
  1889.             vectoangles(dir, bs->ideal_viewangles);
  1890.             aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1);
  1891.             bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy);
  1892.             bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
  1893.             bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy);
  1894.             bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
  1895.             //
  1896.             if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) {
  1897.                 trap_EA_Attack(bs->client);
  1898.             }
  1899.         }
  1900.     }
  1901. }
  1902.  
  1903. /*
  1904. ==================
  1905. BotEntityToActivate
  1906. ==================
  1907. */
  1908. //#define OBSTACLEDEBUG
  1909.  
  1910. int BotEntityToActivate(int entitynum) {
  1911.     int i, ent, cur_entities[10];
  1912.     char model[MAX_INFO_STRING], tmpmodel[128];
  1913.     char target[128], classname[128];
  1914.     float health;
  1915.     char targetname[10][128];
  1916.     aas_entityinfo_t entinfo;
  1917.  
  1918.     BotEntityInfo(entitynum, &entinfo);
  1919.     Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex);
  1920.     for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
  1921.         if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue;
  1922.         if (!strcmp(model, tmpmodel)) break;
  1923.     }
  1924.     if (!ent) {
  1925.         BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity found with model %s\n", model);
  1926.         return 0;
  1927.     }
  1928.     trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname));
  1929.     if (!classname) {
  1930.         BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model %s has no classname\n", model);
  1931.         return 0;
  1932.     }
  1933.     //if it is a door
  1934.     if (!strcmp(classname, "func_door")) {
  1935.         if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) {
  1936.             //if health the door must be shot to open
  1937.             if (health) return ent;
  1938.         }
  1939.     }
  1940.     //get the targetname so we can find an entity with a matching target
  1941.     if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) {
  1942. #ifdef OBSTACLEDEBUG
  1943.         BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with model \"%s\" has no targetname\n", model);
  1944. #endif //OBSTACLEDEBUG
  1945.         return 0;
  1946.     }
  1947.     //only allows single activation chains, tree-like activation can be added back in
  1948.     cur_entities[0] = trap_AAS_NextBSPEntity(0);
  1949.     for (i = 0; i >= 0 && i < 10;) {
  1950.         for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) {
  1951.             if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue;
  1952.             if (!strcmp(targetname[i], target)) {
  1953.                 cur_entities[i] = trap_AAS_NextBSPEntity(ent);
  1954.                 break;
  1955.             }
  1956.         }
  1957.         if (!ent) {
  1958.             BotAI_Print(PRT_ERROR, "BotEntityToActivate: no entity with target \"%s\"\n", targetname[i]);
  1959.             i--;
  1960.             continue;
  1961.         }
  1962.         if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) {
  1963.             BotAI_Print(PRT_ERROR, "BotEntityToActivate: entity with target \"%s\" has no classname\n", targetname[i]);
  1964.             continue;
  1965.         }
  1966.         if (!strcmp(classname, "func_button")) {
  1967.             //BSP button model
  1968.             return ent;
  1969.         }
  1970.         else if (!strcmp(classname, "trigger_multiple")) {
  1971.             //invisible trigger multiple box
  1972.             return ent;
  1973.         }
  1974.         else {
  1975.             i--;
  1976.         }
  1977.     }
  1978.     BotAI_Print(PRT_ERROR, "BotEntityToActivate: unknown activator with classname \"%s\"\n", classname);
  1979.     return 0;
  1980. }
  1981.  
  1982. /*
  1983. ==================
  1984. BotSetMovedir
  1985. ==================
  1986. */
  1987. vec3_t VEC_UP        = {0, -1,  0};
  1988. vec3_t MOVEDIR_UP    = {0,  0,  1};
  1989. vec3_t VEC_DOWN        = {0, -2,  0};
  1990. vec3_t MOVEDIR_DOWN    = {0,  0, -1};
  1991.  
  1992. void BotSetMovedir(vec3_t angles, vec3_t movedir) {
  1993.     if (VectorCompare(angles, VEC_UP)) {
  1994.         VectorCopy(MOVEDIR_UP, movedir);
  1995.     }
  1996.     else if (VectorCompare(angles, VEC_DOWN)) {
  1997.         VectorCopy(MOVEDIR_DOWN, movedir);
  1998.     }
  1999.     else {
  2000.         AngleVectors(angles, movedir, NULL, NULL);
  2001.     }
  2002. }
  2003.  
  2004. /*
  2005. ==================
  2006. BotModelMinsMaxs
  2007.  
  2008. this is ugly
  2009. ==================
  2010. */
  2011. void BotModelMinsMaxs(int modelindex, int eType, vec3_t mins, vec3_t maxs) {
  2012.     gentity_t *ent;
  2013.     int i;
  2014.  
  2015.     ent = &g_entities[0];
  2016.     for (i = 0; i < level.num_entities; i++, ent++) {
  2017.         if ( !ent->inuse ) {
  2018.             continue;
  2019.         }
  2020.         if ( ent->s.eType != eType) {
  2021.             continue;
  2022.         }
  2023.         if (ent->s.modelindex == modelindex) {
  2024.             VectorAdd(ent->r.currentOrigin, ent->r.mins, mins);
  2025.             VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs);
  2026.             return;
  2027.         }
  2028.     }
  2029.     VectorClear(mins);
  2030.     VectorClear(maxs);
  2031. }
  2032.  
  2033. /*
  2034. ==================
  2035. BotAIBlocked
  2036.  
  2037. very basic handling of bots being blocked by other entities
  2038. check what kind of entity is blocking the bot and try to activate
  2039. it otherwise try to walk around the entity
  2040. before the bot ends in this part of the AI it should predict which doors to open,
  2041. which buttons to activate etc.
  2042. ==================
  2043. */
  2044. void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) {
  2045.     int movetype, ent, i, areas[10], numareas, modelindex;
  2046.     char classname[128], model[128];
  2047.     float lip, dist, health, angle;
  2048.     vec3_t hordir, size, start, end, mins, maxs, sideward, angles;
  2049.     vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs;
  2050.     vec3_t up = {0, 0, 1}, extramins = {1, 1, 1}, extramaxs = {-1, -1, -1};
  2051.     aas_entityinfo_t entinfo;
  2052.     //bsp_trace_t bsptrace;
  2053. #ifdef OBSTACLEDEBUG
  2054.     char netname[MAX_NETNAME];
  2055.     char buf[128];
  2056. #endif
  2057.  
  2058.     if (!moveresult->blocked) {
  2059.         bs->notblocked_time = trap_AAS_Time();
  2060.         return;
  2061.     }
  2062.     //
  2063.     BotEntityInfo(moveresult->blockentity, &entinfo);
  2064. #ifdef OBSTACLEDEBUG
  2065.     ClientName(bs->client, netname, sizeof(netname));
  2066.     BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex);
  2067. #endif OBSTACLEDEBUG
  2068.     //if blocked by a bsp model and the bot wants to activate it if possible
  2069.     if (entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex && activate) {
  2070.         //find the bsp entity which should be activated in order to remove
  2071.         //the blocking entity
  2072.         ent = BotEntityToActivate(entinfo.number);
  2073.         if (!ent) {
  2074.             strcpy(classname, "");
  2075. #ifdef OBSTACLEDEBUG
  2076.             BotAI_Print(PRT_MESSAGE, "%s: can't find activator for blocking entity\n", ClientName(bs->client, netname, sizeof(netname)));
  2077. #endif //OBSTACLEDEBUG
  2078.         }
  2079.         else {
  2080.             trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname));
  2081. #ifdef OBSTACLEDEBUG
  2082.             ClientName(bs->client, netname, sizeof(netname));
  2083.             BotAI_Print(PRT_MESSAGE, "%s: I should activate %s\n", netname, classname);
  2084. #endif OBSTACLEDEBUG
  2085.         }
  2086.         if (!strcmp(classname, "func_button")) {
  2087.             //create a bot goal towards the button
  2088.             trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model));
  2089.             modelindex = atoi(model+1);
  2090.             //if the model is not loaded
  2091.             if (!modelindex) return;
  2092.             VectorClear(angles);
  2093.             BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs);
  2094.             //get the lip of the button
  2095.             trap_AAS_FloatForBSPEpairKey(ent, "lip", &lip);
  2096.             if (!lip) lip = 4;
  2097.             //get the move direction from the angle
  2098.             trap_AAS_FloatForBSPEpairKey(ent, "angle", &angle);
  2099.             VectorSet(angles, 0, angle, 0);
  2100.             BotSetMovedir(angles, movedir);
  2101.             //button size
  2102.             VectorSubtract(maxs, mins, size);
  2103.             //button origin
  2104.             VectorAdd(mins, maxs, origin);
  2105.             VectorScale(origin, 0.5, origin);
  2106.             //touch distance of the button
  2107.             dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2];
  2108.             dist *= 0.5;
  2109.             //
  2110.             trap_AAS_FloatForBSPEpairKey(ent, "health", &health);
  2111.             //if the button is shootable
  2112.             if (health) {
  2113.                 //FIXME: walk to a point where the button is visible and shoot at the button
  2114.                 //calculate the goal origin
  2115.                 VectorMA(origin, -dist, movedir, goalorigin);
  2116.                 //
  2117.                 VectorSubtract(goalorigin, bs->origin, movedir);
  2118.                 vectoangles(movedir, moveresult->ideal_viewangles);
  2119.                 moveresult->flags |= MOVERESULT_MOVEMENTVIEW;
  2120.                 moveresult->flags |= MOVERESULT_MOVEMENTWEAPON;
  2121.                 //select the machinegun and shoot
  2122.                 trap_EA_SelectWeapon(bs->client, WEAPONINDEX_MACHINEGUN);
  2123.                 if (bs->cur_ps.weapon == WEAPONINDEX_MACHINEGUN) {
  2124.                     trap_EA_Attack(bs->client);
  2125.                 }
  2126.                 return;
  2127.             }
  2128.             else {
  2129.                 //add bounding box size to the dist
  2130.                 trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs);
  2131.                 for (i = 0; i < 3; i++) {
  2132.                     if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]);
  2133.                     else dist += fabs(movedir[i]) * fabs(bboxmins[i]);
  2134.                 }
  2135.                 //calculate the goal origin
  2136.                 VectorMA(origin, -dist, movedir, goalorigin);
  2137.                 //
  2138.                 VectorCopy(goalorigin, start);
  2139.                 start[2] += 24;
  2140.                 VectorCopy(start, end);
  2141.                 end[2] -= 100;
  2142.                 numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10);
  2143.                 //
  2144.                 for (i = 0; i < numareas; i++) {
  2145.                     if (trap_AAS_AreaReachability(areas[i])) {
  2146.                         break;
  2147.                     }
  2148.                 }
  2149.                 if (i < numareas) {
  2150.                     //
  2151. #ifdef OBSTACLEDEBUG
  2152.                     if (bs->activatemessage_time < trap_AAS_Time()) {
  2153.                         Com_sprintf(buf, sizeof(buf), "I have to activate a button at %1.1f %1.1f %1.1f in area %d\n",
  2154.                                         goalorigin[0], goalorigin[1], goalorigin[2], areas[i]);
  2155.                         trap_EA_Say(bs->client, buf);
  2156.                         bs->activatemessage_time = trap_AAS_Time() + 5;
  2157.                     }
  2158. #endif //OBSTACLEDEBUG
  2159.                     //
  2160.                     VectorCopy(origin, bs->activategoal.origin);
  2161.                     bs->activategoal.areanum = areas[i];
  2162.                     VectorSubtract(mins, origin, bs->activategoal.mins);
  2163.                     VectorSubtract(maxs, origin, bs->activategoal.maxs);
  2164.                     //
  2165.                     for (i = 0; i < 3; i++)
  2166.                     {
  2167.                         if (movedir[i] < 0) bs->activategoal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]);
  2168.                         else bs->activategoal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]);
  2169.                     } //end for
  2170.                     //
  2171.                     bs->activategoal.entitynum = entinfo.number;
  2172.                     bs->activategoal.number = 0;
  2173.                     bs->activategoal.flags = 0;
  2174.                     bs->activate_time = trap_AAS_Time() + 10;
  2175.                     AIEnter_Seek_ActivateEntity(bs);
  2176.                     return;
  2177.                 }
  2178.                 else {
  2179. #ifdef OBSTACLEDEBUG
  2180.                     if (!numareas) BotAI_Print(PRT_MESSAGE, "button not in an area\n");
  2181.                     else BotAI_Print(PRT_MESSAGE, "button area has no reachabilities\n");
  2182. #endif //OBSTACLEDEBUG
  2183.                     if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
  2184.                     else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
  2185.                 }
  2186.             }
  2187.         }
  2188.         else if (!strcmp(classname, "func_door")) {
  2189.             //shoot at the shootable door
  2190.             trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model));
  2191.             modelindex = atoi(model+1);
  2192.             //if the model is not loaded
  2193.             if (!modelindex) return;
  2194.             VectorClear(angles);
  2195.             BotModelMinsMaxs(modelindex, ET_MOVER, mins, maxs);
  2196.             //door origin
  2197.             VectorAdd(mins, maxs, origin);
  2198.             VectorScale(origin, 0.5, origin);
  2199.             //
  2200.             VectorSubtract(origin, bs->eye, movedir);
  2201.             vectoangles(movedir, moveresult->ideal_viewangles);
  2202.             moveresult->flags |= MOVERESULT_MOVEMENTVIEW;
  2203.             moveresult->flags |= MOVERESULT_MOVEMENTWEAPON;
  2204.             //select the machinegun and shoot
  2205.             trap_EA_SelectWeapon(bs->client, WEAPONINDEX_MACHINEGUN);
  2206.             if (bs->cur_ps.weapon == WEAPONINDEX_MACHINEGUN) {
  2207.                 trap_EA_Attack(bs->client);
  2208.             }
  2209.             return;
  2210.         }
  2211.     }
  2212.     //just some basic dynamic obstacle avoidance code
  2213.     hordir[0] = moveresult->movedir[0];
  2214.     hordir[1] = moveresult->movedir[1];
  2215.     hordir[2] = 0;
  2216.     //if no direction just take a random direction
  2217.     if (VectorNormalize(hordir) < 0.1) {
  2218.         VectorSet(angles, 0, 360 * random(), 0);
  2219.         AngleVectors(angles, hordir, NULL, NULL);
  2220.     }
  2221.     //
  2222.     //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP;
  2223.     //else
  2224.     movetype = MOVE_WALK;
  2225.     //if there's an obstacle at the bot's feet and head then
  2226.     //the bot might be able to crouch through
  2227.     VectorCopy(bs->origin, start);
  2228.     start[2] += 18;
  2229.     VectorMA(start, 5, hordir, end);
  2230.     VectorSet(mins, -16, -16, -24);
  2231.     VectorSet(maxs, 16, 16, 4);
  2232.     //
  2233.     //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID);
  2234.     //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH;
  2235.     //get the sideward vector
  2236.     CrossProduct(hordir, up, sideward);
  2237.     //
  2238.     if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward);
  2239.     //try to crouch straight forward?
  2240.     if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) {
  2241.         //perform the movement
  2242.         if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) {
  2243.             //flip the avoid direction flag
  2244.             bs->flags ^= BFL_AVOIDRIGHT;
  2245.             //flip the direction
  2246.             VectorNegate(sideward, sideward);
  2247.             //move in the other direction
  2248.             trap_BotMoveInDirection(bs->ms, sideward, 400, movetype);
  2249.         }
  2250.     }
  2251.     //
  2252.     if (bs->notblocked_time < trap_AAS_Time() - 0.4) {
  2253.         //just reset goals and hope the bot will go into another direction
  2254.         //is this still needed??
  2255.         if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
  2256.         else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
  2257.     }
  2258. }
  2259.  
  2260. /*
  2261. ==================
  2262. BotCheckConsoleMessages
  2263. ==================
  2264. */
  2265. void BotCheckConsoleMessages(bot_state_t *bs) {
  2266.     char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr;
  2267.     float chat_reply;
  2268.     int context, handle;
  2269.     bot_consolemessage_t m;
  2270.     bot_match_t match;
  2271.  
  2272.     //the name of this bot
  2273.     ClientName(bs->client, botname, sizeof(botname));
  2274.     //
  2275.     while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) {
  2276.         //if the chat state is flooded with messages the bot will read them quickly
  2277.         if (trap_BotNumConsoleMessages(bs->cs) < 10) {
  2278.             //if it is a chat message the bot needs some time to read it
  2279.             if (m.type == CMS_CHAT && m.time > trap_AAS_Time() - (1 + random())) break;
  2280.         }
  2281.         //
  2282.         ptr = m.message;
  2283.         //if it is a chat message then don't unify white spaces and don't
  2284.         //replace synonyms in the netname
  2285.         if (m.type == CMS_CHAT) {
  2286.             //
  2287.             if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
  2288.                 ptr = m.message + match.variables[MESSAGE].offset;
  2289.             }
  2290.         }
  2291.         //unify the white spaces in the message
  2292.         trap_UnifyWhiteSpaces(ptr);
  2293.         //replace synonyms in the right context
  2294.         context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES;
  2295.         if (BotCTFTeam(bs) == CTF_TEAM_RED) context |= CONTEXT_CTFREDTEAM;
  2296.         else context |= CONTEXT_CTFBLUETEAM;
  2297.         trap_BotReplaceSynonyms(ptr, context);
  2298.         //if there's no match
  2299.         if (!BotMatchMessage(bs, m.message)) {
  2300.             //if it is a chat message
  2301.             if (m.type == CMS_CHAT && !bot_nochat.integer) {
  2302.                 //
  2303.                 if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
  2304.                     trap_BotRemoveConsoleMessage(bs->cs, handle);
  2305.                     continue;
  2306.                 }
  2307.                 //don't use eliza chats with team messages
  2308.                 if (match.subtype & ST_TEAM) {
  2309.                     trap_BotRemoveConsoleMessage(bs->cs, handle);
  2310.                     continue;
  2311.                 }
  2312.                 //
  2313.                 trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname));
  2314.                 trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message));
  2315.                 //if this is a message from the bot self
  2316.                 if (!Q_stricmp(netname, botname)) {
  2317.                     trap_BotRemoveConsoleMessage(bs->cs, handle);
  2318.                     continue;
  2319.                 }
  2320.                 //unify the message
  2321.                 trap_UnifyWhiteSpaces(message);
  2322.                 //
  2323.                 trap_Cvar_Update(&bot_testrchat);
  2324.                 if (bot_testrchat.integer) {
  2325.                     //
  2326.                     trap_BotLibVarSet("bot_testrchat", "1");
  2327.                     //if bot replies with a chat message
  2328.                     if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
  2329.                                                             NULL, NULL,
  2330.                                                             NULL, NULL,
  2331.                                                             NULL, NULL,
  2332.                                                             botname, netname)) {
  2333.                         BotAI_Print(PRT_MESSAGE, "------------------------\n");
  2334.                     }
  2335.                     else {
  2336.                         BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n");
  2337.                     }
  2338.                 }
  2339.                 //if at a valid chat position and not chatting already
  2340.                 else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs)) {
  2341.                     chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1);
  2342.                     if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) {
  2343.                         //if bot replies with a chat message
  2344.                         if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
  2345.                                                                 NULL, NULL,
  2346.                                                                 NULL, NULL,
  2347.                                                                 NULL, NULL,
  2348.                                                                 botname, netname)) {
  2349.                             //remove the console message
  2350.                             trap_BotRemoveConsoleMessage(bs->cs, handle);
  2351.                             bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
  2352.                             AIEnter_Stand(bs);
  2353.                             //EA_Say(bs->client, bs->cs.chatmessage);
  2354.                             break;
  2355.                         }
  2356.                     }
  2357.                 }
  2358.             }
  2359.         }
  2360.         //remove the console message
  2361.         trap_BotRemoveConsoleMessage(bs->cs, handle);
  2362.     }
  2363. }
  2364.  
  2365. /*
  2366. ==================
  2367. BotCheckEvents
  2368. ==================
  2369. */
  2370. void BotCheckEvents(bot_state_t *bs, entityState_t *state) {
  2371.     int event;
  2372.     char buf[128];
  2373.     //
  2374.     //NOTE: this sucks, we're accessing the gentity_t directly
  2375.     //but there's no other fast way to do it right now
  2376.     if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) {
  2377.         return;
  2378.     }
  2379.     bs->entityeventTime[state->number] = g_entities[state->number].eventTime;
  2380.     //if it's an event only entity
  2381.     if (state->eType > ET_EVENTS) {
  2382.         event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS;
  2383.     }
  2384.     else {
  2385.         event = state->event & ~EV_EVENT_BITS;
  2386.     }
  2387.     //
  2388.     switch(event) {
  2389.         //client obituary event
  2390.         case EV_OBITUARY:
  2391.         {
  2392.             int target, attacker, mod;
  2393.  
  2394.             target = state->otherEntityNum;
  2395.             attacker = state->otherEntityNum2;
  2396.             mod = state->eventParm;
  2397.             //
  2398.             if (target == bs->client) {
  2399.                 bs->botdeathtype = mod;
  2400.                 bs->lastkilledby = attacker;
  2401.                 //
  2402.                 if (target == attacker) bs->botsuicide = qtrue;
  2403.                 else bs->botsuicide = qfalse;
  2404.                 //
  2405.                 bs->num_deaths++;
  2406.             }
  2407.             //else if this client was killed by the bot
  2408.             else if (attacker == bs->client) {
  2409.                 bs->enemydeathtype = mod;
  2410.                 bs->lastkilledplayer = target;
  2411.                 bs->killedenemy_time = trap_AAS_Time();
  2412.                 //
  2413.                 bs->num_kills++;
  2414.             }
  2415.             else if (attacker == bs->enemy && target == attacker) {
  2416.                 bs->enemysuicide = qtrue;
  2417.             }
  2418.             break;
  2419.         }
  2420.         case EV_GLOBAL_SOUND:
  2421.         {
  2422.             if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
  2423.                 BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
  2424.                 break;
  2425.             }
  2426.             trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
  2427.             if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) {
  2428.                 //red flag is returned
  2429.                 bs->redflagstatus = 0;
  2430.                 bs->flagstatuschanged = qtrue;
  2431.             }
  2432.             else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) {
  2433.                 //blue flag is returned
  2434.                 bs->blueflagstatus = 0;
  2435.                 bs->flagstatuschanged = qtrue;
  2436.             }
  2437.             else if (!strcmp(buf, "sound/items/poweruprespawn.wav")) {
  2438.                 //powerup respawned... go get it
  2439.                 BotGoForPowerups(bs);
  2440.             }
  2441.             break;
  2442.         }
  2443.         case EV_PLAYER_TELEPORT_IN:
  2444.         {
  2445.             VectorCopy(state->origin, lastteleport_origin);
  2446.             lastteleport_time = trap_AAS_Time();
  2447.             break;
  2448.         }
  2449.         case EV_GENERAL_SOUND:
  2450.         {
  2451.             //if this sound is played on the bot
  2452.             if (state->number == bs->client) {
  2453.                 if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
  2454.                     BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
  2455.                     break;
  2456.                 }
  2457.                 //check out the sound
  2458.                 trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
  2459.                 //if falling into a death pit
  2460.                 if (!strcmp(buf, "*falling1.wav")) {
  2461.                     //if the bot has a personal teleporter
  2462.                     if (bs->inventory[INVENTORY_TELEPORTER] > 0) {
  2463.                         //use the holdable item
  2464.                         trap_EA_Use(bs->client);
  2465.                     }
  2466.                 }
  2467.             }
  2468.             break;
  2469.         }
  2470.     }
  2471. }
  2472.  
  2473. /*
  2474. ==================
  2475. BotCheckSnapshot
  2476. ==================
  2477. */
  2478. void BotCheckSnapshot(bot_state_t *bs) {
  2479.     int ent;
  2480.     entityState_t state;
  2481.  
  2482.     //
  2483.     ent = 0;
  2484.     while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) {
  2485.         //check the entity state for events
  2486.         BotCheckEvents(bs, &state);
  2487.     }
  2488.     //check the player state for events
  2489.     BotAI_GetEntityState(bs->client, &state);
  2490.     //copy the player state events to the entity state
  2491.     state.event = bs->cur_ps.externalEvent;
  2492.     state.eventParm = bs->cur_ps.externalEventParm;
  2493.     //
  2494.     BotCheckEvents(bs, &state);
  2495. }
  2496.  
  2497. /*
  2498. ==================
  2499. BotCheckAir
  2500. ==================
  2501. */
  2502. void BotCheckAir(bot_state_t *bs) {
  2503.     if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) {
  2504.         if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) {
  2505.             return;
  2506.         }
  2507.     }
  2508.     bs->lastair_time = trap_AAS_Time();
  2509. }
  2510.  
  2511. /*
  2512. ==================
  2513. BotDeathmatchAI
  2514. ==================
  2515. */
  2516. void BotDeathmatchAI(bot_state_t *bs, float thinktime) {
  2517.     char gender[144], name[144], buf[144];
  2518.     char userinfo[MAX_INFO_STRING];
  2519.     int i;
  2520.  
  2521.     //if the bot has just been setup
  2522.     if (bs->setupcount > 0) {
  2523.         bs->setupcount--;
  2524.         if (bs->setupcount > 0) return;
  2525.         //get the gender characteristic
  2526.         trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender));
  2527.         //set the bot gender
  2528.         trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo));
  2529.         Info_SetValueForKey(userinfo, "sex", gender);
  2530.         trap_SetUserinfo(bs->client, userinfo);
  2531.         //set the team
  2532.         if ( g_gametype.integer != GT_TOURNAMENT ) {
  2533.             Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team);
  2534.             trap_EA_Command(bs->client, buf);
  2535.         }
  2536.         //set the chat gender
  2537.         if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE);
  2538.         else if (gender[0] == 'f')  trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE);
  2539.         else  trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS);
  2540.         //set the chat name
  2541.         ClientName(bs->client, name, sizeof(name));
  2542.         trap_BotSetChatName(bs->cs, name);
  2543.         //
  2544.         bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
  2545.         bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
  2546.         //
  2547.         bs->setupcount = 0;
  2548.     }
  2549.     //no ideal view set
  2550.     bs->flags &= ~BFL_IDEALVIEWSET;
  2551.     //set the teleport time
  2552.     BotSetTeleportTime(bs);
  2553.     //update some inventory values
  2554.     BotUpdateInventory(bs);
  2555.     //check the console messages
  2556.     BotCheckConsoleMessages(bs);
  2557.     //check out the snapshot
  2558.     BotCheckSnapshot(bs);
  2559.     //check for air
  2560.     BotCheckAir(bs);
  2561.     //if not in the intermission and not in observer mode
  2562.     if (!BotIntermission(bs) && !BotIsObserver(bs)) {
  2563.         //do team AI
  2564.         BotTeamAI(bs);
  2565.     }
  2566.     //if the bot has no ai node
  2567.     if (!bs->ainode) {
  2568.         AIEnter_Seek_LTG(bs);
  2569.     }
  2570.     //if the bot entered the game less than 8 seconds ago
  2571.     if (!bs->entergamechat && bs->entergame_time > trap_AAS_Time() - 8) {
  2572.         if (BotChat_EnterGame(bs)) {
  2573.             bs->stand_time = trap_AAS_Time() + BotChatTime(bs);
  2574.             AIEnter_Stand(bs);
  2575.         }
  2576.         bs->entergamechat = qtrue;
  2577.     }
  2578.     //reset the node switches from the previous frame
  2579.     BotResetNodeSwitches();
  2580.     //execute AI nodes
  2581.     for (i = 0; i < MAX_NODESWITCHES; i++) {
  2582.         if (bs->ainode(bs)) break;
  2583.     }
  2584.     //if the bot removed itself :)
  2585.     if (!bs->inuse) return;
  2586.     //if the bot executed too many AI nodes
  2587.     if (i >= MAX_NODESWITCHES) {
  2588.         trap_BotDumpGoalStack(bs->gs);
  2589.         trap_BotDumpAvoidGoals(bs->gs);
  2590.         BotDumpNodeSwitches(bs);
  2591.         ClientName(bs->client, name, sizeof(name));
  2592.         BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, trap_AAS_Time(), MAX_NODESWITCHES);
  2593.     }
  2594.     //
  2595.     bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
  2596.     bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
  2597. }
  2598.  
  2599. /*
  2600. ==================
  2601. BotSetupDeathmatchAI
  2602. ==================
  2603. */
  2604. void BotSetupDeathmatchAI(void) {
  2605.     int ent, modelnum;
  2606.     char model[128];
  2607.  
  2608.     gametype = trap_Cvar_VariableIntegerValue("g_gametype");
  2609.     maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
  2610.  
  2611.     trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0);
  2612.     trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0);
  2613.     trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0);
  2614.     trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0);
  2615.     trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0);
  2616.     trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0);
  2617.     //
  2618.     if (gametype == GT_CTF) {
  2619.         if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0)
  2620.             BotAI_Print(PRT_WARNING, "CTF without Red Flag\n");
  2621.         if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0)
  2622.             BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n");
  2623.     }
  2624.  
  2625.     max_bspmodelindex = 0;
  2626.     for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
  2627.         if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue;
  2628.         if (model[0] == '*') {
  2629.             modelnum = atoi(model+1);
  2630.             if (modelnum > max_bspmodelindex)
  2631.                 max_bspmodelindex = modelnum;
  2632.         }
  2633.     }
  2634.     //initialize the waypoint heap
  2635.     BotInitWaypoints();
  2636. }
  2637.  
  2638. /*
  2639. ==================
  2640. BotShutdownDeathmatchAI
  2641. ==================
  2642. */
  2643. void BotShutdownDeathmatchAI(void) {
  2644. }
  2645.  
  2646.