// botman's Half-Life bot example
//
// http://planethalflife.com/botman/
//
// bot.cpp
//

#include "extdll.h"
#include "util.h"
#include "client.h"
#include "cbase.h"
#include "player.h"
#include "items.h"
#include "effects.h"
#include "weapons.h"
#include "soundent.h"
#include "gamerules.h"
#include "animation.h"

#include "bot.h"

#include <sys/types.h>
#include <sys/stat.h>

extern cvar_t pausebots;
extern DLL_GLOBAL ULONG g_ulModelIndexPlayer;

// Set in combat.cpp.  Used to pass the damage inflictor for death messages.
extern entvars_t *g_pevLastInflictor;

extern DLL_GLOBAL BOOL g_fGameOver;
extern int gmsgHealth;
extern int gmsgCurWeapon;
extern int gmsgSetFOV;
extern int gmsgSayText;

#define MODE_CHAT 0

#define PLAYER_SEARCH_RADIUS (float)40

int f_Observer = 0;  // flag to indicate if player is in observer mode
int f_botskill = 3;  // default bot skill level
int f_botdontshoot = 0;

// array of bot respawn information
respawn_t bot_respawn[32] = {
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL},
   {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}};

#define MAX_SKINS 10

// indicate which models are currently used for random model allocation
BOOL skin_used[MAX_SKINS] = {
   FALSE, FALSE, FALSE, FALSE, FALSE,
   FALSE, FALSE, FALSE, FALSE, FALSE};

// store the names of the models...
const char *bot_skins[MAX_SKINS] = {
   "barney", "jamison", "gman", "gordon", "eva01",
   "hgrunt", "haruhi", "robo", "scientist", "vader"};

// store the player names for each of the models...
const char *bot_names[MAX_SKINS] = {
   "Barney", "Jamison_Trumpets", "G-Man", "Gordon", "Evangelion01",
   "H-Grunt", "Haruhi", "Robo", "Scientist", "DarthVader"};

// how often (out of 1000 times) the bot will pause, based on bot skill
float pause_frequency[5] = {4, 7, 10, 15, 20};

// how long the bot will delay when paused, based on bot skill
float pause_time[5][2] = {
   {0.2, 0.5}, {0.5, 1.0}, {0.7, 1.3}, {1.0, 1.7}, {1.2, 2.0}};

extern ammo_check_t ammo_check[];

// sounds for TakeDamage speaking effects...
const char *hgrunt_sounds[] = { HG_SND1, HG_SND2, HG_SND3, HG_SND4, HG_SND5 };
const char *barney_sounds[] = { BA_SND1, BA_SND2, BA_SND3, BA_SND4, BA_SND5 };
const char *scientist_sounds[] = { SC_SND1, SC_SND2, SC_SND3, SC_SND4, SC_SND5 };


LINK_ENTITY_TO_CLASS( bot, CBot );


inline edict_t *CREATE_FAKE_CLIENT( const char *netname )
{
   return (*g_engfuncs.pfnCreateFakeClient)( netname );
}

inline char *GET_INFOBUFFER( edict_t *e )
{
   return (*g_engfuncs.pfnGetInfoKeyBuffer)( e );
}

inline char *GET_INFO_KEY_VALUE( const char *infobuffer, const char *key )
{
   return (g_engfuncs.pfnInfoKeyValue( infobuffer, key ));
}

inline void SET_CLIENT_KEY_VALUE( int clientIndex, const char *infobuffer,
                                  const char *key, const char *value )
{
   (*g_engfuncs.pfnSetClientKeyValue)( clientIndex, infobuffer, key, value );
}


void BotDebug( char *buffer )
{
   // print out debug messages to the HUD of all players
   // this allows you to print messages from bots to your display
   // as you are playing the game.  Use STRING(pev->netname) in
   // buffer to see the name of the bot.

   UTIL_ClientPrintAll( HUD_PRINTNOTIFY, buffer );
}


void BotCreate(const char *skin, const char *name, const char *skill)
{
   edict_t *BotEnt;
   CBot *BotClass;
   int skill_level;
   char c_skill[2];
   char c_skin[BOT_SKIN_LEN+1];
   char c_name[BOT_NAME_LEN+1];
   char c_index[3];
   int i, j, length;
   int index;  // index into array of predefined names
   BOOL found = FALSE;

   if ((skin == NULL) || (*skin == 0))
   {
      // pick a random skin
      index = RANDOM_LONG(0, MAX_SKINS-1);  // there are 15 possible skins

      // check if this skin has already been used...
      while (skin_used[index] == TRUE)
      {
         index++;

         if (index == MAX_SKINS)
            index = 0;
      }

      skin_used[index] = TRUE;

      // check if all skins are used...
      for (i = 0; i < MAX_SKINS; i++)
      {
         if (skin_used[i] == FALSE)
            break;
      }

      // if all skins are used, reset used to FALSE for next selection
      if (i == MAX_SKINS)
      {
         for (i = 0; i < MAX_SKINS; i++)
            skin_used[i] = FALSE;
      }

      strcpy( c_skin, bot_skins[index] );
   }
   else
   {
      strncpy( c_skin, skin, BOT_SKIN_LEN);
      c_skin[BOT_SKIN_LEN] = 0;  // make sure c_skin is null terminated
   }

   for (i = 0; c_skin[i] != 0; i++)
      c_skin[i] = tolower( c_skin[i] );  // convert to all lowercase

   index = 0;

   while ((!found) && (index < MAX_SKINS))
   {
      if (strcmp(c_skin, bot_skins[index]) == 0)
         found = TRUE;
      else
         index++;
   }

   if (found == TRUE)
   {
      if ((name != NULL) && (*name != 0))
      {
         strncpy( c_name, name, 31 );
         c_name[31] = 0;  // make sure c_name is null terminated
      }
      else
      {
         strcpy( c_name, bot_names[index] );
      }
   }
   else
   {
      char dir_name[32];
      char filename[128];

      struct stat stat_str;

      GET_GAME_DIR(dir_name);

      sprintf(filename, "%s\\models\\player\\%s", dir_name, c_skin);

      if (stat(filename, &stat_str) != 0)
      {
         sprintf(filename, "valve\\models\\player\\%s", c_skin);
         if (stat(filename, &stat_str) != 0)
         {
            char err_msg[80];

            sprintf( err_msg, "model \"%s\" is unknown.\n", c_skin );
            UTIL_ClientPrintAll( HUD_PRINTNOTIFY, err_msg );
            if (IS_DEDICATED_SERVER())
               printf( "%s", err_msg );

            UTIL_ClientPrintAll( HUD_PRINTNOTIFY,
               "use barney, gina, gman, gordon, helmet, hgrunt,\n");
            if (IS_DEDICATED_SERVER())
               printf("use barney, gina, gman, gordon, helmet, hgrunt,\n");
            UTIL_ClientPrintAll( HUD_PRINTNOTIFY,
               "    recon, robo, scientist, or zombie\n");
            if (IS_DEDICATED_SERVER())
               printf("    recon, robo, scientist, or zombie\n");
            return;
         }
      }

      // copy the name of the model to the bot's name...
      strncpy( c_name, skin, BOT_SKIN_LEN);
      c_name[BOT_SKIN_LEN] = 0;  // make sure c_skin is null terminated
   }

   length = strlen(c_name);

   // remove any illegal characters from name...
   for (i = 0; i < length; i++)
   {
      if ((c_name[i] <= ' ') || (c_name[i] > '~') ||
          (c_name[i] == '"'))
      {
         for (j = i; j < length; j++)  // shuffle chars left (and null)
            c_name[j] = c_name[j+1];
         length--;
      }               
   }

   skill_level = 0;

   if ((skill != NULL) && (*skill != 0))
      sscanf( skill, "%d", &skill_level );
   else
      skill_level = f_botskill;

   if ((skill_level < 1) || (skill_level > 5))
      skill_level = f_botskill;

   sprintf( c_skill, "%d", skill_level );

   BotEnt = CREATE_FAKE_CLIENT( c_name );

   if (FNullEnt( BotEnt ))
   {
      UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "Max. Players reached.  Can't create bot!\n");

      if (IS_DEDICATED_SERVER())
         printf("Max. Players reached.  Can't create bot!\n");
   }
   else
   {
      char ptr[128];  // allocate space for message from ClientConnect
      char *infobuffer;
      int clientIndex;

      index = 0;
      while ((bot_respawn[index].is_used) && (index < 32))
         index++;

      if (index >= 32)
      {
         UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "Can't create bot!\n");
         return;
      }

      sprintf(c_index, "%d", index);

      bot_respawn[index].is_used = TRUE;  // this slot is used

      // don't store the name here, it might change if same as another
      strcpy(bot_respawn[index].skin, c_skin);
      strcpy(bot_respawn[index].skill, c_skill);

      sprintf(ptr, "Creating bot \"%s\" using model %s with skill=%d\n", c_name, c_skin, skill_level);
      UTIL_ClientPrintAll( HUD_PRINTNOTIFY, ptr);

      if (IS_DEDICATED_SERVER())
         printf("%s", ptr);

      BotClass = GetClassPtr( (CBot *) VARS(BotEnt) );
      infobuffer = GET_INFOBUFFER( BotClass->edict( ) );
      clientIndex = BotClass->entindex( );

      SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "model", c_skin );
      SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "skill", c_skill );
      SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "index", c_index );

      ClientConnect( BotClass->edict( ), c_name, "127.0.0.1", ptr );
      DispatchSpawn( BotClass->edict( ) );
   }
}


void CBot::Spawn( )
{
   char c_skill[2];
   char c_index[3];

   CBasePlayer::Spawn();

   pev->flags = FL_CLIENT | FL_FAKECLIENT;

   // set the respawn index value based on key from BotCreate
   strcpy(c_index, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "index") );
   sscanf(c_index, "%d", &respawn_index);

   bot_respawn[respawn_index].pBot = (CBasePlayer *)this;

   // get the bot's name and save it in respawn array...
   strcpy(bot_respawn[respawn_index].name, STRING(pev->netname));

   bot_respawn[respawn_index].state = BOT_IDLE;

   pev->ideal_yaw = pev->v_angle.y;
   pev->yaw_speed = BOT_YAW_SPEED;

   // bot starts out in "paused" state since it hasn't moved yet...
   bot_was_paused = TRUE;
   v_prev_origin = pev->origin;

   f_shoot_time = 0;

   // get bot's skill level (0=good, 4=bad)
   strcpy(c_skill, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "skill") );
   sscanf(c_skill, "%d", &bot_skill);
   bot_skill--;  // make 0 based for array index (now 0-4)

   f_max_speed = CVAR_GET_FLOAT("sv_maxspeed");

   f_speed_check_time = gpGlobals->time + 2.0f;

   ladder_dir = 0;

   // pick a wander direction (50% of the time to the left, 50% to the right)
   if (RANDOM_LONG(1, 100) <= 50)
      wander_dir = WANDER_LEFT;
   else
      wander_dir = WANDER_RIGHT;

   f_pause_time = 0;
   f_find_item = 0;

   if (g_pGameRules->IsTeamplay())  // is team play enabled?
   {
      strcpy(model_name, g_pGameRules->GetTeamID(this));
   }
   else
   {
      strcpy(model_name, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "model") );
   }

   bot_model = 0;
   if ((strcmp( model_name, "hgrunt" ) == 0) ||
       (strcmp( model_name, "recon" ) == 0))
   {
      bot_model = MODEL_HGRUNT;
   }
   else if (strcmp( model_name, "barney") == 0)
   {
      bot_model = MODEL_BARNEY;
   }
   else if (strcmp( model_name, "scientist") == 0)
   {
      bot_model = MODEL_SCIENTIST;
   }
   else if (strcmp( model_name, "vader") == 0)
   {
	  bot_model = MODEL_VADER;
   }

   f_pain_time = gpGlobals->time + 5.0f;

   b_use_health_station = FALSE;
   b_use_HEV_station = FALSE;
   b_use_button = FALSE;
   f_use_button_time = 0;
   b_lift_moving = FALSE;
   f_use_ladder_time = 0;
   f_fire_gauss = -1;  // -1 means not charging gauss gun

   b_see_tripmine = FALSE;
   b_shoot_tripmine = FALSE;

   f_weapon_inventory_time = 0;
   f_dont_avoid_wall_time = 0;
   f_bot_use_time = 0;
   f_wall_on_right = 0;
   f_wall_on_left = 0;

   pBotEnemy = NULL;
   pBotUser = NULL;
   pBotPickupItem = NULL;
}


int CBot::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker,
                      float flDamage, int bitsDamageType )
{
   CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker);
   char sound[40];
   int ret_damage;

   // do the damage first...
   ret_damage = CBasePlayer::TakeDamage( pevInflictor, pevAttacker, flDamage,
                                         bitsDamageType );

   // if the bot doesn't have an enemy and someone is shooting at it then
   // turn in the attacker's direction...
   if (pBotEnemy == NULL)
   {
      // face the attacker...
      Vector v_enemy = pAttacker->pev->origin - pev->origin;
      Vector bot_angles = UTIL_VecToAngles( v_enemy );

      pev->ideal_yaw = bot_angles.y;

      // check for wrap around of angle...
      if (pev->ideal_yaw > 180)
         pev->ideal_yaw -= 360;
      if (pev->ideal_yaw < -180)
         pev->ideal_yaw += 360;

      // stop using health or HEV stations...
      b_use_health_station = FALSE;
      b_use_HEV_station = FALSE;
   }

   // check if bot model is known, attacker is not a bot,
   // time for pain sound, and bot has some of health left...

   if ((bot_model != 0) && (pAttacker->IsNetClient()) &&
       (f_pain_time <= gpGlobals->time) && (pev->health > 0) &&
       ( !IS_DEDICATED_SERVER() ))
   {
      float distance = (pAttacker->pev->origin - pev->origin).Length( );

      // check if the distance to attacker is close enough (otherwise
      // the attacker is too far away to hear the pain sounds)

      if (distance <= 400)
      {
         // speak pain sounds about 50% of the time
         if (RANDOM_LONG(1, 100) <= 50)
         {
            f_pain_time = gpGlobals->time + 5.0f;

            if (bot_model == MODEL_HGRUNT)
               strcpy( sound, hgrunt_sounds[RANDOM_LONG(0,4)] );
            else if (bot_model == MODEL_BARNEY)
               strcpy( sound, barney_sounds[RANDOM_LONG(0,4)] );
            else if (bot_model == MODEL_SCIENTIST)
               strcpy( sound, scientist_sounds[RANDOM_LONG(0,4)] );

            EMIT_SOUND(ENT(pevAttacker), CHAN_VOICE, sound,
                       RANDOM_FLOAT(0.9, 1.0), ATTN_NORM);
         }
      }
   }

   return ret_damage;
}


void CBot::Use( CBaseEntity *pActivator, CBaseEntity *pCaller,
                USE_TYPE useType, float value )
{
   if (g_pGameRules->IsTeamplay())  // is team play enabled?
   {
      // check the bot and player are on the same team...

      if (UTIL_TeamsMatch(g_pGameRules->GetTeamID(this),
                          g_pGameRules->GetTeamID(pActivator)))
      {
         if (pBotEnemy == NULL)  // is bot NOT currently engaged in combat?
         {
            if (pBotUser == NULL)  // does bot NOT have a "user"
            {
               // tell teammate that bot will cover them...

               EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_SND,
                          1.0, ATTN_NORM);

               pBotUser = pActivator;
               f_bot_use_time = gpGlobals->time;
            }
            else
            {
               // tell teammate that you'll see them later..

               EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_LATER_SND,
                          1.0, ATTN_NORM);

               pBotUser = NULL;
               f_bot_use_time = 0;
            }
         }
         else
         {
            EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_ENEMY_SND, 1.0,
                       ATTN_NORM);
         }
      }
   }
}


int CBot::BotInFieldOfView(Vector dest)
{
   // find angles from source to destination...
   Vector entity_angles = UTIL_VecToAngles( dest );

   // make yaw angle 0 to 360 degrees if negative...
   if (entity_angles.y < 0)
      entity_angles.y += 360;

   // get bot's current view angle...
   float view_angle = pev->v_angle.y;

   // make view angle 0 to 360 degrees if negative...
   if (view_angle < 0)
      view_angle += 360;

   // return the absolute value of angle to destination entity
   // zero degrees means straight ahead,  45 degrees to the left or
   // 45 degrees to the right is the limit of the normal view angle

   return abs((int)view_angle - (int)entity_angles.y);
}


BOOL CBot::BotEntityIsVisible( Vector dest )
{
   TraceResult tr;

   // trace a line from bot's eyes to destination...
   UTIL_TraceLine( pev->origin + pev->view_ofs, dest, ignore_monsters,
                   ENT(pev), &tr );

   // check if line of sight to object is not blocked (i.e. visible)
   if (tr.flFraction >= 1.0f)
      return TRUE;
   else
      return FALSE;
}


float CBot::BotChangeYaw( float speed )
{
   float ideal;
   float current;
   float current_180;  // current +/- 180 degrees
   float diff;

   // turn from the current v_angle yaw to the ideal_yaw by selecting
   // the quickest way to turn to face that direction
   
   current = pev->v_angle.y;
   ideal = pev->ideal_yaw;

   // find the difference in the current and ideal angle
   diff = fabs(current - ideal);

   // check if the bot is already facing the ideal_yaw direction...
   if (diff <= 1)
      return diff;  // return number of degrees turned

   // check if difference is less than the max degrees per turn
   if (diff < speed)
      speed = diff;  // just need to turn a little bit (less than max)

   // here we have four cases, both angle positive, one positive and
   // the other negative, one negative and the other positive, or
   // both negative.  handle each case separately...

   if ((current >= 0) && (ideal >= 0))  // both positive
   {
      if (current > ideal)
         current -= speed;
      else
         current += speed;
   }
   else if ((current >= 0) && (ideal < 0))
   {
      current_180 = current - 180;

      if (current_180 > ideal)
         current += speed;
      else
         current -= speed;
   }
   else if ((current < 0) && (ideal >= 0))
   {
      current_180 = current + 180;
      if (current_180 > ideal)
         current += speed;
      else
         current -= speed;
   }
   else  // (current < 0) && (ideal < 0)  both negative
   {
      if (current > ideal)
         current -= speed;
      else
         current += speed;
   }

   // check for wrap around of angle...
   if (current > 180)
      current -= 360;
   if (current < -180)
      current += 360;

   pev->v_angle.y = current;

   pev->angles.x = 0;
   pev->angles.y = pev->v_angle.y;
   pev->angles.z = 0;

   return speed;  // return number of degrees turned
}


void CBot::BotOnLadder( float moved_distance )
{
   // moves the bot up or down a ladder.  if the bot can't move
   // (i.e. get's stuck with someone else on ladder), the bot will
   // change directions and go the other way on the ladder.

   if (ladder_dir == LADDER_UP)  // is the bot currently going up?
   {
      pev->v_angle.x = -60;  // look upwards

      // check if the bot hasn't moved much since the last location...
      if (moved_distance <= 1)
      {
         // the bot must be stuck, change directions...

         pev->v_angle.x = 60;  // look downwards
         ladder_dir = LADDER_DOWN;
      }
   }
   else if (ladder_dir == LADDER_DOWN)  // is the bot currently going down?
   {
      pev->v_angle.x = 60;  // look downwards

      // check if the bot hasn't moved much since the last location...
      if (moved_distance <= 1)
      {
         // the bot must be stuck, change directions...

         pev->v_angle.x = -60;  // look upwards
         ladder_dir = LADDER_UP;
      }
   }
   else  // the bot hasn't picked a direction yet, try going up...
   {
      pev->v_angle.x = -60;  // look upwards
      ladder_dir = LADDER_UP;
   }

   // move forward (i.e. in the direction the bot is looking, up or down)
   pev->button |= IN_FORWARD;
}


void CBot::BotUnderWater( void )
{
   // handle movements under water.  right now, just try to keep from
   // drowning by swimming up towards the surface and look to see if
   // there is a surface the bot can jump up onto to get out of the
   // water.  bots DON'T like water!

   Vector v_src, v_forward;
   TraceResult tr;
   int contents;

   // swim up towards the surface
   pev->v_angle.x = -60;  // look upwards

   // move forward (i.e. in the direction the bot is looking, up or down)
   pev->button |= IN_FORWARD;

   // set gpGlobals angles based on current view angle (for TraceLine)
   UTIL_MakeVectors( pev->v_angle );

   // look from eye position straight forward (remember: the bot is looking
   // upwards at a 60 degree angle so TraceLine will go out and up...

   v_src = pev->origin + pev->view_ofs;  // EyePosition()
   v_forward = v_src + gpGlobals->v_forward * 90;

   // trace from the bot's eyes straight forward...
   UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), &tr);

   // check if the trace didn't hit anything (i.e. nothing in the way)...
   if (tr.flFraction >= 1.0f)
   {
      // find out what the contents is of the end of the trace...
      contents = UTIL_PointContents( tr.vecEndPos );

      // check if the trace endpoint is in open space...
      if (contents == CONTENTS_EMPTY)
      {
         // ok so far, we are at the surface of the water, continue...

         v_src = tr.vecEndPos;
         v_forward = v_src;
         v_forward.z -= 90;

         // trace from the previous end point straight down...
         UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters,
                         ENT(pev), &tr);

         // check if the trace hit something...
         if (tr.flFraction < 1.0f)
         {
            contents = UTIL_PointContents( tr.vecEndPos );

            // if contents isn't water then assume it's land, jump!
            if (contents != CONTENTS_WATER)
            {
               pev->button |= IN_JUMP;
            }
         }
      }
   }
}


void CBot::BotFindItem( void )
{
   CBaseEntity *pEntity = NULL;
   CBaseEntity *pPickupEntity = NULL;
   Vector pickup_origin;
   Vector entity_origin;
   float radius = 500;
   BOOL can_pickup;
   float min_distance;
   char item_name[40];
   TraceResult tr;
   Vector vecStart;
   Vector vecEnd;
   int angle_to_entity;

   pBotPickupItem = NULL;

   min_distance = radius + 1.0f;

   while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, radius )) != NULL)
   {
      can_pickup = FALSE;  // assume can't use it until known otherwise

      strcpy(item_name, STRING(pEntity->pev->classname));

      // see if this is a "func_" type of entity (func_button, etc.)...
      if (strncmp("func_", item_name, 5) == 0)
      {
         // BModels have 0,0,0 for origin so must use VecBModelOrigin...
         entity_origin = VecBModelOrigin(pEntity->pev);

         vecStart = pev->origin + pev->view_ofs;
         vecEnd = entity_origin;

         angle_to_entity = BotInFieldOfView( vecEnd - vecStart );

         // check if entity is outside field of view (+/- 45 degrees)
         if (angle_to_entity > 45)
            continue;  // skip this item if bot can't "see" it

         // check if entity is a ladder (ladders are a special case)...
         if (strcmp("func_ladder", item_name) == 0)
         {
            // force ladder origin to same z coordinate as bot since
            // the VecBModelOrigin is the center of the ladder.  For
            // LONG ladders, the center MAY be hundreds of units above
            // the bot.  Fake an origin at the same level as the bot...

            entity_origin.z = pev->origin.z;
            vecEnd = entity_origin;

            // trace a line from bot's eyes to func_ladder entity...
            UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, ENT(pev), &tr);

            // check if traced all the way up to the entity (didn't hit wall)
            if (tr.flFraction >= 1.0f)
            {
               // find distance to item for later use...
               float distance = (vecEnd - vecStart).Length( );

               // use the ladder about 100% of the time, if haven't
               // used a ladder in at least 5 seconds...
               if ((RANDOM_LONG(1, 100) <= 100) &&
                   ((f_use_ladder_time + 5) < gpGlobals->time))
               {
                  // if close to ladder...
                  if (distance < 100)
                  {
                     // don't avoid walls for a while
                     f_dont_avoid_wall_time = gpGlobals->time + 5.0f;
                  }

                  can_pickup = TRUE;
               }
            }
         }
         else
         {
            // trace a line from bot's eyes to func_ entity...
            UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, ENT(pev), &tr);

            // check if traced all the way up to the entity (didn't hit wall)
            if (strcmp(item_name, STRING(tr.pHit->v.classname)) == 0)
            {
               // find distance to item for later use...
               float distance = (vecEnd - vecStart).Length( );

               // check if entity is wall mounted health charger...
               if (strcmp("func_healthcharger", item_name) == 0)
               {
                  // check if the bot can use this item and
                  // check if the recharger is ready to use (has power left)...
                  if ((pev->health < 100) && (pEntity->pev->frame == 0))
                  {
                     // check if flag not set...
                     if (!b_use_health_station)
                     {
                        // check if close enough and facing it directly...
                        if ((distance < PLAYER_SEARCH_RADIUS) &&
                            (angle_to_entity <= 10))
                        {
                           b_use_health_station = TRUE;
                           f_use_health_time = gpGlobals->time;
                        }

                        // if close to health station...
                        if (distance < 100)
                        {
                           // don't avoid walls for a while
                           f_dont_avoid_wall_time = gpGlobals->time + 5.0f;
                        }

                        can_pickup = TRUE;
                     }
                  }
                  else
                  {
                     // don't need or can't use this item...
                     b_use_health_station = FALSE;
                  }
               }

               // check if entity is wall mounted HEV charger...
               else if (strcmp("func_recharge", item_name) == 0)
               {
                  // check if the bot can use this item and
                  // check if the recharger is ready to use (has power left)...
                  if ((pev->armorvalue < MAX_NORMAL_BATTERY) &&
                      (pev->weapons & (1<<WEAPON_SUIT)) &&
                      (pEntity->pev->frame == 0))
                  {
                     // check if flag not set and facing it...
                     if (!b_use_HEV_station)
                     {
                        // check if close enough and facing it directly...
                        if ((distance < PLAYER_SEARCH_RADIUS) &&
                            (angle_to_entity <= 10))
                        {
                           b_use_HEV_station = TRUE;
                           f_use_HEV_time = gpGlobals->time;
                        }

                        // if close to HEV recharger...
                        if (distance < 100)
                        {
                           // don't avoid walls for a while
                           f_dont_avoid_wall_time = gpGlobals->time + 5.0f;
                        }

                        can_pickup = TRUE;
                     }
                  }
                  else
                  {
                     // don't need or can't use this item...
                     b_use_HEV_station = FALSE;
                  }
               }

               // check if entity is a button...
               else if (strcmp("func_button", item_name) == 0)
               {
                  // use the button about 100% of the time, if haven't
                  // used a button in at least 5 seconds...
                  if ((RANDOM_LONG(1, 100) <= 100) &&
                      ((f_use_button_time + 5) < gpGlobals->time))
                  {
                     // check if flag not set and facing it...
                     if (!b_use_button)
                     {
                        // check if close enough and facing it directly...
                        if ((distance < PLAYER_SEARCH_RADIUS) &&
                            (angle_to_entity <= 10))
                        {
                           b_use_button = TRUE;
                           b_lift_moving = FALSE;
                           f_use_button_time = gpGlobals->time;
                        }

                        // if close to button...
                        if (distance < 100)
                        {
                           // don't avoid walls for a while
                           f_dont_avoid_wall_time = gpGlobals->time + 5.0f;
                        }

                        can_pickup = TRUE;
                     }
                  }
                  else
                  {
                     // don't need or can't use this item...
                     b_use_button = FALSE;
                  }
               }
            }
         }
      }

      // check if entity is an armed tripmine beam
      else if (strcmp("beam", item_name) == 0)
      {
         CBeam *pBeam = (CBeam *)pEntity;

//         Vector v_beam_start = pBeam->GetStartPos();
//         Vector v_beam_end = pBeam->GetEndPos();
//
//         if (FInViewCone( &v_beam_start ) && FVisible( v_beam_start ))
//         {
//            BotDebug("I see a beam start!\n");
//         }
//
//         if (FInViewCone( &v_beam_end ) && FVisible( v_beam_end ))
//         {
//            BotDebug("I see a beam end!\n");
//         }
      }

      else  // everything else...
      {
         entity_origin = pEntity->pev->origin;

         vecStart = pev->origin + pev->view_ofs;
         vecEnd = entity_origin;

         // find angles from bot origin to entity...
         angle_to_entity = BotInFieldOfView( vecEnd - vecStart );

         // check if entity is outside field of view (+/- 45 degrees)
         if (angle_to_entity > 45)
            continue;  // skip this item if bot can't "see" it

         // check if line of sight to object is not blocked (i.e. visible)
         if (BotEntityIsVisible( vecEnd ))
         {

            // check if entity is a weapon...
            if (strncmp("weapon_", item_name, 7) == 0)
            {
               CBasePlayerWeapon *pWeapon = (CBasePlayerWeapon *)pEntity;

               if ((pWeapon->m_pPlayer) || (pWeapon->pev->effects & EF_NODRAW))
               {
                  // someone owns this weapon or it hasn't respawned yet
                  continue;
               }

               if (g_pGameRules->CanHavePlayerItem( this, pWeapon ))
               {
                  can_pickup = TRUE;
               }
            }

            // check if entity is ammo...
            else if (strncmp("ammo_", item_name, 5) == 0)
            {
               CBasePlayerAmmo *pAmmo = (CBasePlayerAmmo *)pEntity;
               BOOL ammo_found = FALSE;
               int i;

               // check if the item is not visible (i.e. has not respawned)
               if (pAmmo->pev->effects & EF_NODRAW)
                  continue;

               i = 0;
               while (ammo_check[i].ammo_name[0])
               {
                  if (strcmp(ammo_check[i].ammo_name, item_name) == 0)
                  {
                     ammo_found = TRUE;

                     // see if the bot can pick up this item...
                     if (g_pGameRules->CanHaveAmmo( this,
                         ammo_check[i].weapon_name, ammo_check[i].max_carry))
                     {
                        can_pickup = TRUE;
                        break;
                     }
                  }

                  i++;
               }
               if (ammo_found == FALSE)
               {
                  sprintf(message, "unknown ammo: %s\n", item_name);
                  BotDebug(message);
               }
            }

            // check if entity is a battery...
            else if (strcmp("item_battery", item_name) == 0)
            {
               CItem *pBattery = (CItem *)pEntity;

               // check if the item is not visible (i.e. has not respawned)
               if (pBattery->pev->effects & EF_NODRAW)
                  continue;

               // check if the bot can use this item...
               if ((pev->armorvalue < MAX_NORMAL_BATTERY) &&
                   (pev->weapons & (1<<WEAPON_SUIT)))
               {
                  can_pickup = TRUE;
               }
            }

            // check if entity is a healthkit...
            else if (strcmp("item_healthkit", item_name) == 0)
            {
               CItem *pHealthKit = (CItem *)pEntity;

               // check if the item is not visible (i.e. has not respawned)
               if (pHealthKit->pev->effects & EF_NODRAW)
                  continue;

               // check if the bot can use this item...
               if (pev->health < 100)
               {
                  can_pickup = TRUE;
               }
            }

            // check if entity is a packed up weapons box...
            else if (strcmp("weaponbox", item_name) == 0)
            {
               can_pickup = TRUE;
            }

            // check if entity is the spot from RPG laser
            else if (strcmp("laser_spot", item_name) == 0)
            {
            }

            // check if entity is an armed tripmine
            else if (strcmp("monster_tripmine", item_name) == 0)
            {
               float distance = (pEntity->pev->origin - pev->origin).Length( );

               if (b_see_tripmine)
               {
                  // see if this tripmine is closer to bot...
                  if (distance < (v_tripmine_origin - pev->origin).Length())
                  {
                     v_tripmine_origin = pEntity->pev->origin;
                     b_shoot_tripmine = FALSE;

                     // see if bot is far enough to shoot the tripmine...
                     if (distance >= 375)
                     {
                        b_shoot_tripmine = TRUE;
                     }
                  }
               }
               else
               {
                  b_see_tripmine = TRUE;
                  v_tripmine_origin = pEntity->pev->origin;
                  b_shoot_tripmine = FALSE;

                  // see if bot is far enough to shoot the tripmine...
                  if (distance >= 375)  // 375 is damage radius
                  {
                     b_shoot_tripmine = TRUE;
                  }
               }
            }

            // check if entity is an armed satchel charge
            else if (strcmp("monster_satchel", item_name) == 0)
            {
            }

            // check if entity is a snark (squeak grenade)
            else if (strcmp("monster_snark", item_name) == 0)
            {
            }

         }  // end if object is visible
      }  // end else not "func_" entity

      if (can_pickup) // if the bot found something it can pickup...
      {
         float distance = (entity_origin - pev->origin).Length( );

         // see if it's the closest item so far...
         if (distance < min_distance)
         {
            min_distance = distance;        // update the minimum distance
            pPickupEntity = pEntity;        // remember this entity
            pickup_origin = entity_origin;  // remember location of entity
         }
      }
   }  // end while loop

   if (pPickupEntity != NULL)
   {
      // let's head off toward that item...
      Vector v_item = pickup_origin - pev->origin;

      Vector bot_angles = UTIL_VecToAngles( v_item );

      pev->ideal_yaw = bot_angles.y;

      // check for wrap around of angle...
      if (pev->ideal_yaw > 180)
         pev->ideal_yaw -= 360;
      if (pev->ideal_yaw < -180)
         pev->ideal_yaw += 360;

      pBotPickupItem = pPickupEntity;  // save the item bot is trying to get
   }
}


void CBot::BotUseLift( float moved_distance )
{
   // just need to press the button once, when the flag gets set...
   if (f_use_button_time == gpGlobals->time)
   {
      pev->button = IN_USE;

      // face opposite from the button
      pev->ideal_yaw = -pev->ideal_yaw;  // rotate 180 degrees

      // check for wrap around of angle...
      if (pev->ideal_yaw > 180)
         pev->ideal_yaw -= 360;
      if (pev->ideal_yaw < -180)
         pev->ideal_yaw += 360;
   }

   // check if the bot has waited too long for the lift to move...
   if (((f_use_button_time + 2.0f) < gpGlobals->time) &&
       (!b_lift_moving))
   {
      // clear use button flag
      b_use_button = FALSE;

      // bot doesn't have to set f_find_item since the bot
      // should already be facing away from the button

      f_move_speed = f_max_speed;
   }

   // check if lift has started moving...
   if ((moved_distance > 1) && (!b_lift_moving))
   {
      b_lift_moving = TRUE;
   }

   // check if lift has stopped moving...
   if ((moved_distance <= 1) && (b_lift_moving))
   {
      TraceResult tr1, tr2;
      Vector v_src, v_forward, v_right, v_left;
      Vector v_down, v_forward_down, v_right_down, v_left_down;

      b_use_button = FALSE;

      // TraceLines in 4 directions to find which way to go...

      UTIL_MakeVectors( pev->v_angle );

      v_src = pev->origin + pev->view_ofs;
      v_forward = v_src + gpGlobals->v_forward * 90;
      v_right = v_src + gpGlobals->v_right * 90;
      v_left = v_src + gpGlobals->v_right * -90;

      v_down = pev->v_angle;
      v_down.x = v_down.x + 45;  // look down at 45 degree angle

      UTIL_MakeVectors( v_down );

      v_forward_down = v_src + gpGlobals->v_forward * 100;
      v_right_down = v_src + gpGlobals->v_right * 100;
      v_left_down = v_src + gpGlobals->v_right * -100;

      // try tracing forward first...
      UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), &tr1);
      UTIL_TraceLine( v_src, v_forward_down, dont_ignore_monsters, ENT(pev), &tr2);

      // check if we hit a wall or didn't find a floor...
      if ((tr1.flFraction < 1.0f) || (tr2.flFraction >= 1.0f))
      {
         // try tracing to the RIGHT side next...
         UTIL_TraceLine( v_src, v_right, dont_ignore_monsters, ENT(pev), &tr1);
         UTIL_TraceLine( v_src, v_right_down, dont_ignore_monsters, ENT(pev), &tr2);

         // check if we hit a wall or didn't find a floor...
         if ((tr1.flFraction < 1.0f) || (tr2.flFraction >= 1.0f))
         {
            // try tracing to the LEFT side next...
            UTIL_TraceLine( v_src, v_left, dont_ignore_monsters, ENT(pev), &tr1);
            UTIL_TraceLine( v_src, v_left_down, dont_ignore_monsters, ENT(pev), &tr2);

            // check if we hit a wall or didn't find a floor...
            if ((tr1.flFraction < 1.0f) || (tr2.flFraction >= 1.0f))
            {
               // only thing to do is turn around...
               pev->ideal_yaw += 180;  // turn all the way around
            }
            else
            {
               pev->ideal_yaw += 90;  // turn to the LEFT
            }
         }
         else
         {
            pev->ideal_yaw -= 90;  // turn to the RIGHT
         }

         // check for wrap around of angle...
         if (pev->ideal_yaw > 180)
            pev->ideal_yaw -= 360;
         if (pev->ideal_yaw < -180)
            pev->ideal_yaw += 360;
      }

      BotChangeYaw( pev->yaw_speed );

      f_move_speed = f_max_speed;
   }
}


void CBot::BotTurnAtWall( TraceResult *tr )
{
   Vector Normal;
   float Y, Y1, Y2, D1, D2, Z;

   // Find the normal vector from the trace result.  The normal vector will
   // be a vector that is perpendicular to the surface from the TraceResult.

   Normal = UTIL_VecToAngles(tr->vecPlaneNormal);

   // Since the bot keeps it's view angle in -180 < x < 180 degrees format,
   // and since TraceResults are 0 < x < 360, we convert the bot's view
   // angle (yaw) to the same format at TraceResult.

   Y = pev->v_angle.y;
   Y = Y + 180;
   if (Y > 359) Y -= 360;

   // Turn the normal vector around 180 degrees (i.e. make it point towards
   // the wall not away from it.  That makes finding the angles that the
   // bot needs to turn a little easier.

   Normal.y = Normal.y - 180;
   if (Normal.y < 0)
   Normal.y += 360;

   // Here we compare the bots view angle (Y) to the Normal - 90 degrees (Y1)
   // and the Normal + 90 degrees (Y2).  These two angles (Y1 & Y2) represent
   // angles that are parallel to the wall surface, but heading in opposite
   // directions.  We want the bot to choose the one that will require the
   // least amount of turning (saves time) and have the bot head off in that
   // direction.

   Y1 = Normal.y - 90;
   if (RANDOM_LONG(1, 100) <= 50)
   {
      Y1 = Y1 - RANDOM_FLOAT(5.0, 20.0);
   }
   if (Y1 < 0) Y1 += 360;

   Y2 = Normal.y + 90;
   if (RANDOM_LONG(1, 100) <= 50)
   {
      Y2 = Y2 + RANDOM_FLOAT(5.0, 20.0);
   }
   if (Y2 > 359) Y2 -= 360;

   // D1 and D2 are the difference (in degrees) between the bot's current
   // angle and Y1 or Y2 (respectively).

   D1 = fabs(Y - Y1);
   if (D1 > 179) D1 = fabs(D1 - 360);
   D2 = fabs(Y - Y2);
   if (D2 > 179) D2 = fabs(D2 - 360);

   // If difference 1 (D1) is more than difference 2 (D2) then the bot will
   // have to turn LESS if it heads in direction Y1 otherwise, head in
   // direction Y2.  I know this seems backwards, but try some sample angles
   // out on some graph paper and go through these equations using a
   // calculator, you'll see what I mean.

   if (D1 > D2)
      Z = Y1;
   else
      Z = Y2;

   // convert from TraceResult 0 to 360 degree format back to bot's
   // -180 to 180 degree format.

   if (Z > 180)
      Z -= 360;

   // set the direction to head off into...
   pev->ideal_yaw = Z;

   // check for wrap around of angle...
   if (pev->ideal_yaw > 180)
      pev->ideal_yaw -= 360;
   if (pev->ideal_yaw < -180)
      pev->ideal_yaw += 360;

   f_move_speed = 0;  // don't move while turning
}


BOOL CBot::BotCantMoveForward( TraceResult *tr )
{
   // use some TraceLines to determine if anything is blocking the current
   // path of the bot.

   Vector v_src, v_forward;

   UTIL_MakeVectors( pev->v_angle );

   // first do a trace from the bot's eyes forward...

   v_src = pev->origin + pev->view_ofs;  // EyePosition()
   v_forward = v_src + gpGlobals->v_forward * 40;

   // trace from the bot's eyes straight forward...
   UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), tr);

   // check if the trace hit something...
   if (tr->flFraction < 1.0f)
   {
      return TRUE;  // bot's head will hit something
   }

   // bot's head is clear, check at waist level...

   v_src = pev->origin;
   v_forward = v_src + gpGlobals->v_forward * 40;

   // trace from the bot's waist straight forward...
   UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), tr);

   // check if the trace hit something...
   if (tr->flFraction < 1.0f)
   {
      return TRUE;  // bot's body will hit something
   }

   return FALSE;  // bot can move forward, return false
}


BOOL CBot::BotCanJumpUp( void )
{
   // What I do here is trace 3 lines straight out, one unit higher than
   // the highest normal jumping distance.  I trace once at the center of
   // the body, once at the right side, and once at the left side.  If all
   // three of these TraceLines don't hit an obstruction then I know the
   // area to jump to is clear.  I then need to trace from head level,
   // above where the bot will jump to, downward to see if there is anything
   // blocking the jump.  There could be a narrow opening that the body
   // will not fit into.  These horizontal and vertical TraceLines seem
   // to catch most of the problems with falsely trying to jump on something
   // that the bot can not get onto.

   TraceResult tr;
   Vector v_jump, v_source, v_dest;

   // convert current view angle to vectors for TraceLine math...

   v_jump = pev->v_angle;
   v_jump.x = 0;  // reset pitch to 0 (level horizontally)
   v_jump.z = 0;  // reset roll to 0 (straight up and down)

   UTIL_MakeVectors( v_jump );

   // use center of the body first...

   // maximum jump height is 45, so check one unit above that (46)
   v_source = pev->origin + Vector(0, 0, -36 + 46);
   v_dest = v_source + gpGlobals->v_forward * 24;

   // trace a line forward at maximum jump height...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace hit something, return FALSE
   if (tr.flFraction < 1.0f)
      return FALSE;

   // now check same height to one side of the bot...
   v_source = pev->origin + gpGlobals->v_right * 16 + Vector(0, 0, -36 + 46);
   v_dest = v_source + gpGlobals->v_forward * 24;

   // trace a line forward at maximum jump height...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace hit something, return FALSE
   if (tr.flFraction < 1.0f)
      return FALSE;

   // now check same height on the other side of the bot...
   v_source = pev->origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 46);
   v_dest = v_source + gpGlobals->v_forward * 24;

   // trace a line forward at maximum jump height...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace hit something, return FALSE
   if (tr.flFraction < 1.0f)
      return FALSE;

   // now trace from head level downward to check for obstructions...

   // start of trace is 24 units in front of bot, 72 units above head...
   v_source = pev->origin + gpGlobals->v_forward * 24;

   // offset 72 units from top of head (72 + 36)
   v_source.z = v_source.z + 108;

   // end point of trace is 99 units straight down from start...
   // (99 is 108 minus the jump limit height which is 45 - 36 = 9)
   v_dest = v_source + Vector(0, 0, -99);

   // trace a line straight down toward the ground...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace hit something, return FALSE
   if (tr.flFraction < 1.0f)
      return FALSE;

   // now check same height to one side of the bot...
   v_source = pev->origin + gpGlobals->v_right * 16 + gpGlobals->v_forward * 24;
   v_source.z = v_source.z + 108;
   v_dest = v_source + Vector(0, 0, -99);

   // trace a line straight down toward the ground...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace hit something, return FALSE
   if (tr.flFraction < 1.0f)
      return FALSE;

   // now check same height on the other side of the bot...
   v_source = pev->origin + gpGlobals->v_right * -16 + gpGlobals->v_forward * 24;
   v_source.z = v_source.z + 108;
   v_dest = v_source + Vector(0, 0, -99);

   // trace a line straight down toward the ground...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace hit something, return FALSE
   if (tr.flFraction < 1.0f)
      return FALSE;

   return TRUE;
}


BOOL CBot::BotCanDuckUnder( void )
{
   // What I do here is trace 3 lines straight out, one unit higher than
   // the ducking height.  I trace once at the center of the body, once
   // at the right side, and once at the left side.  If all three of these
   // TraceLines don't hit an obstruction then I know the area to duck to
   // is clear.  I then need to trace from the ground up, 72 units, to make
   // sure that there is something blocking the TraceLine.  Then we know
   // we can duck under it.

   TraceResult tr;
   Vector v_duck, v_source, v_dest;

   // convert current view angle to vectors for TraceLine math...

   v_duck = pev->v_angle;
   v_duck.x = 0;  // reset pitch to 0 (level horizontally)
   v_duck.z = 0;  // reset roll to 0 (straight up and down)

   UTIL_MakeVectors( v_duck );

   // use center of the body first...

   // duck height is 36, so check one unit above that (37)
   v_source = pev->origin + Vector(0, 0, -36 + 37);
   v_dest = v_source + gpGlobals->v_forward * 24;

   // trace a line forward at duck height...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace hit something, return FALSE
   if (tr.flFraction < 1.0f)
      return FALSE;

   // now check same height to one side of the bot...
   v_source = pev->origin + gpGlobals->v_right * 16 + Vector(0, 0, -36 + 37);
   v_dest = v_source + gpGlobals->v_forward * 24;

   // trace a line forward at duck height...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace hit something, return FALSE
   if (tr.flFraction < 1.0f)
      return FALSE;

   // now check same height on the other side of the bot...
   v_source = pev->origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 37);
   v_dest = v_source + gpGlobals->v_forward * 24;

   // trace a line forward at duck height...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace hit something, return FALSE
   if (tr.flFraction < 1.0f)
      return FALSE;

   // now trace from the ground up to check for object to duck under...

   // start of trace is 24 units in front of bot near ground...
   v_source = pev->origin + gpGlobals->v_forward * 24;
   v_source.z = v_source.z - 35;  // offset to feet + 1 unit up

   // end point of trace is 72 units straight up from start...
   v_dest = v_source + Vector(0, 0, 72);

   // trace a line straight up in the air...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace didn't hit something, return FALSE
   if (tr.flFraction >= 1.0f)
      return FALSE;

   // now check same height to one side of the bot...
   v_source = pev->origin + gpGlobals->v_right * 16 + gpGlobals->v_forward * 24;
   v_source.z = v_source.z - 35;  // offset to feet + 1 unit up
   v_dest = v_source + Vector(0, 0, 72);

   // trace a line straight up in the air...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace didn't hit something, return FALSE
   if (tr.flFraction >= 1.0f)
      return FALSE;

   // now check same height on the other side of the bot...
   v_source = pev->origin + gpGlobals->v_right * -16 + gpGlobals->v_forward * 24;
   v_source.z = v_source.z - 35;  // offset to feet + 1 unit up
   v_dest = v_source + Vector(0, 0, 72);

   // trace a line straight up in the air...
   UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr);

   // if trace didn't hit something, return FALSE
   if (tr.flFraction >= 1.0f)
      return FALSE;

   return TRUE;
}


BOOL CBot::BotShootTripmine( void )
{
   if (b_shoot_tripmine != TRUE)
      return FALSE;

   // aim at the tripmine and fire the glock...

   Vector v_enemy = v_tripmine_origin - GetGunPosition( );

   pev->v_angle = UTIL_VecToAngles( v_enemy );

   pev->angles.x = 0;
   pev->angles.y = pev->v_angle.y;
   pev->angles.z = 0;

   pev->ideal_yaw = pev->v_angle.y;

   // check for wrap around of angle...
   if (pev->ideal_yaw > 180)
      pev->ideal_yaw -= 360;
   if (pev->ideal_yaw < -180)
      pev->ideal_yaw += 360;

   pev->v_angle.x = -pev->v_angle.x;  //adjust pitch to point gun

   return (BotFireWeapon( v_tripmine_origin, WEAPON_GLOCK, TRUE ));
}


BOOL CBot::BotFollowUser( void )
{
   BOOL user_visible;
   float f_distance;

   Vector vecEnd = pBotUser->pev->origin + pBotUser->pev->view_ofs;

   pev->v_angle.x = 0;  // reset pitch to 0 (level horizontally)
   pev->v_angle.z = 0;  // reset roll to 0 (straight up and down)

   pev->angles.x = 0;
   pev->angles.y = pev->v_angle.y;
   pev->angles.z = 0;

   if (!pBotUser->IsAlive( ))
   {
      // the bot's user is dead!
      pBotUser = NULL;
      return FALSE;
   }

   user_visible = FInViewCone( &vecEnd ) && FVisible( vecEnd );

   // check if the "user" is still visible or if the user has been visible
   // in the last 5 seconds (or the player just starting "using" the bot)

   if (user_visible || (f_bot_use_time + 5 > gpGlobals->time))
   {
      if (user_visible)
         f_bot_use_time = gpGlobals->time;  // reset "last visible time"

      // face the user
      Vector v_user = pBotUser->pev->origin - pev->origin;
      Vector bot_angles = UTIL_VecToAngles( v_user );

      pev->ideal_yaw = bot_angles.y;

      // check for wrap around of angle...
      if (pev->ideal_yaw > 180)
         pev->ideal_yaw -= 360;
      if (pev->ideal_yaw < -180)
         pev->ideal_yaw += 360;

      f_distance = v_user.Length( );  // how far away is the "user"?

      if (f_distance > 200)      // run if distance to enemy is far
         f_move_speed = f_max_speed;
      else if (f_distance > 50)  // walk if distance is closer
         f_move_speed = f_max_speed / 2;
      else                     // don't move if close enough
         f_move_speed = 0.0;

      return TRUE;
   }
   else
   {
      // person to follow has gone out of sight...
      pBotUser = NULL;

      return FALSE;
   }
}


BOOL CBot::BotCheckWallOnLeft( void )
{
   Vector v_src, v_left;
   TraceResult tr;

   UTIL_MakeVectors( pev->v_angle );

   // do a trace to the left...

   v_src = pev->origin;
   v_left = v_src + gpGlobals->v_right * -40;  // 40 units to the left

   UTIL_TraceLine( v_src, v_left, dont_ignore_monsters, ENT(pev), &tr);

   // check if the trace hit something...
   if (tr.flFraction < 1.0f)
   {
      if (f_wall_on_left == 0)
         f_wall_on_left = gpGlobals->time;

      return TRUE;
   }

   return FALSE;
}


BOOL CBot::BotCheckWallOnRight( void )
{
   Vector v_src, v_right;
   TraceResult tr;

   UTIL_MakeVectors( pev->v_angle );

   // do a trace to the right...

   v_src = pev->origin;
   v_right = v_src + gpGlobals->v_right * 40;  // 40 units to the right

   UTIL_TraceLine( v_src, v_right, dont_ignore_monsters, ENT(pev), &tr);

   // check if the trace hit something...
   if (tr.flFraction < 1.0f)
   {
      if (f_wall_on_right == 0)
         f_wall_on_right = gpGlobals->time;

      return TRUE;
   }

   return FALSE;
}


void CBot::BotThink( void )
{
   Vector v_diff;             // vector from previous to current location
   float moved_distance;      // length of v_diff vector (distance bot moved)
   float degrees_turned;

   // check if someone kicked the bot off of the server (DON'T RESPAWN!)...
   if ((pev->takedamage == DAMAGE_NO) && (respawn_index >= 0))
   {
      pev->health = 0;
      pev->deadflag = DEAD_DEAD;  // make the kicked bot be dead

      bot_respawn[respawn_index].is_used = FALSE;  // this slot is now free
      bot_respawn[respawn_index].state = BOT_IDLE;
      respawn_index = -1;  // indicate no slot used

      // fall through to next if statement (respawn_index will be -1)
   }

   // is the round over (time/frag limit) or has the bot been removed?
   if ((g_fGameOver) || (respawn_index == -1))
   {
      CSound *pSound;

      // keep resetting the sound entity until the bot is respawned...
      pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) );
      if ( pSound )
      {
         pSound->Reset();
      }

      return;
   }

   pev->button = 0;  // make sure no buttons are pressed

   // if the bot is dead, randomly press fire to respawn...
   if ((pev->health < 1) || (pev->deadflag != DEAD_NO))
   {
      if (RANDOM_LONG(1, 100) > 50)
         pev->button = IN_ATTACK;

      g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, f_move_speed,
                                   0, 0, pev->button, 0,
                                   gpGlobals->frametime * 1000 );
      return;
   }

   // see if it's time to check for sv_maxspeed change...
   if (f_speed_check_time <= gpGlobals->time)
   {
      // store current sv_maxspeed setting for quick access
      f_max_speed = CVAR_GET_FLOAT("sv_maxspeed");

      // set next check time to 2 seconds from now
      f_speed_check_time = gpGlobals->time + 2.0f;
   }

   // see how far bot has moved since the previous position...
   v_diff = v_prev_origin - pev->origin;
   moved_distance = v_diff.Length();

   v_prev_origin = pev->origin;  // save current position as previous

   f_move_speed = f_max_speed;  // set to max speed unless known otherwise

   // turn towards ideal_yaw by yaw_speed degrees
   degrees_turned = BotChangeYaw( pev->yaw_speed );

   if (degrees_turned >= pev->yaw_speed)
   {
      // if bot is still turning, turn in place by setting speed to 0
      f_move_speed = 0;
   }

   else if (IsOnLadder( ))  // check if the bot is on a ladder...
   {
      f_use_ladder_time = gpGlobals->time;

      BotOnLadder( moved_distance );  // go handle the ladder movement
   }

   else  // else handle movement related actions...
   {
      // bot is not on a ladder so clear ladder direction flag...
      ladder_dir = 0;

      if (f_botdontshoot == 0)  // is bot targeting turned on?
         pBotEnemy = BotFindEnemy( );
      else
         pBotEnemy = NULL;  // clear enemy pointer (no ememy for you!)

      if (pBotEnemy != NULL)  // does an enemy exist?
      {
         BotShootAtEnemy( );  // shoot at the enemy
      }

	  else if (pBotEnemy == NULL && g_pGameRules->IsHeavyRain())
	  {
		 SelectItem("weapon_jason");  // select the crowbar

         pev->button |= IN_ATTACK;  // use primary attack (whack! whack!)

         // set next time to "shoot"
         f_shoot_time = gpGlobals->time + 3 + 1;  
	  }

	  else if (g_pGameRules->IsTest() && pausebots.value)
	  {
		  SelectItem("weapon_jason");
		               f_pause_time = gpGlobals->time + 99999; //Pause the bot dead.
	  }

      else if (f_pause_time > gpGlobals->time)  // is bot "paused"?
      {
         // you could make the bot look left then right, or look up
         // and down, to make it appear that the bot is hunting for
         // something (don't do anything right now)

         f_move_speed = 0;
      }

      // is bot being "used" and can still follow "user"?
      else if ((pBotUser != NULL) && BotFollowUser( ))
      {
         // do nothing here!
         ;
      }

      else
      {
         // no enemy, let's just wander around...

         pev->v_angle.x = 0;  // reset pitch to 0 (level horizontally)
         pev->v_angle.z = 0;  // reset roll to 0 (straight up and down)

         pev->angles.x = 0;
         pev->angles.y = pev->v_angle.y;
         pev->angles.z = 0;

         // check if bot should look for items now or not...
         if (f_find_item < gpGlobals->time)
         {
            BotFindItem( );  // see if there are any visible items
         }

         // check if bot sees a tripmine...
         if (b_see_tripmine)
         {
            // check if bot can shoot the tripmine...
            if ((b_shoot_tripmine) && BotShootTripmine( ))
            {
               // shot at tripmine, may or may not have hit it, clear
               // flags anyway, next BotFindItem will see it again if
               // it is still there...

               b_shoot_tripmine = FALSE;
               b_see_tripmine = FALSE;

               // pause for a while to allow tripmine to explode...
               f_pause_time = gpGlobals->time + 0.5f;
            }
            else  // run away!!!
            {
               Vector tripmine_angles;

               tripmine_angles = UTIL_VecToAngles( v_tripmine_origin - pev->origin );

               // face away from the tripmine
               pev->ideal_yaw = -tripmine_angles.y;

               // check for wrap around of angle...
               if (pev->ideal_yaw > 180)
                  pev->ideal_yaw -= 360;
               if (pev->ideal_yaw < -180)
                  pev->ideal_yaw += 360;

               b_see_tripmine = FALSE;

               f_move_speed = 0;  // don't run while turning
            }
         }

         // check if should use wall mounted health station...
         else if (b_use_health_station)
         {
            if ((f_use_health_time + 10.0f) > gpGlobals->time)
            {
               f_move_speed = 0;  // don't move while using health station

               pev->button = IN_USE;
            }
            else
            {
               // bot is stuck trying to "use" a health station...

               b_use_health_station = FALSE;

               // don't look for items for a while since the bot
               // could be stuck trying to get to an item
               f_find_item = gpGlobals->time + 0.5f;
            }
         }

         // check if should use wall mounted HEV station...
         else if (b_use_HEV_station)
         {
            if ((f_use_HEV_time + 10.0f) > gpGlobals->time)
            {
               f_move_speed = 0;  // don't move while using HEV station

               pev->button = IN_USE;
            }
            else
            {
               // bot is stuck trying to "use" a HEV station...

               b_use_HEV_station = FALSE;

               // don't look for items for a while since the bot
               // could be stuck trying to get to an item
               f_find_item = gpGlobals->time + 0.5f;
            }
         }

         else if (b_use_button)
         {
            f_move_speed = 0;  // don't move while using elevator

            BotUseLift( moved_distance );
         }

         else
         {
            TraceResult tr;

            if (pev->waterlevel == 3)  // check if the bot is underwater...
            {
               BotUnderWater( );
            }

            // check if there is a wall on the left...
            if (!BotCheckWallOnLeft())
            {
               // if there was a wall on the left over 1/2 a second ago then
               // 20% of the time randomly turn between 45 and 60 degrees
           
               if ((f_wall_on_left != 0) &&
                   (f_wall_on_left <= gpGlobals->time - 0.5f) &&
                   (RANDOM_LONG(1, 100) <= 20))
               {
                  pev->ideal_yaw += RANDOM_LONG(45, 60);

                  // check for wrap around of angle...
                  if (pev->ideal_yaw > 180)
                     pev->ideal_yaw -= 360;
                  if (pev->ideal_yaw < -180)
                     pev->ideal_yaw += 360;

                  f_move_speed = 0;  // don't move while turning
                  f_dont_avoid_wall_time = gpGlobals->time + 1.0f;
               }

               f_wall_on_left = 0;  // reset wall detect time
            }

            // check if there is a wall on the right...
            if (!BotCheckWallOnRight())
            {
               // if there was a wall on the right over 1/2 a second ago then
               // 20% of the time randomly turn between 45 and 60 degrees

               if ((f_wall_on_right != 0) &&
                   (f_wall_on_right <= gpGlobals->time - 0.5f) &&
                   (RANDOM_LONG(1, 100) <= 20))
               {
                  pev->ideal_yaw -= RANDOM_LONG(45, 60);

                  // check for wrap around of angle...
                  if (pev->ideal_yaw > 180)
                     pev->ideal_yaw -= 360;
                  if (pev->ideal_yaw < -180)
                     pev->ideal_yaw += 360;

                  f_move_speed = 0;  // don't move while turning
                  f_dont_avoid_wall_time = gpGlobals->time + 1.0f;
               }

               f_wall_on_right = 0;  // reset wall detect time
            }

            // check if bot is about to hit a wall.  TraceResult gets returned
            if ((f_dont_avoid_wall_time <= gpGlobals->time) &&
                BotCantMoveForward( &tr ))
            {
               // ADD LATER
               // need to check if bot can jump up or duck under here...
               // ADD LATER

               BotTurnAtWall( &tr );
            }

            // check if the bot hasn't moved much since the last location
            else if ((moved_distance <= 1) && (!bot_was_paused))
            {
               // the bot must be stuck!
            
               if (BotCanJumpUp( ))  // can the bot jump onto something?
               {
                  pev->button |= IN_JUMP;  // jump up and move forward
               }
               else if (BotCanDuckUnder( ))  // can the bot duck under something?
               {
                  pev->button |= IN_DUCK;  // duck down and move forward
               }
               else
               {
                  f_move_speed = 0;  // don't move while turning

                  // turn randomly between 30 and 60 degress
                  if (wander_dir == WANDER_LEFT)
                     pev->ideal_yaw += RANDOM_LONG(30, 60);
                  else
                     pev->ideal_yaw -= RANDOM_LONG(30, 60);

                  // check for wrap around of angle...
                  if (pev->ideal_yaw > 180)
                     pev->ideal_yaw -= 360;
                  if (pev->ideal_yaw < -180)
                     pev->ideal_yaw += 360;

                  // is the bot trying to get to an item?...
                  if (pBotPickupItem != NULL)
                  {
                     // don't look for items for a while since the bot
                     // could be stuck trying to get to an item
                     f_find_item = gpGlobals->time + 0.5f;
                  }
               }
            }

            // should the bot pause for a while here?
            if ((RANDOM_LONG(1, 1000) <= pause_frequency[bot_skill]) &&
                (pBotUser == NULL))  // don't pause if being "used"
            {
               // set the time that the bot will stop "pausing"
               f_pause_time = gpGlobals->time +
                  RANDOM_FLOAT(pause_time[bot_skill][0],
                               pause_time[bot_skill][1]);
               f_move_speed = 0;  // don't move while turning
            }
         }
      }
   }

   if (f_move_speed < 1)
      bot_was_paused = TRUE;
   else
      bot_was_paused = FALSE;

   // TheFatal START - from www.telefragged.com/thefatal/jumblow.shtml

   g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, f_move_speed,
                                0, 0, pev->button, 0,
                                gpGlobals->frametime * 1000 );
   // TheFatal - END
}
