// ####################################
// #                                  #
// #       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_client.cpp
//
// Handles Messages sent from the Server to a Client Bot

#include "bot_globals.h"


void BotClient_CS_VGUI (void *p, int bot_index)
{
   // This message is sent when the VGUI menu is displayed.

   if (state == 0)
   {
      if ((*(int *) p) == 2)  // is it a team select menu?
         bots[bot_index].start_action = MSG_CS_TEAM_SELECT;
      else if ((*(int *) p) == 26)  // is it a Terrorist class selection menu?
         bots[bot_index].start_action = MSG_CS_T_SELECT;
      else if ((*(int *) p) == 27)  // is it a CT class selection menu?
         bots[bot_index].start_action = MSG_CS_CT_SELECT;
   }
}


void BotClient_CS_ShowMenu (void *p, int bot_index)
{
   // This message is sent when a menu is being displayed.

   if (state < 3)
      return; // ignore first 3 fields of message

   // Team Selection Messages
   if (strcmp ((char *) p, "#Team_Select") == 0)  // team select menu?
      bots[bot_index].start_action = MSG_CS_TEAM_SELECT;
   else if (strcmp ((char *) p, "#CT_Select") == 0)  // CT model select menu?
      bots[bot_index].start_action = MSG_CS_CT_SELECT;
   else if (strcmp ((char *) p, "#Terrorist_Select") == 0)  // T model select?
      bots[bot_index].start_action = MSG_CS_T_SELECT;
}


void BotClient_CS_StatusIcon (void *p, int bot_index)
{
   // this message tells a game client to display (or stop displaying) a certain status icon on
   // his player's HUD.

   static int icon_state = 0;

   if (state == 0)
      icon_state = *(int *) p;

   else if (state == 1)
   {
      // is it the C4 icon ?
      if (strcmp ("c4", (char *) p) == 0)
      {
         // is icon blinking ?
         if (icon_state == 2)
            bots[bot_index].b_bomb_blinking = TRUE; // bomb icon is blinking
         else
            bots[bot_index].b_bomb_blinking = FALSE;
      }

      // else is it the defuser icon ?
      else if (strcmp ("defuser", (char *) p) == 0)
      {
         // is icon lit ?
         if (icon_state > 0)
            bots[bot_index].b_has_defuse_kit = TRUE; // Bot has Defuse Kit
         else
            bots[bot_index].b_has_defuse_kit = FALSE;
      }

      // else is it the buy zone icon ?
      else if (strcmp ("buyzone", (char *) p) == 0)
      {
         // is icon lit ?
         if (icon_state > 0)
            bots[bot_index].b_can_buy = TRUE; // bot can buy
         else
            bots[bot_index].b_can_buy = FALSE;
      }
   }
}


void BotClient_CS_WeaponList (void *p, int bot_index)
{
   // This message is sent when a client joins the game.  All of the weapons
   // are sent with the weapon ID and information about what ammo is used.

   static bot_weapon_t bot_weapon;

   if (state == 0)
      strcpy (bot_weapon.szClassname, (char *) p);
   else if (state == 1)
      bot_weapon.iAmmo1 = *(int *) p;  // ammo index 1
   else if (state == 2)
      bot_weapon.iAmmo1Max = *(int *) p;  // max ammo1
   else if (state == 3)
      bot_weapon.iAmmo2 = *(int *) p;  // ammo index 2
   else if (state == 4)
      bot_weapon.iAmmo2Max = *(int *) p;  // max ammo2
   else if (state == 5)
      bot_weapon.iSlot = *(int *) p;  // slot for this weapon
   else if (state == 6)
      bot_weapon.iPosition = *(int *) p;  // position in slot
   else if (state == 7)
      bot_weapon.iId = *(int *) p;  // weapon ID
   else if (state == 8)
   {
      bot_weapon.iFlags = *(int *) p;  // flags for weapon (WTF???)

      // store away this weapon with it's ammo information...
      weapon_defs[bot_weapon.iId] = bot_weapon;
   }
}


void BotClient_CS_CurrentWeapon (void *p, int bot_index)
{
   // This message is sent when a weapon is selected (either by the bot chosing
   // a weapon or by the server auto assigning the bot a weapon).
   // In CS it's also called when Ammo is increased/decreased

   static int iState;
   static int iId;
   static int iClip;

   bot_t *pBot = &bots[bot_index];

   if (state == 0)
      iState = *(int *) p;  // state of the current weapon (WTF???)
   else if (state == 1)
      iId = *(int *) p;  // weapon ID of current weapon
   else if (state == 2)
   {
      iClip = *(int *) p;  // ammo currently in the clip for this weapon

      if ((iId <= 31) && (iState == 1))
      {
         // Ammo amount decreased ? Must have fired a bullet...
         if ((iId == pBot->current_weapon.iId) && (pBot->current_weapon.iClip > iClip))
         {
            // Time fired withing burst firing time ?
            if (pBot->fTimeLastFired + 1.0 > gpGlobals->time)
               pBot->iBurstShotsFired++;

            pBot->fTimeLastFired = gpGlobals->time; // Remember the last bullet time
         }
         pBot->current_weapon.iId = iId;
         pBot->current_weapon.iClip = iClip;

         // update the ammo counts for this weapon...
         pBot->current_weapon.iAmmo1 = pBot->m_rgAmmo[weapon_defs[iId].iAmmo1];
         pBot->current_weapon.iAmmo2 = pBot->m_rgAmmo[weapon_defs[iId].iAmmo2];
      }
   }
}


void BotClient_CS_AmmoX (void *p, int bot_index)
{
   // This message is sent whenever ammo ammounts are adjusted (up or down).
   // NOTE: Logging reveals that CS uses it very unreliable !

   static int index;
   int ammo_index;

   bot_t *pBot = &bots[bot_index];

   if (state == 0)
      index = *(int *) p; // ammo index (for type of ammo)
   else if (state == 1)
   {
      pBot->m_rgAmmo[index] = *(int *) p; // store it away

      if (pBot->current_weapon.iId > CS_WEAPON_INSWITCH)
      {
         ammo_index = pBot->current_weapon.iId;

         // update the ammo counts for this weapon...
         pBot->current_weapon.iAmmo1 = pBot->m_rgAmmo[weapon_defs[ammo_index].iAmmo1];
         pBot->current_weapon.iAmmo2 = pBot->m_rgAmmo[weapon_defs[ammo_index].iAmmo2];
      }
   }
}


void BotClient_CS_AmmoPickup (void *p, int bot_index)
{
   // This message is sent when the bot picks up some ammo (AmmoX messages are
   // also sent so this message is probably not really necessary except it
   // allows the HUD to draw pictures of ammo that have been picked up.  The
   // bots don't really need pictures since they don't have any eyes anyway.

   static int index;
   int ammo_index;

   if (state == 0)
      index = *(int *) p;
   else if (state == 1)
   {
      bots[bot_index].m_rgAmmo[index] = *(int *) p;
      ammo_index = bots[bot_index].current_weapon.iId;

      // update the ammo counts for this weapon (ONLY if bot knows its current weapon)...
      if ((ammo_index >= 0) && (ammo_index < MAX_WEAPONS))
      {
         bots[bot_index].current_weapon.iAmmo1 = bots[bot_index].m_rgAmmo[weapon_defs[ammo_index].iAmmo1];
         bots[bot_index].current_weapon.iAmmo2 = bots[bot_index].m_rgAmmo[weapon_defs[ammo_index].iAmmo2];
      }
   }
}


void BotClient_CS_Damage (void *p, int bot_index)
{
   // This message gets sent when the bots are getting damaged.

   static int damage_armor;
   static int damage_taken;
   static int damage_bits;
   static Vector damage_origin;

   if (state == 0)
      damage_armor = *(int *) p;
   else if (state == 1)
      damage_taken = *(int *) p;
   else if (state == 2)
      damage_bits = *(int *) p;
   else if (state == 3)
      damage_origin.x = *(float *) p;
   else if (state == 4)
      damage_origin.y = *(float *) p;
   else if (state == 5)
   {
      damage_origin.z = *(float *) p;

      if ((damage_armor > 0) || (damage_taken > 0))
      {
         bot_t *pBot = &bots[bot_index];
         pBot->iLastDamageType = damage_bits;
         edict_t *pEdict = pBot->pEdict;

         BotCollectGoalExperience (pBot, damage_taken);

         edict_t *pEnt = pEdict->v.dmg_inflictor;

         if (pEnt->v.flags & FL_CLIENT)
         {
            if (UTIL_GetTeam (pEnt) == pBot->bot_team)
            {
               // FIXFIXFIXFIXFIXME: THIS IS BLATANTLY CHEATING!!!!
               if (RANDOM_LONG (1, 100) < 10)
               {
                  if (FNullEnt (pBot->pBotEnemy) && (pBot->f_bot_see_enemy_time + 2.0 < gpGlobals->time))
                  {
                     pBot->f_bot_see_enemy_time = gpGlobals->time;
                     pBot->pBotEnemy = pEnt;
                     pBot->pLastEnemy = pEnt;
                     pBot->vecLastEnemyOrigin = pEnt->v.origin;
                  }
               }
            }
            else
            {
               if (pBot->pEdict->v.health > 70)
               {
                  pBot->fAgressionLevel += 0.1;
                  if (pBot->fAgressionLevel > 1.0)
                     pBot->fAgressionLevel = 1.0;
               }
               else
               {
                  pBot->fFearLevel += 0.05;
                  if (pBot->fFearLevel > 1.0)
                     pBot->fFearLevel = 1.0;
               }

               // Stop Bot from Hiding
               BotRemoveCertainTask (pBot, TASK_HIDE);

               // FIXFIXFIXFIXFIXME: THIS IS BLATANTLY CHEATING!!!!
               if (FNullEnt (pBot->pBotEnemy))
               {
                  pBot->pLastEnemy = pEnt;
                  pBot->vecLastEnemyOrigin = pEnt->v.origin;

                  // FIXME - Bot doesn't necessary sees this enemy
                  pBot->f_bot_see_enemy_time = gpGlobals->time;
               }

               BotCollectExperienceData (pEdict, pEnt, damage_armor + damage_taken);
            }
         }

         // Check old waypoint
         else
         {
            if (!WaypointReachable (pEdict->v.origin, pBot->dest_origin, pBot->pEdict))
            {
               DeleteSearchNodes (pBot);
               BotFindWaypoint (pBot);
            }
         }
      }
   }
}


void BotClient_CS_Money (void *p, int bot_index)
{
   // This message gets sent when the bots money ammount changes

   if (state == 0)
      bots[bot_index].bot_money = *(int *) p;  // amount of money
}


void BotClient_CS_DeathMsg (void *p, int bot_index)
{
   // This message gets sent when someone got killed

   static int killer_index;
   static int victim_index;
   static edict_t *killer_edict;
   static bot_t *pBot;

   if (state == 0)
      killer_index = *(int *) p; // ENTINDEX() of killer
   else if (state == 1)
      victim_index = *(int *) p; // ENTINDEX() of victim
   else if (state == 2)
   {
      if ((killer_index != 0) && (victim_index != 0) && (killer_index != victim_index))
      {
         killer_edict = INDEXENT (killer_index);
         pBot = UTIL_GetBotPointer (killer_edict);

         // is this message about a bot who killed somebody ?
         if (pBot != NULL)
         {
            pBot->pLastVictim = INDEXENT (victim_index);
            pBot->f_shootatdead_time = gpGlobals->time + RANDOM_FLOAT (0.0, 2.0);
         }

         else // Did a human kill a Bot on his team ?
         {
            edict_t *victim_edict = INDEXENT (victim_index);
            pBot = UTIL_GetBotPointer (victim_edict);

            if (pBot != NULL)
               pBot->bDead = TRUE;
         }
      }
   }
}


void BotClient_CS_ScreenFade (void *p, int bot_index)
{
   // This message gets sent when the Screen fades (Flashbang)

   static unsigned char r;
   static unsigned char g;
   static unsigned char b;

   if (state == 3)
      r = *(unsigned char *) p;
   else if (state == 4)
      g = *(unsigned char *) p;
   else if (state == 5)
      b = *(unsigned char *) p;
   else if (state == 6)
   {
      unsigned char alpha = *(unsigned char *) p;

      if ((r == 255) && (g == 255) && (b == 255) && (alpha > 200))
      {
         bot_t *pBot = &bots[bot_index];
         pBot->pBotEnemy = NULL;
         pBot->f_view_distance = 1;

         // About 3 seconds
         pBot->f_blind_time = gpGlobals->time + ((float) alpha - 200.0) / 15;

         if (pBot->bot_skill < 50)
         {
            pBot->f_blindmovespeed_forward = 0.0;
            pBot->f_blindmovespeed_side = 0.0;
         }
         else if (pBot->bot_skill < 80)
         {
            pBot->f_blindmovespeed_forward = -pBot->pEdict->v.maxspeed;
            pBot->f_blindmovespeed_side = 0.0;
         }
         else
         {
            if (RANDOM_LONG (1, 100) < 50)
            {
               if (RANDOM_LONG (1, 100) < 50)
                  pBot->f_blindmovespeed_side = pBot->pEdict->v.maxspeed;
               else
                  pBot->f_blindmovespeed_side = -pBot->pEdict->v.maxspeed;
            }
            else
            {
               if (pBot->pEdict->v.health > 80)
                  pBot->f_blindmovespeed_forward = pBot->pEdict->v.maxspeed;
               else
                  pBot->f_blindmovespeed_forward = -pBot->pEdict->v.maxspeed;
            }
         }
      }
   }
}


void BotClient_CS_SayText (void *p, int bot_index)
{
   static unsigned char ucEntIndex;

   if (state == 0)
      ucEntIndex = *(unsigned char *) p;
   else if (state == 1)
   {
      bot_t *pBot = &bots[bot_index];

      if (ENTINDEX (pBot->pEdict) != ucEntIndex)
      {
         pBot->SaytextBuffer.iEntityIndex = (int) ucEntIndex;
         strcpy (pBot->SaytextBuffer.szSayText, (char *) p);
         pBot->SaytextBuffer.fTimeNextChat = gpGlobals->time + pBot->SaytextBuffer.fChatDelay;
      }
   }
}


void BotClient_CS_HLTV (void *p, int bot_index)
{
   // This message gets sent when the round restarts in CS 1.6, among other things.
   // Courtesy of stefanhendriks...

   static int players;

   if (state == 0)
      players = *(int *) p;
   else if (state == 1)
   {
      // new round in CS 1.6
      if ((players == 0) && (*(int *) p == 0))
         UTIL_RoundStart ();
   }
}


void BotClient_CS_BombDrop (void *p, int bot_index)
{
   edict_t *pent = NULL;
   bot_t *pBot = &bots[bot_index];

   // is the bot receiving this message alive and T ?
   if (pBot->is_used && !pBot->bDead && (pBot->bot_team == TEAM_CS_TERRORIST))
   {
      BotRemoveCertainTask (pBot, TASK_CAMP);
      DeleteSearchNodes (pBot); // make all Ts reevaluate their paths immediately

      // find the bomb
      while (!FNullEnt (pent = FIND_ENTITY_BY_STRING (pent, "classname", "weaponbox")))
      {
         if (strcmp (STRING (pent->v.model), "models/w_backpack.mdl") == 0)
         {
            pBot->vecPosition = pent->v.origin;

            // Push move task on to stack
            bottask_t TempTask = {NULL, NULL, TASK_MOVETOPOSITION, TASKPRI_MOVETOPOSITION, -1, 0.0, TRUE};
            BotPushTask (pBot, &TempTask);
            break;
         }
      }
   }

   return;
}


void BotClient_CS_BombPickup (void *p, int bot_index)
{
   bot_t *pBot = &bots[bot_index];

   if (pBot->is_used && !pBot->bDead && (pBot->bot_team == TEAM_CS_COUNTER))
   {
      DeleteSearchNodes (pBot); // make all Ts reevaluate their paths immediately
      BotResetTasks (pBot); // barbarian, but fits the job perfectly.
   }

   return;
}


void BotClient_CS_TextMsgAll (void *p, int bot_index)
{
   int i;
   bot_t *pBot;

   // Check if it's the "Bomb Planted" Message
   if ((state == 1) && (strcmp ("#Bomb_Planted", (char *) p) == 0))
   {
      g_bBombPlanted = g_bBombSayString = TRUE;
      g_fTimeBombPlanted = gpGlobals->time;

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

         if (pBot->is_used && !pBot->bDead && (pBot->bot_team == TEAM_CS_COUNTER))
         {
            DeleteSearchNodes (pBot); // make all CTs reevaluate their paths immediately
            BotResetTasks (pBot); // barbarian, but fits the job perfectly.
         }
      }
   }
}
