// ####################################
// #                                  #
// #       Ping of Death - Bot        #
// #                by                #
// #    Markus Klinge aka Count Floyd #
// #                                  #
// ####################################
//
// Started from the HPB-Bot Alpha Source
// by Botman so Credits for a lot of the basic
// HL Server/Client Stuff goes to him
//
// bot.cpp
//
// Features the core AI of the bot

#include "bot_globals.h"


void EstimateNextFrameDuration (void)
{
   // Rich 'TheFatal' Whitehouse's method for computing the msec value

   if (msecdel <= gpGlobals->time)
   {
      if (msecnum > 0)
         msecval = 450.0 / msecnum;

      msecdel = gpGlobals->time + 0.5; // next check in half a second
      msecnum = 0;
   }
   else
      msecnum++;

   if (msecval < 1)
      msecval = 1; // don't allow the msec delay to be null
   else if (msecval > 100)
      msecval = 100; // don't allow it to last longer than 100 milliseconds either

   return;
}


void BotSpawnInit (bot_t *pBot)
{
   // Initialises a Bot after Creation & at the start of each Round

   int i;

   // Delete all allocated Path Nodes
   DeleteSearchNodes (pBot);

   pBot->wpt_origin = g_vecZero;
   pBot->dest_origin = g_vecZero;
   pBot->curr_wpt_index = -1;
   pBot->curr_travel_flags = 0;
   pBot->bJumpDone = FALSE;
   pBot->vecDesiredVelocity = g_vecZero;
   pBot->prev_goal_index = -1;
   pBot->chosengoal_index = -1;
   pBot->prev_wpt_index[0] = -1;
   pBot->prev_wpt_index[1] = -1;
   pBot->prev_wpt_index[2] = -1;
   pBot->prev_wpt_index[3] = -1;
   pBot->prev_wpt_index[4] = -1;
   pBot->f_wpt_timeset = 0;

   switch (pBot->bot_personality)
   {
   case 0: // Normal
      if (RANDOM_LONG (1, 100) > 50)
         pBot->byPathType = 1;
      else
         pBot->byPathType = 2;
      break;
   case 1: // Psycho
      pBot->byPathType = 0;
      break;
   case 2: // Coward
      pBot->byPathType = 2;
      break;
   }

   // Clear all States & Tasks
   pBot->iStates = 0;
   BotResetTasks (pBot);

   pBot->bCheckMyTeam = TRUE;

   pBot->bIsVIP = FALSE;
   pBot->bIsLeader = FALSE;
   pBot->fTimeTeamOrder = 0.0;

   pBot->fMinSpeed = 260.0;
   pBot->prev_speed = 0.0; // fake "paused" since bot is NOT stuck
   pBot->v_prev_origin = Vector (9999.0, 9999.0, 9999.0);
   pBot->prev_time = gpGlobals->time;

   pBot->bCanDuck = FALSE;
   pBot->f_view_distance = 4096.0;
   pBot->f_maxview_distance = 4096.0;

   pBot->pBotPickupItem = NULL;
   pBot->pItemIgnore = NULL;
   pBot->f_itemcheck_time = 0.0;
   pBot->f_timeDoorOpen = 0.0;

   pBot->pShootBreakable = NULL;
   pBot->vecBreakable = g_vecZero;

   BotResetCollideState (pBot);

   pBot->pBotEnemy = NULL;
   pBot->pLastVictim = NULL;
   pBot->pLastEnemy = NULL;
   pBot->vecLastEnemyOrigin = g_vecZero;
   pBot->pTrackingEdict = NULL;
   pBot->fTimeNextTracking = 0.0;

   pBot->fNextFocusEnemyTime = 0.0;

   pBot->fEnemyUpdateTime = 0.0;
   pBot->f_bot_see_enemy_time = 0.0;
   pBot->f_shootatdead_time = 0.0;
   pBot->oldcombatdesire = 0.0;

   pBot->pAvoidGrenade = NULL;
   pBot->cAvoidGrenade = 0;

   // Reset Damage
   pBot->iLastDamageType = -1;

   pBot->vecPosition = g_vecZero;

   pBot->f_ideal_reaction_time = BotSkillDelays[pBot->bot_skill / 20].fMinSurpriseDelay;
   pBot->f_actual_reaction_time = BotSkillDelays[pBot->bot_skill / 20].fMinSurpriseDelay;

   pBot->pBotUser = NULL;
   pBot->f_bot_use_time = 0.0;

   for (i = 0; i < MAX_HOSTAGES; i++)
      pBot->pHostages[i] = NULL;

   pBot->f_hostage_check_time = gpGlobals->time;

   pBot->bIsReloading = FALSE;
   pBot->f_shoot_time = gpGlobals->time;
   pBot->fTimeLastFired = 0.0;
   pBot->iBurstShotsFired = 0;
   pBot->fTimeFirePause = 0.0;

   pBot->f_primary_charging = -1.0;
   pBot->f_secondary_charging = -1.0;

   pBot->f_grenade_check_time = 0.0;
   pBot->bUsingGrenade = FALSE;

   pBot->charging_weapon_id = 0;

   pBot->f_blind_time = 0.0;
   pBot->f_jumptime = 0.0;

   pBot->f_sound_update_time = gpGlobals->time;
   pBot->f_heard_sound_time = 0.0;

   pBot->fTimePrevThink = gpGlobals->time;
   pBot->fTimeFrameInterval = 0.0;

   pBot->b_bomb_blinking = FALSE;

   pBot->SaytextBuffer.fTimeNextChat = gpGlobals->time;
   pBot->SaytextBuffer.iEntityIndex = -1;
   pBot->SaytextBuffer.szSayText[0] = 0x0;

   pBot->iBuyCount = 1;
   pBot->f_buy_time = gpGlobals->time + RANDOM_FLOAT (1.5, 2.0);

   pBot->f_zoomchecktime = 0.0;
   pBot->iNumWeaponPickups = 0;

   pBot->f_StrafeSetTime = 0.0;
   pBot->byCombatStrafeDir = 0;
   pBot->byFightStyle = 0;
   pBot->f_lastfightstylecheck = 0.0;

   pBot->bCheckWeaponSwitch = TRUE;

   pBot->pRadioEntity = NULL;
   pBot->iRadioOrder = 0;

   pBot->bLogoSprayed = FALSE;
   pBot->bDefendedBomb = FALSE;

   pBot->f_spawn_time = gpGlobals->time;
   pBot->f_lastchattime = gpGlobals->time;
   pBot->pEdict->v.v_angle.y = pBot->pEdict->v.ideal_yaw;

   for (i = 0; i < 2; i++)
   {
      pBot->rgfYawHistory[i] = 0;
      pBot->rgfPitchHistory[i] = 0;
   }

   // Clear its Message Queue
   for (i = 0; i < 32; i++)
      pBot->aMessageQueue[i] = 0;

   pBot->iActMessageIndex = 0;
   pBot->iPushMessageIndex = 0;

   bottask_t TempTask = {NULL, NULL, TASK_NORMAL, TASKPRI_NORMAL, -1, 0.0, TRUE};
   BotPushTask (pBot, &TempTask);
}


void BotCreate (int bot_skill, int bot_personality, int bot_team, int bot_class, const char *bot_name)
{
   // This Function creates the Fakeclient (Bot) - passed Arguments:
   // arg1 - Skill
   // arg2 - Personality
   // arg3 - Team
   // arg4 - Class (Model)
   // arg5 - Botname

   edict_t *pPlayer;
   edict_t *BotEnt;
   bot_t *pBot;
   char c_name[BOT_NAME_LEN + 1];
   int skill;
   int index;
   int name_index;
   char ptr[128]; // allocate space for message from ClientConnect
   char *infobuffer;
   int iUsedCount;
   bool is_used;

   // Don't allow creating Bots when no waypoints are loaded
   if (g_iNumWaypoints < 1)
   {
      UTIL_ServerPrint ("No Waypoints for this Map, can't create Bot !\n");
      memset (BotCreateTab, 0, sizeof (BotCreateTab));
      botcreation_time = 0.0;
      return;
   }

   // Don't allow creating Bots when max_bots is reached
   else if (num_players == max_bots)
   {
      UTIL_ServerPrint ("Max Bots reached, can't create Bot !\n");
      memset (BotCreateTab, 0, sizeof (BotCreateTab));
      botcreation_time = 0.0;
      return;
   }

   // if Waypoints have changed don't allow it because Distance Tables are messed up
   else if (g_bWaypointsChanged)
   {
      UTIL_ServerPrint ("Waypoints changed/not initialised, can't create Bot !\n");
      memset (BotCreateTab, 0, sizeof (BotCreateTab));
      botcreation_time = 0.0;
      return;
   }

   // If Skill is given, assign it
   if ((bot_skill > 0) && (bot_skill <= 100))
      skill = bot_skill;
   else
      skill = RANDOM_LONG (g_iMinBotSkill, g_iMaxBotSkill); // else give random skill

   assert ((skill > 0) && (skill <= 100));

   // Create Random Personality
   if ((bot_personality < 0) || (bot_personality > 2))
      bot_personality = RANDOM_LONG (0, 2);

   if ((bot_team != 1) && (bot_team != 2) && (bot_team != 5))
      bot_team = 5;

   if ((bot_class < 1) || (bot_class > 5))
      bot_class = 5;

   // If No Name is given, do our name stuff
   if ((bot_name == NULL) || (*bot_name == 0))
   {
      // If as many Bots as NumBotnames, don't allow Bot Creation
      if (num_bots >= iNumBotNames)
      {
         UTIL_ServerPrint ("Not enough Bot Names in botnames.txt, can't create Bot !\n");
         memset (BotCreateTab, 0, sizeof (BotCreateTab));
         botcreation_time = 0.0;
         return;
      }

      // Clear Array of used Botnames
      memset (szUsedBotNames, 0, sizeof (szUsedBotNames));

      // Cycle through all Players in Game and pick up Bots Names
      iUsedCount = 0;
      for (index = 1; index < gpGlobals->maxClients; index++)
      {
         pPlayer = INDEXENT (index);

         if (!FNullEnt (pPlayer)
             && (pPlayer->v.flags & FL_FAKECLIENT)
             && (STRING (pPlayer->v.netname)[0] != 0))
            szUsedBotNames[iUsedCount++] = STRING (pPlayer->v.netname);
      }

      // Find a Botname from Botnames.txt which isn't used yet
      do
      {
         name_index = RANDOM_LONG (0, iNumBotNames - 1);

         if (iUsedCount == 0)
            is_used = FALSE;
         else
         {
            is_used = FALSE;

            for (index = 0; index < iUsedCount; index++)
               if ((strstr (szUsedBotNames[index], szBotNames[name_index]) != NULL)/* ||
                   (strcmp (szUsedBotNames[index], szBotNames[name_index]) == 0)*/)
                  is_used = TRUE;
         }
      }
      while (is_used);

      // If Detailnames are on, attach Clan Tag
      if (g_bDetailNames)
      {
         if (bot_personality == 0)
            sprintf (c_name, "[POD]%s (%d)", szBotNames[name_index], skill);
         else if (bot_personality == 1)
            sprintf (c_name, "[P*D]%s (%d)", szBotNames[name_index], skill);
         else
            sprintf (c_name, "[P0D]%s (%d)", szBotNames[name_index], skill);
      }
      else
         strncpy (c_name, szBotNames[name_index], 32);
   }

   // a name has been given
   else
   {
      // If Detailnames are on, see if we NEED to attach Clan Tag
      if (g_bDetailNames)
      {
         if ((strstr (bot_name, "[POD]") != NULL)
             || (strstr (bot_name, "[P*D]") != NULL)
             || (strstr (bot_name, "[P0D]") != NULL))
         {
            strncpy (c_name, bot_name, 32);

            if (bot_personality == 0)
               c_name[2] = 'O';
            else if (bot_personality == 1)
               c_name[2] = '*';
            else
               c_name[2] = '0';
         }
         else
         {
            if (bot_personality == 0)
               sprintf (c_name, "[POD]%s (%d)", bot_name, skill);
            else if (bot_personality == 1)
               sprintf (c_name, "[P*D]%s (%d)", bot_name, skill);
            else
               sprintf (c_name, "[P0D]%s (%d)", bot_name, skill);
         }
      }
      else
         strncpy (c_name, bot_name, 32);
   }

   // This call creates the Fakeclient
   BotEnt = (*g_engfuncs.pfnCreateFakeClient) (c_name);

   // Did the Call succeed ?
   if (FNullEnt (BotEnt))
   {
      UTIL_ServerPrint ("Max. Players reached.  Can't create bot!\n");
      memset (BotCreateTab, 0, sizeof (BotCreateTab));
      botcreation_time = 0.0;
      return;
   }

   // YEP! Our little Bot has spawned !

   // Notify calling Player of the creation
   UTIL_ServerPrint ("Creating bot...\n");

   if (BotEnt->pvPrivateData != NULL)
      FREE_PRIVATE (BotEnt);
   BotEnt->pvPrivateData = NULL;
   BotEnt->v.frags = 0;

   // create the player entity by calling MOD's player function
   CALL_GAME_ENTITY (PLID, "player", &BotEnt->v);

   // Find a free slot in our Table of Bots
   index = ENTINDEX (BotEnt) - 1;

   // Set all Infobuffer Keys for this Bot
   infobuffer = GET_INFOKEYBUFFER (BotEnt);
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "model", "gordon");
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "rate", "3500.000000");
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "cl_updaterate", "20");
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "cl_lw", "1");
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "cl_lc", "1");
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "tracker", "0");
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "cl_dlmax", "128");
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "lefthand", "1");
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "friends", "0");
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "dm", "0");
   SET_CLIENT_KEYVALUE (index + 1, infobuffer, "ah", "1");
   // - End Infobuffer -

   // Connect this client with the local loopback
   MDLL_ClientConnect (BotEnt, c_name, "127.0.0.1", ptr);

   // Pieter van Dijk - use instead of DispatchSpawn() - Hip Hip Hurray!
   MDLL_ClientPutInServer (BotEnt);

   // PMB - do this because MDLL_ClientPutInServer() does NOT call our own ClientPutInServer()
   clients[index].pEdict = BotEnt;

   // set the third party bot flag
   BotEnt->v.flags |= FL_FAKECLIENT;
   BotEnt->v.spawnflags |= FL_FAKECLIENT;

   // initialize all the variables for this bot...
   pBot = &bots[index];
   pBot->pEdict = BotEnt;

   sprintf (pBot->name, STRING (pBot->pEdict->v.netname));
   pBot->is_used = TRUE;
   pBot->not_started = 1; // hasn't joined game yet
   pBot->start_action = MSG_CS_IDLE;
   pBot->bot_money = 0;
   pBot->bDead = TRUE;
   pBot->bot_skill = skill;
   pBot->bot_personality = bot_personality;

   // Assign a random Spraypaint
   pBot->iSprayLogo = RANDOM_LONG (0, g_iNumLogos - 1);

   // Assign how talkative this Bot will be
   pBot->SaytextBuffer.fChatDelay = RANDOM_FLOAT (4.0, 10.0);
   pBot->SaytextBuffer.cChatProbability = RANDOM_LONG (1, 100);

   BotEnt->v.idealpitch = BotEnt->v.v_angle.x;
   BotEnt->v.ideal_yaw = BotEnt->v.v_angle.y;
   BotEnt->v.yaw_speed = RANDOM_FLOAT (BotTurnSpeeds[skill / 20].fMinTurnSpeed,
      BotTurnSpeeds[skill / 20].fMaxTurnSpeed);
   BotEnt->v.pitch_speed = RANDOM_FLOAT (BotTurnSpeeds[skill / 20].fMinTurnSpeed,
      BotTurnSpeeds[skill / 20].fMaxTurnSpeed);

   // Set the Base Fear/Agression Levels for this Personality
   switch (bot_personality)
   {
      // Normal
   case 0:
      pBot->fBaseAgressionLevel = RANDOM_FLOAT (0.5, 0.6);
      pBot->fBaseFearLevel = RANDOM_FLOAT (0.5, 0.6);
      break;
      // Psycho
   case 1:
      pBot->fBaseAgressionLevel = RANDOM_FLOAT (0.7, 1.0);
      pBot->fBaseFearLevel = RANDOM_FLOAT (0.0, 0.4);
      break;
      // Coward
   case 2:
      pBot->fBaseAgressionLevel = RANDOM_FLOAT (0.0, 0.4);
      pBot->fBaseFearLevel = RANDOM_FLOAT (0.7, 1.0);
      break;
   }

   // Copy them over to the temp Level Variables
   pBot->fAgressionLevel = pBot->fBaseAgressionLevel;
   pBot->fFearLevel = pBot->fBaseFearLevel;
   pBot->fNextEmotionUpdate = gpGlobals->time + 0.5;

   // Just to be sure
   pBot->iActMessageIndex = 0;
   pBot->iPushMessageIndex = 0;

   // Assign Team & Class
   pBot->bot_team = bot_team;
   pBot->bot_class = bot_class;

   BotSpawnInit (pBot);

   return;
}


void BotStartGame (bot_t *pBot)
{
   // Handles the selection of Teams & Class

   // handle Counter-Strike stuff here...
   if (pBot->start_action == MSG_CS_TEAM_SELECT)
   {
      pBot->start_action = MSG_CS_IDLE; // switch back to idle

      if ((pBot->bot_team < 1) || (pBot->bot_team > 2))
         pBot->bot_team = 5;

      FakeClientCommand (pBot->pEdict, "menuselect %d\n", pBot->bot_team);
      return;
   }

   else if ((pBot->start_action == MSG_CS_CT_SELECT)
            || (pBot->start_action == MSG_CS_T_SELECT))
   {
      pBot->start_action = MSG_CS_IDLE; // switch back to idle

      if ((pBot->bot_class < 1) || (pBot->bot_class > 4))
         pBot->bot_class = RANDOM_LONG (1, 4); // use random if invalid

      FakeClientCommand (pBot->pEdict, "menuselect %d\n", pBot->bot_class);

      // bot has now joined the game (doesn't need to be started)
      pBot->not_started = FALSE;
      return;
   }
}


void UserRemoveAllBots (void)
{
   // Called from Usercommand "removebots"
   // Removes all Bots from the Server, sets minbots/maxbots to 0

   int index;
   char cmd[80];

   for (index = 0; index < gpGlobals->maxClients; index++)
   {
      // Reset our Creation Tab if there are still Bots waiting to be spawned
      memset (&BotCreateTab[index], 0, sizeof (createbot_t));

      // is this slot used?
      if (bots[index].is_used && !FNullEnt (bots[index].pEdict))
      {
         sprintf (cmd, "kick \"%s\"\n", STRING (bots[index].pEdict->v.netname));
         SERVER_COMMAND (cmd); // kick the bot using (kick "name")
      }
   }

   UTIL_HostPrint ("All Bots removed !\n");
}


void UserKillAllBots (void)
{
   // Called by UserCommand "killbots"
   // Kills all Bots

   int i;
   bot_t *pBot;

   for (i = 0; i < gpGlobals->maxClients; i++)
   {
      pBot = &bots[i];

      if (pBot->is_used && !pBot->bDead)
      {
         // If a Bot gets killed it decreases his frags, so add 1 here to not give human
         // players an advantage using this command
         pBot->pEdict->v.frags++;
         MDLL_ClientKill (pBot->pEdict);
      }
   }

   UTIL_HostPrint ("All Bots killed !\n");
}


void UserNewroundAll (void)
{
   // Called by UserCommand "newround"
   // Kills all Clients in Game including humans

   edict_t *pPlayer;

   for (int i = 1; i <= gpGlobals->maxClients; i++)
   {
      pPlayer = INDEXENT (i);

      // is this player slot valid
      if (!FNullEnt (pPlayer) && (pPlayer->v.flags & FL_CLIENT) && IsAlive (pPlayer))
      {
         pPlayer->v.frags++;
         MDLL_ClientKill (pPlayer);
      }
   }

   UTIL_HostPrint ("Round Restarted !\n");
}


void UserSelectWeaponMode (int iSelection)
{
   int i;
   int rgiWeaponTab[7][NUM_WEAPONS] =
   {
      {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Knife only
      {-1,-1,-1, 2, 2, 0, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Pistols only
      {-1,-1,-1,-1,-1,-1,-1, 2, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, // Shotgun only
      {-1,-1,-1,-1,-1,-1,-1,-1,-1, 2, 1, 2, 0, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2,-1}, // Machine Guns only
      {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0, 0, 1, 0, 1, 1,-1,-1,-1,-1,-1,-1}, // Rifles only
      {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2, 2, 0, 1,-1,-1}, // Snipers only
      {-1,-1,-1, 2, 2, 0, 1, 2, 2, 2, 1, 2, 0, 2, 0, 0, 1, 0, 1, 1, 2, 2, 0, 1, 2, 1}  // Standard
   };

   char szMode[7][12] =
   {
      {"Knife"},
      {"Pistol"},
      {"Shotgun"},
      {"Machine Gun"},
      {"Rifle"},
      {"Sniper"},
      {"Standard"}
   };

   iSelection--;
   for (i = 0; i < NUM_WEAPONS; i++)
   {
      cs_weapon_select[i].iTeamStandard = rgiWeaponTab[iSelection][i];
      cs_weapon_select[i].iTeamAS = rgiWeaponTab[iSelection][i];
   }

   if (iSelection == 0)
      g_bJasonMode = TRUE;
   else
      g_bJasonMode = FALSE;

   UTIL_HostPrint ("%s Weapon Mode selected!\n", &szMode[iSelection][0]);
}


int BotGetMessageQueue (bot_t *pBot)
{
   // Get the current Message from the Bots Message Queue

   int iMSG;

   iMSG = pBot->aMessageQueue[pBot->iActMessageIndex++];
   pBot->iActMessageIndex &= 0x1f; // Wraparound

   return (iMSG);
}


void BotPushMessageQueue (bot_t *pBot, int iMessage)
{
   // Put a Message into the Message Queue

   pBot->aMessageQueue[pBot->iPushMessageIndex++] = iMessage;
   pBot->iPushMessageIndex &= 0x1f; // Wraparound

   return;
}



int BotInFieldOfView (bot_t *pBot, Vector dest)
{
   int angle;
   float entity_angle;
   float view_angle;

   // find angles from source to destination...
   entity_angle = UTIL_VecToAngles (dest).y;

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

   // get bot's current view angle...
   view_angle = pBot->pEdict->v.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

   // rsm - START angle bug fix
   angle = abs ((int) view_angle - (int) entity_angle);

   if (angle > 180)
      angle = 360 - angle;

   return (angle);
   // rsm - END
}


bool BotItemIsVisible (bot_t *pBot, Vector vecDest, char *pszItemName)
{
   TraceResult tr;

   // trace a line from bot's eyes to destination...
   TRACE_LINE (pBot->pEdict->v.origin + pBot->pEdict->v.view_ofs, vecDest, ignore_monsters, pBot->pEdict, &tr);

   // check if line of sight to object is not blocked (i.e. visible)
   if (tr.flFraction != 1.0)
   {
      if (strcmp (STRING (tr.pHit->v.classname), pszItemName) == 0)
         return (TRUE);

      return (FALSE);
   }

   return (TRUE);
}


bool BotEntityIsVisible (bot_t *pBot, Vector vecDest)
{
   TraceResult tr;

   // trace a line from bot's eyes to destination...
   TRACE_LINE (pBot->pEdict->v.origin + pBot->pEdict->v.view_ofs, vecDest, ignore_monsters, pBot->pEdict, &tr);

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

   return (TRUE);
}


void BotCheckSmokeGrenades (bot_t *pBot)
{
   // Check if Bot 'sees' a SmokeGrenade to simulate the effect of being blinded by smoke
   // + notice Bot of Grenades flying towards him
   // TODO: Split this up and make it more reliable

   edict_t *pEdict = pBot->pEdict;
   edict_t *pent = pBot->pAvoidGrenade;
   Vector vecView = pEdict->v.origin + pEdict->v.view_ofs;
   float fDistance;

   // Check if old ptr to Grenade is invalid
   if (FNullEnt (pent))
   {
      pBot->pAvoidGrenade = NULL;
      pBot->cAvoidGrenade = 0;
   }

   else if ((pent->v.flags & FL_ONGROUND) || (pent->v.effects & EF_NODRAW))
   {
      pBot->pAvoidGrenade = NULL;
      pBot->cAvoidGrenade = 0;
   }

   pent = NULL;

   // Find all Grenades on the map
   while (!FNullEnt (pent = FIND_ENTITY_BY_STRING (pent, "classname", "grenade")))
   {
      // If Grenade is invisible don't care for it
      if (pent->v.effects & EF_NODRAW)
         continue;

      // Check if visible to the Bot
      if (!BotEntityIsVisible (pBot, pent->v.origin)
          && BotInFieldOfView (pBot, pent->v.origin - vecView) > pEdict->v.fov / 2)
         continue;

      if (FNullEnt (pBot->pAvoidGrenade))
      {
         // Is this a flying Grenade ?
         if ((pent->v.flags & FL_ONGROUND) == 0)
         {
            fDistance = (pent->v.origin - pEdict->v.origin).Length ();
            float fDistanceMoved = ((pent->v.origin + pent->v.velocity * pBot->fTimeFrameInterval) - pEdict->v.origin).Length ();

            // Is the Grenade approaching this Bot ?
            if ((fDistanceMoved < fDistance) && (fDistance < 600))
            {
               Vector2D vec2DirToPoint;
               Vector2D vec2RightSide;

               // to start strafing, we have to first figure out if the target is on the left side or right side
               MAKE_VECTORS (pEdict->v.angles);

               vec2DirToPoint = (pEdict->v.origin - pent->v.origin).Make2D ().Normalize ();
               vec2RightSide = gpGlobals->v_right.Make2D ().Normalize ();

               if (DotProduct (vec2DirToPoint, vec2RightSide) > 0)
                  pBot->cAvoidGrenade = -1;
               else
                  pBot->cAvoidGrenade = 1;

               pBot->pAvoidGrenade = pent;
               return;
            }
         }
      }

      // Is this a SmokeGrenade and on Ground (smoking) ?
      if (!FStrEq (STRING (pent->v.model), "models/w_smokegrenade.mdl")
          || (pent->v.flags & FL_ONGROUND) == 0)
         continue;

      fDistance = (pent->v.origin - pEdict->v.origin).Length ();

      // Shrink Bots Viewing Distance to Smoke Grens Distance
      if (pBot->f_view_distance > fDistance)
         pBot->f_view_distance = fDistance;
   }
}


int GetBestWeaponCarried (bot_t *pBot)
{
   // Returns the best weapon of this Bot (based on Personality Prefs)

   int *ptrWeaponTab = ptrWeaponPrefs[pBot->bot_personality];
   bot_weapon_select_t *pWeaponTab = &cs_weapon_select[0];
   int iWeaponIndex;
   int i;

   int iWeapons = pBot->pEdict->v.weapons;

   if (BotHasShield (pBot))
      iWeapons |= (1 << CS_WEAPON_SHIELDGUN);

   for (i = 0; i < NUM_WEAPONS; i++)
   {
      if ((pBot->pEdict->v.weapons & (1 << pWeaponTab[*ptrWeaponTab].iId)))
         iWeaponIndex = i;
      ptrWeaponTab++;
   }

   return iWeaponIndex;
}


int BotRateGroundWeapon (bot_t *pBot, edict_t *pent)
{
   // Compares Weapons on the Ground to the one the Bot is using

   int i;
   int iHasWeapon = GetBestWeaponCarried (pBot);
   int *ptrWeaponTab = ptrWeaponPrefs[pBot->bot_personality];
   bot_weapon_select_t *pWeaponTab = &cs_weapon_select[0];

   int iGroundIndex = 0;
   char szModelName[40];

   strcpy (szModelName, STRING (pent->v.model));

   for (i = 0; i < NUM_WEAPONS; i++)
   {
      if (FStrEq (pWeaponTab[*ptrWeaponTab].model_name, szModelName))
      {
         iGroundIndex = i;
         break;
      }

      ptrWeaponTab++;
   }

   // Don't care for pistols
   // TODO Include Pistols as well...
   if (iGroundIndex < 7)
      iGroundIndex = 0;

   return (iGroundIndex - iHasWeapon);
}


bool BotFindBreakable (bot_t *pBot)
{
   // Checks if Bot is blocked by a Shootable Breakable in his moving direction

   TraceResult tr;
   edict_t *pEdict = pBot->pEdict;

   Vector v_src = pEdict->v.origin;
   Vector vecDirection = (pBot->dest_origin - v_src).Normalize ();
   Vector v_dest = v_src + vecDirection * 50;

   TRACE_LINE (v_src, v_dest, dont_ignore_monsters, pEdict, &tr);
   if ((tr.flFraction != 1.0)
       && (strcmp ("func_breakable", STRING (tr.pHit->v.classname)) == 0)
       && IsShootableBreakable (tr.pHit))
   {
      pBot->vecBreakable = tr.vecEndPos;
      return (TRUE);
   }

   v_src = pEdict->v.origin + pEdict->v.view_ofs;
   vecDirection = (pBot->dest_origin - v_src).Normalize ();
   v_dest = v_src + vecDirection * 50;

   TRACE_LINE (v_src, v_dest, dont_ignore_monsters, pEdict, &tr);
   if ((tr.flFraction != 1.0)
       && (strcmp ("func_breakable", STRING (tr.pHit->v.classname)) == 0)
       && IsShootableBreakable (tr.pHit))
   {
      pBot->vecBreakable = tr.vecEndPos;
      return (TRUE);
   }

   pBot->pShootBreakable = NULL;
   pBot->vecBreakable = g_vecZero;
   return (FALSE);
}


void BotFindItem (bot_t *pBot)
{
   // Finds Items to collect or use in the near of a Bot

   edict_t *pEdict = pBot->pEdict;
   edict_t *pent = NULL;
   edict_t *pPickupEntity = NULL;
   edict_t *pTrigger = NULL;
   bool bItemExists;
   Vector vecPosition;
   int iPickType = 0;
   Vector pickup_origin;
   Vector entity_origin;
   bool bCanPickup;
   float distance;
   float min_distance = 9999;
   float rescue_distance;
   float rescue_min_distance = 9999;
   float fTraveltime;
   float fTimeMidBlowup;
   float fTimeBlowup;
   Vector vecEnd;
   int iIndex;
   int i;
   int h;

   // Don't try to pickup anything while on ladder...
   if (pBot->bOnLadder)
   {
      pBot->pBotPickupItem = NULL;
      pBot->iPickupType = PICKUP_NONE;
      return;
   }

   if (!FNullEnt (pBot->pBotPickupItem))
   {
      bItemExists = FALSE;
      pPickupEntity = pBot->pBotPickupItem;

      while (!FNullEnt (pent = FIND_ENTITY_IN_SPHERE (pent, pEdict->v.origin, 500)))
      {
         if (pent->v.effects & EF_NODRAW)
            continue; // someone owns this weapon or it hasn't respawned yet

         if (pent == pPickupEntity)
         {
            if (pent->v.absmin != g_vecZero)
               vecPosition = VecBModelOrigin (pent);
            else
               vecPosition = pent->v.origin;

            if (BotItemIsVisible (pBot, vecPosition, (char *) STRING (pent->v.classname)))
               bItemExists = TRUE;

            break;
         }
      }

      if (bItemExists)
         return;

      else
      {
         pBot->pBotPickupItem = NULL;
         pBot->iPickupType = PICKUP_NONE;
      }
   }

   pent = NULL;
   pPickupEntity = NULL;

   pBot->pBotPickupItem = NULL;
   pBot->iPickupType = PICKUP_NONE;
   
   while (!FNullEnt (pent = FIND_ENTITY_IN_SPHERE (pent, pEdict->v.origin, 500)))
   {
      bCanPickup = FALSE; // assume can't use it until known otherwise

      if ((pent->v.effects & EF_NODRAW) || (pent == pBot->pItemIgnore))
         continue; // someone owns this weapon or it hasn't respawned yet

      // see if this is a "func_" type of entity (func_button, etc.)...
      if (pent->v.absmin != g_vecZero)
         entity_origin = VecBModelOrigin (pent);
      else
         entity_origin = pent->v.origin;
      
      vecEnd = entity_origin;
      
      // check if line of sight to object is not blocked (i.e. visible)
      if (BotItemIsVisible (pBot, vecEnd, (char *) STRING (pent->v.classname)))
      {
         if ((strcmp ("hostage_entity", STRING (pent->v.classname)) == 0)
                  && (pent->v.velocity == g_vecZero))
         {
            bCanPickup = TRUE;
            iPickType = PICKUP_HOSTAGE;
         }

         else if (strcmp ("weapon_shield", STRING (pent->v.classname)) == 0)
         {
            bCanPickup = TRUE;
            iPickType = PICKUP_SHIELD;
         }

         else if (((strcmp ("weaponbox", STRING (pent->v.classname)) == 0)
                   || (strcmp ("armoury_entity", STRING (pent->v.classname)) == 0))
                  && (strcmp ("models/w_backpack.mdl", STRING (pent->v.model)) != 0)
                  && !pBot->bUsingGrenade && !BotHasShield (pBot))
         {
            bCanPickup = TRUE;
            iPickType = PICKUP_WEAPON;
         }

         else if ((strcmp ("weaponbox", STRING (pent->v.classname)) == 0)
                  && (strcmp ("models/w_backpack.mdl", STRING (pent->v.model)) == 0))
         {
            bCanPickup = TRUE;
            iPickType = PICKUP_DROPPED_C4;
         }

         else if ((strcmp ("grenade", STRING (pent->v.classname)) == 0)
                  && (strcmp ("models/w_c4.mdl", STRING (pent->v.model)) == 0))
         {
            bCanPickup = TRUE;
            iPickType = PICKUP_PLANTED_C4;
         }
         else if ((strcmp ("item_thighpack", STRING (pent->v.classname)) == 0)
                  && (pBot->bot_team == TEAM_CS_COUNTER) && !pBot->b_has_defuse_kit)
         {
            bCanPickup = TRUE;
            iPickType = PICKUP_DEFUSEKIT;
         }
      }

      // if the bot found something it can pickup...
      if (bCanPickup)
      {
         distance = (entity_origin - pEdict->v.origin).Length ();

         // see if it's the closest item so far...
         if (distance < min_distance)
         {
            // Found weapon on ground ?
            if (iPickType == PICKUP_WEAPON)
            {
               if (pBot->bIsVIP || (BotRateGroundWeapon (pBot, pent) <= 0)
                   || (pBot->iNumWeaponPickups > g_iMaxWeaponPickup))
                  bCanPickup = FALSE;
            }

            // Found shield on ground ? (code courtesy of Wei Mingzhi)
            else if (iPickType == PICKUP_SHIELD)
            {
               if ((pEdict->v.weapons & (1 << CS_WEAPON_ELITE))
                   || BotHasShield (pBot) || pBot->bIsVIP
                   || (BotHasPrimaryWeapon (pBot) && (BotRateGroundWeapon (pBot, pent) <= 0)))
                  bCanPickup = FALSE;
            }

            // Terrorist Team specific
            else if (pBot->bot_team == TEAM_CS_TERRORIST)
            {
               if (iPickType == PICKUP_HOSTAGE)
               {
                  if (g_bHostageRescued)
                  {
                     if (RANDOM_LONG (1, 100) > 20)
                        bCanPickup = FALSE;
                  }
                  else
                  {
                     if (pBot->f_hostage_check_time + 10.0 < gpGlobals->time)
                     {
                        // Check if Hostage has been moved away
                        for (i = 0; i < g_iNumWaypoints; i++)
                        {
                           if (paths[i]->flags & W_FL_GOAL)
                           {
                              rescue_distance = (paths[i]->origin - entity_origin).Length ();

                              if (rescue_distance < rescue_min_distance)
                              {
                                 rescue_min_distance = rescue_distance;

                                 if (rescue_min_distance < 300)
                                 {
                                    bCanPickup = FALSE;
                                    break;
                                 }
                              }
                           }
                        }

                        pBot->f_hostage_check_time = gpGlobals->time;
                     }
                     else
                        bCanPickup = FALSE;
                  }

                  if (bCanPickup)
                  {
                     for (i = 0; i < gpGlobals->maxClients; i++)
                     {
                        if (bots[i].is_used && !bots[i].bDead)
                        {
                           for (h = 0; h < MAX_HOSTAGES; h++)
                           {
                              if (bots[i].pHostages[h] == pent)
                              {
                                 bCanPickup = FALSE;
                                 break;
                              }
                           }
                        }
                     }
                  }
               }

               else if (iPickType == PICKUP_PLANTED_C4)
               {
                  bCanPickup = FALSE;

                  if (!pBot->bDefendedBomb)
                  {
                     pBot->bDefendedBomb = TRUE;

                     iIndex = BotFindDefendWaypoint (pBot, entity_origin);
                     fTraveltime = GetTravelTime (pBot->pEdict->v.maxspeed, pEdict->v.origin, paths[iIndex]->origin);
                     fTimeMidBlowup = g_fTimeBombPlanted + (CVAR_GET_FLOAT ("mp_c4timer") / 2) - fTraveltime;

                     if (fTimeMidBlowup > gpGlobals->time)
                     {
                        // Remove any Move Tasks
                        BotRemoveCertainTask (pBot, TASK_MOVETOPOSITION);

                        // Push camp task on to stack
                        bottask_t TempTask = {NULL, NULL, TASK_CAMP, TASKPRI_CAMP, -1, fTimeMidBlowup, TRUE};
                        BotPushTask (pBot, &TempTask);

                        // Push Move Command
                        TempTask.iTask = TASK_MOVETOPOSITION;
                        TempTask.fDesire = TASKPRI_MOVETOPOSITION;
                        TempTask.iData = iIndex;
                        BotPushTask (pBot, &TempTask);

                        pBot->iCampButtons|=IN_DUCK;
                     }
                     else
                        BotPlayRadioMessage (pBot, RADIO_SHESGONNABLOW); // Issue an additional Radio Message
                  }
               }
            }

            // CT Team specific
            else
            {
               if (iPickType == PICKUP_HOSTAGE)
               {
                  for (i = 0; i < gpGlobals->maxClients; i++)
                  {
                     if (bots[i].is_used && !bots[i].bDead)
                     {
                        for (h = 0; h < MAX_HOSTAGES; h++)
                        {
                           if (bots[i].pHostages[h] == pent)
                           {
                              bCanPickup = FALSE;
                              break;
                           }
                        }
                     }
                  }
               }

               else if (iPickType == PICKUP_PLANTED_C4)
               {
                  edict_t *pPlayer;

                  // search the world for players...
                  for (i = 0; i < gpGlobals->maxClients; i++)
                  {
                     if (!clients[i].IsUsed
                         || !clients[i].IsAlive
                         || (clients[i].iTeam != pBot->bot_team)
                         || (clients[i].pEdict == pEdict))
                        continue;

                     pPlayer = clients[i].pEdict;

                     // find the distance to the target waypoint
                     if ((pPlayer->v.origin - entity_origin).Length() < 60)
                     {
                        bCanPickup = FALSE;

                        if (!pBot->bDefendedBomb)
                        {
                           pBot->bDefendedBomb = TRUE;

                           iIndex = BotFindDefendWaypoint (pBot, entity_origin);
                           fTraveltime = GetTravelTime (pBot->pEdict->v.maxspeed, pEdict->v.origin, paths[iIndex]->origin);
                           fTimeBlowup = g_fTimeBombPlanted + CVAR_GET_FLOAT ("mp_c4timer") - fTraveltime;

                           // Remove any Move Tasks
                           BotRemoveCertainTask (pBot, TASK_MOVETOPOSITION);

                           // Push camp task on to stack
                           bottask_t TempTask = {NULL, NULL, TASK_CAMP, TASKPRI_CAMP, -1, fTimeBlowup, TRUE};

                           BotPushTask (pBot, &TempTask);

                           // Push Move Command
                           TempTask.iTask = TASK_MOVETOPOSITION;
                           TempTask.fDesire = TASKPRI_MOVETOPOSITION;
                           TempTask.iData = iIndex;
                           BotPushTask (pBot, &TempTask);
                           pBot->iCampButtons |= IN_DUCK;

                           return;
                        }
                     }
                  }
               }

               else if (iPickType == PICKUP_DROPPED_C4)
               {
                  pBot->pItemIgnore = pent;
                  bCanPickup = FALSE;

                  if ((pBot->pEdict->v.health < 50) || (RANDOM_LONG (1, 100) > 50))
                  {
                     // Push camp task on to stack
                     bottask_t TempTask = {NULL, NULL, TASK_CAMP, TASKPRI_CAMP, -1, gpGlobals->time + RANDOM_FLOAT (20.0, 40.0), TRUE};
                     BotPushTask (pBot, &TempTask);

                     // Push Move Command
                     TempTask.iTask = TASK_MOVETOPOSITION;
                     TempTask.fDesire = TASKPRI_MOVETOPOSITION;
                     TempTask.iData = BotFindDefendWaypoint (pBot, entity_origin);
                     BotPushTask (pBot, &TempTask);
                     pBot->iCampButtons |= IN_DUCK;

                     return;
                  }
               }
            }

            if (bCanPickup)
            {
               min_distance = distance; // update the minimum distance
               pPickupEntity = pent; // remember this entity
               pickup_origin = entity_origin; // remember location of entity
               pBot->iPickupType = iPickType;
            }
            else
               iPickType = PICKUP_NONE;
         }
      }
   } // end while loop

   if (pPickupEntity != NULL)
   {
      for (i = 0; i < gpGlobals->maxClients; i++)
      {
         if (bots[i].is_used && !bots[i].bDead)
         {
            if (bots[i].pBotPickupItem == pPickupEntity)
            {
               pBot->pBotPickupItem = NULL;
               pBot->iPickupType = PICKUP_NONE;

               return;
            }
         }
      }

      // Check if Item is too high to reach
      if (pickup_origin.z > (pEdict->v.origin + pEdict->v.view_ofs).z)
      {
         pBot->pBotPickupItem = NULL;
         pBot->iPickupType = PICKUP_NONE;

         return;
      }

      // Check if getting the item would hurt Bot      
      if (IsDeadlyDrop (pBot, pickup_origin))
      {
         pBot->pBotPickupItem = NULL;
         pBot->iPickupType = PICKUP_NONE;

         return;
      }

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


void BotChangePitch (bot_t *pBot, float speed)
{
   edict_t *pEdict = pBot->pEdict;
   float ideal;
   float current;
   float current_180; // current +/- 180 degrees
   float diff;
   float fOld = 0;

   UTIL_ClampAngle (&pEdict->v.idealpitch);

   if (g_bInstantTurns)
   {
      pEdict->v.v_angle.x = pEdict->v.idealpitch;
      return;
   }

   // turn from the current v_angle pitch to the idealpitch by selecting
   // the quickest way to turn to face that direction
   current = pEdict->v.v_angle.x;
   ideal = pEdict->v.idealpitch;

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

   // 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)

   // Sum up old Pitch Changes
   fOld += pBot->rgfPitchHistory[0];
   fOld += pBot->rgfPitchHistory[1];

   fOld += speed;
   fOld /= 3;

   pBot->rgfPitchHistory[1] = pBot->rgfPitchHistory[0];
   pBot->rgfPitchHistory[0] = speed;

   speed = fOld;

   // 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)
         speed = -speed;
   }
   else if ((current >= 0) && (ideal < 0))
   {
      current_180 = current - 180;

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

   current += speed;
   pEdict->v.v_angle.x = current;
}



void BotChangeYaw (bot_t *pBot, float speed)
{
   edict_t *pEdict = pBot->pEdict;
   float ideal;
   float current;
   float current_180; // current +/- 180 degrees
   float diff;
   float fOld = 0;

   UTIL_ClampAngle (&pEdict->v.ideal_yaw);
   if (g_bInstantTurns)
   {
      pEdict->v.v_angle.y = pEdict->v.ideal_yaw;
      return;
   }

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

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

   // 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)

   // Sum up old Yaw Changes
   fOld += pBot->rgfYawHistory[0];
   fOld += pBot->rgfYawHistory[1];

   fOld += speed;
   fOld /= 3;

   pBot->rgfYawHistory[1] = pBot->rgfYawHistory[0];
   pBot->rgfYawHistory[0] = speed;

   speed = fOld;

   // 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)
         speed = -speed;
   }
   else if ((current >= 0) && (ideal < 0))
   {
      current_180 = current - 180;

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

   current += speed;
   pEdict->v.v_angle.y = current;
}


bool BotFindWaypoint (bot_t *pBot)
{
   // Finds a Waypoint in the near of the Bot if he lost his Path or if Pathfinding needs
   // to be started over again

   int i, c, wpt_index[3], select_index;
   int covered_wpt = -1;
   float distance, min_distance[3];
   Vector v_src, v_dest;
   TraceResult tr;
   bool bWaypointInUse;
   bot_t *pOtherBot;
   edict_t *pEdict = pBot->pEdict;
   bool bHasHostage = BotHasHostage (pBot);

   for (i = 0; i < 3; i++)
   {
      wpt_index[i] = -1;
      min_distance[i] = 9999.0;
   }

   for (i = 0; i < g_iNumWaypoints; i++)
   {
      // ignore current waypoint and previous recent waypoints...
      if ((i == pBot->curr_wpt_index)
          || (i == pBot->prev_wpt_index[0])
          || (i == pBot->prev_wpt_index[1])
          || (i == pBot->prev_wpt_index[2])
          || (i == pBot->prev_wpt_index[3])
          || (i == pBot->prev_wpt_index[4]))
         continue;

      if (WaypointReachable (pEdict->v.origin, paths[i]->origin, pEdict))
      {
         // Don't use duck Waypoints if Bot got a hostage
         if (bHasHostage && (paths[i]->flags & W_FL_NOHOSTAGE))
            continue;

         bWaypointInUse = FALSE;

         // check if this Waypoint is already in use by another bot
         for (c = 0; c < gpGlobals->maxClients; c++)
         {
            pOtherBot = &bots[c];

            if (pOtherBot->is_used && !pOtherBot->bDead
                && (pOtherBot != pBot) && (pOtherBot->curr_wpt_index == i))
            {
               covered_wpt = i;
               bWaypointInUse = TRUE;
               break;
            }
         }

         if (bWaypointInUse)
            continue;

         distance = (paths[i]->origin - pEdict->v.origin).Length ();

         if (distance < min_distance[0])
         {
            wpt_index[0] = i;
            min_distance[0] = distance;
         }
         else if (distance < min_distance[1])
         {
            wpt_index[1] = i;
            min_distance[1] = distance;
         }
         else if (distance < min_distance[2])
         {
            wpt_index[2] = i;
            min_distance[2] = distance;
         }
      }
   }

   select_index = -1;

   if (wpt_index[2] != -1)
      i = RANDOM_LONG (0, 2);
   else if (wpt_index[1] != -1)
      i = RANDOM_LONG (0, 1);
   else if (wpt_index[0] != -1)
      i = 0;
   else if (covered_wpt != -1)
   {
      wpt_index[0] = covered_wpt;
      i = 0;
   }
   else
   {
      wpt_index[0] = RANDOM_LONG (0, g_iNumWaypoints - 1);
      i = 0;
   }

   select_index = wpt_index[i];

   pBot->prev_wpt_index[4] = pBot->prev_wpt_index[3];
   pBot->prev_wpt_index[3] = pBot->prev_wpt_index[2];
   pBot->prev_wpt_index[2] = pBot->prev_wpt_index[1];
   pBot->prev_wpt_index[1] = pBot->prev_wpt_index[0];
   pBot->prev_wpt_index[0] = pBot->curr_wpt_index;

   pBot->curr_wpt_index = select_index;
   pBot->f_wpt_timeset = gpGlobals->time;

   return (TRUE);
}


inline void GetValidWaypoint (bot_t *pBot)
{
   // Checks if the last Waypoint the Bot was heading for is still valid

   edict_t *pEdict = pBot->pEdict;

   // If Bot hasn't got a Waypoint we need a new one anyway
   if (pBot->curr_wpt_index == -1)
   {
      DeleteSearchNodes (pBot);
      BotFindWaypoint (pBot);
      pBot->wpt_origin = paths[pBot->curr_wpt_index]->origin;

      // FIXME
      // Do some error checks if we got a waypoint
   }

   // If time to get there expired get new one as well
   else if ((pBot->f_wpt_timeset + 3.0 < gpGlobals->time) && FNullEnt (pBot->pBotEnemy))
   {
      DeleteSearchNodes (pBot);
      BotFindWaypoint (pBot);
      pBot->wpt_origin = paths[pBot->curr_wpt_index]->origin;
   }
}


void CTBombPointClear (int iIndex)
{
   // Little Helper routine to tell that a Bomb Waypoint just got visited

   int i;

   for (i = 0; i < MAXNUMBOMBSPOTS; i++)
   {
      if (g_rgiBombSpotsVisited[i] == -1)
      {
         g_rgiBombSpotsVisited[i] = iIndex;
         return;
      }

      else if (g_rgiBombSpotsVisited[i] == iIndex)
         return;
   }
}


bool WasBombPointVisited (int iIndex)
{
   // Little Helper routine to check if a Bomb Waypoint got visited

   int i;

   for (i = 0; i < MAXNUMBOMBSPOTS; i++)
   {
      if (g_rgiBombSpotsVisited[i] == -1)
         return (FALSE);

      else if (g_rgiBombSpotsVisited[i] == iIndex)
         return (TRUE);
   }

   return (FALSE);
}


Vector GetBombPosition (void)
{
   // Stores the Bomb Position as a Vector

   Vector vecBomb = g_vecZero;
   edict_t *pent = NULL;

   while (!FNullEnt (pent = FIND_ENTITY_BY_STRING (pent, "classname", "grenade")))
   {
      if (FStrEq (STRING (pent->v.model), "models/w_c4.mdl"))
      {
         vecBomb = pent->v.origin;
         break;
      }
   }

   assert (vecBomb != g_vecZero);
   return (vecBomb);
}


bool BotHearsBomb (Vector vecBotPos)
{
   // Returns if Bomb is hearable and if so the exact Position as a Vector

   if (g_vecBomb == g_vecZero)
      g_vecBomb = GetBombPosition ();

   if ((vecBotPos - g_vecBomb).Length () < BOMBMAXHEARDISTANCE)
      return (TRUE);

   return (FALSE);
}


int BotChooseBombWaypoint (bot_t *pBot)
{
   // Finds the Best Goal (Bomb) Waypoint for CTs when searching for a planted Bomb

   float min_distance = 9999.0;
   float act_distance;
   int iGoal = 0;
   int iCount = 0;
   int i;
   edict_t *pent = NULL;
   edict_t *pEdict = pBot->pEdict;
   Vector vecPosition;

   if (BotHearsBomb (pEdict->v.origin))
      vecPosition = g_vecBomb;
   else
      vecPosition = pEdict->v.origin;

   // Find nearest Goal Waypoint either to Bomb (if "heard" or Player)
   for (i = 0; i < g_iNumGoalPoints; i++)
   {
      act_distance = (paths[g_rgiGoalWaypoints[i]]->origin - vecPosition).Length ();

      if (act_distance < min_distance)
      {
         min_distance = act_distance;
         iGoal = g_rgiGoalWaypoints[i];
      }
   }

   while (WasBombPointVisited (iGoal))
   {
      if (g_iNumGoalPoints == 1)
         iGoal = g_rgiGoalWaypoints[0];
      else
         iGoal = g_rgiGoalWaypoints[RANDOM_LONG (0, g_iNumGoalPoints - 1)];

      iCount++;

      if (iCount == g_iNumGoalPoints)
         break;
   }

   return (iGoal);
}


int BotFindDefendWaypoint (bot_t *pBot, Vector vecPosition)
{
   // Tries to find a good waypoint which
   // a) has a Line of Sight to vecPosition and
   // b) provides enough cover
   // c) is far away from the defending position

   int wpt_index[8];
   int min_distance[8];
   int iDistance, i;
   float fMin = 9999.0;
   float fPosMin = 9999.0;
   float fDistance;
   int iSourceIndex;
   int iPosIndex;
   edict_t *pEdict = pBot->pEdict;
   TraceResult tr;
   Vector vecSource;
   Vector vecDest;
   int iExp;
   int index1;
   int index2;
   int tempindex;
   bool bOrderchange;

   for (i = 0; i < 8; i++)
   {
      wpt_index[i] = -1;
      min_distance[i] = 128;
   }

   // Get nearest waypoint to Bot & Position
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      fDistance = (paths[i]->origin - pEdict->v.origin).Length ();

      if (fDistance < fMin)
      {
         iSourceIndex = i;
         fMin = fDistance;
      }

      fDistance = (paths[i]->origin - vecPosition).Length ();
      if (fDistance < fPosMin)
      {
         iPosIndex = i;
         fPosMin = fDistance;
      }
   }

   vecDest = paths[iPosIndex]->origin;

   // Find Best Waypoint now
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      // Exclude Ladder, Goal, Rescue & current Waypoints
      if ((paths[i]->flags & W_FL_LADDER) || (i == iSourceIndex) || !WaypointIsVisible (i, iPosIndex))
         continue;

      // Use the 'real' Pathfinding Distances
      iDistance = GetPathDistance (iSourceIndex, i);

      if (iDistance < 1024)
      {
         vecSource = paths[i]->origin;
         UTIL_TraceLine (vecSource, vecDest, ignore_monsters, ignore_glass, pEdict, &tr);
         if (tr.flFraction != 1.0)
            continue;

         if (iDistance > min_distance[0])
         {
            wpt_index[0] = i;
            min_distance[0] = iDistance;
         }
         else if (iDistance > min_distance[1])
         {
            wpt_index[1] = i;
            min_distance[1] = iDistance;
         }
         else if (iDistance > min_distance[2])
         {
            wpt_index[2] = i;
            min_distance[2] = iDistance;
         }
         else if (iDistance > min_distance[3])
         {
            wpt_index[3] = i;
            min_distance[3] = iDistance;
         }
         else if (iDistance > min_distance[4])
         {
            wpt_index[4] = i;
            min_distance[4] = iDistance;
         }
         else if (iDistance > min_distance[5])
         {
            wpt_index[5] = i;
            min_distance[5] = iDistance;
         }
         else if (iDistance > min_distance[6])
         {
            wpt_index[6] = i;
            min_distance[6] = iDistance;
         }
         else if (iDistance > min_distance[7])
         {
            wpt_index[7] = i;
            min_distance[7] = iDistance;
         }
      }
   }

   // Use statistics if we have them...
   if (g_bUseExperience)
   {
      for (i = 0; i < 8; i++)
      {
         if (wpt_index[i] != -1)
         {
            if (pBot->bot_team == TEAM_CS_TERRORIST)
            {
               iExp = (pBotExperienceData + (wpt_index[i] * g_iNumWaypoints) + wpt_index[i])->uTeam0Damage;
               iExp = (iExp * 100) / 240;
               min_distance[i] = (iExp * 100) / 8192;
               min_distance[i] += iExp;
               iExp >>= 1;
               iExp = (pBotExperienceData + (wpt_index[i] * g_iNumWaypoints) + wpt_index[i])->uTeam1Damage;
               iExp = (iExp * 100) / 240;
               min_distance[i] = (iExp * 100) / 8192;
               min_distance[i] += iExp;
            }
            else
            {
               iExp = (pBotExperienceData + (wpt_index[i] * g_iNumWaypoints) + wpt_index[i])->uTeam1Damage;
               iExp = (iExp * 100) / 240;
               min_distance[i] = (iExp * 100) / 8192;
               min_distance[i] += iExp;
               iExp >>= 1;
               iExp = (pBotExperienceData + (wpt_index[i] * g_iNumWaypoints) + wpt_index[i])->uTeam0Damage;
               iExp = (iExp * 100) / 240;
               min_distance[i] = (iExp * 100) / 8192;
               min_distance[i] += iExp;
            }
         }
      }
   }

   // If not use old method of relying on the wayzone radius
   else
   {
      for (i = 0; i < 8; i++)
         if (wpt_index[i] != -1)
            min_distance[i] -= paths[wpt_index[i]]->Radius * 3;
   }

   // Sort resulting Waypoints for farest distance
   do
   {
      bOrderchange = FALSE;

      for (i = 0; i < 3; i++)
      {
         index1 = wpt_index[i];
         index2 = wpt_index[i + 1];

         if ((index1 != -1) && (index2 != -1))
         {
            if (min_distance[i] > min_distance[i + 1])
            {
               tempindex = wpt_index[i];
               wpt_index[i] = wpt_index[i + 1];
               wpt_index[i + 1] = tempindex;
               tempindex = min_distance[i];
               min_distance[i] = min_distance[i + 1];
               min_distance[i + 1] = tempindex;
               bOrderchange = TRUE;
            }
         }
      }
   }
   while (bOrderchange);

   for (i = 0; i < 8; i++)
   {
      if (wpt_index[i] != -1)
         return (wpt_index[i]);
   }

   // Worst case: If no waypoint was found, just use a random one
   return (RANDOM_LONG (0, g_iNumWaypoints - 1));
}


int BotFindCoverWaypoint (bot_t *pBot, float maxdistance)
{
   // Tries to find a good Cover Waypoint if Bot wants to hide

   int i, j;
   int wpt_index[8];
   int distance,enemydistance, iEnemyWPT;
   int rgiEnemyIndices[MAX_PATH_INDEX];
   int iEnemyNeighbours = 0;
   int min_distance[8];
   float f_min = 9999.0;
   float f_enemymin = 9999.0;
   float f_distance;
   Vector vecEnemy = pBot->vecLastEnemyOrigin;
   edict_t *pEdict = pBot->pEdict;
   int iSourceIndex = pBot->curr_wpt_index;
   bool bNeighbourVisible;
   int iExp;
   int index1;
   int index2;
   int tempindex;
   bool bOrderchange;
   Vector v_source;
   Vector v_dest;
   TraceResult tr;

   // Get Enemies Distance
   // FIXME: Would be better to use the actual Distance returned
   // from Pathfinding because we might get wrong distances here
   f_distance = (vecEnemy- pEdict->v.origin).Length ();

   // Don't move to a position nearer the enemy
   if (maxdistance > f_distance)
      maxdistance = f_distance;
   if (maxdistance < 300)
      maxdistance = 300;

   for (i = 0; i < 8; i++)
   {
      wpt_index[i] = -1;
      min_distance[i] = maxdistance;
   }

   // Get nearest waypoint to enemy & Bot
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      f_distance = (paths[i]->origin - pEdict->v.origin).Length ();

      if (f_distance < f_min)
      {
         iSourceIndex = i;
         f_min = f_distance;
      }

      f_distance = (paths[i]->origin - vecEnemy).Length ();

      if (f_distance < f_enemymin)
      {
         iEnemyWPT = i;
         f_enemymin = f_distance;
      }
   }

   // Now Get Enemies Neigbouring Waypoints
   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      if (paths[iEnemyWPT]->index[i] != -1)
      {
         rgiEnemyIndices[iEnemyNeighbours] = paths[iEnemyWPT]->index[i];
         iEnemyNeighbours++;
      }
   }

   pBot->curr_wpt_index = iSourceIndex;

   // Find Best Waypoint now
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      // Exclude Ladder, current Waypoints
      if ((paths[i]->flags & W_FL_LADDER) || (i == iSourceIndex) || WaypointIsVisible (iEnemyWPT, i))
         continue;

      // Now check neighbour Waypoints for Visibility
      bNeighbourVisible = FALSE;
      for (j = 0; j < iEnemyNeighbours; j++)
      {
         if (WaypointIsVisible (rgiEnemyIndices[j], i))
         {
            bNeighbourVisible = TRUE;
            break;
         }
      }

      if (bNeighbourVisible)
         continue;

      // Use the 'real' Pathfinding Distances
      distance = GetPathDistance (iSourceIndex, i);
      enemydistance = GetPathDistance (iEnemyWPT, i);

      if (distance < enemydistance)
      {
         if (distance < min_distance[0])
         {
            wpt_index[0] = i;
            min_distance[0] = distance;
         }
         else if (distance < min_distance[1])
         {
            wpt_index[1] = i;
            min_distance[1] = distance;
         }
         else if (distance < min_distance[2])
         {
            wpt_index[2] = i;
            min_distance[2] = distance;
         }
         else if (distance < min_distance[3])
         {
            wpt_index[3] = i;
            min_distance[3] = distance;
         }
         else if (distance < min_distance[4])
         {
            wpt_index[4] = i;
            min_distance[4] = distance;
         }
         else if (distance < min_distance[5])
         {
            wpt_index[5] = i;
            min_distance[5] = distance;
         }
         else if (distance < min_distance[6])
         {
            wpt_index[6] = i;
            min_distance[6] = distance;
         }
         else if (distance < min_distance[7])
         {
            wpt_index[7] = i;
            min_distance[7] = distance;
         }
      }
   }

   // Use statistics if we have them...
   if (g_bUseExperience)
   {
      if (pBot->bot_team == TEAM_CS_TERRORIST)
      {
         for (i = 0; i < 8; i++)
         {
            if (wpt_index[i]!= -1)
            {
               iExp = (pBotExperienceData + (wpt_index[i] * g_iNumWaypoints) + wpt_index[i])->uTeam0Damage;
               iExp = (iExp * 100) / 240;
               min_distance[i] = (iExp * 100) / 8192;
               min_distance[i] += iExp;
               iExp >>= 1;
            }
         }
      }
      else
      {
         for (i = 0; i < 8; i++)
         {
            if (wpt_index[i]!= -1)
            {
               iExp = (pBotExperienceData + (wpt_index[i] * g_iNumWaypoints) + wpt_index[i])->uTeam1Damage;
               iExp = (iExp * 100) / 240;
               min_distance[i] = (iExp * 100) / 8192;
               min_distance[i] += iExp;
               iExp >>= 1;
            }
         }
      }

   }

   // If not use old method of relying on the wayzone radius
   else
   {
      for (i = 0; i < 8; i++)
         if (wpt_index[i] != -1)
            min_distance[i] += paths[wpt_index[i]]->Radius * 3;
   }

   // Sort resulting Waypoints for nearest distance
   do
   {
      bOrderchange = FALSE;
      for (i = 0; i < 3; i++)
      {
         index1 = wpt_index[i];
         index2 = wpt_index[i + 1];

         if ((index1 != -1) && (index2 != -1))
         {
            if (min_distance[i] > min_distance[i + 1])
            {
               tempindex = wpt_index[i];
               wpt_index[i] = wpt_index[i + 1];
               wpt_index[i + 1] = tempindex;
               tempindex = min_distance[i];
               min_distance[i] = min_distance[i + 1];
               min_distance[i + 1] = tempindex;
               bOrderchange = TRUE;
            }
         }
      }
   }
   while (bOrderchange);

   // Take the first one which isn't spotted by the enemy
   for (i = 0; i < 8; i++)
   {
      if (wpt_index[i] != -1)
      {
         v_source = pBot->vecLastEnemyOrigin + Vector (0, 0, 36);
         v_dest = paths[wpt_index[i]]->origin;

         UTIL_TraceLine (v_source, v_dest, ignore_monsters, ignore_glass, pEdict, &tr);
         if (tr.flFraction < 1.0)
            return (wpt_index[i]);
      }
   }

   // If all are seen by the enemy, take the first one
   if (wpt_index[0] != -1)
      return (wpt_index[0]);

   // Worst case: if no waypoint was found, just use a random one
   return (RANDOM_LONG (0, g_iNumWaypoints - 1));
}


inline bool IsConnectedWithWaypoint (int a, int b)
{
   // Checks if Waypoint A has a Connection to Waypoint Nr. B

   int ix;

   for (ix = 0; ix < MAX_PATH_INDEX; ix++)
      if (paths[a]->index[ix] == b)
         return (TRUE);

   return (FALSE);
}


bool GetBestNextWaypoint (bot_t *pBot)
{
   // Does a realtime postprocessing of Waypoints returned from
   // Pathfinding, to vary Paths and find best Waypoints on the way

   int iNextWaypointIndex = pBot->pWaypointNodes->NextNode->iIndex;
   int iCurrentWaypointIndex = pBot->pWaypointNodes->iIndex;
   int iPrevWaypointIndex = pBot->curr_wpt_index;
   int ix = 0;
   int iUsedWaypoints[32];
   int num;
   int c;
   edict_t *pEdict = pBot->pEdict;
   bot_t *pOtherBot;
   bool bWaypointInUse = FALSE;

   // Get waypoints used by other Bots
   for (c = 0; c < gpGlobals->maxClients; c++)
   {
      pOtherBot = &bots[c];

      if (pOtherBot->is_used && !pOtherBot->bDead && (pOtherBot != pBot))
         iUsedWaypoints[c] = pOtherBot->curr_wpt_index;
   }

   for (c = 0; c < gpGlobals->maxClients; c++)
   {
      if (iUsedWaypoints[c] == iCurrentWaypointIndex)
      {
         bWaypointInUse = TRUE;
         break;
      }
   }

   if (bWaypointInUse)
   {
      while (ix < MAX_PATH_INDEX)
      {
         num = paths[iPrevWaypointIndex]->index[ix];
         if (num != -1)
         {
            if (IsConnectedWithWaypoint (num, iNextWaypointIndex)
                && IsConnectedWithWaypoint (num, iPrevWaypointIndex))
            {
               // Don't use ladder waypoints as alternative
               if (paths[num]->flags & W_FL_LADDER)
               {
                  ix++;
                  continue;
               }

               // check if this Waypoint is already in use by another bot
               bWaypointInUse = FALSE;

               for (c = 0; c < gpGlobals->maxClients; c++)
               {
                  if (iUsedWaypoints[c] == num)
                  {
                     bWaypointInUse = TRUE;
                     break;
                  }
               }

               // Waypoint not used by another Bot - feel free to use it
               if (!bWaypointInUse)
               {
                  pBot->pWaypointNodes->iIndex = num;
                  return (TRUE);
               }
            }
         }

         ix++;
      }
   }

   return (FALSE);
}


bool BotHeadTowardWaypoint (bot_t *pBot)
{
   // Advances in our Pathfinding list and sets the appropiate Destination Origins for this Bot

   int i, c;
   Vector v_src, v_dest;
   TraceResult tr;
   edict_t *pEdict = pBot->pEdict;
   int iWaypoint;
   float fKills;
   float fTime;
   PATH *p;
   bool will_jump;
   float jump_distance;

   // Check if old waypoints is still reliable
   GetValidWaypoint (pBot);

   // No Waypoints from pathfinding ?
   if (pBot->pWaypointNodes == NULL)
      return (FALSE);

   // Reset Travel Flags (jumping etc.)
   pBot->curr_travel_flags = 0;

   // Advance in List
   pBot->pWaypointNodes = pBot->pWaypointNodes->NextNode;

   // We're not at the end of the List ?
   if (pBot->pWaypointNodes != NULL)
   {
      // If in between a route, postprocess the waypoint (find better alternatives)
      if ((pBot->pWaypointNodes != pBot->pWayNodesStart)
          && (pBot->pWaypointNodes->NextNode != NULL))
      {
         GetBestNextWaypoint (pBot);

         pBot->fMinSpeed = pEdict->v.maxspeed;
         if ((BotGetSafeTask (pBot)->iTask == TASK_NORMAL) && !g_bBombPlanted)
         {
            pBot->iCampButtons = 0;
            iWaypoint = pBot->pWaypointNodes->NextNode->iIndex;

            if (pBot->bot_team == TEAM_CS_TERRORIST)
               fKills = (pBotExperienceData + (iWaypoint * g_iNumWaypoints) + iWaypoint)->uTeam0Damage;
            else
               fKills = (pBotExperienceData + (iWaypoint * g_iNumWaypoints) + iWaypoint)->uTeam1Damage;

            if ((fKills > 1) && (g_fTimeRoundMid > gpGlobals->time) && (g_cKillHistory > 0))
            {
               fKills = (fKills * 100) / g_cKillHistory;
               fKills /= 100;

               switch (pBot->bot_personality)
               {
               case 1: // Psycho
                  fKills /= 3;
                  break;
               default:
                  fKills /= 2;
               }

               if ((pBot->fBaseAgressionLevel < fKills)
                   && !((pBot->bot_team == TEAM_CS_COUNTER) && g_bBombPlanted))
               {
                  fTime = pBot->fFearLevel * (g_fTimeRoundMid - gpGlobals->time) * 0.5;

                  // Push camp task on to stack
                  bottask_t TempTask = {NULL, NULL, TASK_CAMP, TASKPRI_CAMP, -1, gpGlobals->time + fTime, TRUE};
                  BotPushTask (pBot, &TempTask);

                  // Push Move Command
                  TempTask.iTask = TASK_MOVETOPOSITION;
                  TempTask.fDesire = TASKPRI_MOVETOPOSITION;
                  TempTask.iData = BotFindDefendWaypoint (pBot, paths[iWaypoint]->origin);
                  BotPushTask (pBot, &TempTask);
                  pBot->iCampButtons |= IN_DUCK;
               }
            }
            else if (g_bBotsCanPause && !pBot->bOnLadder
                     && !pBot->bInWater && (pBot->curr_travel_flags == 0)
                     && (pEdict->v.flags & FL_ONGROUND))
            {
               if (fKills == pBot->fBaseAgressionLevel)
                  pBot->iCampButtons |= IN_DUCK;
               else if (RANDOM_LONG (1, 100) > pBot->bot_skill + RANDOM_LONG (1, 20))
                  pBot->fMinSpeed = 120.0;
            }
         }
      }

      if (pBot->pWaypointNodes != NULL)
      {
         // Find out about connection flags
         if (pBot->curr_wpt_index != -1)
         {
            p = paths[pBot->curr_wpt_index];

            for (i = 0; i < MAX_PATH_INDEX; i++)
            {
               if (p->index[i] == pBot->pWaypointNodes->iIndex)
               {
                  pBot->curr_travel_flags = p->connectflag[i];
                  pBot->vecDesiredVelocity = p->vecConnectVel[i];
                  pBot->bJumpDone = FALSE;
                  break;
               }
            }

            // Find out about FUTURE connection flags
            will_jump = FALSE;
            if (pBot->pWaypointNodes->NextNode != NULL)
            {
               for (i = 0; i < MAX_PATH_INDEX; i++)
               {
                  if ((paths[pBot->pWaypointNodes->iIndex]->index[i] == pBot->pWaypointNodes->NextNode->iIndex)
                      && (paths[pBot->pWaypointNodes->iIndex]->connectflag[i] & C_FL_JUMP))
                  {
                     will_jump = TRUE;
                     v_src = paths[pBot->pWaypointNodes->iIndex]->origin;
                     v_dest = paths[pBot->pWaypointNodes->NextNode->iIndex]->origin;
                     jump_distance = (paths[pBot->pWaypointNodes->iIndex]->origin - paths[pBot->pWaypointNodes->NextNode->iIndex]->origin).Length ();
                     break;
                  }
               }
            }

            // is there a jump waypoint right ahead and do we need to draw out the knife ?
            if (will_jump
                && ((jump_distance > 220)
                    || ((v_dest.z + 32 > v_src.z) && (jump_distance > 150))
                    || (((v_dest - v_src).Length2D () < 50) && (jump_distance > 60)))
                && FNullEnt (pBot->pBotEnemy))
               FakeClientCommand (pEdict, "weapon_knife\n"); // draw out the knife if needed

            // Bot not already on ladder ? but will be soon ?
            if (!pBot->bOnLadder && (paths[pBot->pWaypointNodes->iIndex]->flags & W_FL_LADDER))
            {
               // Get ladder waypoints used by other (first moving) Bots
               for (c = 0; c < gpGlobals->maxClients; c++)
               {
                  // If another Bot uses this ladder, wait 3 secs
                  if (bots[c].is_used && !bots[c].bDead && (&bots[c] != pBot)
                      && (bots[c].curr_wpt_index == pBot->pWaypointNodes->iIndex))
                  {
                     bottask_t TempTask = {NULL, NULL, TASK_PAUSE, TASKPRI_PAUSE, -1, gpGlobals->time + 3.0, FALSE};
                     BotPushTask (pBot, &TempTask);
                     return (TRUE);
                  }
               }
            }
         }

         pBot->curr_wpt_index = pBot->pWaypointNodes->iIndex;
      }
   }

   pBot->wpt_origin = paths[pBot->curr_wpt_index]->origin;

   // If wayzone radius != 0 vary origin a bit depending on body angles
   if (paths[pBot->curr_wpt_index]->Radius != 0)
   {
      MAKE_VECTORS (pEdict->v.angles);
      Vector v_x = pBot->wpt_origin + gpGlobals->v_right * RANDOM_FLOAT (-paths[pBot->curr_wpt_index]->Radius, paths[pBot->curr_wpt_index]->Radius);
      Vector v_y = pBot->wpt_origin + gpGlobals->v_forward * RANDOM_FLOAT (0, paths[pBot->curr_wpt_index]->Radius);
      pBot->wpt_origin = (v_x + v_y) / 2;
   }

   // Bot on Ladder ?
   if (pBot->bOnLadder)
   {
      v_src = pEdict->v.origin;
      v_src.z = pEdict->v.absmin.z;
      v_dest = pBot->wpt_origin;

      // Is the Bot inside a Ladder Cage ? Then we need to adjust
      // the Waypoint Origin to make sure Bot doesn't get stuck
      TRACE_LINE (v_src, v_dest, ignore_monsters, pEdict, &tr);
      if (tr.flFraction < 1.0)
         pBot->wpt_origin = pBot->wpt_origin + (pEdict->v.origin - pBot->wpt_origin) / 2 + Vector (0, 0, 32);

      // Uncomment this to have a temp line showing the direction to the destination waypoint
      //WaypointDrawBeam (pEdict->v.origin, pBot->wpt_origin, 10, 255, 255, 255);
      //WaypointDrawBeam (pBot->wpt_origin + Vector (0, 0, -20), pBot->wpt_origin + Vector (0, 0, 20), 10, 255, 255, 255);
   }

   pBot->f_wpt_timeset = gpGlobals->time;

   return (TRUE);
}


bool BotCantMoveForward (bot_t *pBot, Vector vNormal, TraceResult *tr)
{
   // Checks if Bot is blocked in his movement direction (excluding doors)
   // use some TraceLines to determine if anything is blocking the current path of the bot.

   edict_t *pEdict = pBot->pEdict;
   Vector v_src, v_forward, v_center;

   v_center = pEdict->v.angles;
   v_center.z = 0;
   v_center.x = 0;
   MAKE_VECTORS (v_center);

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

   v_src = pEdict->v.origin + pEdict->v.view_ofs; // EyePosition ()
   v_forward = v_src + vNormal * 24;

   // trace from the bot's eyes straight forward...
   TRACE_LINE (v_src, v_forward, ignore_monsters, pEdict, tr);

   // check if the trace hit something...
   if (tr->flFraction < 1.0)
   {
      if (strncmp ("func_door", STRING (tr->pHit->v.classname), 9) == 0)
         return (FALSE);

      return (TRUE); // bot's head will hit something
   }

   // bot's head is clear, check at shoulder level...
   // trace from the bot's shoulder left diagonal forward to the right shoulder...
   v_src = pEdict->v.origin + pEdict->v.view_ofs + Vector (0, 0, -16) + gpGlobals->v_right * -16;
   v_forward = pEdict->v.origin + pEdict->v.view_ofs + Vector (0, 0, -16) + gpGlobals->v_right * 16 + vNormal * 24;

   TRACE_LINE (v_src, v_forward, ignore_monsters, pEdict, tr);

   // check if the trace hit something...
   if ((tr->flFraction < 1.0) && (strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0))
      return (TRUE); // bot's body will hit something

   // bot's head is clear, check at shoulder level...
   // trace from the bot's shoulder right diagonal forward to the left shoulder...
   v_src = pEdict->v.origin + pEdict->v.view_ofs + Vector (0, 0, -16) + gpGlobals->v_right * 16;
   v_forward = pEdict->v.origin + pEdict->v.view_ofs + Vector (0, 0, -16) + gpGlobals->v_right * -16 + vNormal * 24;

   TRACE_LINE (v_src, v_forward, ignore_monsters, pEdict, tr);

   // check if the trace hit something...
   if ((tr->flFraction < 1.0) && (strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0))
      return (TRUE); // bot's body will hit something

   // Now check below Waist

   if (pEdict->v.flags & FL_DUCKING)
   {
      v_src = pEdict->v.origin + Vector (0, 0, -19 + 19);
      v_forward = v_src + Vector (0, 0, 10) + vNormal * 24;

      TRACE_LINE (v_src, v_forward, ignore_monsters, pEdict, tr);

      // check if the trace hit something...
      if ((tr->flFraction < 1.0) && (strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0))
         return (TRUE); // bot's body will hit something

      v_src = pEdict->v.origin + Vector (0, 0, -19 + 19);
      v_forward = v_src + vNormal * 24;

      TRACE_LINE (v_src, v_forward, ignore_monsters, pEdict, tr);

      // check if the trace hit something...
      if ((tr->flFraction < 1.0) && (strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0))
         return (TRUE); // bot's body will hit something
   }
   else
   {
      // Trace from the left Waist to the right forward Waist Pos
      v_src = pEdict->v.origin + Vector (0, 0, -17) + gpGlobals->v_right * -16;
      v_forward = pEdict->v.origin + Vector (0, 0, -17) + gpGlobals->v_right * 16 + vNormal * 24;

      // trace from the bot's waist straight forward...
      TRACE_LINE (v_src, v_forward, ignore_monsters, pEdict, tr);

      // check if the trace hit something...
      if ((tr->flFraction < 1.0) && (strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0))
         return (TRUE); // bot's body will hit something

      // Trace from the left Waist to the right forward Waist Pos
      v_src = pEdict->v.origin + Vector (0, 0, -17) + gpGlobals->v_right * 16;
      v_forward = pEdict->v.origin + Vector (0, 0, -17) + gpGlobals->v_right * -16 + vNormal * 24;

      TRACE_LINE (v_src, v_forward, ignore_monsters, pEdict, tr);

      // check if the trace hit something...
      if ((tr->flFraction < 1.0) && (strncmp ("func_door", STRING (tr->pHit->v.classname), 9) != 0))
         return (TRUE); // bot's body will hit something
   }

   return (FALSE); // bot can move forward, return (FALSE)
}


bool BotCanStrafeLeft (bot_t *pBot, TraceResult *tr)
{
   // Check if Bot can move sideways

   edict_t *pEdict = pBot->pEdict;
   Vector v_src;
   Vector v_left;

   MAKE_VECTORS (pEdict->v.v_angle);
   v_src = pEdict->v.origin;
   v_left = v_src + gpGlobals->v_right * -40;

   // trace from the bot's waist straight left...
   TRACE_LINE (v_src, v_left, ignore_monsters, pEdict, tr);

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

   v_left = v_left + gpGlobals->v_forward * 40;

   // trace from the strafe pos straight forward...
   TRACE_LINE (v_src, v_left, ignore_monsters, pEdict, tr);

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

   return (TRUE);
}


bool BotCanStrafeRight (bot_t *pBot, TraceResult *tr)
{
   // Check if Bot can move sideways

   edict_t *pEdict = pBot->pEdict;
   Vector v_src;
   Vector v_right;

   MAKE_VECTORS (pEdict->v.v_angle);
   v_src = pEdict->v.origin;
   v_right = v_src + gpGlobals->v_right * 40;

   // trace from the bot's waist straight right...
   TRACE_LINE (v_src, v_right, ignore_monsters, pEdict, tr);

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

   v_right = v_right + gpGlobals->v_forward * 40;

   // trace from the strafe pos straight forward...
   TRACE_LINE (v_src, v_right, ignore_monsters, pEdict, tr);

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

   return (TRUE);
}


bool BotCanJumpUp (bot_t *pBot, Vector vNormal)
{
   // Check if Bot can jump over some obstacle

   TraceResult tr;
   Vector v_jump;
   Vector v_source;
   Vector v_dest;
   edict_t *pEdict = pBot->pEdict;

   // Can't jump if not on ground and not on ladder/swimming
   if (!(pEdict->v.flags & (FL_ONGROUND | FL_PARTIALGROUND)) && (pBot->bOnLadder || !pBot->bInWater))
      return (FALSE);

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

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

   MAKE_VECTORS (v_jump);

   // Check for normal jump height first...

   v_source = pEdict->v.origin + Vector (0, 0, -36 + 45);
   v_dest = v_source + vNormal * 32;

   // trace a line forward at maximum jump height...
   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

   if (tr.flFraction < 1.0)
      goto CheckDuckJump;
   else
   {
      // now trace from jump height upward to check for obstructions...
      v_source = v_dest;
      v_dest.z = v_dest.z + 37;

      TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

      if (tr.flFraction < 1.0)
         return (FALSE);
   }

   // now check same height to one side of the bot...
   v_source = pEdict->v.origin + gpGlobals->v_right * 16 + Vector (0, 0, -36 + 45);
   v_dest = v_source + vNormal * 32;

   // trace a line forward at maximum jump height...
   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   // now trace from jump height upward to check for obstructions...
   v_source = v_dest;
   v_dest.z = v_dest.z + 37;

   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   // now check same height on the other side of the bot...
   v_source = pEdict->v.origin + (-gpGlobals->v_right * 16) + Vector (0, 0, -36 + 45);
   v_dest = v_source + vNormal * 32;

   // trace a line forward at maximum jump height...
   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   // now trace from jump height upward to check for obstructions...
   v_source = v_dest;
   v_dest.z = v_dest.z + 37;

   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   return (TRUE);

   // Here we check if a Duck Jump would work...
CheckDuckJump:
   // use center of the body first...

   // maximum duck jump height is 62, so check one unit above that (63)
   v_source = pEdict->v.origin + Vector (0, 0, -36 + 63);
   v_dest = v_source + vNormal * 32;

   // trace a line forward at maximum jump height...
   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

   if (tr.flFraction < 1.0)
      return (FALSE);
   else
   {
      // now trace from jump height upward to check for obstructions...
      v_source = v_dest;
      v_dest.z = v_dest.z + 37;

      TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   // now check same height to one side of the bot...
   v_source = pEdict->v.origin + gpGlobals->v_right * 16 + Vector (0, 0, -36 + 63);
   v_dest = v_source + vNormal * 32;

   // trace a line forward at maximum jump height...
   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   // now trace from jump height upward to check for obstructions...
   v_source = v_dest;
   v_dest.z = v_dest.z + 37;

   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   // now check same height on the other side of the bot...
   v_source = pEdict->v.origin + (-gpGlobals->v_right * 16) + Vector (0, 0, -36 + 63);
   v_dest = v_source + vNormal * 32;

   // trace a line forward at maximum jump height...
   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   // now trace from jump height upward to check for obstructions...
   v_source = v_dest;
   v_dest.z = v_dest.z + 37;

   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   return (TRUE);
}


bool BotCanDuckUnder (bot_t *pBot, Vector vNormal)
{
   // Check if Bot can duck under Obstacle

   TraceResult tr;
   Vector v_duck, v_baseheight,v_source, v_dest;
   edict_t *pEdict = pBot->pEdict;

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

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

   MAKE_VECTORS (v_duck);

   // use center of the body first...

   if (pEdict->v.flags & FL_DUCKING)
      v_baseheight = pEdict->v.origin + Vector (0, 0, -17);
   else
      v_baseheight = pEdict->v.origin;

   v_source = v_baseheight;
   v_dest = v_source + vNormal * 32;

   // trace a line forward at duck height...
   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   // now check same height to one side of the bot...
   v_source = v_baseheight + gpGlobals->v_right * 16;
   v_dest = v_source + vNormal * 32;

   // trace a line forward at duck height...
   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   // now check same height on the other side of the bot...
   v_source = v_baseheight + (-gpGlobals->v_right * 16);
   v_dest = v_source + vNormal * 32;

   // trace a line forward at duck height...
   TRACE_LINE (v_source, v_dest, ignore_monsters, pEdict, &tr);

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

   return (TRUE);
}


bool BotFollowUser (bot_t *pBot)
{
   // Check if Bot can still follow a User

   bool user_visible;
   float f_distance;

   if (FNullEnt (pBot->pBotUser))
      return (FALSE);

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

   edict_t *pEdict = pBot->pEdict;
   Vector vecUser;

   user_visible = FVisible (pBot->pBotUser->v.origin, pEdict);

   // 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 || (pBot->f_bot_use_time + 5 > gpGlobals->time))
   {
      if (user_visible)
         pBot->f_bot_use_time = gpGlobals->time; // reset "last visible time"

      Vector v_user = pBot->pBotUser->v.origin - pEdict->v.origin;
      f_distance = v_user.Length (); // how far away is the "user"?

      if (f_distance > 150)      // run if distance to enemy is far
         pBot->f_move_speed = pEdict->v.maxspeed;

      else if (f_distance > 80)  // don't move
         pBot->f_move_speed = 0.0;
      else
         pBot->f_move_speed = -pEdict->v.maxspeed;

      return (TRUE);
   }
   else
   {
      // person to follow has gone out of sight...
      pBot->pBotUser = NULL;

      return (FALSE);
   }
}


bool BotIsBlockedLeft (bot_t *pBot)
{
   edict_t *pEdict = pBot->pEdict;
   Vector v_src, v_left;
   TraceResult tr;

   int iDirection = 48;

   if (pBot->f_move_speed < 0)
      iDirection = -48;

   MAKE_VECTORS (pEdict->v.angles);

   // do a trace to the left...

   v_src = pEdict->v.origin;
   v_left = v_src + (gpGlobals->v_forward * iDirection) + (gpGlobals->v_right * -48); // 48 units to the left

   TRACE_LINE (v_src, v_left, ignore_monsters, pEdict, &tr);

   // check if the trace hit something...
   if ((tr.flFraction < 1.0) && (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0))
      return (TRUE);

   return (FALSE);
}


bool BotIsBlockedRight (bot_t *pBot)
{
   edict_t *pEdict = pBot->pEdict;
   Vector v_src, v_left;
   TraceResult tr;

   int iDirection = 48;

   if (pBot->f_move_speed < 0)
      iDirection = -48;
   MAKE_VECTORS (pEdict->v.angles);

   // do a trace to the left...

   v_src = pEdict->v.origin;
   v_left = v_src + (gpGlobals->v_forward * iDirection) + (gpGlobals->v_right * 48); // 48 units to the right

   TRACE_LINE (v_src, v_left, ignore_monsters, pEdict, &tr);

   // check if the trace hit something...
   if ((tr.flFraction < 1.0) && (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) != 0))
      return (TRUE);

   return (FALSE);
}


bool BotCheckWallOnLeft (bot_t *pBot)
{
   edict_t *pEdict = pBot->pEdict;
   Vector v_src, v_left;
   TraceResult tr;

   MAKE_VECTORS (pEdict->v.angles);

   // do a trace to the left...

   v_src = pEdict->v.origin;
   v_left = v_src + (-gpGlobals->v_right * 40); // 40 units to the left

   TRACE_LINE (v_src, v_left, ignore_monsters, pEdict, &tr);

   // check if the trace hit something...
   if (tr.flFraction < 1.0)
      return (TRUE);

   return (FALSE);
}


bool BotCheckWallOnRight (bot_t *pBot)
{
   edict_t *pEdict = pBot->pEdict;
   Vector v_src, v_right;
   TraceResult tr;

   MAKE_VECTORS (pEdict->v.angles);

   // do a trace to the right...

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

   TRACE_LINE (v_src, v_right, ignore_monsters, pEdict, &tr);

   // check if the trace hit something...
   if (tr.flFraction < 1.0)
      return (TRUE);

   return (FALSE);
}


void BotGetCampDirection (bot_t *pBot, Vector *vecDest)
{
   // Check if View on last Enemy Position is blocked - replace with better Vector then
   // Mostly used for getting a good Camping Direction Vector if not camping on a camp waypoint

   TraceResult tr;
   edict_t *pEdict;
   Vector vecSource;
   float distance1;
   float min_distance1;
   float distance2;
   float min_distance2;
   float f_length;
   int iLookAtWaypoint;
   int indexbot;
   int indexenemy;
   int i;

   pEdict = pBot->pEdict;
   vecSource = pEdict->v.origin + pEdict->v.view_ofs; // EyePosition ()

   TRACE_LINE (vecSource, *vecDest, ignore_monsters, pEdict, &tr);

   // check if the trace hit something...
   if (tr.flFraction < 1.0)
   {
      min_distance1 = 9999.0;
      min_distance2 = 9999.0;
      f_length = (tr.vecEndPos - vecSource).Length ();

      if (f_length > 100)
         return;

      // Find Nearest Waypoint to Bot and Position
      for (i = 0; i < g_iNumWaypoints; i++)
      {
         distance1 = (paths[i]->origin - pEdict->v.origin).Length ();

         if (distance1 < min_distance1)
         {
            min_distance1 = distance1;
            indexbot = i;
         }

         distance2 = (paths[i]->origin - *vecDest).Length ();

         if (distance2 < min_distance2)
         {
            min_distance2 = distance2;
            indexenemy = i;
         }
      }

      min_distance1 = 9999.0;
      iLookAtWaypoint = -1;

      for (i = 0; i < MAX_PATH_INDEX; i++)
      {
         if (paths[indexbot]->index[i] == -1)
            continue;

         distance1 = GetPathDistance (paths[indexbot]->index[i], indexenemy);

         if (distance1 < min_distance1)
         {
            min_distance1 = distance1;
            iLookAtWaypoint = i;
         }
      }

      if ((iLookAtWaypoint != -1) && (iLookAtWaypoint < g_iNumWaypoints))
         *vecDest = paths[iLookAtWaypoint]->origin;
   }
}


void BotPlayRadioMessage (bot_t *pBot, int iMessage)
{
   // Inserts the Radio Message into the Message Queue

   pBot->iRadioSelect = iMessage;
   BotPushMessageQueue (pBot, MSG_CS_RADIO);
}


void BotCheckMessageQueue (bot_t *pBot)
{
   // Checks and executes pending Messages

   int iCurrentMSG;

   // No new message ?
   if (pBot->iActMessageIndex == pBot->iPushMessageIndex)
      return;

   // reliability check
   if (FNullEnt (pBot->pEdict))
      return;

   // Get Message from Stack
   iCurrentMSG = BotGetMessageQueue (pBot);

   switch (iCurrentMSG)
   {
      // General Radio Message issued
      case MSG_CS_RADIO:

         // If last Bot Radio Command (global) happened just a second ago, delay response
         if (g_rgfLastRadioTime[pBot->bot_team - 1] + 1 < gpGlobals->time)
         {
            // If same message like previous just do a yes/no
            if ((pBot->iRadioSelect != RADIO_AFFIRMATIVE)
                && (pBot->iRadioSelect != RADIO_NEGATIVE)
                && (pBot->iRadioSelect != RADIO_REPORTINGIN))
            {
               if ((pBot->iRadioSelect == g_rgfLastRadio[pBot->bot_team - 1])
                   && (g_rgfLastRadioTime[pBot->bot_team - 1] + 1.5 > gpGlobals->time))
                  pBot->iRadioSelect = RADIO_AFFIRMATIVE;
               else
                  g_rgfLastRadio[pBot->bot_team - 1] = pBot->iRadioSelect;
            }

            if (pBot->iRadioSelect == RADIO_REPORTINGIN)
            {
               char szReport[80];
               int iTask = BotGetSafeTask (pBot)->iTask;
               int iWPT = pBot->pTasks->iData;

               switch (iTask)
               {
               case TASK_NORMAL:
                  if (iWPT != -1)
                  {
                     if (paths[iWPT]->flags & W_FL_GOAL)
                        sprintf (szReport, "Heading for a Map Goal!");
                     else if (paths[iWPT]->flags & W_FL_RESCUE)
                        sprintf (szReport, "Heading to Rescue Point");
                     else if (paths[iWPT]->flags & W_FL_CAMP)
                        sprintf (szReport, "Moving to Camp Spot");
                     else
                        sprintf (szReport, "Roaming around");
                  }
                  else
                     sprintf (szReport, "Roaming around");
                  break;
               case TASK_MOVETOPOSITION:
                  sprintf (szReport, "Moving to position");
                  break;
               case TASK_FOLLOWUSER:
                  if (!FNullEnt (pBot->pBotUser))
                     sprintf (szReport, "Following %s", STRING (pBot->pBotUser->v.netname));
                  break;
               case TASK_WAITFORGO:
                  sprintf (szReport, "Waiting for GO!");
                  break;
               case TASK_CAMP:
                  sprintf (szReport, "Camping...");
                  break;
               case TASK_PLANTBOMB:
                  sprintf (szReport, "Planting the Bomb!");
                  break;
               case TASK_DEFUSEBOMB:
                  sprintf (szReport, "Defusing the Bomb!");
                  break;
               case TASK_ATTACK:
                  if (!FNullEnt (pBot->pBotEnemy))
                     sprintf (szReport, "Attacking %s", STRING (pBot->pBotEnemy->v.netname));
                  break;
               case TASK_ENEMYHUNT:
                  if (!FNullEnt (pBot->pLastEnemy))
                     sprintf (szReport, "Hunting %s", STRING (pBot->pLastEnemy->v.netname));
                  break;
               case TASK_SEEKCOVER:
                  sprintf (szReport, "Fleeing from Battle");
                  break;
               case TASK_HIDE:
                  sprintf (szReport, "Hiding from Enemy");
                  break;
               default:
                  sprintf (szReport, "Nothing special here...");
                  break;
               }

               FakeClientCommand (pBot->pEdict, "say_team %s\n", szReport);
            }

            if (pBot->iRadioSelect < 10)
               FakeClientCommand (pBot->pEdict, "radio1;menuselect %d\n", pBot->iRadioSelect);
            else if (pBot->iRadioSelect < 20)
               FakeClientCommand (pBot->pEdict, "radio2;menuselect %d\n", pBot->iRadioSelect - 10);
            else
               FakeClientCommand (pBot->pEdict, "radio3;menuselect %d\n", pBot->iRadioSelect - 20);

            pBot->iRadioSelect = 0;

            // Store last radio usage
            g_rgfLastRadioTime[pBot->bot_team - 1] = gpGlobals->time;
         }
         else
            BotPushMessageQueue (pBot, MSG_CS_RADIO);
         break;

      // Team independant Saytext
      case MSG_CS_SAY:

         char szMessage[256];
         int iEntIndex;
         int bot_index;

         FakeClientCommand (pBot->pEdict, "say %s\n", pBot->szMiscStrings);

         // Notify other Bots of the spoken Text otherwise Bots won't respond to other Bots
         // (Network Messages aren't sent to Bots)
         iEntIndex = ENTINDEX (pBot->pEdict);

         // Add this so the Chat Parser doesn't get confused
         sprintf (szMessage, "%s:%s", STRING (pBot->pEdict->v.netname), pBot->szMiscStrings);

         for (bot_index = 0; bot_index < gpGlobals->maxClients; bot_index++)
         {
            bot_t *pOtherBot = &bots[bot_index];

            if (pOtherBot->is_used && !FNullEnt (pOtherBot->pEdict) && (pOtherBot != pBot))
            {
               if (!pOtherBot->bDead)
               {
                  pOtherBot->SaytextBuffer.iEntityIndex = iEntIndex;
                  strcpy (pOtherBot->SaytextBuffer.szSayText, szMessage);
               }

               pOtherBot->SaytextBuffer.fTimeNextChat = gpGlobals->time + pOtherBot->SaytextBuffer.fChatDelay;
            }
         }

         break;

      default:
         break;
   }

   return;
}


void BotBuyStuff (bot_t *pBot)
{
   // Does all the work in selecting correct Buy Menus for most Weapons/Items

   int iCount = 0;
   int iFoundWeapons = 0;
   int iBuyChoices[NUM_WEAPONS];
   int iChosenWeapon;
   edict_t *pEdict = pBot->pEdict;

   // If Bot has finished buying
   if (pBot->iBuyCount == 0)
      return;

   // If Fun-Mode no need to buy
   if (g_bJasonMode)
   {
      pBot->iBuyCount = 0;
      SelectWeaponByName (pBot, "weapon_knife");
      return;
   }

   // Prevent VIP from buying
   if ((g_iMapType & MAP_AS)
       && (strcmp ("vip", INFOKEY_VALUE (GET_INFOKEYBUFFER (pEdict), "model")) == 0))
   {
      pBot->iBuyCount = 0;
      pBot->bIsVIP = TRUE;
      pBot->byPathType = 2;
      return;
   }

   // Needs a Weapon ?
   if (pBot->iBuyCount == 1)
   {
      if (BotHasPrimaryWeapon (pBot) || BotHasShield (pBot))
      {
         pBot->iBuyCount = 5; // only buy ammo & items
         pBot->pEdict->v.button |= IN_RELOAD;
         return;
      }

      if (pBot->bot_money > 650)
      {
         // Select the Priority Tab for this Personality
         int *ptrWeaponTab = ptrWeaponPrefs[pBot->bot_personality];

         // Start from most desired Weapon
         ptrWeaponTab += NUM_WEAPONS;

         do
         {
            ptrWeaponTab--;
            assert ((*ptrWeaponTab > -1) && (*ptrWeaponTab < NUM_WEAPONS));
            pBot->pSelected_weapon = &cs_weapon_select[*ptrWeaponTab];
            iCount++;

            // Weapon available for every Team ?
            if (g_iMapType & MAP_AS)
            {
               if ((pBot->pSelected_weapon->iTeamAS != 2)
                   && (pBot->pSelected_weapon->iTeamAS != pBot->bot_team - 1))
                  continue;
            }
            else
            {
               if ((pBot->pSelected_weapon->iTeamStandard != 2)
                   && (pBot->pSelected_weapon->iTeamStandard != pBot->bot_team - 1))
                  continue;
            }

            if (pBot->pSelected_weapon->iPrice <= pBot->bot_money)
               iBuyChoices[iFoundWeapons++] = *ptrWeaponTab;
         }
         while ((iCount < NUM_WEAPONS) && (iFoundWeapons < 4));

         // Found a desired weapon ?
         if (iFoundWeapons > 0)
         {
            // Choose randomly from the best ones...
            if (iFoundWeapons > 1)
               iChosenWeapon = iBuyChoices[RANDOM_LONG (0, iFoundWeapons - 1)];
            else
               iChosenWeapon = iBuyChoices[iFoundWeapons - 1];

            pBot->pSelected_weapon = &cs_weapon_select[iChosenWeapon];

            if (g_bIsOldCS15)
               FakeClientCommand (pEdict, "%s\n", pBot->pSelected_weapon->buy_command);
            else
               FakeClientCommand (pEdict, "%s\n", pBot->pSelected_weapon->buy_shortcut);
         }
      }

      pBot->iBuyCount++;
      pBot->f_buy_time = gpGlobals->time + RANDOM_FLOAT (0.3, 0.8);
      return;
   }

   // Needs Ammo ?
   else if (pBot->iBuyCount < 4)
   {
      if (!BotHasPrimaryWeapon (pBot)) // Pistol ?
      {
         if (g_bIsOldCS15)
            FakeClientCommand (pEdict, "buyammo2\n");
         else
            FakeClientCommand (pEdict, "secammo\n");
      }
      else
      {
         if (g_bIsOldCS15)
            FakeClientCommand (pEdict, "buyammo1\n");
         else
            FakeClientCommand (pEdict, "primammo\n");
      }

      pBot->iBuyCount++;
      pBot->f_buy_time = gpGlobals->time + RANDOM_FLOAT (0.2, 0.5);
      return;
   }

   // Needs an Item ?
   else if (pBot->iBuyCount < 7)
   {
      // Care first about buying Armor
      if (pBot->iBuyCount == 4)
      {
         if ((pBot->pEdict->v.armorvalue == 0) && (pBot->bot_money > 650))
         {
            if (pBot->bot_money > 1000)
            {
               if (g_bIsOldCS15)
                  FakeClientCommand (pEdict, "buy;menuselect 8;menuselect 2\n");
               else
                  FakeClientCommand (pEdict, "vesthelm\n");
            }
            else
            {
               if (g_bIsOldCS15)
                  FakeClientCommand (pEdict, "buy;menuselect 8;menuselect 1\n");
               else
                  FakeClientCommand (pEdict, "vest\n");
            }
         }
      }

      // Buy Grenade or Defuse Kit
      else
      {
         if (pBot->bot_money > 300)
         {
            // If Defusion Map & Counter buy Defusion Kit
            if ((g_iMapType & MAP_DE) && (pBot->bot_team == TEAM_CS_COUNTER) && !pBot->b_has_defuse_kit)
            {
               if (g_bIsOldCS15)
                  FakeClientCommand (pEdict, "buy;menuselect 8;menuselect 6\n");
               else
                  FakeClientCommand (pEdict, "defuser\n");
            }

            // Else buy Grenade
            else
            {
               int iGrenadeType = RANDOM_LONG (1, 100);

               // Focus on HE Grenades
               if (iGrenadeType < 70)
               {
                  if (g_bIsOldCS15)
                     FakeClientCommand (pEdict, "buy;menuselect 8;menuselect 4\n");
                  else
                     FakeClientCommand (pEdict, "hegren\n");
               }
               else if (iGrenadeType < 90)
               {
                  if (g_bIsOldCS15)
                     FakeClientCommand (pEdict, "buy;menuselect 8;menuselect 5\n");
                  else
                     FakeClientCommand (pEdict, "sgren\n");
               }
               else if (iGrenadeType < 100)
               {
                  if (g_bIsOldCS15)
                     FakeClientCommand (pEdict, "buy;menuselect 8;menuselect 3\n");
                  else
                     FakeClientCommand (pEdict, "flash\n");
               }
            }
         }
      }

      pBot->iBuyCount = 0; // Finished Buying
      return;
   }

   else
      pBot->iBuyCount = 0; // Finished Buying

   return;
}


void UpdateGlobalExperienceData (void)
{
   // Called after each End of the Round to update knowledge about
   // the most dangerous waypoints for each Team.

   unsigned short min_damage;
   unsigned short act_damage;
   int iBestIndex, i, j;
   bool bRecalcKills = FALSE;
   int iClip;

   // No waypoints, no experience used or waypoints edited OR being edited ?
   if ((g_iNumWaypoints < 1) || !g_bUseExperience || g_bWaypointsChanged || g_bWaypointOn)
      return;

   // Get the most dangerous Waypoint for this Position for Team 0
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      min_damage = 0;
      iBestIndex = -1;

      for (j = 0; j < g_iNumWaypoints; j++)
      {
         if (i == j)
            continue;

         act_damage = (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam0Damage;

         if (act_damage > min_damage)
         {
            min_damage = act_damage;
            iBestIndex = j;
         }
      }

      if (min_damage > MAX_DAMAGE_VAL)
         bRecalcKills = TRUE;

      (pBotExperienceData + (i * g_iNumWaypoints) + i)->iTeam0_danger_index = (short) iBestIndex;
   }

   // Get the most dangerous Waypoint for this Position for Team 1
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      min_damage = 0;
      iBestIndex = -1;

      for (j = 0; j < g_iNumWaypoints; j++)
      {
         if (i == j)
            continue;

         act_damage = (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam1Damage;

         if (act_damage > min_damage)
         {
            min_damage = act_damage;
            iBestIndex = j;
         }
      }

      if (min_damage >= MAX_DAMAGE_VAL)
         bRecalcKills = TRUE;

      (pBotExperienceData + (i * g_iNumWaypoints) + i)->iTeam1_danger_index = (short) iBestIndex;
   }

   // Adjust Values if overflow is about to happen
   if (bRecalcKills)
   {
      for (i = 0; i < g_iNumWaypoints; i++)
      {
         for (j = 0; j < g_iNumWaypoints; j++)
         {
            if (i == j)
               continue;

            iClip = (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam0Damage;
            iClip -= MAX_DAMAGE_VAL / 2;

            if (iClip < 0)
               iClip = 0;

            (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam0Damage = (unsigned short) iClip;

            iClip = (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam1Damage;
            iClip -= MAX_DAMAGE_VAL / 2;

            if (iClip < 0)
               iClip = 0;

            (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam1Damage = (unsigned short) iClip;
         }
      }
   }

   g_cKillHistory++;

   if (g_cKillHistory == MAX_KILL_HIST)
   {
      for (i = 0; i < g_iNumWaypoints; i++)
      {
         (pBotExperienceData + (i * g_iNumWaypoints) + i)->uTeam0Damage /= gpGlobals->maxClients / 2;
         (pBotExperienceData + (i * g_iNumWaypoints) + i)->uTeam1Damage /= gpGlobals->maxClients / 2;
      }

      g_cKillHistory = 1;
   }

   return;
}


void BotCollectGoalExperience (bot_t *pBot, int iDamage)
{
   // Gets called each time a Bot gets damaged by some enemy.
   // Tries to achieve a statistic about most/less dangerous waypoints
   // for a destination goal used for pathfinding

   int iWPTValue;

   if ((g_iNumWaypoints < 1) || !g_bUseExperience || g_bWaypointsChanged
       || (pBot->chosengoal_index < 0) || (pBot->prev_goal_index < 0))
      return;

   // Only rate Goal Waypoint if Bot died because of the damage
   // FIXME: Could be done a lot better, however this cares most
   // about damage done by sniping or really deadly weapons
   if (pBot->pEdict->v.health - iDamage <= 0)
   {
      if (pBot->bot_team == TEAM_CS_TERRORIST)
      {
         iWPTValue = (pBotExperienceData + (pBot->chosengoal_index * g_iNumWaypoints) + pBot->prev_goal_index)->wTeam0Value - (pBot->pEdict->v.health / 20);
         if (iWPTValue < -MAX_GOAL_VAL)
            iWPTValue = -MAX_GOAL_VAL;
         else if (iWPTValue > MAX_GOAL_VAL)
            iWPTValue = MAX_GOAL_VAL;
         (pBotExperienceData + (pBot->chosengoal_index * g_iNumWaypoints) + pBot->prev_goal_index)->wTeam0Value = (signed short) iWPTValue;
      }
      else
      {
         iWPTValue = (pBotExperienceData + (pBot->chosengoal_index * g_iNumWaypoints) + pBot->prev_goal_index)->wTeam1Value - (pBot->pEdict->v.health / 20);
         if (iWPTValue<-MAX_GOAL_VAL)
            iWPTValue = -MAX_GOAL_VAL;
         else if (iWPTValue>MAX_GOAL_VAL)
            iWPTValue = MAX_GOAL_VAL;
         (pBotExperienceData + (pBot->chosengoal_index * g_iNumWaypoints) + pBot->prev_goal_index)->wTeam1Value = (signed short) iWPTValue;
      }
   }
}


void BotCollectExperienceData (edict_t *pVictimEdict, edict_t *pAttackerEdict, int iDamage)
{
   // Gets called each time a Bot gets damaged by some enemy.
   // Stores the damage (teamspecific) done to the Victim
   // FIXME: Should probably rate damage done by humans higher...

   if (!g_bUseExperience || FNullEnt (pVictimEdict) || FNullEnt (pAttackerEdict))
      return;

   int iVictimTeam = UTIL_GetTeam (pVictimEdict);
   int iAttackerTeam = UTIL_GetTeam (pAttackerEdict);

   if (iVictimTeam == iAttackerTeam)
      return;

   bot_t *pBot = UTIL_GetBotPointer (pVictimEdict);

   // If these are Bots also remember damage to rank the destination of the Bot
   if (pBot != NULL)
      pBot->f_goal_value -= iDamage;

   pBot = UTIL_GetBotPointer (pAttackerEdict);
   if (pBot != NULL)
      pBot->f_goal_value += iDamage;

   float distance;
   float min_distance_victim = 9999.0;
   float min_distance_attacker = 9999.0;

   int VictimIndex;
   int AttackerIndex;
   int i;

   // Find Nearest Waypoint to Attacker/Victim
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      distance = (paths[i]->origin - pVictimEdict->v.origin).Length ();
      if (distance < min_distance_victim)
      {
         min_distance_victim = distance;
         VictimIndex = i;
      }

      distance = (paths[i]->origin - pAttackerEdict->v.origin).Length ();
      if (distance < min_distance_attacker)
      {
         min_distance_attacker = distance;
         AttackerIndex = i;
      }
   }

   if (pVictimEdict->v.health - iDamage < 0)
   {
      if (iVictimTeam == TEAM_CS_TERRORIST)
         (pBotExperienceData + (VictimIndex * g_iNumWaypoints) + VictimIndex)->uTeam0Damage++;
      else
         (pBotExperienceData + (VictimIndex * g_iNumWaypoints) + VictimIndex)->uTeam1Damage++;

      if ((pBotExperienceData + (VictimIndex * g_iNumWaypoints) + VictimIndex)->uTeam0Damage > 240)
         (pBotExperienceData + (VictimIndex * g_iNumWaypoints) + VictimIndex)->uTeam0Damage = 240;

      if ((pBotExperienceData + (VictimIndex * g_iNumWaypoints) + VictimIndex)->uTeam1Damage > 240)
         (pBotExperienceData + (VictimIndex * g_iNumWaypoints) + VictimIndex)->uTeam1Damage = 240;
   }

   if (VictimIndex == AttackerIndex)
      return;

   int iValue;

   // Store away the damage done
   if (iVictimTeam == TEAM_CS_TERRORIST)
   {
      iValue = (pBotExperienceData + (VictimIndex * g_iNumWaypoints) + AttackerIndex)->uTeam0Damage;
      iValue += ((float) iDamage / 10.0);
      if (iValue > MAX_DAMAGE_VAL)
         iValue = MAX_DAMAGE_VAL;

      (pBotExperienceData + (VictimIndex * g_iNumWaypoints) + AttackerIndex)->uTeam0Damage = (unsigned short)iValue;
   }
   else
   {
      iValue = (pBotExperienceData + (VictimIndex * g_iNumWaypoints) + AttackerIndex)->uTeam1Damage;
      iValue += ((float) iDamage / 10.0);
      if (iValue > MAX_DAMAGE_VAL)
         iValue = MAX_DAMAGE_VAL;

      (pBotExperienceData + (VictimIndex * g_iNumWaypoints) + AttackerIndex)->uTeam1Damage = (unsigned short)iValue;
   }
}


void BotSetConditions (bot_t *pBot)
{
   // Carried out each Frame.
   // Does all of the sensing, calculates Emotions and finally
   // sets the desired Action after applying all of the Filters

   edict_t *pEdict = pBot->pEdict;
   float f_distance;
   float min_distance;
   bool bUsingGrenade = pBot->bUsingGrenade;
   bool bCheckNoiseOrigin;
   unsigned char *pas;
   int ind;
   edict_t *pPlayer;
   Vector v_distance;
   unsigned char cHit;
   Vector vecVisPos;

   pBot->iAimFlags = 0;

   // Slowly increase/decrease dynamic Emotions back to their Base Level
   if (pBot->fNextEmotionUpdate < gpGlobals->time)
   {
      if (pBot->fAgressionLevel > pBot->fBaseAgressionLevel)
         pBot->fAgressionLevel -= 0.05;
      else
         pBot->fAgressionLevel += 0.05;

      if (pBot->fFearLevel > pBot->fBaseFearLevel)
         pBot->fFearLevel -= 0.05;
      else
         pBot->fFearLevel += 0.05;

      if (pBot->fAgressionLevel < 0.0)
         pBot->fAgressionLevel = 0.0;
      if (pBot->fFearLevel < 0.0)
         pBot->fFearLevel = 0.0;

      pBot->fNextEmotionUpdate = gpGlobals->time + 0.5;
   }

   // Does Bot see an Enemy ?
   if (!g_bIgnoreEnemies && BotFindEnemy (pBot))
      pBot->iStates |= STATE_SEEINGENEMY;
   else
   {
      pBot->iStates &= ~STATE_SEEINGENEMY;
      pBot->pBotEnemy = NULL;
   }

   // Did Bot just kill an Enemy ?
   if (!FNullEnt (pBot->pLastVictim))
   {
      if (UTIL_GetTeam (pBot->pLastVictim) != pBot->bot_team)
      {
         // Add some agression because we just killed somebody MUWHAHA !!
         pBot->fAgressionLevel += 0.1;
         if (pBot->fAgressionLevel > 1.0)
            pBot->fAgressionLevel = 1.0;

         // Taunt Enemy if we feel like it
         int iRandomMessage = RANDOM_LONG (0, 100);
         if (g_bBotChat)
         {
            if (iRandomMessage < 20)
            {
               BotPrepareChatMessage (pBot, szKillChat[RANDOM_LONG (0, iNumKillChats - 1)]);
               BotPushMessageQueue (pBot, MSG_CS_SAY);
            }
         }

         // Sometimes give some radio message
         if (iRandomMessage < 50)
            BotPlayRadioMessage (pBot, RADIO_ENEMYDOWN);
      }

      pBot->pLastVictim = NULL;
   }

   // Check if our current enemy is still valid
   if (!FNullEnt (pBot->pLastEnemy))
   {
      if (!IsAlive (pBot->pLastEnemy) && (pBot->f_shootatdead_time < gpGlobals->time))
      {
         pBot->vecLastEnemyOrigin = g_vecZero;
         pBot->pLastEnemy = NULL;
      }
   }
   else
   {
      pBot->pLastEnemy = NULL;
      pBot->vecLastEnemyOrigin = g_vecZero;
   }

   bCheckNoiseOrigin = FALSE;

   // Check sounds of other players
   // FIXME: Hearing is done by simulating and aproximation
   // Need to check if hooking the Server Playsound Routines
   // wouldn't give better results because the current method
   // is far too sensitive and unreliable

   // Don't listen if seeing enemy, just checked for sounds or being blinded ('cause its inhuman)
   if (!g_bIgnoreEnemies && (pBot->f_sound_update_time < gpGlobals->time)
       && FNullEnt (pBot->pBotEnemy) && (pBot->f_blind_time < gpGlobals->time))
   {
      pBot->f_sound_update_time = gpGlobals->time + g_fTimeSoundUpdate;

      // Let Hearing be affected by Movement Speed
      float fSensitivity = pEdict->v.velocity.Length2D ();
      if (fSensitivity != 0.0)
      {
         fSensitivity /= 240.0;
         fSensitivity = -fSensitivity;
      }
      fSensitivity += 1.5;

      // If Bot just shot, half hearing Sensibility
      if (pEdict->v.oldbuttons & IN_ATTACK)
         fSensitivity *= 0.5;

      // Setup Engines Potentially Audible Set for this Bot
      Vector vecOrg = pEdict->v.origin + pEdict->v.view_ofs;
      if (pEdict->v.flags & FL_DUCKING)
         vecOrg = vecOrg + (VEC_HULL_MIN - VEC_DUCK_HULL_MIN);

      pas = ENGINE_SET_PAS ((float *) &vecOrg);

      pPlayer = NULL;
      min_distance = 9999;

      // Loop through all enemy clients to check for hearable stuff
      for (ind = 0; ind < gpGlobals->maxClients; ind++)
      {
         if (!clients[ind].IsUsed
             || !clients[ind].IsAlive
             || (clients[ind].iTeam == pBot->bot_team)
             || (clients[ind].pEdict == pEdict)
             || (clients[ind].fTimeSoundLasting < gpGlobals->time))
            continue;

         // Despite its name it also checks for sounds...
         // NOTE: This only checks if sounds could be heard from
         // this position in theory but doesn't care for Volume or
         // real Sound Events. Even if there's no noise it returns true,
         // so we still have the work of simulating sound levels
         if (!ENGINE_CHECK_VISIBILITY (clients[ind].pEdict, pas))
            continue;

         f_distance = (clients[ind].vecSoundPosition - pEdict->v.origin).Length ();
         if (f_distance > clients[ind].fHearingDistance)
            continue;

         if (f_distance > min_distance)
            continue;

         min_distance = f_distance;
         pPlayer = clients[ind].pEdict;
      }

      // Did the Bot hear someone ?
      if (!FNullEnt (pPlayer))
      {
         // Change to best weapon if heard something
         if ((pBot->current_weapon.iId == CS_WEAPON_KNIFE) && !g_bJasonMode)
            BotSelectBestWeapon (pBot);

         pBot->f_heard_sound_time = gpGlobals->time;
         pBot->iStates |= STATE_HEARINGENEMY;

         // Didn't Bot already have an enemy ? Take this one...
         if (pBot->vecLastEnemyOrigin == g_vecZero)
         {
            pBot->pLastEnemy = pPlayer;
            pBot->vecLastEnemyOrigin = pPlayer->v.origin;
         }

         // Bot had an enemy, check if it's the heard one
         else
         {
            if (pPlayer == pBot->pLastEnemy)
            {
               // Bot sees enemy ? then bail out !
               if (pBot->iStates & STATE_SEEINGENEMY)
                  goto endhearing;
               pBot->vecLastEnemyOrigin = pPlayer->v.origin;
            }
            else
            {
               // If Bot had an enemy but the heard one is nearer, take it instead
               f_distance = (pBot->vecLastEnemyOrigin - pEdict->v.origin).Length ();

               if ((f_distance > (pPlayer->v.origin - pEdict->v.origin).Length ())
                   && (pBot->f_bot_see_enemy_time + 2.0 < gpGlobals->time))
               {
                  pBot->pLastEnemy = pPlayer;
                  pBot->vecLastEnemyOrigin = pPlayer->v.origin;
               }
               else
                  goto endhearing;
            }
         }

         // Check if heard enemy can be seen
         if (FBoxVisible (pEdict, pPlayer, &vecVisPos, &cHit))
         {
            pBot->pBotEnemy = pPlayer;
            pBot->pLastEnemy = pPlayer;
            pBot->vecLastEnemyOrigin = vecVisPos;
            pBot->ucVisibility = cHit;
            pBot->iStates |= STATE_SEEINGENEMY;
            pBot->f_bot_see_enemy_time = gpGlobals->time;
         }

         // Check if heard enemy can be shoot through some obstacle
         else if ((pBot->pLastEnemy == pPlayer)
                  && (pBot->f_bot_see_enemy_time + 3.0 > gpGlobals->time))
         {
            int iShootThruFreq = BotAimTab[pBot->bot_skill / 20].iHeardShootThruProb;

            if (g_bShootThruWalls && WeaponShootsThru (pBot->current_weapon.iId)
                && (RANDOM_LONG (1, 100) < iShootThruFreq))
            {
               if (IsShootableThruObstacle (pEdict, pPlayer->v.origin))
               {
                  pBot->pBotEnemy = pPlayer;
                  pBot->pLastEnemy = pPlayer;
                  pBot->vecLastEnemyOrigin = pPlayer->v.origin;
                  pBot->iStates |= STATE_SEEINGENEMY;
                  pBot->iStates |= STATE_SUSPECTENEMY;
                  pBot->f_bot_see_enemy_time = gpGlobals->time;
               }
            }
         }
      }
   }
   else
      pBot->iStates &= ~STATE_HEARINGENEMY;

endhearing:

   if (FNullEnt (pBot->pBotEnemy) && !FNullEnt (pBot->pLastEnemy))
   {
      pBot->iAimFlags |= AIM_PREDICTPATH;
      if (BotEntityIsVisible (pBot, pBot->vecLastEnemyOrigin))
         pBot->iAimFlags |= AIM_LASTENEMY;
   }

   // Check if throwing a Grenade is a good thing to do...
   if ((pBot->f_grenade_check_time < gpGlobals->time) && !bUsingGrenade
       && !pBot->bIsReloading && (pBot->current_weapon.iId != CS_WEAPON_INSWITCH))
   {
      // Check again in some seconds
      pBot->f_grenade_check_time = gpGlobals->time + g_fTimeGrenadeUpdate;

      if (!FNullEnt (pBot->pLastEnemy) && IsAlive (pBot->pLastEnemy)
          && !(pBot->iStates & STATE_SEEINGENEMY))
      {
         // Check if we have Grenades to throw
         int iGrenadeType = BotCheckGrenades (pBot);

         // If we don't have grenades no need to check
         // it this round again
         if (iGrenadeType == -1)
            pBot->f_grenade_check_time = gpGlobals->time + 9999.0;

         if (iGrenadeType != -1)
         {
            edict_t *pEnemy = pBot->pLastEnemy;
            Vector v_distance = pBot->vecLastEnemyOrigin - pEdict->v.origin;
            f_distance = v_distance.Length ();

            // Too high to throw ?
            if (pEnemy->v.origin.z > pEdict->v.origin.z + 500.0)
               f_distance = 9999;

            // Enemy within a good Throw distance ?
            if ((f_distance > 400) && (f_distance < 1600))
            {
               Vector v_enemypos;
               Vector vecSource;
               Vector v_dest;
               TraceResult tr;
               bool bThrowGrenade = TRUE;
               int iThrowIndex;
               Vector vecPredict;
               int rgi_WaypointTab[4];
               int iIndexCount;
               float fRadius;

               // Care about different Grenades
               switch (iGrenadeType)
               {
               case CS_WEAPON_HEGRENADE:
                  if (NumTeammatesNearPos (pBot, pBot->pLastEnemy->v.origin, 256) > 0)
                     bThrowGrenade = FALSE;
                  else
                  {
                     vecPredict = pBot->pLastEnemy->v.velocity * 0.5;
                     vecPredict.z = 0.0;
                     vecPredict = vecPredict + pBot->pLastEnemy->v.origin;
                     fRadius = pBot->pLastEnemy->v.velocity.Length2D ();

                     if (fRadius < 128)
                        fRadius = 128;
                     iIndexCount = 4;

                     WaypointFindInRadius (vecPredict, fRadius, &rgi_WaypointTab[0], &iIndexCount);

                     while (iIndexCount > 0)
                     {
                        bThrowGrenade = TRUE;

                        pBot->vecThrow = paths[rgi_WaypointTab[iIndexCount--]]->origin;
                        vecSource = VecCheckThrow (pEdict, GetGunPosition (pEdict), pBot->vecThrow, 1.0);

                        if (vecSource == g_vecZero)
                           vecSource = VecCheckToss (pEdict, GetGunPosition (pEdict), pBot->vecThrow);

                        if (vecSource == g_vecZero)
                           bThrowGrenade = FALSE;
                        else
                           break;
                     }
                  }

                  // Start throwing ?
                  if (bThrowGrenade)
                     pBot->iStates |= STATE_THROWHEGREN;
                  else
                     pBot->iStates &= ~STATE_THROWHEGREN;

                  break;

               case CS_WEAPON_FLASHBANG:
                  vecPredict = pBot->pLastEnemy->v.velocity * 0.5;
                  vecPredict.z = 0.0;
                  vecPredict = vecPredict + pBot->pLastEnemy->v.origin;
                  iThrowIndex = WaypointFindNearestToMove (vecPredict);
                  pBot->vecThrow = paths[iThrowIndex]->origin;

                  if (NumTeammatesNearPos (pBot, pBot->vecThrow, 256) > 0)
                     bThrowGrenade = FALSE;

                  if (bThrowGrenade)
                  {
                     vecSource = VecCheckThrow (pEdict, GetGunPosition (pEdict), pBot->vecThrow, 1.0);
                     if (vecSource == g_vecZero)
                        vecSource = VecCheckToss (pEdict, GetGunPosition (pEdict), pBot->vecThrow);
                     if (vecSource == g_vecZero)
                        bThrowGrenade = FALSE;
                  }

                  if (bThrowGrenade)
                     pBot->iStates |= STATE_THROWFLASHBANG;
                  else
                     pBot->iStates &= ~STATE_THROWFLASHBANG;

                  break;

               case CS_WEAPON_SMOKEGRENADE:
                  // Check if Enemy is directly facing us
                  // Don't throw if that's the case !!
                  if (bThrowGrenade && !FNullEnt (pBot->pBotEnemy))
                  {
                     if (GetShootingConeDeviation (pBot->pBotEnemy, &pEdict->v.origin) >= 0.9)
                        bThrowGrenade = FALSE;
                  }

                  if (bThrowGrenade)
                     pBot->iStates |= STATE_THROWSMOKEGREN;
                  else
                     pBot->iStates &= ~STATE_THROWSMOKEGREN;

                  break;
               }
               bottask_t TempTask = {NULL, NULL, TASK_THROWHEGRENADE, TASKPRI_THROWGRENADE, -1, gpGlobals->time + TIME_GRENPRIME, FALSE};

               if (pBot->iStates & STATE_THROWHEGREN)
                  BotPushTask (pBot, &TempTask);
               else if (pBot->iStates & STATE_THROWFLASHBANG)
               {
                  TempTask.iTask = TASK_THROWFLASHBANG;
                  BotPushTask (pBot, &TempTask);
               }
               else if (pBot->iStates & STATE_THROWSMOKEGREN)
               {
                  TempTask.iTask = TASK_THROWSMOKEGRENADE;
                  BotPushTask (pBot, &TempTask);
               }
            }
            else
            {
               pBot->iStates &= ~STATE_THROWHEGREN;
               pBot->iStates &= ~STATE_THROWFLASHBANG;
               pBot->iStates &= ~STATE_THROWSMOKEGREN;
            }
         }
         else
         {
            pBot->iStates &= ~STATE_THROWHEGREN;
            pBot->iStates &= ~STATE_THROWFLASHBANG;
            pBot->iStates &= ~STATE_THROWSMOKEGREN;
         }
      }
   }
   else
   {
      pBot->iStates &= ~STATE_THROWHEGREN;
      pBot->iStates &= ~STATE_THROWFLASHBANG;
      pBot->iStates &= ~STATE_THROWSMOKEGREN;
   }

   // Check if there are Items needing to be used/collected
   if ((pBot->f_itemcheck_time < gpGlobals->time) || !FNullEnt (pBot->pBotPickupItem))
   {
      pBot->f_itemcheck_time = gpGlobals->time + g_fTimePickupUpdate;
      BotFindItem (pBot);
   }

   float fTempFear = pBot->fFearLevel;
   float fTempAgression = pBot->fAgressionLevel;

   // Decrease Fear if Teammates near
   int iFriendlyNum = 0;
   if (pBot->vecLastEnemyOrigin != g_vecZero)
      iFriendlyNum = NumTeammatesNearPos (pBot, pEdict->v.origin, 300) - NumEnemiesNearPos (pBot, pBot->vecLastEnemyOrigin, 500);
   if (iFriendlyNum > 0)
      fTempFear = fTempFear * 0.5;

   // Increase/Decrease Fear/Agression if Bot uses a sniping weapon
   // to be more careful
   if (BotUsesSniper (pBot))
   {
      fTempFear = fTempFear * 1.5;
      fTempFear = fTempAgression * 0.5;
   }

   // Initialize & Calculate the Desire for all Actions based on
   // distances, Emotions and other Stuff

   BotGetSafeTask (pBot);

   // Bot found some Item to use ?
   if (pBot->pBotPickupItem)
   {
      pBot->iStates |= STATE_PICKUPITEM;
      edict_t *pItem = pBot->pBotPickupItem;
      Vector vecPickme;

      if (strncmp ("func_", STRING (pItem->v.classname), 5) == 0)
         vecPickme = VecBModelOrigin (pItem);
      else
         vecPickme = pItem->v.origin;

      f_distance = (vecPickme - pEdict->v.origin).Length ();
      f_distance = 500 - f_distance;
      f_distance = (100 * f_distance) / 500;

      if (f_distance > 50)
         f_distance = 50;

      taskFilters[TASK_PICKUPITEM].fDesire = f_distance;
   }
   else
   {
      pBot->iStates &= ~STATE_PICKUPITEM;
      taskFilters[TASK_PICKUPITEM].fDesire = 0.0;
   }

   float fLevel;
   float fMaxView = pBot->f_maxview_distance;

   // Calculate Desire to Attack
   if (pBot->iStates & STATE_SEEINGENEMY)
   {
      if (BotReactOnEnemy(pBot))
         taskFilters[TASK_ATTACK].fDesire = TASKPRI_ATTACK;
      else
         taskFilters[TASK_ATTACK].fDesire = 0;
   }
   else
      taskFilters[TASK_ATTACK].fDesire = 0;

   // Calculate Desires to seek Cover or Hunt
   if (!FNullEnt (pBot->pLastEnemy))
   {
      f_distance = (pBot->vecLastEnemyOrigin - pEdict->v.origin).Length ();
      float fRetreatLevel = (100 - pBot->pEdict->v.health) * fTempFear;
      float fTimeSeen = pBot->f_bot_see_enemy_time - gpGlobals->time;
      float fTimeHeard = pBot->f_heard_sound_time - gpGlobals->time;
      float fRatio;

      if (fTimeSeen > fTimeHeard)
      {
         fTimeSeen += 10.0;
         fRatio = fTimeSeen / 10;
      }
      else
      {
         fTimeHeard += 10.0;
         fRatio = fTimeHeard / 10;
      }

      taskFilters[TASK_SEEKCOVER].fDesire = fRetreatLevel * fRatio;

      // If half of the Round is over, allow hunting
      // FIXME: It probably should be also team/map dependant
      if (FNullEnt (pBot->pBotEnemy) && (g_fTimeRoundMid < gpGlobals->time) && !pBot->bUsingGrenade)
      {
         fLevel = 4096.0 - ((1.0 - fTempAgression) * f_distance);
         fLevel = (100 * fLevel) / 4096.0;
         fLevel = fLevel - fRetreatLevel;
         if (fLevel > 89)
            fLevel = 89;
         taskFilters[TASK_ENEMYHUNT].fDesire = fLevel;
      }
      else
         taskFilters[TASK_ENEMYHUNT].fDesire = 0;
   }
   else
   {
      taskFilters[TASK_SEEKCOVER].fDesire = 0;
      taskFilters[TASK_ENEMYHUNT].fDesire = 0;
   }

   // Blinded Behaviour
   if (pBot->f_blind_time > gpGlobals->time)
      taskFilters[TASK_BLINDED].fDesire = TASKPRI_BLINDED;
   else
      taskFilters[TASK_BLINDED].fDesire = 0.0;


   // Now we've initialised all the desires go through the hard work
   // of filtering all Actions against each other to pick the most
   // rewarding one to the Bot
   // Credits for the basic Idea of filtering comes out of the paper
   // "Game Agent Control Using Parallel Behaviors"
   // by Robert Zubek
   //
   // FIXME: Instead of going through all of the Actions it might be
   // better to use some kind of decision tree to sort out impossible
   // actions
   //
   // Most of the values were found out by Trial-and-Error and a Helper
   // Utility I wrote so there could still be some weird behaviours, it's
   // hard to check them all out 


   pBot->oldcombatdesire = hysteresisdesire (taskFilters[TASK_ATTACK].fDesire, 40.0, 90.0, pBot->oldcombatdesire);
   taskFilters[TASK_ATTACK].fDesire = pBot->oldcombatdesire;
   bottask_t *ptaskOffensive = &taskFilters[TASK_ATTACK];

   bottask_t * ptaskPickup = &taskFilters[TASK_PICKUPITEM];

   // Calc Survive (Cover/Hide)
   bottask_t *ptaskSurvive = thresholddesire (&taskFilters[TASK_SEEKCOVER], 40.0, 0.0);
   ptaskSurvive = subsumedesire (&taskFilters[TASK_HIDE], ptaskSurvive);

   // Don't allow hunting if Desire's 60<
   bottask_t *pDefault = thresholddesire (&taskFilters[TASK_ENEMYHUNT], 60.0, 0.0);

   // If offensive Task, don't allow picking up stuff
   ptaskOffensive = subsumedesire (ptaskOffensive, ptaskPickup);

   // Default normal & defensive Tasks against Offensive Actions
   bottask_t *pSub1 = maxdesire (ptaskOffensive, pDefault);

   // Reason about fleeing instead
   bottask_t *pFinal = maxdesire (ptaskSurvive, pSub1);

   pFinal = subsumedesire (&taskFilters[TASK_BLINDED], pFinal);

   if (pBot->pTasks != NULL)
      pFinal = maxdesire (pFinal, pBot->pTasks);

   // Push the final Behaviour in our Tasklist to carry out
   BotPushTask (pBot, pFinal);
}


void BotResetTasks (bot_t *pBot)
{
   bottask_t *pPrevTask;
   bottask_t *pNextTask;

   if (pBot->pTasks == NULL)
      return;

   pNextTask = BotGetSafeTask (pBot)->pNextTask;
   pPrevTask = BotGetSafeTask (pBot);

   while (pBot->pTasks != NULL)
   {
      pPrevTask = pBot->pTasks->pPreviousTask;
      delete (pBot->pTasks);
      pBot->pTasks = pPrevTask;
   }
   pBot->pTasks = pNextTask;

   while (pBot->pTasks != NULL)
   {
      pNextTask = pBot->pTasks->pNextTask;
      delete (pBot->pTasks);
      pBot->pTasks = pNextTask;
   }
   pBot->pTasks = NULL;
}


void BotPushTask (bot_t *pBot, bottask_t *pTask)
{
   if (pBot->pTasks != NULL)
   {
      if (BotGetSafeTask (pBot)->iTask == pTask->iTask)
      {
         if (BotGetSafeTask (pBot)->fDesire != pTask->fDesire)
            BotGetSafeTask (pBot)->fDesire = pTask->fDesire;
         return;
      }
      else
         DeleteSearchNodes (pBot);
   }

   pBot->fNoCollTime = gpGlobals->time + 0.5;

   bottask_t *pNewTask = new bottask_t;
   pNewTask->iTask = pTask->iTask;
   pNewTask->fDesire = pTask->fDesire;
   pNewTask->iData = pTask->iData;
   pNewTask->fTime = pTask->fTime;
   pNewTask->bCanContinue = pTask->bCanContinue;

   pNewTask->pPreviousTask = NULL;
   pNewTask->pNextTask = NULL;

   if (pBot->pTasks != NULL)
   {
      while (BotGetSafeTask (pBot)->pNextTask)
         pBot->pTasks = BotGetSafeTask (pBot)->pNextTask;

      BotGetSafeTask (pBot)->pNextTask = pNewTask;
      pNewTask->pPreviousTask = pBot->pTasks;
   }

   pBot->pTasks = pNewTask;

   // Leader Bot ?
   if (pBot->bIsLeader)
   {
      // Reorganize Team if fleeing
      if (pNewTask->iTask == TASK_SEEKCOVER)
         BotCommandTeam(pBot);
   }

   return;
}


bottask_t *BotGetSafeTask (bot_t *pBot)
{
   if (pBot->pTasks == NULL)
   {
      bottask_t TempTask = {NULL, NULL, TASK_NORMAL, TASKPRI_NORMAL, -1, 0.0, TRUE};
      BotPushTask (pBot, &TempTask);
   }

   return (pBot->pTasks);
}


void BotRemoveCertainTask (bot_t *pBot, int iTaskNum)
{
   bottask_t *pTask = pBot->pTasks;
   bottask_t *pNextTask;
   bottask_t *pPrevTask;

   if (pTask == NULL)
      return;

   while (pTask->pPreviousTask != NULL)
      pTask = pTask->pPreviousTask;

   while (pTask != NULL)
   {
      pNextTask = pTask->pNextTask;
      pPrevTask = pTask->pPreviousTask;

      if (pTask->iTask == iTaskNum)
      {
         if (pPrevTask != NULL)
            pPrevTask->pNextTask = pNextTask;
         if (pNextTask != NULL)
            pNextTask->pPreviousTask = pPrevTask;

         delete (pTask);
      }

      pTask = pNextTask;
   }

   pBot->pTasks = pPrevTask;
   DeleteSearchNodes (pBot);
}


void BotTaskComplete (bot_t *pBot)
{
   // Called whenever a Task is completed

   bottask_t *pPrevTask;

   if (pBot->pTasks == NULL)
   {
      DeleteSearchNodes (pBot); // Delete all Pathfinding Nodes
      return;
   }

   do
   {
      pPrevTask = BotGetSafeTask (pBot)->pPreviousTask;
      delete (pBot->pTasks);
      pBot->pTasks = NULL;

      if (pPrevTask != NULL)
      {
         pPrevTask->pNextTask = NULL;
         pBot->pTasks = pPrevTask;
      }

      if (pBot->pTasks == NULL)
         break;

   } while (!BotGetSafeTask (pBot)->bCanContinue);

   // Delete all Pathfinding Nodes
   DeleteSearchNodes(pBot);
}


inline void BotFacePosition (bot_t *pBot, Vector vecPos)
{
   // Adjust all Bot Body and View Angles to face an absolute Vector

   edict_t *pEdict = pBot->pEdict;
   Vector vecDirection;

   vecDirection = UTIL_VecToAngles (vecPos - GetGunPosition (pEdict));
   vecDirection = vecDirection - pEdict->v.punchangle;

   vecDirection.x = -vecDirection.x;
   pEdict->v.ideal_yaw = vecDirection.y;
   pEdict->v.idealpitch = vecDirection.x;

   UTIL_ClampAngle (&pEdict->v.ideal_yaw);
   UTIL_ClampAngle (&pEdict->v.idealpitch);

   if (pEdict->v.idealpitch > 89)
      pEdict->v.idealpitch = 89;
   else if (pEdict->v.idealpitch < -89)
      pEdict->v.idealpitch = -89;

   return;
}


bool BotEnemyIsThreat (bot_t *pBot)
{
   edict_t *pEdict = pBot->pEdict;
   float f_distance;

   if (FNullEnt (pBot->pBotEnemy) || (pBot->iStates & STATE_SUSPECTENEMY)
       || (BotGetSafeTask (pBot)->iTask == TASK_SEEKCOVER))
      return (FALSE);

   if (!FNullEnt (pBot->pBotEnemy))
   {
      Vector vDest = pBot->pBotEnemy->v.origin - pBot->pEdict->v.origin;
      f_distance = vDest.Length ();
   }
   else
      return (FALSE);

   // If Bot is camping, he should be firing anyway and NOT leaving his position
   if (BotGetSafeTask (pBot)->iTask == TASK_CAMP)
      return (FALSE);

   // If Enemy is near or facing us directly
   if ((f_distance < 256) || (GetShootingConeDeviation (pBot->pBotEnemy, &pEdict->v.origin) >= 0.9))
      return (TRUE);

   return (FALSE);
}


bool BotReactOnEnemy (bot_t *pBot)
{
   // Check if Task has to be interrupted because an Enemy is near (run Attack Actions then)

   if (BotEnemyIsThreat (pBot))
   {
      if (pBot->fEnemyReachableTimer < gpGlobals->time)
      {
         int iBotIndex = WaypointFindNearestToMove (pBot->pEdict->v.origin);
         int iEnemyIndex = WaypointFindNearestToMove (pBot->pBotEnemy->v.origin);
         int fLinDist = (pBot->pBotEnemy->v.origin - pBot->pEdict->v.origin).Length ();
         int fPathDist = GetPathDistance (iBotIndex, iEnemyIndex);

         if (fPathDist - fLinDist > 112)
            pBot->bEnemyReachable = FALSE;
         else
            pBot->bEnemyReachable = TRUE;

         pBot->fEnemyReachableTimer = gpGlobals->time + 1.0;
      }

      if (pBot->bEnemyReachable)
      {
         // Override existing movement by attack movement
         pBot->f_wpt_timeset = gpGlobals->time;
//         BotDoAttackMovement (pBot);

         return (TRUE);
      }
   }

   return (FALSE);
}


bool BotLastEnemyVisible (bot_t *pBot)
{
   // Checks if Line of Sight established to last Enemy

   edict_t *pEdict = pBot->pEdict;
   TraceResult tr;

   // trace a line from bot's eyes to destination...
   TRACE_LINE (pEdict->v.origin + pEdict->v.view_ofs,
               pBot->vecLastEnemyOrigin, ignore_monsters,
               pEdict, &tr);

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

   return (FALSE);
}


bool BotLastEnemyShootable (bot_t *pBot)
{
   edict_t *pEdict = pBot->pEdict;
   float flDot;

   if (!(pBot->iAimFlags & AIM_LASTENEMY))
      return (FALSE);

   flDot = GetShootingConeDeviation (pEdict, &pBot->vecLastEnemyOrigin);
   if (flDot >= 0.90)
      return (TRUE);

   return (FALSE);
}


bool BotDoWaypointNav (bot_t *pBot)
{
   // Does the main Path Navigation...

   static char target_name[64];
   edict_t *pEdict = pBot->pEdict;
   edict_t *pButton;
   edict_t *pNearestButton;
   Vector v_trace_start;
   Vector v_button_origin;
   float f_button_distance;
   float f_button_min_distance;
   TraceResult tr;
   TraceResult tr2;
   float wpt_distance;
   float fDesiredDistance;
   int iWPTValue;
   int iStartIndex;
   int iGoalIndex;
   int iWPTIndex;
   int i;

   if (pBot->curr_wpt_index == -1)
      GetValidWaypoint (pBot); // check if we need to find a waypoint...

   pBot->dest_origin = pBot->wpt_origin;
   wpt_distance = (pEdict->v.origin - pBot->wpt_origin).Length ();

   // Initialize the radius for a special waypoint type, where the wpt
   // is considered to be reached
   if ((pEdict->v.flags & FL_DUCKING) || (paths[pBot->curr_wpt_index]->flags & W_FL_GOAL))
      fDesiredDistance = 25;

   else if (pBot->bOnLadder)
      fDesiredDistance = 15;

   else
      fDesiredDistance = 50;

   // This waypoint has additional Travel Flags - care about them
   if (pBot->curr_travel_flags & C_FL_JUMP)
   {
      // Not jumped yet ?
      if (!pBot->bJumpDone)
      {
         fDesiredDistance = 0;

         // If Bot's on ground or on ladder we're free to jump.
         // Yes, I'm cheating here by setting the correct velocity for the jump. Pressing
         // the jump button gives the illusion of the Bot actual jumping
         if ((pEdict->v.flags & (FL_ONGROUND | FL_PARTIALGROUND)) || pBot->bOnLadder)
         {
            pEdict->v.velocity = pBot->vecDesiredVelocity;
            pEdict->v.button |= IN_JUMP;
            pBot->bJumpDone = TRUE;
            pBot->vecDesiredVelocity = g_vecZero;
            pBot->bCheckTerrain = FALSE;
         }
      }

      // Jump made
      else
      {
         // if bot was doing a knife-jump, switch back to normal weapon
         if (!g_bJasonMode && (pBot->current_weapon.iId == CS_WEAPON_KNIFE)
             && (pEdict->v.flags & (FL_ONGROUND | FL_PARTIALGROUND)))
            BotSelectBestWeapon (pBot);
      }
   }

   // Special Ladder Handling
   if (paths[pBot->curr_wpt_index]->flags & W_FL_LADDER)
   {
      fDesiredDistance = 15;

      if (pBot->wpt_origin.z < pEdict->v.origin.z)
      {
         if (!pBot->bOnLadder
             && (pEdict->v.flags & FL_ONGROUND)
             && !(pEdict->v.flags & FL_DUCKING))
         {
            // Slowly approach ladder if going down
            pBot->f_move_speed = wpt_distance;

            if (pBot->f_move_speed < 150.0)
               pBot->f_move_speed = 150.0;
            else if (pBot->f_move_speed > pEdict->v.maxspeed)
               pBot->f_move_speed = pEdict->v.maxspeed;
         }
      }
   }

   // Special Lift Handling
   if (paths[pBot->curr_wpt_index]->flags & W_FL_LIFT)
   {
      fDesiredDistance = 50;

      TRACE_LINE (paths[pBot->curr_wpt_index]->origin, paths[pBot->curr_wpt_index]->origin + Vector (0, 0, -50), ignore_monsters, pEdict, &tr);

      // lift found at waypoint ?
      if (tr.flFraction < 1.0)
      {
         // is lift activated AND bot is standing on it AND lift is moving ?
         if ((tr.pHit->v.nextthink > 0) && (pEdict->v.groundentity == tr.pHit)
             && (pEdict->v.velocity.z != 0) && (pEdict->v.flags & (FL_ONGROUND | FL_PARTIALGROUND)))
         {
            // When lift is moving, pause the bot
            pBot->f_wpt_timeset = gpGlobals->time;
            pBot->f_move_speed = 0.0;
            pBot->f_sidemove_speed = 0.0;
            pBot->iAimFlags |= AIM_DEST;
         }

         // lift found but won't move ?
         else if ((tr.pHit->v.nextthink <= 0) && (pBot->f_itemcheck_time < gpGlobals->time))
         {
            DeleteSearchNodes (pBot);
            BotFindWaypoint (pBot);

            return (FALSE);
         }
      }

      // lift not found at waypoint ?
      else
      {
         // button has been pressed, lift should come
         pBot->f_wpt_timeset = gpGlobals->time;
         pBot->f_move_speed = 0.0;
         pBot->f_sidemove_speed = 0.0;
         pBot->iAimFlags |= AIM_DEST;

         if (pBot->f_itemcheck_time + 4.0 < gpGlobals->time)
         {
            DeleteSearchNodes (pBot);
            BotFindWaypoint (pBot);

            return (FALSE);
         }
      }
   }

   // Special Button Handling
   if (paths[pBot->curr_wpt_index]->flags & W_FL_USE_BUTTON)
   {
      fDesiredDistance = 0;

      pBot->iAimFlags = AIM_DEST; // look at button and only at it (so bot can trigger it)

      pButton = NULL;
      pNearestButton = NULL;
      f_button_min_distance = 100;

      // find the closest reachable button
      while (!FNullEnt (pButton = FIND_ENTITY_IN_SPHERE (pButton, pBot->wpt_origin, 100)))
      {
         if ((strcmp ("func_button", STRING (pButton->v.classname)) != 0)
             && (strcmp ("func_pushable", STRING (pButton->v.classname)) != 0)
             && (strcmp ("trigger_once", STRING (pButton->v.classname)) != 0)
             && (strcmp ("trigger_multiple", STRING (pButton->v.classname)) != 0))
            continue;

         v_button_origin = VecBModelOrigin (pButton);
         f_button_distance = (v_button_origin - pBot->wpt_origin).Length ();

         TRACE_LINE (pBot->wpt_origin, v_button_origin, ignore_monsters, pEdict, &tr);
         if (((tr.pHit == pButton) || (tr.flFraction > 0.95))
             && (f_button_distance < f_button_min_distance))
         {
            f_button_min_distance = f_button_distance;
            pNearestButton = pButton;
         }
      }

      // found one ?
      if (!FNullEnt (pNearestButton))
      {
         pBot->dest_origin = VecBModelOrigin (pNearestButton);
         pBot->f_wpt_timeset = gpGlobals->time;
         pBot->bCheckTerrain = FALSE;

         // reached it?
         if ((pBot->dest_origin - pEdict->v.origin).Length () < 50)
         {
            if (!(pEdict->v.button & IN_USE))
               pEdict->v.button |= IN_USE;

            pBot->f_itemcheck_time = gpGlobals->time + 4.0;
         }
      }
   }

   if ((pBot->prev_wpt_index[0] >= 0) && (pBot->prev_wpt_index[0] < g_iNumWaypoints))
      v_trace_start = paths[pBot->prev_wpt_index[0]]->origin;
   else
      v_trace_start = pEdict->v.origin;

   TRACE_LINE (v_trace_start, pBot->wpt_origin, ignore_monsters, pEdict, &tr);

   // special door handling
   if (strncmp (STRING (tr.pHit->v.classname), "func_door", 9) == 0)
   {
      fDesiredDistance = 0;
      pBot->iAimFlags = AIM_DEST; // look at door and only at it (so it opens in the right direction)

      // does this door has a target button ?
      if (STRING (tr.pHit->v.targetname)[0] != 0)
      {
         // find a reachable target button
         while (!FNullEnt (pButton = FIND_ENTITY_BY_STRING (pButton, "target", STRING (tr.pHit->v.targetname))))
         {
            v_button_origin = VecBModelOrigin (pButton);

            TRACE_LINE (v_trace_start, v_button_origin, ignore_monsters, pEdict, &tr2);
            if ((tr2.pHit == pButton) || (tr2.flFraction > 0.95))
            {
               pBot->dest_origin = v_button_origin;
               pBot->f_wpt_timeset = gpGlobals->time;
               pBot->bCheckTerrain = FALSE;

               if (((v_button_origin - pEdict->v.origin).Length () < 40)
                   && (RANDOM_LONG (1, 100) < 10))
                  pEdict->v.button |= IN_USE;

               break;
            }
         }
      }

      // if bot hits the door, then it opens, so wait a bit to let it open safely
      if ((pEdict->v.velocity.Length2D () < 2) && (pBot->f_timeDoorOpen < gpGlobals->time))
      {
         bottask_t TempTask = {NULL, NULL, TASK_PAUSE, TASKPRI_PAUSE, -1, gpGlobals->time + 0.5, FALSE};
         BotPushTask (pBot, &TempTask);
         pBot->f_timeDoorOpen = gpGlobals->time + 1.0; // retry in 1 sec until door is open
      }
   }

   // special breakable handling
   else if ((strcmp (STRING (tr.pHit->v.classname), "func_breakable") == 0)
            && IsShootableBreakable (tr.pHit))
   {
      pBot->iAimFlags = AIM_DEST; // look at breakable and only at it (so we can shoot it)
      pBot->pShootBreakable = tr.pHit;
      pBot->iCampButtons = pEdict->v.button & IN_DUCK;
      bottask_t TempTask = {NULL, NULL, TASK_SHOOTBREAKABLE, TASKPRI_SHOOTBREAKABLE, -1, 0.0, FALSE};
      BotPushTask (pBot, &TempTask);
   }

   // Check if Waypoint has a special Travelflag, so they need to be reached more precisely
   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      if (paths[pBot->curr_wpt_index]->connectflag[i] != 0)
      {
         fDesiredDistance = 0;
         break;
      }
   }

   // Needs precise placement - check if we get past the point
   if ((fDesiredDistance < 16) && (wpt_distance < 30))
   {
      Vector v_OriginNextFrame = pEdict->v.origin + (pEdict->v.velocity * pBot->fTimeFrameInterval);

      if ((v_OriginNextFrame - pBot->wpt_origin).Length () > wpt_distance)
         fDesiredDistance = wpt_distance + 1;
   }

   if (wpt_distance < fDesiredDistance)
   {
      // Did we reach a destination Waypoint ?
      if (BotGetSafeTask (pBot)->iData == pBot->curr_wpt_index)
      {
         // Add Goal Values
         if (g_bUseExperience && (pBot->chosengoal_index != -1))
         {
            iStartIndex = pBot->chosengoal_index;
            iGoalIndex = pBot->curr_wpt_index;

            if (pBot->bot_team == TEAM_CS_TERRORIST)
            {
               iWPTValue = (pBotExperienceData + (iStartIndex * g_iNumWaypoints) + iGoalIndex)->wTeam0Value;
               iWPTValue += pBot->pEdict->v.health / 20;
               iWPTValue += pBot->f_goal_value / 20;

               if (iWPTValue < -MAX_GOAL_VAL)
                  iWPTValue = -MAX_GOAL_VAL;
               else if (iWPTValue > MAX_GOAL_VAL)
                  iWPTValue = MAX_GOAL_VAL;

               (pBotExperienceData + (iStartIndex * g_iNumWaypoints) + iGoalIndex)->wTeam0Value = iWPTValue;
            }
            else
            {
               iWPTValue = (pBotExperienceData + (iStartIndex * g_iNumWaypoints) + iGoalIndex)->wTeam1Value;
               iWPTValue += pBot->pEdict->v.health / 20;
               iWPTValue += pBot->f_goal_value / 20;

               if (iWPTValue < -MAX_GOAL_VAL)
                  iWPTValue = -MAX_GOAL_VAL;
               else if (iWPTValue > MAX_GOAL_VAL)
                  iWPTValue = MAX_GOAL_VAL;

               (pBotExperienceData + (iStartIndex * g_iNumWaypoints) + iGoalIndex)->wTeam1Value = iWPTValue;
            }
         }

         return (TRUE);
      }

      else if (pBot->pWaypointNodes == NULL)
         return (FALSE);

      // Defusion Map ?
      if (g_iMapType & MAP_DE)
      {
         // Bomb planted and CT ?
         if (g_bBombPlanted && (pBot->bot_team == TEAM_CS_COUNTER))
         {
            iWPTIndex = BotGetSafeTask (pBot)->iData;

            if (iWPTIndex != -1)
            {
               float fDistance = (pEdict->v.origin - paths[iWPTIndex]->origin).Length ();

               // Bot within 'hearable' Bomb Tick Noises ?
               if (fDistance < BOMBMAXHEARDISTANCE)
               {
                  // Does hear Bomb ?
                  if (BotHearsBomb (pEdict->v.origin))
                  {
                     fDistance = (g_vecBomb - paths[iWPTIndex]->origin).Length ();

                     if (fDistance > 512.0)
                     {
                        // Doesn't hear so not a good goal
                        CTBombPointClear (iWPTIndex);
                        BotTaskComplete (pBot);
                     }
                  }
                  else
                  {
                     // Doesn't hear so not a good goal
                     CTBombPointClear (iWPTIndex);
                     BotTaskComplete (pBot);
                  }
               }
            }
         }
      }

      // Do the actual movement checking
      BotHeadTowardWaypoint (pBot);
   }

   return (FALSE);
}


inline bool BotGoalIsValid (bot_t* pBot)
{
   int iGoal = BotGetSafeTask (pBot)->iData;

   if (iGoal == -1)
      return (FALSE); // Not decided about a goal

   else if (iGoal == pBot->curr_wpt_index)
      return (TRUE); // No Nodes needed

   else if (pBot->pWaypointNodes == NULL)
      return (FALSE); // No Path calculated

   // Got Path - check if still valid
   PATHNODE *Node = pBot->pWaypointNodes;

   while (Node->NextNode != NULL)
      Node = Node->NextNode;

   if (Node->iIndex == iGoal)
      return (TRUE);

   return (FALSE);
}


void BotCheckRadioCommands (bot_t *pBot)
{
   // Radio Handling and Reactings to them

   edict_t *pPlayer = pBot->pRadioEntity; // Entity who used this Radio Command
   edict_t *pEdict = pBot->pEdict; // Bots Entity
   float f_distance = (pPlayer->v.origin - pEdict->v.origin).Length ();

   switch (pBot->iRadioOrder)
   {
      case RADIO_FOLLOWME:
         // check if line of sight to object is not blocked (i.e. visible)
         if (BotEntityIsVisible (pBot, pPlayer->v.origin))
         {
            // If Bot isn't already 'used' then follow him about half of the time
            if (FNullEnt (pBot->pBotUser) && (RANDOM_LONG (0, 100) < 50))
            {
               int iNumFollowers = 0;
               int i;

               // Check if no more followers are allowed
               for (i = 0; i < gpGlobals->maxClients; i++)
               {
                  if (bots[i].is_used && !bots[i].bDead)
                  {
                     if (bots[i].pBotUser == pPlayer)
                        iNumFollowers++;
                  }
               }

               if (iNumFollowers < g_iMaxNumFollow)
               {
                  BotPlayRadioMessage (pBot, RADIO_AFFIRMATIVE);
                  pBot->pBotUser = pPlayer;

                  // don't pause/camp/follow anymore
                  int iTask = BotGetSafeTask (pBot)->iTask;

                  if ((iTask == TASK_PAUSE) || (iTask == TASK_CAMP))
                     BotGetSafeTask (pBot)->fTime = gpGlobals->time;

                  pBot->f_bot_use_time = gpGlobals->time;
                  bottask_t TempTask = {NULL, NULL, TASK_FOLLOWUSER, TASKPRI_FOLLOWUSER, -1, 0.0, TRUE};
                  BotPushTask (pBot, &TempTask);
               }
               else
                  BotPlayRadioMessage (pBot, RADIO_NEGATIVE);
            }
            else if (pBot->pBotUser == pPlayer)
               BotPlayRadioMessage (pBot, RADIO_IMINPOSITION);
            else if (RANDOM_LONG (0, 100) < 50)
               BotPlayRadioMessage (pBot, RADIO_NEGATIVE);
         }
         break;

      case RADIO_HOLDPOSITION:
         if (!FNullEnt (pBot->pBotUser) && (pBot->pBotUser == pPlayer))
         {
            pBot->pBotUser = NULL;
            BotPlayRadioMessage (pBot, RADIO_AFFIRMATIVE);
            pBot->iCampButtons = 0;
            bottask_t TempTask = {NULL, NULL, TASK_PAUSE, TASKPRI_PAUSE, -1, gpGlobals->time + RANDOM_FLOAT (30.0, 60.0), FALSE};
            BotPushTask (pBot, &TempTask);
         }
         break;

      // Someone called for Assistance
      case RADIO_TAKINGFIRE:
         if (FNullEnt (pBot->pBotUser))
         {
            if (FNullEnt (pBot->pBotEnemy))
            {
               // Decrease Fear Levels to lower probability of Bot seeking Cover again
               pBot->fFearLevel -= 0.2;

               if (pBot->fFearLevel < 0.0)
                  pBot->fFearLevel = 0.0;

               BotPlayRadioMessage (pBot, RADIO_AFFIRMATIVE);

               // don't pause/camp anymore
               int iTask = BotGetSafeTask (pBot)->iTask;

               if ((iTask == TASK_PAUSE) || (iTask == TASK_CAMP))
                  BotGetSafeTask (pBot)->fTime = gpGlobals->time;

               pBot->f_bot_use_time = gpGlobals->time;
               pBot->vecPosition = pPlayer->v.origin;
               DeleteSearchNodes (pBot);

               bottask_t TempTask = {NULL, NULL, TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, -1, 0.0, TRUE};
               BotPushTask (pBot, &TempTask);
            }
            else
               BotPlayRadioMessage (pBot, RADIO_NEGATIVE);
         }
         break;

      case RADIO_NEEDBACKUP:
         if ((FNullEnt (pBot->pBotEnemy) && (BotEntityIsVisible (pBot, pPlayer->v.origin)))
             || (f_distance < 2048))
         {
            pBot->fFearLevel -= 0.1;
            if (pBot->fFearLevel < 0.0)
               pBot->fFearLevel = 0.0;

            BotPlayRadioMessage (pBot, RADIO_AFFIRMATIVE);

            // don't pause/camp anymore
            int iTask = BotGetSafeTask (pBot)->iTask;
            if ((iTask == TASK_PAUSE) || (iTask == TASK_CAMP))
               BotGetSafeTask (pBot)->fTime = gpGlobals->time;

            pBot->f_bot_use_time = gpGlobals->time;
            pBot->vecPosition = pPlayer->v.origin;
            DeleteSearchNodes (pBot);

            bottask_t TempTask = {NULL, NULL, TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, -1, 0.0, TRUE};
            BotPushTask (pBot, &TempTask);
         }
         else
            BotPlayRadioMessage (pBot, RADIO_NEGATIVE);

      case RADIO_GOGOGO:
         if (pPlayer == pBot->pBotUser)
         {
            BotPlayRadioMessage (pBot, RADIO_AFFIRMATIVE);
            pBot->pBotUser = NULL;
            pBot->fFearLevel -= 0.3;

            if (pBot->fFearLevel < 0.0)
               pBot->fFearLevel = 0.0;
         }
         else if ((FNullEnt (pBot->pBotEnemy) && (BotEntityIsVisible (pBot, pPlayer->v.origin)))
                  || (f_distance < 2048))
         {
            int iTask = BotGetSafeTask (pBot)->iTask;

            if ((iTask == TASK_PAUSE) || (iTask == TASK_CAMP))
            {
               pBot->fFearLevel -= 0.3;

               if (pBot->fFearLevel < 0.0)
                  pBot->fFearLevel = 0.0;

               BotPlayRadioMessage (pBot, RADIO_AFFIRMATIVE);

               // don't pause/camp anymore
               BotGetSafeTask (pBot)->fTime = gpGlobals->time;
               pBot->f_bot_use_time = gpGlobals->time;
               pBot->pBotUser = NULL;
               MAKE_VECTORS (pPlayer->v.v_angle);
               pBot->vecPosition = pPlayer->v.origin + gpGlobals->v_forward * RANDOM_LONG (1024, 2048);
               DeleteSearchNodes (pBot);
               bottask_t TempTask = {NULL, NULL, TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, -1, 0.0, TRUE};
               BotPushTask (pBot, &TempTask);
            }
         }
         else
            BotPlayRadioMessage (pBot, RADIO_NEGATIVE);
         break;

      case RADIO_STORMTHEFRONT:
         if ((FNullEnt (pBot->pBotEnemy) && BotEntityIsVisible (pBot, pPlayer->v.origin))
             || (f_distance < 1024))
         {
            BotPlayRadioMessage (pBot, RADIO_AFFIRMATIVE);

            // don't pause/camp anymore
            int iTask = BotGetSafeTask (pBot)->iTask;

            if ((iTask == TASK_PAUSE) || (iTask == TASK_CAMP))
               BotGetSafeTask (pBot)->fTime = gpGlobals->time;

            pBot->f_bot_use_time = gpGlobals->time;
            pBot->pBotUser = NULL;
            MAKE_VECTORS (pPlayer->v.v_angle);
            pBot->vecPosition = pPlayer->v.origin + gpGlobals->v_forward * RANDOM_LONG (1024, 2048);
            DeleteSearchNodes (pBot);
            bottask_t TempTask = {NULL, NULL, TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, -1, 0.0, TRUE};
            BotPushTask (pBot, &TempTask);

            pBot->fFearLevel -= 0.3;
            if (pBot->fFearLevel < 0.0)
               pBot->fFearLevel = 0.0;

            pBot->fAgressionLevel += 0.3;
            if (pBot->fAgressionLevel > 1.0)
               pBot->fAgressionLevel = 1.0;
         }
         break;

      case RADIO_FALLBACK:
         if ((FNullEnt (pBot->pBotEnemy) && (BotEntityIsVisible (pBot, pPlayer->v.origin)))
             || (f_distance < 1024))
         {
            pBot->fFearLevel += 0.5;
            if (pBot->fFearLevel > 1.0)
               pBot->fFearLevel = 1.0;

            pBot->fAgressionLevel -= 0.5;
            if (pBot->fAgressionLevel < 0.0)
               pBot->fAgressionLevel = 0.0;

            BotPlayRadioMessage (pBot, RADIO_AFFIRMATIVE);
            if (BotGetSafeTask (pBot)->iTask == TASK_CAMP)
               BotGetSafeTask (pBot)->fTime += RANDOM_FLOAT (10.0, 15.0);
            else
            {
               // don't pause/camp anymore
               int iTask = BotGetSafeTask (pBot)->iTask;
               if (iTask == TASK_PAUSE)
                  BotGetSafeTask (pBot)->fTime = gpGlobals->time;

               pBot->f_bot_use_time = gpGlobals->time;
               pBot->pBotUser = NULL;

               // FIXME : Bot doesn't see enemy yet!
               pBot->f_bot_see_enemy_time = gpGlobals->time;

               // If Bot has no enemy
               if (pBot->vecLastEnemyOrigin == g_vecZero)
               {
                  float distance;
                  float nearestdistance = 9999.0;
                  int ind;

                  // Take nearest enemy to ordering Player
                  for (ind = 0; ind < gpGlobals->maxClients; ind++)
                  {
                     if (!clients[ind].IsUsed
                         || !clients[ind].IsAlive
                         || (clients[ind].iTeam == pBot->bot_team))
                        continue;

                     edict_t *pEnemy = clients[ind].pEdict;
                     distance = (pPlayer->v.origin - pEnemy->v.origin).Length ();

                     if (distance < nearestdistance)
                     {
                        nearestdistance = distance;
                        pBot->pLastEnemy = pEnemy;
                        pBot->vecLastEnemyOrigin = pEnemy->v.origin;
                     }
                  }
               }

               DeleteSearchNodes(pBot);
            }
         }
         break;

      case RADIO_REPORTTEAM:
         BotPlayRadioMessage (pBot, RADIO_REPORTINGIN);
         break;

      case RADIO_SECTORCLEAR:
         // Is Bomb planted and it's a Counter
         if (g_bBombPlanted)
         {
            // Check if it's a Counter Command
            if ((UTIL_GetTeam (pPlayer) == TEAM_CS_COUNTER) && (pBot->bot_team == TEAM_CS_COUNTER))
            {
               if (g_fTimeNextBombUpdate < gpGlobals->time)
               {
                  float min_distance = 9999.0;
                  int i;

                  // Find Nearest Bomb Waypoint to Player
                  for (i = 0; i < g_iNumGoalPoints; i++)
                  {
                     f_distance = (paths[g_rgiGoalWaypoints[i]]->origin - pPlayer->v.origin).Length ();

                     if (f_distance < min_distance)
                     {
                        min_distance = f_distance;
                        g_iLastBombPoint = g_rgiGoalWaypoints[i];
                     }
                  }

                  // Enter this WPT Index as taboo wpt
                  CTBombPointClear (g_iLastBombPoint);
                  g_fTimeNextBombUpdate = gpGlobals->time + 0.5;
               }

               // Does this Bot want to defuse ?
               if (BotGetSafeTask (pBot)->iTask == TASK_NORMAL)
               {
                  // Is he approaching this goal ?
                  if (BotGetSafeTask (pBot)->iData == g_iLastBombPoint)
                  {
                     BotGetSafeTask (pBot)->iData = -1;
                     BotPlayRadioMessage (pBot, RADIO_AFFIRMATIVE);
                  }
               }
            }
         }
         break;

      case RADIO_GETINPOSITION:
         if ((FNullEnt (pBot->pBotEnemy) && (BotEntityIsVisible (pBot, pPlayer->v.origin)))
             || (f_distance < 1024))
         {
            BotPlayRadioMessage (pBot, RADIO_AFFIRMATIVE);
            if (BotGetSafeTask (pBot)->iTask == TASK_CAMP)
               BotGetSafeTask (pBot)->fTime = gpGlobals->time + RANDOM_FLOAT (30.0, 60.0);
            else
            {
               // don't pause anymore
               int iTask = BotGetSafeTask (pBot)->iTask;
               if (iTask == TASK_PAUSE)
                  BotGetSafeTask (pBot)->fTime = gpGlobals->time;

               pBot->f_bot_use_time = gpGlobals->time;
               pBot->pBotUser = NULL;

               // FIXME : Bot doesn't see enemy yet!
               pBot->f_bot_see_enemy_time = gpGlobals->time;

               // If Bot has no enemy
               if (pBot->vecLastEnemyOrigin == g_vecZero)
               {
                  float distance;
                  float nearestdistance = 9999.0;
                  int ind;

                  // Take nearest enemy to ordering Player
                  for (ind = 0; ind < gpGlobals->maxClients; ind++)
                  {
                     if (!clients[ind].IsUsed
                         || !clients[ind].IsAlive
                         || (clients[ind].iTeam == pBot->bot_team))
                        continue;

                     edict_t *pEnemy = clients[ind].pEdict;
                     distance = (pPlayer->v.origin - pEnemy->v.origin).Length ();

                     if (distance < nearestdistance)
                     {
                        nearestdistance = distance;
                        pBot->pLastEnemy = pEnemy;
                        pBot->vecLastEnemyOrigin = pEnemy->v.origin;
                     }
                  }
               }

               DeleteSearchNodes(pBot);

               // Push camp task on to stack
               bottask_t TempTask = {NULL, NULL, TASK_CAMP, TASKPRI_CAMP, -1, gpGlobals->time + RANDOM_FLOAT (30.0, 60.0), TRUE};
               BotPushTask (pBot, &TempTask);

               // Push Move Command
               TempTask.iTask = TASK_MOVETOPOSITION;
               TempTask.fDesire = TASKPRI_MOVETOPOSITION;
               TempTask.iData = BotFindDefendWaypoint (pBot, pPlayer->v.origin);
               BotPushTask (pBot, &TempTask);
               pBot->iCampButtons |= IN_DUCK;
            }
         }

         break;
   }

   // Radio Command has been handled, reset
   pBot->iRadioOrder = 0;

   return;
}


int BotFindGoal (bot_t *pBot)
{
   // Chooses a Destination (Goal) Waypoint for a Bot

   int iTactic;
   int iOffensive;
   int iDefensive;
   int iGoalDesire;
   int iForwardDesire;
   int iCampDesire;
   int iBackoffDesire;
   int iTacticChoice;
   int *pOffensiveWPTS = NULL;
   int iNumOffensives;
   int *pDefensiveWPTS = NULL;
   int iNumDefensives;
   bool bHasHostage = BotHasHostage (pBot);
   int index;
   int min_index;
   float distance;
   float min_distance;
   int iGoalChoices[4];
   float iGoalDistances[4];
   edict_t *pEdict;

   pEdict = pBot->pEdict;

   // Pathfinding Behaviour depending on Maptype
   if (g_bUseExperience)
   {
      if (pBot->bot_team == TEAM_CS_TERRORIST)
      {
         pOffensiveWPTS = &g_rgiCTWaypoints[0];
         iNumOffensives = g_iNumCTPoints;
         pDefensiveWPTS = &g_rgiTerrorWaypoints[0];
         iNumDefensives = g_iNumTerrorPoints;
      }
      else
      {
         pOffensiveWPTS = &g_rgiTerrorWaypoints[0];
         iNumOffensives = g_iNumTerrorPoints;
         pDefensiveWPTS = &g_rgiCTWaypoints[0];
         iNumDefensives = g_iNumCTPoints;
      }

      // Terrorist carrying the C4 ?
      if ((pBot->pEdict->v.weapons & (1 << CS_WEAPON_C4))
          || pBot->bIsVIP)
      {
         iTactic = 3;
         goto tacticchosen;
      }
      else if (bHasHostage && (pBot->bot_team == TEAM_CS_COUNTER))
      {
         min_distance = 9999;
         min_index = -1;

         for (index = 0; index < g_iNumWaypoints; index++)
         {
            distance = (paths[index]->origin - pEdict->v.origin).Length();

            if ((paths[index]->flags & W_FL_RESCUE) && (distance < min_distance))
            {
               min_distance = distance;
               min_index = index;
            }
         }

         if (min_index != -1)
            return (min_index);
      }

      iOffensive = pBot->fAgressionLevel * 100;
      iDefensive = pBot->fFearLevel * 100;

      if ((g_iMapType & MAP_AS) || (g_iMapType & MAP_CS))
      {
         if (pBot->bot_team == TEAM_CS_TERRORIST)
         {
            iDefensive += 25;
            iOffensive -= 25;
         }
      }

      if (g_iMapType & MAP_DE)
      {
         if (pBot->bot_team == TEAM_CS_COUNTER)
         {
            if (g_bBombPlanted)
            {
               if (g_bBotChat && g_bBombSayString)
               {
                  BotPrepareChatMessage (pBot, szBombChat[RANDOM_LONG (0, iNumBombChats - 1)]);
                  BotPushMessageQueue (pBot, MSG_CS_SAY);
                  g_bBombSayString = FALSE;
               }

               return (BotChooseBombWaypoint (pBot));
            }

            iDefensive += 25;
            iOffensive -= 25;
         }
      }

      iGoalDesire = RANDOM_LONG (0, 100) + iOffensive;
      iForwardDesire = RANDOM_LONG (0, 100) + iOffensive;
      iCampDesire = RANDOM_LONG (0, 100) + iDefensive;
      iBackoffDesire = RANDOM_LONG (0, 100) + iDefensive;

      iTacticChoice = iBackoffDesire;
      iTactic = 0;

      if (iCampDesire > iTacticChoice)
      {
         iTacticChoice = iCampDesire;
         iTactic = 1;
      }
      if (iForwardDesire > iTacticChoice)
      {
         iTacticChoice = iForwardDesire;
         iTactic = 2;
      }
      if (iGoalDesire > iTacticChoice)
      {
         iTacticChoice = iGoalDesire;
         iTactic = 3;
      }

tacticchosen:
      for (index = 0; index < 4; index++)
      {
         iGoalChoices[index] = -1;
         iGoalDistances[index] = 9999.0;
      }

      // Defensive Goal
      if (iTactic == 0)
      {
         for (index = 0; index < 4; index++)
         {
            iGoalChoices[index] = pDefensiveWPTS[RANDOM_LONG (0, iNumDefensives - 1)];
            assert (iGoalChoices[index] >= 0 && iGoalChoices[index] < g_iNumWaypoints);
         }
      }

      // Camp Waypoint Goal
      else if (iTactic == 1)
      {
         for (index = 0; index < 4; index++)
         {
            iGoalChoices[index] = g_rgiCampWaypoints[RANDOM_LONG (0, g_iNumCampPoints - 1)];
            assert (iGoalChoices[index] >= 0 && iGoalChoices[index] < g_iNumWaypoints);
         }
      }

      // Offensive Goal
      else if (iTactic == 2)
      {
         for (index = 0; index < 4; index++)
         {
            iGoalChoices[index] = pOffensiveWPTS[RANDOM_LONG (0, iNumOffensives - 1)];
            assert (iGoalChoices[index] >= 0 && iGoalChoices[index] < g_iNumWaypoints);
         }
      }

      // Map Goal Waypoint
      else if (iTactic == 3)
      {
         for (index = 0; index < 4; index++)
         {
            iGoalChoices[index] = g_rgiGoalWaypoints[RANDOM_LONG (0, g_iNumGoalPoints - 1)];
            assert (iGoalChoices[index] >= 0 && iGoalChoices[index] < g_iNumWaypoints);
         }
      }

      if ((pBot->curr_wpt_index < 0) || (pBot->curr_wpt_index >= g_iNumWaypoints))
         pBot->curr_wpt_index = WaypointFindNearestToMove (pBot->pEdict->v.origin);

      int iBotIndex = pBot->curr_wpt_index;
      int iTestIndex;
      bool bSorting;

      do
      {
         bSorting = FALSE;
         for (index = 0; index < 3; index++)
         {
            iTestIndex = iGoalChoices[index + 1];
            if (iTestIndex < 0)
               break;

            if (pBot->bot_team == TEAM_CS_TERRORIST)
            {
               if ((pBotExperienceData + (iBotIndex * g_iNumWaypoints) + iGoalChoices[index])->wTeam0Value
                   < (pBotExperienceData + (iBotIndex * g_iNumWaypoints) + iTestIndex)->wTeam0Value)
               {
                  iGoalChoices[index + 1] = iGoalChoices[index];
                  iGoalChoices[index] = iTestIndex;
                  bSorting = TRUE;
               }
            }
            else
            {
               if ((pBotExperienceData + (iBotIndex * g_iNumWaypoints) + iGoalChoices[index])->wTeam1Value
                   < (pBotExperienceData + (iBotIndex * g_iNumWaypoints) + iTestIndex)->wTeam1Value)
               {
                  iGoalChoices[index + 1] = iGoalChoices[index];
                  iGoalChoices[index] = iTestIndex;
                  bSorting = TRUE;
               }
            }

         }
      } while (bSorting);

      return (iGoalChoices[0]);
   }
   else
   {
      int iRandomPick = RANDOM_LONG (1, 100);

      if (g_iMapType & MAP_AS)
      {
         if (pBot->bIsVIP)
         {
            // 90% choose a Goal Waypoint
            if (iRandomPick < 70)
               return (g_rgiGoalWaypoints[RANDOM_LONG (0, g_iNumGoalPoints - 1)]);
            else if (iRandomPick < 90)
               return (g_rgiCTWaypoints[RANDOM_LONG (0, g_iNumCTPoints - 1)]);
            else
               return (g_rgiTerrorWaypoints[RANDOM_LONG (0, g_iNumTerrorPoints - 1)]);
         }
         else
         {
            if (pBot->bot_team == TEAM_CS_TERRORIST)
            {
               if (iRandomPick < 30)
                  return (g_rgiGoalWaypoints[RANDOM_LONG (0, g_iNumGoalPoints - 1)]);
               else if (iRandomPick < 60)
                  return (g_rgiTerrorWaypoints[RANDOM_LONG (0, g_iNumTerrorPoints - 1)]);
               else if (iRandomPick < 90)
                  return (g_rgiCampWaypoints[RANDOM_LONG (0, g_iNumCampPoints - 1)]);
               else
                  return (g_rgiCTWaypoints[RANDOM_LONG (0, g_iNumCTPoints - 1)]);
            }
            else
            {
               if (iRandomPick < 50)
                  return (g_rgiGoalWaypoints[RANDOM_LONG (0, g_iNumGoalPoints - 1)]);
               else if (iRandomPick < 70)
                  return (g_rgiTerrorWaypoints[RANDOM_LONG (0, g_iNumTerrorPoints - 1)]);
               else if (iRandomPick < 90)
                  return (g_rgiCampWaypoints[RANDOM_LONG (0, g_iNumCampPoints - 1)]);
               else
                  return (g_rgiCTWaypoints[RANDOM_LONG (0, g_iNumCTPoints - 1)]);
            }
         }
      }

      if (g_iMapType & MAP_CS)
      {
         bHasHostage = BotHasHostage (pBot);

         if (pBot->bot_team == TEAM_CS_TERRORIST)
         {
            if (iRandomPick < 30)
               return (g_rgiGoalWaypoints[RANDOM_LONG (0, g_iNumGoalPoints - 1)]);
            else if (iRandomPick < 60)
               return (g_rgiTerrorWaypoints[RANDOM_LONG (0, g_iNumTerrorPoints - 1)]);
            else if (iRandomPick < 90)
               return (g_rgiCampWaypoints[RANDOM_LONG (0, g_iNumCampPoints - 1)]);
            else
               return (g_rgiCTWaypoints[RANDOM_LONG (0, g_iNumCTPoints - 1)]);
         }
         else
         {
            if (bHasHostage)
            {
               min_distance = 9999;
               min_index = -1;

               for (index = 0; index < g_iNumWaypoints; index++)
               {
                  distance = (paths[index]->origin - pEdict->v.origin).Length();

                  if ((paths[index]->flags & W_FL_RESCUE) && (distance < min_distance))
                  {
                     min_distance = distance;
                     min_index = index;
                  }
               }

               if (min_index != -1)
                  return (min_index);
            }
            else if (iRandomPick < 50)
               return (g_rgiGoalWaypoints[RANDOM_LONG (0, g_iNumGoalPoints - 1)]);
            else if (iRandomPick < 70)
               return (g_rgiTerrorWaypoints[RANDOM_LONG (0, g_iNumTerrorPoints - 1)]);
            else if (iRandomPick < 90)
               return (g_rgiCampWaypoints[RANDOM_LONG (0, g_iNumCampPoints - 1)]);
            else
               return (g_rgiCTWaypoints[RANDOM_LONG (0, g_iNumCTPoints - 1)]);
         }
      }

      if (g_iMapType & MAP_DE)
      {
         if (pBot->bot_team == TEAM_CS_TERRORIST)
         {
            // Terrorist carrying the C4 ?
            if (pBot->pEdict->v.weapons & (1 << CS_WEAPON_C4))
               return (g_rgiGoalWaypoints[RANDOM_LONG (0, g_iNumGoalPoints - 1)]);
            else if (iRandomPick < 30)
               return (g_rgiGoalWaypoints[RANDOM_LONG (0, g_iNumGoalPoints - 1)]);
            else if (iRandomPick < 60)
               return (g_rgiCTWaypoints[RANDOM_LONG (0, g_iNumCTPoints - 1)]);
            else if (iRandomPick < 90)
               return (g_rgiCampWaypoints[RANDOM_LONG (0, g_iNumCampPoints - 1)]);
            else
               return (g_rgiTerrorWaypoints[RANDOM_LONG (0, g_iNumTerrorPoints - 1)]);
         }
         else
         {
            if (g_bBombPlanted)
            {
               if (g_bBotChat && g_bBombSayString)
               {
                  BotPrepareChatMessage (pBot, szBombChat[RANDOM_LONG (0, iNumBombChats - 1)]);
                  BotPushMessageQueue (pBot, MSG_CS_SAY);
                  g_bBombSayString = FALSE;
               }

               return (BotChooseBombWaypoint (pBot));
            }
            else if (iRandomPick < 50)
               return (g_rgiGoalWaypoints[RANDOM_LONG (0, g_iNumGoalPoints - 1)]);
            else if (iRandomPick < 70)
               return (g_rgiCTWaypoints[RANDOM_LONG (0, g_iNumCTPoints - 1)]);
            else if (iRandomPick < 90)
               return (g_rgiCampWaypoints[RANDOM_LONG (0, g_iNumCampPoints - 1)]);
            else
               return (g_rgiTerrorWaypoints[RANDOM_LONG (0, g_iNumTerrorPoints - 1)]);
         }
      }
   }

   return (0);
}


int GetHighestFragsBot (int iTeam)
{
   bot_t *pFragBot;
   int iBestIndex = 0;
   float fBestFrags = -1;
   int bot_index;

   // Search Bots in this team
   for (bot_index = 0; bot_index < gpGlobals->maxClients; bot_index++)
   {
      pFragBot = &bots[bot_index];

      if (pFragBot->is_used && !FNullEnt (pFragBot->pEdict) && !pFragBot->bDead
          && (pFragBot->bot_team == iTeam) && (pFragBot->pEdict->v.frags > fBestFrags))
      {
         iBestIndex = bot_index;
         fBestFrags = pFragBot->pEdict->v.frags;
      }
   }

   return (iBestIndex);
}


void SelectLeaderEachTeam (bot_t *pBot)
{
   bot_t *pBotLeader;
   edict_t *pEdict = pBot->pEdict;

   if (g_iMapType & MAP_AS)
   {
      if (pBot->bIsVIP && !g_bLeaderChosenCT)
      {
         // VIP Bot is the leader
         pBot->bIsLeader = TRUE;

         if (RANDOM_LONG (1, 100) < 50)
         {
            BotPlayRadioMessage (pBot, RADIO_FOLLOWME);
            pBot->iCampButtons = 0;
            bottask_t TempTask = {NULL, NULL, TASK_PAUSE, TASKPRI_PAUSE, -1, gpGlobals->time + 3.0, FALSE};
            BotPushTask (pBot, &TempTask);
         }

         g_bLeaderChosenCT = TRUE;
      }
      else if ((pBot->bot_team == TEAM_CS_TERRORIST) && !g_bLeaderChosenT)
      {
         pBotLeader = &bots[GetHighestFragsBot (pBot->bot_team)];
         pBotLeader->bIsLeader = TRUE;

         if (RANDOM_LONG (1, 100) < 50)
         {
            BotPlayRadioMessage (pBotLeader, RADIO_FOLLOWME);
            bottask_t TempTask = {NULL, NULL, TASK_PAUSE, TASKPRI_PAUSE, -1, gpGlobals->time + 3.0, FALSE};
            BotPushTask (pBotLeader, &TempTask);
         }

         g_bLeaderChosenT = TRUE;
      }
   }

   if (g_iMapType & MAP_CS)
   {
      if (pBot->bot_team == TEAM_CS_TERRORIST)
      {
         pBotLeader = &bots[GetHighestFragsBot (pBot->bot_team)];
         pBotLeader->bIsLeader = TRUE;

         if (RANDOM_LONG (1, 100) < 50)
         {
            BotPlayRadioMessage (pBotLeader, RADIO_FOLLOWME);
            bottask_t TempTask = {NULL, NULL, TASK_PAUSE, TASKPRI_PAUSE, -1, gpGlobals->time + 3.0, FALSE};
            BotPushTask (pBotLeader, &TempTask);
         }
      }
      else
      {
         pBotLeader = &bots[GetHighestFragsBot (pBot->bot_team)];
         pBotLeader->bIsLeader = TRUE;

         if (RANDOM_LONG (1, 100) < 50)
         {
            BotPlayRadioMessage (pBotLeader, RADIO_FOLLOWME);
            bottask_t TempTask = {NULL, NULL, TASK_PAUSE, TASKPRI_PAUSE, -1, gpGlobals->time + 3.0, FALSE};
            BotPushTask (pBotLeader, &TempTask);
         }
      }
   }

   if (g_iMapType & MAP_DE)
   {
      if ((pBot->bot_team == TEAM_CS_TERRORIST) && !g_bLeaderChosenT)
      {
         if (pEdict->v.weapons & (1 << CS_WEAPON_C4))
         {
            // Bot carrying the Bomb is the leader
            pBot->bIsLeader = TRUE;

            // Terrorist carrying a Bomb needs to have some company so order some Bots sometimes
            if (RANDOM_LONG (1, 100) < 50)
            {
               BotPlayRadioMessage (pBot, RADIO_FOLLOWME);
               pBot->iCampButtons = 0;
               bottask_t TempTask = {NULL, NULL, TASK_PAUSE, TASKPRI_PAUSE, -1, gpGlobals->time + 3.0, FALSE};
               BotPushTask (pBot, &TempTask);
            }

            g_bLeaderChosenT = TRUE;
         }
      }
      else if (!g_bLeaderChosenCT)
      {
         pBotLeader = &bots[GetHighestFragsBot (pBot->bot_team)];
         pBotLeader->bIsLeader = TRUE;

         if (RANDOM_LONG (1, 100) < 50)
         {
            BotPlayRadioMessage (pBotLeader, RADIO_FOLLOWME);
            bottask_t TempTask = {NULL, NULL, TASK_PAUSE, TASKPRI_PAUSE, -1, gpGlobals->time + 3.0, FALSE};
            BotPushTask (pBotLeader, &TempTask);
         }

         g_bLeaderChosenCT = TRUE;
      }
   }

   return;
}


void BotChooseAimDirection (bot_t *pBot)
{
   bool bCanChoose = TRUE;
   unsigned int iFlags = pBot->iAimFlags;

   // Don't allow Bot to look at danger positions under certain circumstances
   if (!(iFlags & (AIM_GRENADE | AIM_ENEMY | AIM_ENTITY)))
   {
      if (pBot->bOnLadder || pBot->bInWater
         || (pBot->curr_travel_flags & C_FL_JUMP) || (pBot->iWPTFlags & W_FL_LADDER))
      {
         iFlags &= ~(AIM_LASTENEMY | AIM_PREDICTPATH);
         bCanChoose = FALSE;
      }
   }

   if (iFlags & AIM_OVERRIDE)
      pBot->vecLookAt = pBot->vecCamp;
   else if (iFlags & AIM_GRENADE)
      pBot->vecLookAt = GetGunPosition (pBot->pEdict) + (pBot->vecGrenade.Normalize () * 1);
   else if (iFlags & AIM_ENEMY)
   {
      if (pBot->fNextFocusEnemyTime < gpGlobals->time)
      {
         pBot->fNextFocusEnemyTime = gpGlobals->time + 0.05;
         pBot->vecLookAt = pBot->vecEnemy;
         BotFocusEnemy (pBot);
      }
   }
   else if (iFlags & AIM_ENTITY)
      pBot->vecLookAt = pBot->vecEntity;
   else if (iFlags & AIM_LASTENEMY)
   {
      pBot->vecLookAt = pBot->vecLastEnemyOrigin;

      // Did Bot just see Enemy and is quite agressive ?
      if (pBot->f_bot_see_enemy_time - pBot->f_actual_reaction_time + pBot->fBaseAgressionLevel > gpGlobals->time)
      {
         // Not using a Sniper Weapon ?
         if (!BotUsesSniper (pBot))
         {
            // Feel free to fire if shootable
            if (BotLastEnemyShootable (pBot))
               pBot->bWantsToFire = TRUE;
         }
      }
   }
   else if (iFlags & AIM_PREDICTPATH)
   {
      bool bRecalcPath = TRUE;

      if ((pBot->pTrackingEdict == pBot->pLastEnemy)
          && (pBot->fTimeNextTracking < gpGlobals->time))
         bRecalcPath = FALSE;

      if (bRecalcPath)
      {
         pBot->vecLookAt = paths[GetAimingWaypoint (pBot, pBot->vecLastEnemyOrigin, 8)]->origin;
         pBot->vecCamp = pBot->vecLookAt;
         pBot->fTimeNextTracking = gpGlobals->time + 0.5;
         pBot->pTrackingEdict = pBot->pLastEnemy;
      }
      else
         pBot->vecLookAt = pBot->vecCamp;
   }
   else if (iFlags & AIM_CAMP)
      pBot->vecLookAt = pBot->vecCamp;
   else if (iFlags & AIM_DEST)
   {
      pBot->vecLookAt = pBot->dest_origin;

      if (bCanChoose && g_bUseExperience && (pBot->curr_wpt_index != -1)
          && !(paths[pBot->curr_wpt_index]->flags & W_FL_LADDER))
      {
         int iIndex = pBot->curr_wpt_index;

         if (pBot->bot_team == TEAM_CS_TERRORIST)
         {
            if ((pBotExperienceData + (iIndex * g_iNumWaypoints) + iIndex)->iTeam0_danger_index != -1)
               pBot->vecLookAt = paths[(pBotExperienceData + (iIndex * g_iNumWaypoints) + iIndex)->iTeam0_danger_index]->origin;
         }
         else
         {
            if ((pBotExperienceData + (iIndex * g_iNumWaypoints) + iIndex)->iTeam1_danger_index != -1)
               pBot->vecLookAt = paths[(pBotExperienceData + (iIndex * g_iNumWaypoints) + iIndex)->iTeam1_danger_index]->origin;
         }
      }

      pBot->vecLookAt = pBot->vecLookAt + Vector (0, 0, 16);
   }

   if (pBot->vecLookAt == g_vecZero)
      pBot->vecLookAt = pBot->dest_origin;
}


void BotSetStrafeSpeed (bot_t *pBot, Vector vecMoveDir, float fStrafeSpeed)
{
   edict_t *pEdict = pBot->pEdict;
   Vector2D vec2LOS;
   Vector vecDestination = pEdict->v.origin + vecMoveDir * 2;
   float flDot;

   MAKE_VECTORS (pEdict->v.angles);

   vec2LOS = (vecMoveDir - pEdict->v.origin).Make2D ();
   vec2LOS = vec2LOS.Normalize ();

   flDot = DotProduct (vec2LOS, gpGlobals->v_forward.Make2D ());

   if (flDot > 0)
      pBot->f_sidemove_speed = fStrafeSpeed;
   else
      pBot->f_sidemove_speed = -fStrafeSpeed;
}


void BotThink (bot_t *pBot)
{
   // This function gets called each Frame and is the core of
   // all Bot AI. From here all other Subroutines are called

   int index = 0;
   Vector v_diff; // vector from previous to current location
   float moved_distance; // length of v_diff vector (distance bot moved)
   TraceResult tr;
   bool bBotMovement = FALSE;
   float flDot;
   bot_t *pOtherBot;
   int i, c;
   Vector v_direction;
   Vector v_angles;
   Vector v_dest;
   Vector v_src;
   bool bMoveToGoal = TRUE;

   edict_t *pEdict = pBot->pEdict;
   pEdict->v.flags |= FL_FAKECLIENT;

   pBot->fTimeFrameInterval = gpGlobals->time - pBot->fTimePrevThink;
   pBot->fTimePrevThink = gpGlobals->time;

   pEdict->v.button = 0;
   pBot->f_move_speed = 0.0;
   pBot->f_sidemove_speed = 0.0;
   pBot->bDead = (IsAlive (pEdict) ? FALSE : TRUE);

   // if the bot hasn't selected stuff to start the game yet, go do that...
   if (pBot->not_started)
      BotStartGame (pBot); // Select Team & Class

   // In Cs Health seems to be refilled in Spectator Mode that's why I'm doing this extra
   else if (pBot->bDead)
   {
      // Bot chatting turned on ?
      if (g_bBotChat)
      {
         if (!BotRepliesToPlayer (pBot) && (pBot->f_lastchattime + 10 < gpGlobals->time)
             && (g_fLastChatTime + 5.0 < gpGlobals->time))
         {
            // Say a Text every now and then
            if (RANDOM_LONG (1, 1500) < 2)
            {
               pBot->f_lastchattime = gpGlobals->time;
               g_fLastChatTime = gpGlobals->time;

               // Rotate used Strings Array up
               szUsedDeadChat[7] = szUsedDeadChat[6];
               szUsedDeadChat[6] = szUsedDeadChat[5];
               szUsedDeadChat[5] = szUsedDeadChat[4];
               szUsedDeadChat[4] = szUsedDeadChat[3];
               szUsedDeadChat[3] = szUsedDeadChat[2];
               szUsedDeadChat[2] = szUsedDeadChat[1];
               szUsedDeadChat[1] = szUsedDeadChat[0];

               bool bStringUsed = TRUE;
               int iStringIndex;
               int iCount = 0;

               while (bStringUsed)
               {
                  iStringIndex = RANDOM_LONG (0, iNumDeadChats - 1);
                  bStringUsed = FALSE;

                  for (i = 0; i < 8; i++)
                  {
                     if (szUsedDeadChat[i] == szDeadChat[iStringIndex])
                     {
                        iCount++;
                        if (iCount < 9)
                           bStringUsed = TRUE;
                     }
                  }
               }

               // Save new String
               szUsedDeadChat[0] = szDeadChat[iStringIndex];
               BotPrepareChatMessage (pBot, szDeadChat[iStringIndex]);
               BotPushMessageQueue (pBot, MSG_CS_SAY);
            }
         }
      }
   }

   // Bot is still buying - don't move
   else if ((pBot->iBuyCount > 0) && (pBot->iBuyCount < 8) && pBot->b_can_buy)
   {
      if (pBot->f_buy_time < gpGlobals->time)
         BotBuyStuff (pBot);

      // Bot has spawned 10 secs ago ? Something went wrong in buying so cancel it
      if (pBot->f_spawn_time + 10.0 < gpGlobals->time)
         pBot->iBuyCount = 0;
   }
   else
      bBotMovement = TRUE;

   // Check for pending Messages
   BotCheckMessageQueue (pBot);

   if (!bBotMovement || (gpGlobals->time <= g_fTimeRoundStart) || g_bWaypointsChanged)
   {
      g_engfuncs.pfnRunPlayerMove (pEdict, pEdict->v.v_angle, 0.0, 0, 0, 0, 0, msecval);
      return;
   }

   if (pBot->bCheckMyTeam)
   {
      pBot->bot_team = UTIL_GetTeam (pEdict);
      pBot->bCheckMyTeam = FALSE;
   }

   // Update Turnspeeds every frame...
   // Makes them turn less smooth which is more humanlike
   pEdict->v.yaw_speed = RANDOM_FLOAT (BotTurnSpeeds[pBot->bot_skill / 20].fMinTurnSpeed,
      BotTurnSpeeds[pBot->bot_skill / 20].fMaxTurnSpeed);
   pEdict->v.pitch_speed = RANDOM_FLOAT (BotTurnSpeeds[pBot->bot_skill / 20].fMinTurnSpeed,
      BotTurnSpeeds[pBot->bot_skill / 20].fMaxTurnSpeed);


   pBot->bOnLadder = (pEdict->v.movetype == MOVETYPE_FLY);
   pBot->bInWater = ((pEdict->v.waterlevel == 2) || (pEdict->v.waterlevel == 3));

   // Check if we already switched weapon mode
   if (pBot->bCheckWeaponSwitch && (pBot->f_spawn_time + 4.0 < gpGlobals->time))
   {
      if (BotHasShield (pBot))
      {
         if (BotHasShieldDrawn (pBot))
            pEdict->v.button |= IN_ATTACK2;
      }
      else
      {
         // Bot owns a Sniper Weapon ? Try to switch back to no zoom so Bot isn't moving slow
         if (BotUsesSniper (pBot))
         {
            if (pEdict->v.fov < 90)
               pEdict->v.button |= IN_ATTACK2;
         }

         // If no sniper weapon use a secondary mode at random times
         else
         {
            if ((pBot->current_weapon.iId == CS_WEAPON_M4A1) || (pBot->current_weapon.iId == CS_WEAPON_USP))
            {
               // Aggressive bots don't like the silencer (courtesy of Wei Mingzhi - good idea)
               if (RANDOM_LONG (1, 100) <= (pBot->bot_personality == 1 ? 25 : 75))
               {
                  if (pEdict->v.weaponanim > 6) // is the silencer not attached...
                     pEdict->v.button |= IN_ATTACK2; // attach the silencer
               }
               else
               {
                  if (pEdict->v.weaponanim <= 6) // is the silencer attached...
                     pEdict->v.button |= IN_ATTACK2; // detach the silencer
               }
            }
         }

         // If Psycho Bot switch to Knife
         if (pBot->bot_personality == 1)
            SelectWeaponByName (pBot, "weapon_knife");
      }

      if (RANDOM_LONG (1, 100) < 20)
      {
         bottask_t TempTask = {NULL, NULL, TASK_SPRAYLOGO, TASKPRI_SPRAYLOGO, -1, gpGlobals->time + 1.0, FALSE};
         BotPushTask (pBot, &TempTask);
      }

      // Select a Leader Bot for this team
      SelectLeaderEachTeam (pBot);

      pBot->bCheckWeaponSwitch = FALSE;
   }

   // FIXME: The following Timers aren't frame independant so it varies on slower/faster computers
   // Increase Reaction Time
   pBot->f_actual_reaction_time += 0.2;
   if (pBot->f_actual_reaction_time > pBot->f_ideal_reaction_time)
      pBot->f_actual_reaction_time = pBot->f_ideal_reaction_time;

   // Bot could be blinded by FlashBang or Smoke, recover from it
   pBot->f_view_distance += 3.0;
   if (pBot->f_view_distance > pBot->f_maxview_distance)
      pBot->f_view_distance = pBot->f_maxview_distance;

   // Maxspeed is set in ClientSetMaxspeed
   pBot->f_move_speed = pEdict->v.maxspeed;

   if (pBot->prev_time <= gpGlobals->time)
   {
      // see how far bot has moved since the previous position...
      v_diff = pBot->v_prev_origin - pEdict->v.origin;
      moved_distance = v_diff.Length ();

      // save current position as previous
      pBot->v_prev_origin = pEdict->v.origin;
      pBot->prev_time = gpGlobals->time + 0.2;
   }
   else
      moved_distance = 2.0;

   // If there's some Radio Message to respond, check it
   if (pBot->iRadioOrder != 0)
      BotCheckRadioCommands (pBot);

   // Do all Sensing, calculate/filter all Actions here
   BotSetConditions (pBot);

   int iDestIndex = pBot->curr_wpt_index;
   pBot->bCheckTerrain = TRUE;
   bool bShootLastPosition = FALSE;
   bool bMadShoot = FALSE;
   pBot->bWantsToFire = FALSE;

   // Get affected by SmokeGrenades (very basic!) and sense Grenades flying towards us
   BotCheckSmokeGrenades (pBot);
   pBot->bUsingGrenade = FALSE;

   if (BotUsesSniper (pBot) && FNullEnt (pBot->pBotEnemy)
       && (pBot->f_bot_see_enemy_time + 4.0 < gpGlobals->time))
   {
      // Try zooming out to move fast again
      if (pEdict->v.fov < 90)
         pEdict->v.button |= IN_ATTACK2;
   }

   switch (BotGetSafeTask (pBot)->iTask)
   {
   // Normal (roaming) Task
   case TASK_NORMAL:
      pBot->iAimFlags |= AIM_DEST;

      if (BotHasShieldDrawn (pBot))
         pEdict->v.button |= IN_ATTACK2;

      if ((g_fTimeRoundEnd < gpGlobals->time) && FNullEnt (pBot->pBotEnemy))
         pEdict->v.button |= IN_RELOAD; // Reload at the end of the round

      // User forced a Waypoint as a Goal ?
      if (g_iDebugGoalIndex != -1)
      {
         if (BotGetSafeTask (pBot)->iData != g_iDebugGoalIndex)
         {
            DeleteSearchNodes (pBot);
            BotGetSafeTask (pBot)->iData = g_iDebugGoalIndex;
         }
      }

      // If Bomb planted and it's a Counter
      // calculate new path to Bomb Point if he's not already heading for
      if (g_bBombPlanted && (pBot->bot_team == TEAM_CS_COUNTER))
      {
         if (BotGetSafeTask (pBot)->iData != -1)
         {
            if (!(paths[BotGetSafeTask (pBot)->iData]->flags & W_FL_GOAL))
            {
               DeleteSearchNodes (pBot);
               BotGetSafeTask (pBot)->iData = -1;
            }
         }
      }

      // Reached the destination (goal) waypoint ?
      if (BotDoWaypointNav (pBot))
      {
         // Spray Logo sometimes if allowed to do so
         if (!pBot->bLogoSprayed && g_bBotSpray)
         {
            if (RANDOM_LONG (1, 100) < 50)
            {
               bottask_t TempTask = {NULL, NULL, TASK_SPRAYLOGO, TASKPRI_SPRAYLOGO, -1, gpGlobals->time + 1.0, FALSE};
               BotPushTask (pBot, &TempTask);
            }
         }

         BotTaskComplete (pBot);
         pBot->prev_goal_index = -1;

         // Reached Waypoint is a Camp Waypoint
         if (paths[pBot->curr_wpt_index]->flags & W_FL_CAMP)
         {
            // Check if Bot has got a primary weapon and hasn't camped before
            if (BotHasPrimaryWeapon (pBot) && (pBot->fTimeCamping + 10.0 < gpGlobals->time)
                && !((pBot->bot_team == TEAM_CS_COUNTER) && g_bBombPlanted))
            {
               bool bCampingAllowed = TRUE;

               // Check if it's not allowed for this team to camp here
               if (pBot->bot_team == TEAM_CS_TERRORIST)
               {
                  if (paths[pBot->curr_wpt_index]->flags & W_FL_COUNTER)
                     bCampingAllowed = FALSE;
               }
               else
               {
                  if (paths[pBot->curr_wpt_index]->flags & W_FL_TERRORIST)
                     bCampingAllowed = FALSE;
               }

               // Check if another Bot is already camping here
               for (c = 0; c < gpGlobals->maxClients; c++)
               {
                  pOtherBot = &bots[c];

                  if (pOtherBot->is_used)
                  {
                     if (pOtherBot == pBot)
                        continue;

                     if (!pOtherBot->bDead && (pOtherBot->bot_team == pBot->bot_team)
                         && (pOtherBot->curr_wpt_index == pBot->curr_wpt_index))
                        bCampingAllowed = FALSE;
                  }
               }

               if (bCampingAllowed)
               {
                  // Crouched camping here ?
                  if (paths[pBot->curr_wpt_index]->flags & W_FL_CROUCH)
                     pBot->iCampButtons = IN_DUCK;
                  else
                     pBot->iCampButtons = 0;

                  if (!(pBot->iStates & STATE_SEEINGENEMY))
                  {
                     pEdict->v.button |= IN_RELOAD;   // reload - just to be sure
                     pBot->bIsReloading = TRUE;
                  }

                  pBot->fTimeCamping = gpGlobals->time + RANDOM_FLOAT (BotSkillDelays[pBot->bot_skill / 20].fBotCampStartDelay, BotSkillDelays[pBot->bot_skill / 20].fBotCampEndDelay);
                  bottask_t TempTask = {NULL, NULL, TASK_CAMP, TASKPRI_CAMP, -1, pBot->fTimeCamping, TRUE};
                  BotPushTask (pBot, &TempTask);

                  MAKE_VECTORS (Vector (paths[pBot->curr_wpt_index]->fcampstartx, paths[pBot->curr_wpt_index]->fcampstarty, 0));
                  pBot->vecCamp = paths[pBot->curr_wpt_index]->origin + gpGlobals->v_forward * 500;
                  pBot->iAimFlags |= AIM_CAMP;
                  pBot->iCampDirection = 0;

                  // Tell the world we're camping
                  BotPlayRadioMessage (pBot, RADIO_IMINPOSITION);
                  bMoveToGoal = FALSE;
                  pBot->bCheckTerrain = FALSE;
                  pBot->f_move_speed = 0;
                  pBot->f_sidemove_speed = 0;
               }
            }
         }
         else
         {
            // Some Goal Waypoints are map dependant so check it out...
            if (g_iMapType & MAP_CS)
            {
               // CT Bot has some stupid hossies following ?
               if (BotHasHostage (pBot) && (pBot->bot_team == TEAM_CS_COUNTER))
               {
                  // and reached a Rescue Point ?
                  if ((paths[pBot->curr_wpt_index]->flags & W_FL_RESCUE))
                  {
                     // Clear Array of Hostage ptrs
                     for (i = 0; i < MAX_HOSTAGES; i++)
                        pBot->pHostages[i] = NULL;

                     g_bHostageRescued = TRUE; // Notify T Bots that there's a rescue going on
                  }
               }
            }

            if (g_iMapType & MAP_DE)
            {
               // Reached Goal Waypoint
               if (paths[pBot->curr_wpt_index]->flags & W_FL_GOAL)
               {
                  // Is it a Terrorist carrying the bomb ?
                  if ((pBot->bot_team == TEAM_CS_TERRORIST) && (pEdict->v.weapons & (1 << CS_WEAPON_C4)))
                  {
                     FakeClientCommand (pEdict, "weapon_c4");
                     bottask_t TempTask = {NULL, NULL, TASK_PLANTBOMB, TASKPRI_PLANTBOMB, -1, 0.0, FALSE};
                     BotPushTask (pBot, &TempTask);

                     // Tell Teammates to move over here...
                     BotPlayRadioMessage (pBot, RADIO_NEEDBACKUP);
                  }

                  // Counter searching the Bomb ?
                  else if ((pBot->bot_team == TEAM_CS_COUNTER) && g_bBombPlanted)
                  {
                     CTBombPointClear (pBot->curr_wpt_index);
                     BotPlayRadioMessage (pBot, RADIO_SECTORCLEAR);
                  }
               }
            }
         }
      }

      // No more Nodes to follow - search new ones
      else if (!BotGoalIsValid (pBot))
      {
         pBot->f_move_speed = pEdict->v.maxspeed;
         DeleteSearchNodes (pBot);

         // Did we already decide about a goal before ?
         if (BotGetSafeTask (pBot)->iData != -1)
            iDestIndex = BotGetSafeTask (pBot)->iData;
         else
            iDestIndex = BotFindGoal (pBot);

         pBot->prev_goal_index = iDestIndex;

         // Remember Index
         BotGetSafeTask (pBot)->iData = iDestIndex;

         if (iDestIndex!= pBot->curr_wpt_index)
         {
            // Do Pathfinding if it's not the current waypoint
            pBot->pWayNodesStart = FindLeastCostPath (pBot, pBot->curr_wpt_index, iDestIndex);
            pBot->pWaypointNodes = pBot->pWayNodesStart;
         }
      }
      else
      {
         if (!(pEdict->v.flags & FL_DUCKING)
             && (pBot->fMinSpeed != pEdict->v.maxspeed))
            pBot->f_move_speed = pBot->fMinSpeed;
      }
      break;

   // Bot sprays messy Logos all over the place...
   case TASK_SPRAYLOGO:
      pBot->iAimFlags |= AIM_ENTITY;

      // Bot didn't spray this round ?
      if (!pBot->bLogoSprayed && (BotGetSafeTask (pBot)->fTime > gpGlobals->time))
      {
         MAKE_VECTORS (pEdict->v.v_angle);
         Vector vecSprayPos = pEdict->v.origin + pEdict->v.view_ofs + gpGlobals->v_forward * 128;
         TRACE_LINE (pEdict->v.origin + pEdict->v.view_ofs, vecSprayPos, ignore_monsters, pEdict, &tr);

         // No Wall in Front ?
         if (tr.flFraction >= 1.0)
            vecSprayPos.z -= 128.0;
         pBot->vecEntity = vecSprayPos;

         if (BotGetSafeTask (pBot)->fTime - 0.5 < gpGlobals->time)
         {
            // Emit Spraycan sound
            EMIT_SOUND_DYN2 (pEdict, CHAN_VOICE, "player/sprayer.wav", 1.0, ATTN_NORM, 0, 100);
            TRACE_LINE (pEdict->v.origin + pEdict->v.view_ofs, pEdict->v.origin + pEdict->v.view_ofs + gpGlobals->v_forward * 128, ignore_monsters, pEdict, &tr);

            // Paint the actual Logo Decal
            UTIL_DecalTrace (&tr, &szSprayNames[pBot->iSprayLogo][0]);
            pBot->bLogoSprayed = TRUE;
         }
      }
      else
         BotTaskComplete (pBot);

      bMoveToGoal = FALSE;
      pBot->bCheckTerrain = FALSE;
      pBot->f_wpt_timeset = gpGlobals->time;
      pBot->f_move_speed = 0;
      pBot->f_sidemove_speed = 0.0;
      break;

   // Hunt down Enemy
   case TASK_ENEMYHUNT:
      pBot->iAimFlags |= AIM_DEST;

      // Reached last Enemy Pos ?
      if (!FNullEnt (pBot->pBotEnemy) || FNullEnt (pBot->pLastEnemy))
      {
         // Forget about it...
         BotRemoveCertainTask (pBot, TASK_ENEMYHUNT);
         pBot->prev_goal_index = -1;
      }
      else if (BotDoWaypointNav (pBot))
      {
         // Forget about it...
         BotTaskComplete (pBot);
         pBot->prev_goal_index = -1;
         pBot->vecLastEnemyOrigin = g_vecZero;
      }

      // Do we need to calculate a new Path ?
      else if (pBot->pWaypointNodes == NULL)
      {
         DeleteSearchNodes (pBot);

         // Is there a remembered Index ?
         if ((BotGetSafeTask (pBot)->iData != -1)
             && (BotGetSafeTask (pBot)->iData < g_iNumWaypoints))
            iDestIndex = BotGetSafeTask (pBot)->iData;

         // No. We need to find a new one
         else
         {
            float min_distance;
            float distance;
            min_distance = 9999.0;

            // Search nearest waypoint to enemy
            for (i = 0; i < g_iNumWaypoints; i++)
            {
               distance = (paths[i]->origin - pBot->vecLastEnemyOrigin).Length ();

               if (distance < min_distance)
               {
                  iDestIndex = i;
                  min_distance = distance;
               }
            }
         }

         // Remember Index
         pBot->prev_goal_index = iDestIndex;
         BotGetSafeTask (pBot)->iData = iDestIndex;
         pBot->pWayNodesStart = FindLeastCostPath (pBot, pBot->curr_wpt_index, iDestIndex);
         pBot->pWaypointNodes = pBot->pWayNodesStart;
      }

      // Bots skill higher than 70 ?
      if (pBot->bot_skill > 70)
      {
         // Then make him move slow if near Enemy
         if (!(pBot->curr_travel_flags & C_FL_JUMP))
         {
            if (pBot->curr_wpt_index != -1)
            {
               if ((paths[pBot->curr_wpt_index]->Radius < 32) && !pBot->bOnLadder
                   && !pBot->bInWater && (pBot->f_bot_see_enemy_time + 1.0 < gpGlobals->time))
                  pEdict->v.button |= IN_DUCK;
            }

            Vector v_diff = pBot->vecLastEnemyOrigin - pEdict->v.origin;
            float fDistance = v_diff.Length ();

            if ((fDistance < 700.0) && !(pEdict->v.flags & FL_DUCKING))
               pBot->f_move_speed = pEdict->v.maxspeed / 2;
         }
      }
      break;

   // Bot seeks Cover from Enemy
   case TASK_SEEKCOVER:
      pBot->iAimFlags |= AIM_DEST;
      if (FNullEnt (pBot->pLastEnemy) || !IsAlive (pBot->pLastEnemy))
      {
         BotTaskComplete (pBot);
         pBot->prev_goal_index = -1;
      }

      // Reached final Cover waypoint ?
      else if (BotDoWaypointNav (pBot))
      {
         pBot->byPathType = 2;

         // Yep. Activate Hide Behaviour
         BotTaskComplete (pBot);
         pBot->prev_goal_index = -1;
         bottask_t TempTask = {NULL, NULL, TASK_HIDE, TASKPRI_HIDE, -1, gpGlobals->time + RANDOM_FLOAT (2.0, 10.0), FALSE};
         BotPushTask (pBot, &TempTask);
         v_dest = pBot->vecLastEnemyOrigin;

         // Get a valid look direction
         BotGetCampDirection (pBot, &v_dest);
         pBot->iAimFlags |= AIM_CAMP;
         pBot->vecCamp = v_dest;
         pBot->iCampDirection = 0;

         // Chosen Waypoint is a Camp Waypoint ?
         if (paths[pBot->curr_wpt_index]->flags & W_FL_CAMP)
         {
            // Use the existing camp wpt prefs
            if (paths[pBot->curr_wpt_index]->flags & W_FL_CROUCH)
               pBot->iCampButtons = IN_DUCK;
            else
               pBot->iCampButtons = 0;
         }
         else
         {
            // Choose a crouch or stand pos
            if ((RANDOM_LONG (1, 100) < 30) && (paths[pBot->curr_wpt_index]->Radius < 32))
               pBot->iCampButtons = IN_DUCK;
            else
               pBot->iCampButtons = 0;

            // Enter look direction from previously calculated positions
            Vector vecAnglesToEnemy = UTIL_VecToAngles (pBot->vecCamp - pEdict->v.origin);

            paths[pBot->curr_wpt_index]->fcampstartx = vecAnglesToEnemy.x;
            paths[pBot->curr_wpt_index]->fcampstarty = vecAnglesToEnemy.y;
            paths[pBot->curr_wpt_index]->fcampendx = vecAnglesToEnemy.x;
            paths[pBot->curr_wpt_index]->fcampendy = vecAnglesToEnemy.y;
         }

         int iId = pBot->current_weapon.iId;

         if ((pBot->bIsReloading == FALSE) && (pBot->current_weapon.iClip < 5)
             && (pBot->current_weapon.iAmmo1 > 0))
         {
            pEdict->v.button |= IN_RELOAD;   // reload - just to be sure
            pBot->bIsReloading = TRUE;
         }

         pBot->f_move_speed = 0;
         pBot->f_sidemove_speed = 0;
         bMoveToGoal = FALSE;
         pBot->bCheckTerrain = FALSE;
      }

      // We didn't choose a Cover Waypoint yet or lost it due to an attack ?
      else if (!BotGoalIsValid (pBot))
      {
         DeleteSearchNodes (pBot);

         if (BotGetSafeTask (pBot)->iData != -1)
            iDestIndex = BotGetSafeTask (pBot)->iData;
         else
         {
            iDestIndex = BotFindCoverWaypoint(pBot, 1024);
            if (iDestIndex == -1)
               iDestIndex = RANDOM_LONG (0, g_iNumWaypoints - 1);
         }

         pBot->iCampDirection = 0;
         pBot->prev_goal_index = iDestIndex;
         BotGetSafeTask (pBot)->iData = iDestIndex;

         if (iDestIndex != pBot->curr_wpt_index)
         {
            pBot->pWayNodesStart = FindLeastCostPath (pBot, pBot->curr_wpt_index, iDestIndex);
            pBot->pWaypointNodes = pBot->pWayNodesStart;
         }
      }
      break;

   // Plain Attacking
   case TASK_ATTACK:
      bMoveToGoal = FALSE;
      pBot->bCheckTerrain = FALSE;

      if (!FNullEnt (pBot->pBotEnemy))
         BotDoAttackMovement (pBot);
      else
      {
         BotTaskComplete (pBot);
         pBot->dest_origin = pBot->vecLastEnemyOrigin;
      }

      pBot->f_wpt_timeset = gpGlobals->time;
      break;

   // Bot is pausing
   case TASK_PAUSE:
      bMoveToGoal = FALSE;
      pBot->bCheckTerrain = FALSE;
      pBot->f_wpt_timeset = gpGlobals->time;
      pBot->f_move_speed = 0.0;
      pBot->f_sidemove_speed = 0.0;
      pBot->iAimFlags |= AIM_DEST;

      // Is Bot blinded and above average skill ?
      if ((pBot->f_view_distance < 500.0) && (pBot->bot_skill > 60))
      {
         // Go mad !
         pBot->f_move_speed = -fabs ((pBot->f_view_distance - 500.0) / 2);
         if (pBot->f_move_speed < -pEdict->v.maxspeed)
            pBot->f_move_speed = -pEdict->v.maxspeed;

         MAKE_VECTORS (pEdict->v.v_angle);
         pBot->vecCamp = GetGunPosition (pEdict) + gpGlobals->v_forward * 500;
         pBot->iAimFlags |= AIM_OVERRIDE;
         pBot->bWantsToFire = TRUE;
      }
      else
         pEdict->v.button |= pBot->iCampButtons;

      // body angles = view angles
      pEdict->v.angles.y = pEdict->v.v_angle.y;

      // Stop camping if Time over or gets Hurt by something else than bullets
      if ((BotGetSafeTask (pBot)->fTime < gpGlobals->time) || (pBot->iLastDamageType > 0))
         BotTaskComplete (pBot);
      break;

   // Blinded (flashbanged) Behaviour
   case TASK_BLINDED:

      bMoveToGoal = FALSE;
      pBot->bCheckTerrain = FALSE;
      pBot->f_wpt_timeset = gpGlobals->time;

      switch (pBot->bot_personality)
      {
         // Normal
         case 0:
            if ((pBot->bot_skill > 60) && (pBot->vecLastEnemyOrigin != g_vecZero))
               bShootLastPosition = TRUE;
            break;
         // Psycho
         case 1:
            if (pBot->vecLastEnemyOrigin != g_vecZero)
               bShootLastPosition = TRUE;
            else
               bMadShoot = TRUE;
      }

      // If Bot remembers last Enemy Position
      if (bShootLastPosition)
      {
         // Face it and shoot
         pBot->vecLookAt = pBot->vecLastEnemyOrigin;
         pBot->bWantsToFire = TRUE;
      }

      // If Bot is mad
      else if (bMadShoot)
      {
         // Just shoot in forward direction
         MAKE_VECTORS (pEdict->v.v_angle);
         pBot->vecLookAt = GetGunPosition (pEdict) + gpGlobals->v_forward * 500;
         pBot->bWantsToFire = TRUE;
      }

      pBot->f_move_speed = pBot->f_blindmovespeed_forward;
      pBot->f_sidemove_speed = pBot->f_blindmovespeed_side;

      if (pBot->f_blind_time < gpGlobals->time)
         BotTaskComplete (pBot);
      break;

   // Camping Behaviour
   case TASK_CAMP:
      {
      pBot->iAimFlags |= AIM_CAMP;
      pBot->bCheckTerrain = FALSE;
      bMoveToGoal = FALSE;

      // half the reaction time if camping because you're more aware of enemies if camping
      pBot->f_ideal_reaction_time = RANDOM_FLOAT (BotSkillDelays[pBot->bot_skill / 20].fMinSurpriseDelay,
                                                  BotSkillDelays[pBot->bot_skill / 20].fMaxSurpriseDelay) / 2;
      pBot->f_wpt_timeset = gpGlobals->time;
      pBot->f_move_speed = 0;
      pBot->f_sidemove_speed = 0.0;
      GetValidWaypoint (pBot);

      if (pBot->fNextCampDirectionTime < gpGlobals->time)
      {
         int i;
         float distance;

         if (pBot->iCampDirection < 1)
         {
            v_dest.x = paths[pBot->curr_wpt_index]->fcampstartx;
            v_dest.y = paths[pBot->curr_wpt_index]->fcampstarty;
            v_dest.z = 0;
         }
         else
         {
            v_dest.x = paths[pBot->curr_wpt_index]->fcampendx;
            v_dest.y = paths[pBot->curr_wpt_index]->fcampendy;
            v_dest.z = 0;
         }

         if (v_dest == g_vecZero)
         {
            for (i = 0; i < g_iNumWaypoints; i++)
            {
               distance = (paths[i]->origin - pEdict->v.origin).Length ();

               if ((distance > 200) && (distance < 500) && FVisible (paths[i]->origin, pEdict))
                  break;
            }

            if (i < g_iNumWaypoints)
               v_dest = UTIL_VecToAngles (paths[i]->origin - pEdict->v.origin);
            else
               v_dest = Vector (0, RANDOM_FLOAT (-180, 180), 0); // last chance
         }

         MAKE_VECTORS (v_dest);
         pBot->vecCamp = paths[pBot->curr_wpt_index]->origin + gpGlobals->v_forward * 500;

         // Switch from 1 direction to the other
         pBot->fNextCampDirectionTime = gpGlobals->time + RANDOM_FLOAT (1, 4);
      }

      // Press remembered crouch Button
      pEdict->v.button |= pBot->iCampButtons;

      // body angles = view angles
      pEdict->v.angles.y = pEdict->v.v_angle.y;

      // Stop camping if Time over or gets Hurt by something else than bullets
      if ((BotGetSafeTask (pBot)->fTime < gpGlobals->time) || (pBot->iLastDamageType > 0))
         BotTaskComplete (pBot);
      break;
      }

   // Hiding Behaviour
   case TASK_HIDE:
      pBot->iAimFlags |= AIM_CAMP;
      pBot->bCheckTerrain = FALSE;
      bMoveToGoal = FALSE;

      // half the reaction time if camping
      pBot->f_ideal_reaction_time = RANDOM_FLOAT (BotSkillDelays[pBot->bot_skill / 20].fMinSurpriseDelay,
                                                  BotSkillDelays[pBot->bot_skill / 20].fMaxSurpriseDelay) / 2;
      pBot->f_wpt_timeset = gpGlobals->time;
      pBot->f_move_speed = 0;
      pBot->f_sidemove_speed = 0.0;
      GetValidWaypoint (pBot);

      if (BotHasShield (pBot))
      {
         if (!pBot->bIsReloading)
         {
            if (!BotHasShieldDrawn (pBot))
               pEdict->v.button |= IN_ATTACK2; // draw the shield!
            else
               pEdict->v.button |= IN_DUCK; // duck under if the shield is already drawn
         }
      }

      // If we see an enemy and aren't at a good camping point leave the spot
      if (pBot->iStates & STATE_SEEINGENEMY)
      {
         if (!(paths[pBot->curr_wpt_index]->flags & W_FL_CAMP))
         {
            BotTaskComplete (pBot);
            pBot->iCampButtons = 0;
            pBot->prev_goal_index = -1;
            if (!FNullEnt (pBot->pBotEnemy))
               BotDoAttackMovement(pBot);
            break;
         }
      }

      // If we don't have an enemy we're also free to leave
      else if (pBot->vecLastEnemyOrigin == g_vecZero)
      {
         BotTaskComplete (pBot);
         pBot->iCampButtons = 0;
         pBot->prev_goal_index = -1;
         if (BotGetSafeTask (pBot)->iTask == TASK_HIDE)
            BotTaskComplete (pBot);
         break;
      }

      pEdict->v.button |= pBot->iCampButtons;
      pBot->f_wpt_timeset = gpGlobals->time;

      // body angles = view angles
      pEdict->v.angles.y = pEdict->v.v_angle.y;

      // Stop camping if Time over or gets Hurt by something else than bullets
      if ((BotGetSafeTask (pBot)->fTime < gpGlobals->time) || (pBot->iLastDamageType > 0))
         BotTaskComplete (pBot);
      break;

   // Moves to a Position specified in pBot->vecPosition
   // Has a higher Priority than TASK_NORMAL
   case TASK_MOVETOPOSITION:
      pBot->iAimFlags |= AIM_DEST;

      if (BotHasShieldDrawn (pBot))
         pEdict->v.button |= IN_ATTACK2;

      // Reached destination ?
      if (BotDoWaypointNav (pBot))
      {
         // We're done
         BotTaskComplete (pBot);
         pBot->prev_goal_index = -1;
         pBot->vecPosition = g_vecZero;
      }

      // Didn't choose Goal Waypoint yet ?
      else if (!BotGoalIsValid (pBot))
      {
         DeleteSearchNodes (pBot);
         if ((BotGetSafeTask (pBot)->iData != -1)
             && (BotGetSafeTask (pBot)->iData < g_iNumWaypoints))
            iDestIndex = BotGetSafeTask (pBot)->iData;
         else
         {
            float min_distance = 9999.0;
            float distance;

            // Search nearest waypoint to Position
            for (i = 0; i < g_iNumWaypoints; i++)
            {
               distance = (paths[i]->origin - pBot->vecPosition).Length ();

               if (distance < min_distance)
               {
                  iDestIndex = i;
                  min_distance = distance;
               }
            }
         }

         pBot->prev_goal_index = iDestIndex;
         BotGetSafeTask (pBot)->iData = iDestIndex;
         pBot->pWayNodesStart = FindLeastCostPath (pBot, pBot->curr_wpt_index, iDestIndex);
         pBot->pWaypointNodes = pBot->pWayNodesStart;
      }
      break;

   // Planting the Bomb right now
   case TASK_PLANTBOMB:
      pBot->iAimFlags |= AIM_DEST;

      // We're still in the planting time and got the c4 ?
      if (pBot->b_bomb_blinking && (pEdict->v.weapons & (1 << CS_WEAPON_C4)))
      {
         bMoveToGoal = FALSE;
         pBot->bCheckTerrain = FALSE;
         pBot->f_wpt_timeset = gpGlobals->time;
         pEdict->v.button |= IN_ATTACK;
         pEdict->v.button |= IN_DUCK;
         pBot->f_move_speed = 0;
         pBot->f_sidemove_speed = 0;
      }

      // Done with planting
      else
      {
         BotTaskComplete (pBot);

         if (!(pEdict->v.weapons & (1 << CS_WEAPON_C4)))
         {
            // Notify the Team of this heroic action
            FakeClientCommand (pBot->pEdict, "say_team Planted the Bomb!\n");

            DeleteSearchNodes (pBot);

            // Push camp task on to stack
            float f_c4timer = CVAR_GET_FLOAT ("mp_c4timer");
            bottask_t TempTask = {NULL, NULL, TASK_CAMP, TASKPRI_CAMP, -1, gpGlobals->time + (f_c4timer / 2), TRUE};
            BotPushTask (pBot, &TempTask);

            // Push Move Command
            TempTask.iTask = TASK_MOVETOPOSITION;
            TempTask.fDesire = TASKPRI_MOVETOPOSITION;
            TempTask.iData = BotFindDefendWaypoint (pBot, pEdict->v.origin);
            BotPushTask (pBot, &TempTask);
            pBot->iCampButtons |= IN_DUCK;
         }
      }
      break;

   // Bomb defusing Behaviour
   case TASK_DEFUSEBOMB:
      pBot->iAimFlags |= AIM_ENTITY;
      bMoveToGoal = FALSE;
      pBot->bCheckTerrain = FALSE;

      pBot->f_wpt_timeset = gpGlobals->time;

      // Bomb still there ?
      if (!FNullEnt (pBot->pBotPickupItem))
      {
         // Get Face Position
         pBot->vecEntity = pBot->pBotPickupItem->v.origin;
         pEdict->v.button |= IN_USE;
         pEdict->v.button |= IN_DUCK;
      }
      else
         BotTaskComplete (pBot);

      pBot->f_move_speed = 0;
      pBot->f_sidemove_speed = 0;
      break;

   // Follow User Behaviour
   case TASK_FOLLOWUSER:
      pBot->iAimFlags |= AIM_DEST;

      // Follow User ?
      if (BotFollowUser (pBot))
      {
         // Imitate Users crouching
         pEdict->v.button |= (pBot->pBotUser->v.button & IN_DUCK);
         pBot->dest_origin = pBot->pBotUser->v.origin;
         pBot->vecLookAt = pBot->dest_origin;
      }
      else
         BotTaskComplete (pBot);

      break;

   // HE Grenade Throw Behaviour
   case TASK_THROWHEGRENADE:
      pBot->iAimFlags |= AIM_GRENADE;

      v_dest = pBot->vecThrow;

      if (!(pBot->iStates & STATE_SEEINGENEMY))
      {
         pBot->f_move_speed = 0.0;
         pBot->f_sidemove_speed = 0.0;
         bMoveToGoal = FALSE;
      }
      else if (!(pBot->iStates & STATE_SUSPECTENEMY) && !FNullEnt (pBot->pBotEnemy))
      {
         v_dest = pBot->pBotEnemy->v.origin;
         v_src = pBot->pBotEnemy->v.velocity;
         v_src.z = 0.0;
         v_dest = v_dest + (v_src * 0.3);
      }

      pBot->bUsingGrenade = TRUE;
      pBot->bCheckTerrain = FALSE;

      pBot->vecGrenade = VecCheckThrow  (pEdict, GetGunPosition (pEdict), v_dest, 1.0);
      if (pBot->vecGrenade == g_vecZero && !bMoveToGoal)
         pBot->vecGrenade = VecCheckToss  (pEdict, GetGunPosition (pEdict), v_dest);

      // We're done holding the Button (about to throw) ?
      if ((pBot->vecGrenade == g_vecZero) || (BotGetSafeTask (pBot)->fTime < gpGlobals->time)
          || (pEdict->v.weapons & (1 << CS_WEAPON_HEGRENADE) == 0))
      {
         if (pBot->vecGrenade == g_vecZero)
         {
            pBot->f_grenade_check_time = gpGlobals->time + RANDOM_FLOAT (2, 5);
            BotSelectBestWeapon (pBot);
         }

         BotTaskComplete (pBot);
      }
      else
      {
         pEdict->v.button |= IN_ATTACK;

         // Select Grenade if we're not already using it
         if (pBot->current_weapon.iId != CS_WEAPON_HEGRENADE)
         {
            if (pBot->current_weapon.iId != CS_WEAPON_INSWITCH)
            {
               SelectWeaponByName(pBot, "weapon_hegrenade");
               BotGetSafeTask (pBot)->fTime = gpGlobals->time + TIME_GRENPRIME;
            }
            else
               BotGetSafeTask (pBot)->fTime = gpGlobals->time + TIME_GRENPRIME;
         }
      }

      pEdict->v.button |= pBot->iCampButtons;
      break;

   // Flashbang Throw Behaviour
   // Basically the same code like for HE's
   case TASK_THROWFLASHBANG:
      pBot->iAimFlags |= AIM_GRENADE;
      v_dest = pBot->vecThrow;

      if (!(pBot->iStates & STATE_SEEINGENEMY))
      {
         pBot->f_move_speed = 0.0;
         pBot->f_sidemove_speed = 0.0;
         bMoveToGoal = FALSE;
      }
      else
      {
         if (!(pBot->iStates & STATE_SUSPECTENEMY) && !FNullEnt (pBot->pBotEnemy))
         {
            v_dest = pBot->pBotEnemy->v.origin;
            v_src = pBot->pBotEnemy->v.velocity;
            v_src.z = 0.0;
            v_dest = v_dest + (v_src * 0.3);
         }
      }

      pBot->bUsingGrenade = TRUE;
      pBot->bCheckTerrain = FALSE;

      pBot->vecGrenade = VecCheckThrow  (pEdict, GetGunPosition (pEdict), v_dest, 1.0);
      if (pBot->vecGrenade == g_vecZero && !bMoveToGoal)
         pBot->vecGrenade = VecCheckToss  (pEdict, GetGunPosition (pEdict), v_dest);

      // We're done holding the Button (about to throw) ?
      if ((pBot->vecGrenade == g_vecZero) || (BotGetSafeTask (pBot)->fTime < gpGlobals->time)
          || (pEdict->v.weapons & (1 << CS_WEAPON_FLASHBANG) == 0))
      {
         if (pBot->vecGrenade == g_vecZero)
         {
            pBot->f_grenade_check_time = gpGlobals->time + RANDOM_FLOAT (2, 5);
            BotSelectBestWeapon (pBot);
         }

         BotTaskComplete (pBot);
      }
      else
      {
         pEdict->v.button |= IN_ATTACK;

         // Select Grenade if we're not already using it
         if (pBot->current_weapon.iId != CS_WEAPON_FLASHBANG)
         {
            if (pBot->current_weapon.iId != CS_WEAPON_INSWITCH)
            {
               SelectWeaponByName (pBot, "weapon_flashbang");
               BotGetSafeTask (pBot)->fTime = gpGlobals->time + TIME_GRENPRIME;
            }
            else
               BotGetSafeTask (pBot)->fTime = gpGlobals->time + TIME_GRENPRIME;
         }
      }
      pEdict->v.button |= pBot->iCampButtons;
      break;

   // Smoke Grenade Throw Behaviour
   // A bit different to the others because it mostly
   // tries to throw the Gren on the ground
   case TASK_THROWSMOKEGRENADE:
      pBot->iAimFlags |= AIM_GRENADE;
      if (!(pBot->iStates & STATE_SEEINGENEMY))
      {
         pBot->f_move_speed = 0.0;
         pBot->f_sidemove_speed = 0.0;
         bMoveToGoal = FALSE;
      }
      pBot->bCheckTerrain = FALSE;
      pBot->bUsingGrenade = TRUE;
      v_src = pBot->vecLastEnemyOrigin;
      v_src = v_src - pEdict->v.velocity;

      // Predict where the enemy is in 0.5 secs
      if (!FNullEnt (pBot->pBotEnemy))
         v_src = v_src + pBot->pBotEnemy->v.velocity * 0.5;

      pBot->vecGrenade = v_src;
      if ((BotGetSafeTask (pBot)->fTime < gpGlobals->time)
          || !(pEdict->v.weapons & (1 << CS_WEAPON_SMOKEGRENADE)))
      {
         BotTaskComplete (pBot);
         break;
      }

      pEdict->v.button |= IN_ATTACK;
      if (pBot->current_weapon.iId != CS_WEAPON_SMOKEGRENADE)
      {
         if (pBot->current_weapon.iId != CS_WEAPON_INSWITCH)
         {
            SelectWeaponByName(pBot, "weapon_smokegrenade");
            BotGetSafeTask (pBot)->fTime = gpGlobals->time + TIME_GRENPRIME;
         }
         else
            BotGetSafeTask (pBot)->fTime = gpGlobals->time + TIME_GRENPRIME;
      }
      break;

   // Shooting breakables in the way action
   case TASK_SHOOTBREAKABLE:
      pBot->iAimFlags |= AIM_OVERRIDE;

      // Breakable destroyed ?
      if (!BotFindBreakable (pBot))
      {
         BotTaskComplete (pBot);
         break;
      }
      pEdict->v.button |= pBot->iCampButtons;

      pBot->bCheckTerrain = FALSE;
      bMoveToGoal = FALSE;
      pBot->f_wpt_timeset = gpGlobals->time;
      v_src = pBot->vecBreakable;
      pBot->vecCamp = v_src;

      flDot = GetShootingConeDeviation (pEdict, &v_src);

      // Is Bot facing the Breakable ?
      if (flDot>= 0.90)
      {
         pBot->f_move_speed = 0.0;
         pBot->f_sidemove_speed = 0.0;
         pBot->bWantsToFire = TRUE;
      }
      else
      {
         pBot->bCheckTerrain = TRUE;
         bMoveToGoal = TRUE;
      }
      break;

   // Picking up Items and stuff behaviour
   case TASK_PICKUPITEM:
      if (FNullEnt (pBot->pBotPickupItem))
      {
         pBot->pBotPickupItem = NULL;
         BotTaskComplete (pBot);
         break;
      }

      // func Models need special origin handling
      if ((strncmp ("func_", STRING (pBot->pBotPickupItem->v.classname), 5) == 0)
          || (pBot->pBotPickupItem->v.flags & FL_MONSTER))
         v_dest = VecBModelOrigin (pBot->pBotPickupItem);
      else
         v_dest = pBot->pBotPickupItem->v.origin;

      pBot->dest_origin = v_dest;
      pBot->vecEntity = v_dest;

      // find the distance to the item
      float f_item_distance = (v_dest - pEdict->v.origin).Length ();

      switch (pBot->iPickupType)
      {
      case PICKUP_WEAPON:
         pBot->iAimFlags |= AIM_DEST;

         // Near to Weapon ?
         if (f_item_distance < 50)
         {
            if (BotHasShield (pBot))
               FakeClientCommand (pEdict, "drop"); // discard both shield and pistol

            // Get current best weapon to check if it's a primary in need to be dropped
            int iWeaponNum = HighestWeaponOfEdict (pEdict);
            if (iWeaponNum > 6)
            {
               if (pBot->current_weapon.iId != cs_weapon_select[iWeaponNum].iId)
               {
                  if (pBot->current_weapon.iId != CS_WEAPON_INSWITCH)
                     SelectWeaponbyNumber (pBot, iWeaponNum);
               }
               else
                  FakeClientCommand (pEdict, "drop");
            }
            else
               pBot->iNumWeaponPickups++;
         }
         break;

      case PICKUP_SHIELD:
         pBot->iAimFlags |= AIM_DEST;

         // shield code courtesy of Wei Mingzhi
         if (BotHasShield (pBot))
         {
            pBot->pBotPickupItem = NULL;
            break;
         }

         // Near to Weapon ?
         else if (f_item_distance < 50)
         {
            // Get current best weapon to check if it's a primary in need to be dropped
            int iWeaponNum = HighestWeaponOfEdict (pEdict);
            if ((iWeaponNum > 6) || BotHasShield (pBot))
            {
               SelectWeaponbyNumber (pBot, iWeaponNum);
               FakeClientCommand (pEdict, "drop");
            }
         }
         break;

      case PICKUP_HOSTAGE:
         pBot->iAimFlags |= AIM_ENTITY;
         v_src = pEdict->v.origin + pEdict->v.view_ofs;

         if (f_item_distance < 50)
         {
            float angle_to_entity = BotInFieldOfView (pBot, v_dest - v_src);

            // Bot faces hostage ?
            if (angle_to_entity <= 10)
            {
               pEdict->v.button |= IN_USE;

               // Notify Team
               FakeClientCommand (pBot->pEdict, "say_team Using a Hostage!\n");

               // Store ptr to hostage so other Bots don't steal
               // from this one or Bot tries to reuse it
               for (i = 0; i < MAX_HOSTAGES; i++)
               {
                  if (FNullEnt (pBot->pHostages[i]))
                  {
                     pBot->pHostages[i] = pBot->pBotPickupItem;
                     pBot->pBotPickupItem = NULL;
                     break;
                  }
               }
            }
         }
         break;

      case PICKUP_PLANTED_C4:
         pBot->iAimFlags |= AIM_ENTITY;

         // Bomb Defuse needs to be checked
         if ((pBot->bot_team == TEAM_CS_COUNTER) && (f_item_distance < 50))
         {
            bMoveToGoal = FALSE;
            pBot->bCheckTerrain = FALSE;
            pBot->f_move_speed = 0;
            pBot->f_sidemove_speed = 0;
            pEdict->v.button |= IN_DUCK;

            // Notify Team of defusing
            BotPlayRadioMessage (pBot, RADIO_NEEDBACKUP);
            FakeClientCommand (pBot->pEdict, "say_team Trying to defuse the Bomb!\n");

            bottask_t TempTask = {NULL, NULL, TASK_DEFUSEBOMB, TASKPRI_DEFUSEBOMB, -1, 0.0, FALSE};
            BotPushTask (pBot, &TempTask);
         }
         break;

      case PICKUP_DEFUSEKIT:
         pBot->iAimFlags |= AIM_DEST; // don't bother looking at it

         if (pBot->b_has_defuse_kit)
         {
            pBot->pBotPickupItem = NULL;
            pBot->iPickupType = PICKUP_NONE;
         }
         break;
      }
      break;
   }

   // --- End of executing Task Actions ---

   // Get current Waypoint Flags
   // FIXME: Would make more sense to store it each time
   // in the Bot struct when a Bot gets a new wpt instead doing this here
   if (pBot->curr_wpt_index != -1)
      pBot->iWPTFlags = paths[pBot->curr_wpt_index]->flags;
   else
      pBot->iWPTFlags = 0;

   BotChooseAimDirection (pBot);
   BotFacePosition (pBot, pBot->vecLookAt);

   // Get Speed Multiply Factor by dividing Target FPS by real FPS
   float fSpeedFactor = CVAR_GET_FLOAT ("fps_max") / (1.0 / gpGlobals->frametime);
   if (fSpeedFactor < 1)
      fSpeedFactor = 1.0;

   // Slow turning if ZOOMED!
   float fZoomFactor = pEdict->v.fov / 90;

   if (pBot->iAimFlags & AIM_ENEMY)
   {
      BotChangeYaw (pBot, pEdict->v.yaw_speed * fSpeedFactor * fZoomFactor);
      BotChangePitch (pBot, pEdict->v.pitch_speed * fSpeedFactor * fZoomFactor);
   }
   else
   {
      BotChangeYaw (pBot, pEdict->v.yaw_speed * 0.3 * fSpeedFactor * fZoomFactor);
      BotChangePitch (pBot, pEdict->v.pitch_speed * 0.3 * fSpeedFactor * fZoomFactor);
   }

   pEdict->v.angles.x = -pEdict->v.v_angle.x / 3;
   pEdict->v.angles.y = pEdict->v.v_angle.y;
   UTIL_ClampVector (&pEdict->v.angles);
   UTIL_ClampVector (&pEdict->v.v_angle);
   pEdict->v.angles.z = pEdict->v.v_angle.z = 0;

   // Enemy behind Obstacle ? Bot wants to fire ?
   if ((pBot->iStates & STATE_SUSPECTENEMY) && pBot->bWantsToFire)
   {
      int iTask = BotGetSafeTask (pBot)->iTask;

      // Don't allow shooting through walls when camping
      if ((iTask == TASK_PAUSE) || (iTask == TASK_CAMP))
         pBot->bWantsToFire = FALSE;
   }

   // The Bots wants to fire at something ?
   if (pBot->bWantsToFire && !pBot->bUsingGrenade
       && (pBot->f_shoot_time <= gpGlobals->time))
   {
      // If Bot didn't fire a bullet try again next frame
      if (!BotFireWeapon (pBot->vecLookAt - GetGunPosition (pEdict), pBot))
         pBot->f_shoot_time = gpGlobals->time;
   }

   // Set the reaction time (surprise momentum) different each frame
   // according to skill
   pBot->f_ideal_reaction_time = RANDOM_FLOAT (BotSkillDelays[pBot->bot_skill/20].fMinSurpriseDelay,
      BotSkillDelays[pBot->bot_skill/20].fMaxSurpriseDelay);

   // Calculate 2 direction Vectors, 1 without the up/down component
   v_direction = pBot->dest_origin - (pEdict->v.origin + (pEdict->v.velocity * pBot->fTimeFrameInterval));
   Vector vecDirectionNormal = v_direction.Normalize ();
   Vector vecDirection = vecDirectionNormal;
   vecDirectionNormal.z = 0.0;

   Vector vecMoveAngles = UTIL_VecToAngles (v_direction);
   vecMoveAngles.x = -vecMoveAngles.x;
   vecMoveAngles.z = 0;
   UTIL_ClampVector (&vecMoveAngles);

   // Allowed to move to a destination position ?
   if (bMoveToGoal)
   {
      GetValidWaypoint (pBot);

      // Press duck button if we need to
      if ((paths[pBot->curr_wpt_index]->flags & W_FL_CROUCH)
          && !(paths[pBot->curr_wpt_index]->flags & W_FL_CAMP)
          && ((!(pEdict->v.flags & FL_ONGROUND) && !pBot->bInWater)
              || (paths[pBot->curr_wpt_index]->origin.z < pEdict->v.origin.z + 32)))
         pEdict->v.button |= IN_DUCK;

      float fDistance = (pBot->dest_origin - (pEdict->v.origin + (pEdict->v.velocity * pBot->fTimeFrameInterval))).Length2D ();
      if (fDistance < (pBot->f_move_speed * pBot->fTimeFrameInterval))
         pBot->f_move_speed = fDistance;

      if (pBot->f_move_speed < 2.0)
         pBot->f_move_speed = 2.0;

      pBot->fTimeWaypointMove = gpGlobals->time;

      // Special Movement for swimming here
      if (pBot->bInWater)
      {
         // Check if we need to go forward or back

         // Press the correct buttons
         if (BotInFieldOfView (pBot, pBot->dest_origin - GetGunPosition (pEdict)) > 90)
            pEdict->v.button |= IN_BACK;
         else
            pEdict->v.button |= IN_FORWARD;

         if (vecMoveAngles.x > 60.0)
            pEdict->v.button |= IN_DUCK;
         else if (vecMoveAngles.x < -60.0)
            pEdict->v.button |= IN_JUMP;
      }
   }

   // Are we allowed to check blocking Terrain (and react to it) ?
   if (pBot->bCheckTerrain)
   {
      edict_t *pent;
      bool bBackOffPlayer = FALSE;
      bool bBotIsStuck = FALSE;

      Vector v_angles = UTIL_VecToAngles (v_direction);

      // Test if there's a shootable breakable in our way
      v_src = pEdict->v.origin + pEdict->v.view_ofs; // EyePosition ()
      v_dest = v_src + vecDirection * 50;
      TRACE_HULL (v_src, v_dest, dont_ignore_monsters, head_hull, pEdict, &tr);

      if (tr.flFraction != 1.0)
      {
         if (strcmp ("func_breakable", STRING (tr.pHit->v.classname)) == 0)
         {
            pent = tr.pHit;
            if (IsShootableBreakable (pent))
            {
               pBot->pShootBreakable = pent;
               pBot->iCampButtons = pEdict->v.button & IN_DUCK;
               bottask_t TempTask = {NULL, NULL, TASK_SHOOTBREAKABLE, TASKPRI_SHOOTBREAKABLE, -1, 0.0, FALSE};
               BotPushTask (pBot, &TempTask);
            }
         }
      }
      else
      {
         v_src = pEdict->v.origin;
         v_dest = v_src + vecDirection * 50;
         TRACE_HULL (v_src, v_dest, dont_ignore_monsters, head_hull, pEdict, &tr);

         if (tr.flFraction != 1.0)
         {
            if (strcmp ("func_breakable", STRING (tr.pHit->v.classname)) == 0)
            {
               pent = tr.pHit;

               // Check if this isn't a triggered (bomb) breakable and
               // if it takes damage. If true, shoot the crap!
               if (IsShootableBreakable (pent))
               {
                  pBot->pShootBreakable = pent;
                  pBot->iCampButtons = pEdict->v.button & IN_DUCK;
                  bottask_t TempTask = {NULL, NULL, TASK_SHOOTBREAKABLE, TASKPRI_SHOOTBREAKABLE, -1, 0.0, FALSE};
                  BotPushTask (pBot, &TempTask);
               }
            }
         }
      }

      // No Breakable blocking ?
      if (FNullEnt (pBot->pShootBreakable))
      {
         // Standing still, no need to check ?
         // FIXME: Doesn't care for ladder movement (handled separately)
         // should be included in some way
         if ((pBot->f_move_speed != 0) || (pBot->f_sidemove_speed != 0))
         {
            pent = NULL;
            edict_t *pNearestPlayer = NULL;
            float f_nearestdistance = 256.0;
            float f_distance_now;

            // Find nearest player to Bot
            while (!FNullEnt (pent = FIND_ENTITY_IN_SPHERE (pent, pEdict->v.origin, pEdict->v.maxspeed)))
            {
               // Spectator or not drawn ?
               if (pent->v.effects & EF_NODRAW)
                  continue;

               // Player Entity ?
               if (pent->v.flags & FL_CLIENT)
               {
                  // Our Team, alive, moving and not myself ?
                  if ((UTIL_GetTeam (pent) != pBot->bot_team)
                      || !IsAlive (pent) || (pent == pEdict))
                     continue;

                  f_distance_now = (pent->v.origin - pEdict->v.origin).Length ();
                  if (f_distance_now < f_nearestdistance)
                  {
                     pNearestPlayer = pent;
                     f_nearestdistance = f_distance_now;
                  }
               }
            }

            // Found somebody ?
            if (!FNullEnt (pNearestPlayer) && FInViewCone (&pNearestPlayer->v.origin, pEdict))
            {
               // bot found a visible teammate
               float teammate_angle = pNearestPlayer->v.velocity.y - pEdict->v.velocity.y;
               float teammate_viewangle = pNearestPlayer->v.v_angle.y - pEdict->v.v_angle.y;
               UTIL_ClampAngle (&teammate_angle);
               UTIL_ClampAngle (&teammate_viewangle);

               // is that teammate near us OR coming in front of us and within a certain distance ?
               if ((f_nearestdistance < 100)
                   || ((f_nearestdistance < 300)
                       && (fabs (teammate_angle) > 160)
                       && (fabs (teammate_viewangle) > 165)))
               {
                  // if we are moving full speed AND there's room forward OR teammate is very close...
                  if ((f_nearestdistance < 70)
                      || ((tr.flFraction == 1.0) && (pEdict->v.velocity.Length2D () > 50)))
                  {
                     bBackOffPlayer = TRUE;

                     // is the player coming on the left ?
                     if (teammate_angle > 0)
                        pBot->f_sidemove_speed = pEdict->v.maxspeed;

                     // else is the player coming on the right ?
                     else if (teammate_angle < 0)
                        pBot->f_sidemove_speed = -pEdict->v.maxspeed;

                     if (f_nearestdistance <= 56.0)
                        pBot->f_move_speed = -pEdict->v.maxspeed;

                     BotResetCollideState (pBot);

                     if (moved_distance < 2.0)
                        pEdict->v.button |= IN_DUCK;
                  }
               }
            }
         }

         // No Player collision ?
         if (!bBackOffPlayer && (pBot->fNoCollTime < gpGlobals->time)
             && (BotGetSafeTask (pBot)->iTask != TASK_ATTACK))
         {
            // Didn't we move enough previously ?
            if ((moved_distance < 2.0) && (pBot->prev_speed >= 1.0))
            {
               // Then consider being stuck
               pBot->prev_time = gpGlobals->time;
               bBotIsStuck = TRUE;
               if (pBot->f_firstcollide_time == 0.0)
                  pBot->f_firstcollide_time = gpGlobals->time + 0.2;
            }

            // Not stuck yet
            else
            {
               // Test if there's something ahead blocking the way
               if (BotCantMoveForward (pBot, vecDirectionNormal, &tr) && !pBot->bOnLadder)
               {
                  if (pBot->f_firstcollide_time == 0.0)
                     pBot->f_firstcollide_time = gpGlobals->time + 0.2;
                  else if (pBot->f_firstcollide_time <= gpGlobals->time)
                     bBotIsStuck = TRUE;
               }
               else
                  pBot->f_firstcollide_time = 0.0;
            }

            // Not stuck ?
            if (!bBotIsStuck)
            {
               if (pBot->f_probe_time + 0.5 < gpGlobals->time)
               {
                  // Reset Collision Memory if not being stuck for 0.5 secs
                  BotResetCollideState(pBot);
               }
               else
               {
                  // Remember to keep pressing duck if it was necessary ago
                  if (pBot->cCollideMoves[pBot->cCollStateIndex] == COLLISION_DUCK)
                     pEdict->v.button |= IN_DUCK;
               }
            }

            // Bot is stuck !!
            else
            {
               // Not yet decided what to do ?
               if (pBot->cCollisionState == COLLISION_NOTDECIDED)
               {
                  char cBits = 0;

                  if (pBot->bInWater)
                  {
                     cBits |= PROBE_JUMP;
                     cBits |= PROBE_STRAFE;
                  }
                  else
                  {
                     cBits |= PROBE_JUMP;
                     cBits |= PROBE_DUCK;
                     cBits |= PROBE_STRAFE;
                  }

                  // Collision check allowed if not flying through the air
                  if (pEdict->v.flags & FL_ONGROUND)
                  {
                     char cState[8];
                     i = 0;

                     // First 4 Entries hold the possible
                     // Collision States
                     cState[i] = COLLISION_JUMP;
                     i++;
                     cState[i] = COLLISION_DUCK;
                     i++;
                     cState[i] = COLLISION_STRAFELEFT;
                     i++;
                     cState[i] = COLLISION_STRAFERIGHT;
                     i++;

                     // Now weight all possible States
                     if (cBits & PROBE_JUMP)
                     {
                        cState[i] = 0;

                        if (BotCanJumpUp (pBot, vecDirectionNormal))
                           cState[i] += 10;
                        if (pBot->dest_origin.z >= pEdict->v.origin.z + 18.0)
                           cState[i] += 5;

                        if (BotEntityIsVisible (pBot, pBot->dest_origin))
                        {
                           MAKE_VECTORS (vecMoveAngles);
                           v_src = pEdict->v.origin + pEdict->v.view_ofs;
                           v_src = v_src + gpGlobals->v_right * 15;
                           UTIL_TraceLine (v_src, pBot->dest_origin, ignore_monsters, ignore_glass, pEdict, &tr);

                           if (tr.flFraction >= 1.0)
                           {
                              v_src = pEdict->v.origin + pEdict->v.view_ofs;
                              v_src = v_src + (-gpGlobals->v_right * 15);
                              UTIL_TraceLine (v_src, pBot->dest_origin, ignore_monsters, ignore_glass, pEdict, &tr);

                              if (tr.flFraction >= 1.0)
                                 cState[i] += 5;
                           }
                        }

                        if (pEdict->v.flags & FL_DUCKING)
                           v_src = pEdict->v.origin;
                        else
                           v_src = pEdict->v.origin + Vector (0, 0, -17);
                        v_dest = v_src + vecDirectionNormal * 30;
                        UTIL_TraceLine (v_src, v_dest, ignore_monsters, ignore_glass, pEdict, &tr);

                        if (tr.flFraction != 1.0)
                           cState[i] += 10;
                     }
                     else
                        cState[i] = 0;

                     i++;

                     if (cBits & PROBE_DUCK)
                     {
                        cState[i] = 0;

                        if (BotCanDuckUnder (pBot, vecDirectionNormal))
                           cState[i] += 10;

                        if ((pBot->dest_origin.z + 36.0 <= pEdict->v.origin.z)
                            && BotEntityIsVisible (pBot, pBot->dest_origin))
                           cState[i] += 5;
                     }
                     else
                        cState[i] = 0;

                     i++;

                     if (cBits & PROBE_STRAFE)
                     {
                        cState[i] = 0;
                        cState[i + 1] = 0;
                        Vector2D vec2DirToPoint;
                        Vector2D vec2RightSide;

                        // to start strafing, we have to first figure out if the target is on the left side or right side
                        MAKE_VECTORS (vecMoveAngles);

                        vec2DirToPoint =  (pEdict->v.origin - pBot->dest_origin).Make2D ().Normalize ();
                        vec2RightSide = gpGlobals->v_right.Make2D ().Normalize ();
                        bool bDirRight = FALSE;
                        bool bDirLeft = FALSE;
                        bool bBlockedLeft = FALSE;
                        bool bBlockedRight = FALSE;

                        if (pBot->f_move_speed > 0)
                           vecDirection = gpGlobals->v_forward;
                        else
                           vecDirection = -gpGlobals->v_forward;

                        if (DotProduct  (vec2DirToPoint, vec2RightSide) > 0)
                           bDirRight = TRUE;
                        else
                           bDirLeft = TRUE;

                        // Now check which side is blocked
                        v_src = pEdict->v.origin + gpGlobals->v_right * 32;
                        v_dest = v_src + vecDirection * 32;
                        TRACE_LINE (v_src, v_dest, ignore_monsters, pEdict, &tr);

                        if (tr.flFraction != 1.0)
                           bBlockedRight = TRUE;

                        v_src = pEdict->v.origin + -gpGlobals->v_right * 32;
                        v_dest = v_src + vecDirection * 32;
                        TRACE_LINE (v_src, v_dest, ignore_monsters, pEdict, &tr);

                        if (tr.flFraction != 1.0)
                           bBlockedLeft = TRUE;

                        if (bDirLeft)
                           cState[i] += 5;
                        else
                           cState[i] -= 5;

                        if (bBlockedLeft)
                           cState[i] -= 5;

                        i++;

                        if (bDirRight)
                           cState[i] += 5;
                        else
                           cState[i] -= 5;

                        if (bBlockedRight)
                           cState[i] -= 5;
                     }
                     else
                     {
                        cState[i] = 0;
                        i++;
                        cState[i] = 0;
                     }

                     // Weighted all possible Moves, now sort them to start with most probable
                     char cTemp;
                     bool bSorting;
                     do
                     {
                        bSorting = FALSE;
                        for (i = 0; i < 3; i++)
                        {
                           if (cState[i + 4] < cState[i + 1 + 4])
                           {
                              cTemp = cState[i];
                              cState[i] = cState[i + 1];
                              cState[i + 1] = cTemp;
                              cTemp = cState[i + 4];
                              cState[i + 4] = cState[i + 1 + 4];
                              cState[i + 1 + 4] = cTemp;
                              bSorting = TRUE;
                           }
                        }
                     } while (bSorting);

                     for (i = 0; i < 4; i++)
                        pBot->cCollideMoves[i] = cState[i];

                     pBot->f_probe_time = gpGlobals->time + 0.5;
                     pBot->cCollisionProbeBits = cBits;
                     pBot->cCollisionState = COLLISION_PROBING;
                     pBot->cCollStateIndex = 0;
                  }
               }

               if (pBot->cCollisionState == COLLISION_PROBING)
               {
                  if (pBot->f_probe_time < gpGlobals->time)
                  {
                     pBot->cCollStateIndex++;
                     pBot->f_probe_time = gpGlobals->time + 0.5;

                     if (pBot->cCollStateIndex > 4)
                     {
                        pBot->f_wpt_timeset = gpGlobals->time - 5.0;
                        BotResetCollideState (pBot);
                     }
                  }
                  if (pBot->cCollStateIndex <= 4)
                  {
                     switch (pBot->cCollideMoves[pBot->cCollStateIndex])
                     {
                     case COLLISION_JUMP:
                        if ((pEdict->v.flags & FL_ONGROUND)
                            || pBot->bInWater || !pBot->bOnLadder)
                           pEdict->v.button |= IN_JUMP;
                        break;

                     case COLLISION_DUCK:
                        pEdict->v.button |= IN_DUCK;
                        break;

                     case COLLISION_STRAFELEFT:
                        BotSetStrafeSpeed (pBot, vecDirectionNormal, -pEdict->v.maxspeed);
                        break;

                     case COLLISION_STRAFERIGHT:
                        BotSetStrafeSpeed (pBot, vecDirectionNormal, pEdict->v.maxspeed);
                        break;
                     }
                  }
               }
            }
         }
      }
   }

   // Must avoid a Grenade ?
   if (pBot->cAvoidGrenade != 0)
   {
      // Don't duck to get away faster
      pEdict->v.button &= ~IN_DUCK;
      pBot->f_move_speed = -pEdict->v.maxspeed;
      pBot->f_sidemove_speed = pEdict->v.maxspeed * pBot->cAvoidGrenade;
   }

   // FIXME: time to reach waypoint should be calculated when getting this waypoint
   // depending on maxspeed and movetype instead of being hardcoded
   if ((pBot->f_wpt_timeset + 5.0 < gpGlobals->time) && FNullEnt (pBot->pBotEnemy))
   {
      GetValidWaypoint (pBot);

      // Clear these pointers, Bot might be stuck getting to them
      if (!FNullEnt (pBot->pBotPickupItem))
         pBot->pItemIgnore = pBot->pBotPickupItem;

      pBot->pBotPickupItem = NULL;
      pBot->iPickupType = PICKUP_NONE;
      pBot->f_itemcheck_time = gpGlobals->time + 5.0;
      pBot->pShootBreakable = NULL;
   }

   if (pEdict->v.button & IN_JUMP)
      pBot->f_jumptime = gpGlobals->time;

   if (pBot->f_jumptime + 1.0 > gpGlobals->time)
   {
      if (!(pEdict->v.flags & FL_ONGROUND) && !pBot->bInWater)
         pEdict->v.button |= IN_DUCK;
   }

   if (!(pEdict->v.button & (IN_FORWARD | IN_BACK)))
   {
      if (pBot->f_move_speed > 0)
         pEdict->v.button |= IN_FORWARD;
      else if (pBot->f_move_speed < 0)
         pEdict->v.button |= IN_BACK;
   }

   if (!(pEdict->v.button & (IN_MOVELEFT | IN_MOVERIGHT)))
   {
      if (pBot->f_sidemove_speed > 0)
         pEdict->v.button |= IN_MOVERIGHT;
      else if (pBot->f_sidemove_speed < 0)
         pEdict->v.button |= IN_MOVELEFT;
   }

   // save the previous speed (for checking if stuck)
   pBot->prev_speed = fabs (pBot->f_move_speed);

   // Reset Damage
   pBot->iLastDamageType = -1;

   UTIL_ClampVector (&pEdict->v.angles);
   UTIL_ClampVector (&pEdict->v.v_angle);
   UTIL_ClampAngle (&pEdict->v.idealpitch);
   UTIL_ClampAngle (&pEdict->v.ideal_yaw);

   assert ((pEdict->v.v_angle.x > -181.0) && (pEdict->v.v_angle.x < 181.0)
           && (pEdict->v.v_angle.y > -181.0) && (pEdict->v.v_angle.y < 181.0)
           && (pEdict->v.angles.x > -181.0) && (pEdict->v.angles.x < 181.0)
           && (pEdict->v.angles.y > -181.0) && (pEdict->v.angles.y < 181.0));

   g_engfuncs.pfnRunPlayerMove (pEdict, vecMoveAngles, pBot->f_move_speed, pBot->f_sidemove_speed, 0, pEdict->v.button, 0, msecval);
   return;
}


bool BotHasHostage (bot_t *pBot)
{
   int i;

   for (i = 0; i < MAX_HOSTAGES; i++)
      if (!FNullEnt (pBot->pHostages[i]))
         return (TRUE);

   return (FALSE);
}


void BotResetCollideState (bot_t *pBot)
{
   pBot->f_probe_time = 0.0;
   pBot->cCollisionProbeBits = 0;
   pBot->cCollisionState = COLLISION_NOTDECIDED;
   pBot->cCollStateIndex = 0;
}


bool IsDeadlyDrop (bot_t *pBot, Vector vecTargetPos)
{
   // Returns if given location would hurt Bot with falling damage

   edict_t *pEdict = pBot->pEdict;
   Vector vecBot = pEdict->v.origin;
   TraceResult tr;
   float height, last_height, distance;

   Vector vecMove = UTIL_VecToAngles (vecTargetPos - vecBot);
   vecMove.x = 0; // reset pitch to 0 (level horizontally)
   vecMove.z = 0; // reset roll to 0 (straight up and down)
   MAKE_VECTORS (vecMove);

   Vector v_direction = (vecTargetPos - vecBot).Normalize (); // 1 unit long
   Vector v_check = vecBot;
   Vector v_down = vecBot;

   v_down.z = v_down.z - 1000.0; // straight down 1000 units

   TRACE_LINE (v_check, v_down, ignore_monsters, pEdict, &tr);

   // We're not on ground anymore ?
   if (tr.flFraction > 0.036)
      tr.flFraction = 0.036;

   last_height = tr.flFraction * 1000.0; // height from ground
   distance = (vecTargetPos - v_check).Length (); // distance from goal

   while (distance > 16.0)
   {
      // move 10 units closer to the goal...
      v_check = v_check + (v_direction * 16.0);

      v_down = v_check;
      v_down.z = v_down.z - 1000.0; // straight down 1000 units

      TRACE_LINE (v_check, v_down, ignore_monsters, pEdict, &tr);

      // Wall blocking ?
      if (tr.fStartSolid)
         return (FALSE);

      height = tr.flFraction * 1000.0; // height from ground

      // Drops more than 100 Units ?
      if (last_height < height - 100)
         return (TRUE);

      last_height = height;

      distance = (vecTargetPos - v_check).Length (); // distance from goal
   }

   return (FALSE);
}


void BotFreeAllMemory (void)
{
   STRINGNODE *pNextNode;
   replynode_t *pNextReply;
   PATH *pNextPath;
   int i;

   for (i = 0; i < 32; i++)
   {
      DeleteSearchNodes (&bots[i]); // Delete Nodes from Pathfinding
      BotResetTasks (&bots[i]);
   }

   // Delete all Waypoint Data

   while (paths[0] != NULL)
   {
      pNextPath = paths[0]->next;
      delete (paths[0]);
      paths[0] = pNextPath;
   }
   paths[0] = NULL;

   memset (paths, 0, sizeof (paths));
   g_iNumWaypoints = 0;
   g_vecLastWaypoint = g_vecZero;
   g_fWPDisplayTime = 0.0;

   if (pBotExperienceData != NULL)
      delete [](pBotExperienceData);
   pBotExperienceData = NULL;

   if (g_pFloydDistanceMatrix != NULL)
      delete [](g_pFloydDistanceMatrix);
   g_pFloydDistanceMatrix = NULL;

   if (g_pFloydPathMatrix != NULL)
      delete [](g_pFloydPathMatrix);
   g_pFloydPathMatrix = NULL;

   if (g_pWithHostageDistMatrix != NULL)
      delete [](g_pWithHostageDistMatrix);
   g_pWithHostageDistMatrix = NULL;

   if (g_pWithHostagePathMatrix != NULL)
      delete [](g_pWithHostagePathMatrix);
   g_pWithHostagePathMatrix = NULL;

   // Delete all Textnodes/strings

   while (pChatReplies != NULL)
   {
      while (pChatReplies->pReplies != NULL)
      {
         pNextNode = pChatReplies->pReplies->Next;
         delete (pChatReplies->pReplies);
         pChatReplies->pReplies = pNextNode;
      }
      pChatReplies->pReplies = NULL;

      pNextReply = pChatReplies->pNextReplyNode;
      delete (pChatReplies);
      pChatReplies = pNextReply;
   }
   pChatReplies = NULL;

   return; // KABLAM! everything is nuked, wiped, cleaned, memory stick is shining like a coin =)
}


STRINGNODE *GetNodeSTRING (STRINGNODE *pNode, int NodeNum)
{
   STRINGNODE *pTempNode = pNode;
   int i = 0;

   while (i < NodeNum)
   {
      pTempNode = pTempNode->Next;
      assert (pTempNode!= NULL);

      if (pTempNode == NULL)
         break;
      i++;
   }

   return (pTempNode);
}
