// ####################################
// #                                  #
// #       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
//
// dll.cpp
//
// Links Functions, handles Client Commands, initializes DLL and misc Stuff

#include "bot_globals.h"



// server command handler
void PODBot_ServerCommand (void);




// START of Metamod stuff

enginefuncs_t meta_engfuncs;
gamedll_funcs_t *gpGamedllFuncs;
mutil_funcs_t *gpMetaUtilFuncs;
meta_globals_t *gpMetaGlobals;

META_FUNCTIONS gMetaFunctionTable =
{
   NULL, // pfnGetEntityAPI()
   NULL, // pfnGetEntityAPI_Post()
   GetEntityAPI2, // pfnGetEntityAPI2()
   GetEntityAPI2_Post, // pfnGetEntityAPI2_Post()
   NULL, // pfnGetNewDLLFunctions()
   NULL, // pfnGetNewDLLFunctions_Post()
   GetEngineFunctions, // pfnGetEngineFunctions()
   NULL, // pfnGetEngineFunctions_Post()
};

plugin_info_t Plugin_info = {
   META_INTERFACE_VERSION, // interface version
   "POD-Bot", // plugin name
   "2.6mm", // plugin version
   __DATE__, // date of creation
   "Count Floyd & Bots United", // plugin author
   "http://www.bots-united.com", // plugin URL
   "PODBOT", // plugin logtag
   PT_CHANGELEVEL, // when loadable
   PT_ANYTIME, // when unloadable
};


C_DLLEXPORT int Meta_Query (char *ifvers, plugin_info_t **pPlugInfo, mutil_funcs_t *pMetaUtilFuncs)
{
   // this function is the first function ever called by metamod in the plugin DLL. Its purpose
   // is for metamod to retrieve basic information about the plugin, such as its meta-interface
   // version, for ensuring compatibility with the current version of the running metamod.

   // keep track of the pointers to metamod function tables metamod gives us
   gpMetaUtilFuncs = pMetaUtilFuncs;
   *pPlugInfo = &Plugin_info;

   // check for interface version compatibility
   if (strcmp (ifvers, Plugin_info.ifvers) != 0)
   {
      int mmajor = 0, mminor = 0, pmajor = 0, pminor = 0;

      LOG_CONSOLE (PLID, "%s: meta-interface version mismatch (metamod: %s, %s: %s)", Plugin_info.name, ifvers, Plugin_info.name, Plugin_info.ifvers);
      LOG_MESSAGE (PLID, "%s: meta-interface version mismatch (metamod: %s, %s: %s)", Plugin_info.name, ifvers, Plugin_info.name, Plugin_info.ifvers);

      // if plugin has later interface version, it's incompatible (update metamod)
      sscanf (ifvers, "%d:%d", &mmajor, &mminor);
      sscanf (META_INTERFACE_VERSION, "%d:%d", &pmajor, &pminor);

      if ((pmajor > mmajor) || ((pmajor == mmajor) && (pminor > mminor)))
      {
         LOG_CONSOLE (PLID, "metamod version is too old for this plugin; update metamod");
         LOG_ERROR (PLID, "metamod version is too old for this plugin; update metamod");
         return (FALSE);
      }

      // if plugin has older major interface version, it's incompatible (update plugin)
      else if (pmajor < mmajor)
      {
         LOG_CONSOLE (PLID, "metamod version is incompatible with this plugin; please find a newer version of this plugin");
         LOG_ERROR (PLID, "metamod version is incompatible with this plugin; please find a newer version of this plugin");
         return (FALSE);
      }
   }

   return (TRUE); // tell metamod this plugin looks safe
}


C_DLLEXPORT int Meta_Attach (PLUG_LOADTIME now, META_FUNCTIONS *pFunctionTable, meta_globals_t *pMGlobals, gamedll_funcs_t *pGamedllFuncs)
{
   // this function is called when metamod attempts to load the plugin. Since it's the place
   // where we can tell if the plugin will be allowed to run or not, we wait until here to make
   // our initialization stuff, like registering CVARs and dedicated server commands.

   // are we allowed to load this plugin now ?
   if (now > Plugin_info.loadable)
   {
      LOG_CONSOLE (PLID, "%s: plugin NOT attaching (can't load plugin right now)", Plugin_info.name);
      LOG_ERROR (PLID, "%s: plugin NOT attaching (can't load plugin right now)", Plugin_info.name);
      return (FALSE); // returning FALSE prevents metamod from attaching this plugin
   }

   // keep track of the pointers to engine function tables metamod gives us
   gpMetaGlobals = pMGlobals;
   memcpy (pFunctionTable, &gMetaFunctionTable, sizeof (META_FUNCTIONS));
   gpGamedllFuncs = pGamedllFuncs;

   // print a message to notify about plugin attaching
   LOG_CONSOLE (PLID, "%s: plugin attaching", Plugin_info.name);
   LOG_MESSAGE (PLID, "%s: plugin attaching", Plugin_info.name);

   // ask the engine to register the server commands this plugin uses
   REG_SVR_COMMAND (CONSOLE_CMD_POD, PODBot_ServerCommand);
   
   return (TRUE); // returning TRUE enables metamod to attach this plugin
}


C_DLLEXPORT int Meta_Detach (PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
{
   // this function is called when metamod unloads the plugin. A basic check is made in order
   // to prevent unloading the plugin if its processing should not be interrupted.

   // is metamod allowed to unload the plugin ?
   if ((now > Plugin_info.unloadable) && (reason != PNL_CMD_FORCED))
   {
      LOG_CONSOLE (PLID, "%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name);
      LOG_ERROR (PLID, "%s: plugin NOT detaching (can't unload plugin right now)", Plugin_info.name);
      return (FALSE); // returning FALSE prevents metamod from unloading this plugin
   }

   // Delete all allocated Memory
   BotFreeAllMemory();

   return (TRUE); // returning TRUE enables metamod to unload this plugin
}

// END of Metamod stuff


#ifndef __linux__
BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
   // Required DLL entry point

   return (TRUE);
}
#endif


void WINAPI GiveFnptrsToDll (enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals)
{
   // get the engine functions from the engine...

   memcpy (&g_engfuncs, pengfuncsFromEngine, sizeof (enginefuncs_t));
   gpGlobals = pGlobals;
}


void GameDLLInit (void)
{
   // First function called from HL in our replacement Server DLL

   unsigned char *tempbuf;
   int tempsize;
   g_bIsDedicatedServer = (IS_DEDICATED_SERVER () > 0);

   // Counter-Strike 1.5 detection
   tempbuf = LOAD_FILE_FOR_ME ("models/w_famas.mdl", &tempsize);
   if (tempbuf != NULL)
   {
      g_bIsOldCS15 = FALSE;
      FREE_FILE (tempbuf);
   }
   else
      g_bIsOldCS15 = TRUE;

   // Reset the bot creation tab
   memset (BotCreateTab, 0, sizeof (BotCreateTab));

   RETURN_META (MRES_IGNORED);
}


int Spawn (edict_t *pent)
{
   // Something gets spawned in the game

   if (strcmp (STRING (pent->v.classname), "worldspawn") == 0)
   {
      g_iMapType = 0; // reset g_iMapType as worldspawn is the first entity spawned
      PRECACHE_SOUND ("weapons/xbow_hit1.wav"); // waypoint add
      PRECACHE_SOUND ("weapons/mine_activate.wav"); // waypoint delete
      PRECACHE_SOUND ("common/wpn_hudon.wav"); // path add/delete done
      m_spriteTexture = PRECACHE_MODEL ("sprites/lgtning.spr");
   }

   else if (strcmp (STRING (pent->v.classname), "info_player_start") == 0)
   {
      SET_MODEL (pent, "models/player/urban/urban.mdl");
      pent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency
      pent->v.renderamt = 127; // set its transparency amount
      pent->v.effects |= EF_NODRAW;
   }

   else if (strcmp (STRING (pent->v.classname), "info_player_deathmatch") == 0)
   {
      SET_MODEL (pent, "models/player/terror/terror.mdl");
      pent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency
      pent->v.renderamt = 127; // set its transparency amount
      pent->v.effects |= EF_NODRAW;
   }

   else if (strcmp (STRING (pent->v.classname), "info_vip_start") == 0)
   {
      SET_MODEL (pent, "models/player/vip/vip.mdl");
      pent->v.rendermode = kRenderTransAlpha; // set its render mode to transparency
      pent->v.renderamt = 127; // set its transparency amount
      pent->v.effects |= EF_NODRAW;
   }

   else if ((strcmp (STRING (pent->v.classname), "func_vip_safetyzone") == 0)
            || (strcmp (STRING (pent->v.classname), "info_vip_safetyzone") == 0))
      g_iMapType |= MAP_AS; // assassination map

   else if (strcmp (STRING (pent->v.classname), "hostage_entity") == 0)
      g_iMapType |= MAP_CS; // rescue map

   else if ((strcmp (STRING (pent->v.classname), "func_bomb_target") == 0)
            || (strcmp (STRING (pent->v.classname), "info_bomb_target") == 0))
      g_iMapType |= MAP_DE; // defusion map

   RETURN_META_VALUE (MRES_IGNORED, 0);
}


int Spawn_Post (edict_t *pent)
{
   // solves the bots unable to see through certain types of glass bug.
   // MAPPERS: NEVER EVER ALLOW A TRANSPARENT ENTITY TO WEAR THE FL_WORLDBRUSH FLAG !!!

   if (pent->v.rendermode == kRenderTransTexture)
      pent->v.flags &= ~FL_WORLDBRUSH; // clear the FL_WORLDBRUSH flag out of transparent ents

   RETURN_META_VALUE (MRES_IGNORED, 0);
}


BOOL ClientConnect (edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ])
{
   // Client connects to this Server

   char cmd[80];
   int i;

   // check if this client is the listen server client
   if (strcmp (pszAddress, "loopback") == 0)
      pHostEdict = pEntity; // save the edict of the listen server client...

   // check if this is NOT a bot joining the server...
   if (strcmp (pszAddress, "127.0.0.1") != 0)
   {
      // if there are currently more than the minimum number of bots running
      // then kick one of the bots off the server...
      if ((num_bots > min_bots) && (min_bots != -1))
      {
         for (i = 0; i < 32; i++)
         {
            if (bots[i].is_used && !FNullEnt (bots[i].pEdict))  // is this slot used?
            {
               sprintf (cmd, "kick \"%s\"\n", STRING (bots[i].pEdict->v.netname));
               SERVER_COMMAND (cmd); // kick the bot using (kick "name")
               break;
            }
         }
      }
   }

   RETURN_META_VALUE (MRES_IGNORED, 0);
}


void ClientDisconnect (edict_t *pEntity)
{
   // Client disconnects from this Server

   int i, index;
   if (FNullEnt (pEntity))
      RETURN_META (MRES_IGNORED);

   i = ENTINDEX (pEntity) - 1;

   // Find & remove this Client from our list of Clients connected
   clients[i].welcome_time = 0;
   clients[i].wptmessage_time = 0;

   // Check if its a Bot
   if (bots[i].pEdict == pEntity)
   {
      // Delete Nodes from Pathfinding
      DeleteSearchNodes (&bots[i]);
      BotResetTasks (&bots[i]);
      bots[i].is_used = FALSE; // this slot is now free to use
      bots[i].f_kick_time = gpGlobals->time; // save the kicked time
   }
   else if (num_players < max_bots)
   {
      for (index = 0; index < gpGlobals->maxClients; index++)
         if (!BotCreateTab[index].bNeedsCreation)
            break;
      if (index < gpGlobals->maxClients)
      {
         memset (&BotCreateTab[index], 0, sizeof (createbot_t));
         BotCreateTab[index].bot_skill = 101;
         BotCreateTab[index].bot_personality = 5;
         BotCreateTab[index].bot_team = 5;
         BotCreateTab[index].bot_class = 5;
         BotCreateTab[index].bNeedsCreation = TRUE;
         if (botcreation_time == 0.0)
            botcreation_time = gpGlobals->time;
      }
      else
         UTIL_ServerPrint ("Bot creation queue full!\n");
   }

   // Check if its the Host disconnecting
   if (pEntity == pHostEdict)
      pHostEdict = NULL;

   RETURN_META (MRES_IGNORED);
}


void ClientPutInServer (edict_t *pEntity)
{
   // Client is finally put into the Server

   int i;

   if (FNullEnt (pEntity))
      RETURN_META (MRES_IGNORED);

   i = ENTINDEX (pEntity) - 1;

   clients[i].welcome_time = gpGlobals->time + 10.0;
   clients[i].wptmessage_time = gpGlobals->time + 1.0;

   RETURN_META (MRES_IGNORED);
}


void ClientCommand (edict_t *pEntity)
{
   // Executed if a client typed some sort of command into the console

   static char pcmd[128];
   static char arg1[128];
   static char arg2[128];
   static char arg3[128];
   static char arg4[128];
   static char arg5[128];
   static char arg6[128];
   static char arg7[128];
   static char arg8[128];
   static char arg9[128];
   char kick_command[128];
   edict_t *pSpawnPoint = NULL;
   int iClientIndex;
   int iRadioCommand;
   int i;

   if (FNullEnt (pEntity))
      RETURN_META (MRES_IGNORED);

   sprintf (pcmd, CMD_ARGV (0));
   sprintf (arg1, CMD_ARGV (1));
   sprintf (arg2, CMD_ARGV (2));
   sprintf (arg3, CMD_ARGV (3));
   sprintf (arg4, CMD_ARGV (4));
   sprintf (arg5, CMD_ARGV (5));
   sprintf (arg6, CMD_ARGV (6));
   sprintf (arg7, CMD_ARGV (7));
   sprintf (arg8, CMD_ARGV (8));
   sprintf (arg9, CMD_ARGV (9));

   // don't search ClientCommands of Bots or other Edicts than Host!
   if (!isFakeClientCommand && (pEntity == pHostEdict) && !g_bIsDedicatedServer)
   {
      // "pb" Check if any were the Podbot commands..
      if (FStrEq (pcmd, CONSOLE_CMD_POD))
      {
         PODBot_ServerCommand();
         RETURN_META (MRES_SUPERCEDE);
      }

      // "waypoint"
      else if (FStrEq (pcmd, CONSOLE_CMD_WAYPOINT)
               || FStrEq (pcmd, CONSOLE_CMD_WP))
      {
         if (FStrEq (arg1, "stats"))
         {
            int iButtonPoints = 0;
            int iLiftPoints = 0;
            int iCrouchPoints = 0;
            int iCrossingPoints = 0;
            int iGoalPoints = 0;
            int iLadderPoints = 0;
            int iRescuePoints = 0;
            int iCampPoints = 0;
            int iNoHostagePoints = 0;
            int iTerroristPoints = 0;
            int iCounterPoints = 0;
            int iNormalPoints = 0;

            for (i = 0; i < g_iNumWaypoints; i++)
            {
               if (paths[i]->flags & W_FL_USE_BUTTON)
                  iButtonPoints++;
               if (paths[i]->flags & W_FL_LIFT)
                  iLiftPoints++;
               if (paths[i]->flags & W_FL_CROUCH)
                  iCrouchPoints++;
               if (paths[i]->flags & W_FL_CROSSING)
                  iCrossingPoints++;
               if (paths[i]->flags & W_FL_GOAL)
                  iGoalPoints++;
               if (paths[i]->flags & W_FL_LADDER)
                  iLadderPoints++;
               if (paths[i]->flags & W_FL_RESCUE)
                  iRescuePoints++;
               if (paths[i]->flags & W_FL_CAMP)
                  iCampPoints++;
               if (paths[i]->flags & W_FL_NOHOSTAGE)
                  iNoHostagePoints++;
               if (paths[i]->flags & W_FL_TERRORIST)
                  iTerroristPoints++;
               if (paths[i]->flags & W_FL_COUNTER)
                  iCounterPoints++;
               if (paths[i]->flags == 0)
                  iNormalPoints++;
            }

            UTIL_ServerPrint ("Waypoint Statistics:\n");
            UTIL_ServerPrint ("--------------------\n");
            UTIL_ServerPrint ("Waypoints classification per flag:\n");
            UTIL_ServerPrint ("W_FL_USE_BUTTON: %d\n", iButtonPoints);
            UTIL_ServerPrint ("W_FL_LIFT: %d\n", iLiftPoints);
            UTIL_ServerPrint ("W_FL_CROUCH: %d\n", iCrouchPoints);
            UTIL_ServerPrint ("W_FL_CROSSING: %d\n", iCrossingPoints);
            UTIL_ServerPrint ("W_FL_GOAL: %d\n", iGoalPoints);
            UTIL_ServerPrint ("W_FL_LADDER: %d\n", iLadderPoints);
            UTIL_ServerPrint ("W_FL_RESCUE: %d\n", iRescuePoints);
            UTIL_ServerPrint ("W_FL_CAMP: %d\n", iCampPoints);
            UTIL_ServerPrint ("W_FL_NOHOSTAGE: %d\n", iNoHostagePoints);
            UTIL_ServerPrint ("W_FL_TERRORIST: %d\n", iTerroristPoints);
            UTIL_ServerPrint ("W_FL_COUNTER: %d\n", iCounterPoints);
            UTIL_ServerPrint ("Not flagged: %d\n", iNormalPoints);
            UTIL_ServerPrint ("--------------------\n");
            UTIL_ServerPrint ("Total number of waypoints: %d\n", g_iNumWaypoints);
         }
         else if (FStrEq (arg1, "teleport"))
         {
            if ((atoi (arg2) >= 0) && (atoi (arg2) < g_iNumWaypoints))
            {
               TraceResult tr;
               TRACE_HULL (paths[atoi (arg2)]->origin + Vector (0, 0, 32),
                           paths[atoi (arg2)]->origin + Vector (0, 0, -32),
                           ignore_monsters, human_hull, pEntity, &tr);
               SET_ORIGIN (pEntity, tr.vecEndPos);
            }
         }
         else
         {
            UTIL_ServerPrint ("Unknown waypoint command.\n");
            UTIL_ServerPrint ("waypoint commands are: stats teleport\n");
         }

         RETURN_META (MRES_SUPERCEDE);
      }

      // "podbotmenu"
      else if (FStrEq (pcmd, CONSOLE_CMD_PODMENU))
      {
         UTIL_ShowMenu (&menuPODBotMain);
         RETURN_META (MRES_SUPERCEDE);
      }

      // "wpmenu"
      else if (FStrEq (pcmd, CONSOLE_CMD_WPMENU))
      {
         UTIL_ShowMenu (&menuWpMain);
         RETURN_META (MRES_SUPERCEDE);
      }

      // Care for Menus instead...
      else if (pUserMenu != NULL)
      {
         if (FStrEq (pcmd, "menuselect"))
         {
            // Waypoint Main Menu
            if (pUserMenu == &menuWpMain)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1"))
               {
                  g_bWaypointOn = TRUE; // turn waypoints on if off
                  UTIL_ShowMenu (&menuWpAdd);
               }
               else if (FStrEq (arg1, "2"))
               {
                  g_bWaypointOn = TRUE; // turn waypoints on if off
                  UTIL_ShowMenu (&menuWpDelete);
               }
               else if (FStrEq (arg1, "3"))
               {
                  g_bWaypointOn = TRUE; // turn waypoints on if off
                  UTIL_ShowMenu (&menuWpSetRadius);
               }
               else if (FStrEq (arg1, "4"))
               {
                  g_bWaypointOn = TRUE; // turn waypoints on if off
                  UTIL_ShowMenu (&menuWpSetFlags);
               }
               else if (FStrEq (arg1, "5"))
               {
                  g_bWaypointOn = TRUE; // turn waypoints on if off
                  UTIL_ShowMenu (&menuWpAddPath);
               }
               else if (FStrEq (arg1, "6"))
               {
                  g_bWaypointOn = TRUE; // turn waypoints on if off
                  UTIL_ShowMenu (&menuWpDeletePath);
               }
               else if (FStrEq (arg1, "7"))
               {
                  if (WaypointNodesValid ())
                     UTIL_HostPrint ("All Nodes work fine !\n");
               }
               else if (FStrEq (arg1, "8"))
               {
                  if (WaypointNodesValid ())
                  {
                     WaypointSave ();
                     UTIL_HostPrint ("Waypoints saved!\n");
                  }
                  else
                     UTIL_ShowMenu (&menuWpSave);
               }
               else if (FStrEq (arg1, "9"))
                  UTIL_ShowMenu (&menuWpOptions);

               RETURN_META (MRES_SUPERCEDE);
            }

            // Waypoint Add Menu
            else if (pUserMenu == &menuWpAdd)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1"))
                  WaypointAdd (WAYPOINT_ADD_NORMAL); // normal
               else if (FStrEq (arg1, "2"))
                  WaypointAdd (WAYPOINT_ADD_TERRORIST); // t important
               else if (FStrEq (arg1, "3"))
                  WaypointAdd (WAYPOINT_ADD_COUNTER); // ct important
               else if (FStrEq (arg1, "4"))
                  WaypointAdd (WAYPOINT_ADD_LADDER); // ladder
               else if (FStrEq (arg1, "5"))
                  WaypointAdd (WAYPOINT_ADD_RESCUE); // rescue
               else if (FStrEq (arg1, "6"))
                  WaypointAdd (WAYPOINT_ADD_CAMP_START); // camp start
               else if (FStrEq (arg1, "7"))
                  WaypointAdd (WAYPOINT_ADD_CAMP_END); // camp end
               else if (FStrEq (arg1, "8"))
                  WaypointAdd (WAYPOINT_ADD_GOAL); // goal
               else if (FStrEq (arg1, "9"))
               {
                  g_bLearnJumpWaypoint = TRUE;
                  UTIL_HostPrint ("Observation on !\n");
                  if (g_bUseSpeech)
                     SERVER_COMMAND ("speak \"movement check ok\"\n");
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Waypoint Delete Menu
            else if (pUserMenu == &menuWpDelete)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1"))
                  WaypointDelete ();

               RETURN_META (MRES_SUPERCEDE);
            }

            // Waypoint SetRadius Menu
            else if (pUserMenu == &menuWpSetRadius)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1"))
                  WaypointChangeRadius (0);
               else if (FStrEq (arg1, "2"))
                  WaypointChangeRadius (8);
               else if (FStrEq (arg1, "3"))
                  WaypointChangeRadius (16);
               else if (FStrEq (arg1, "4"))
                  WaypointChangeRadius (32);
               else if (FStrEq (arg1, "5"))
                  WaypointChangeRadius (48);
               else if (FStrEq (arg1, "6"))
                  WaypointChangeRadius (64);
               else if (FStrEq (arg1, "7"))
                  WaypointChangeRadius (80);
               else if (FStrEq (arg1, "8"))
                  WaypointChangeRadius (96);
               else if (FStrEq (arg1, "9"))
                  WaypointChangeRadius (112);

               RETURN_META (MRES_SUPERCEDE);
            }

            // Waypoint SetFlags Menu
            else if (pUserMenu == &menuWpSetFlags)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1"))
                  WaypointChangeFlag (W_FL_USE_BUTTON, FLAG_TOGGLE);
               else if (FStrEq (arg1, "2"))
                  WaypointChangeFlag (W_FL_LIFT, FLAG_TOGGLE);
               else if (FStrEq (arg1, "3"))
                  WaypointChangeFlag (W_FL_CROUCH, FLAG_TOGGLE);
               else if (FStrEq (arg1, "4"))
                  WaypointChangeFlag (W_FL_GOAL, FLAG_TOGGLE);
               else if (FStrEq (arg1, "5"))
                  WaypointChangeFlag (W_FL_LADDER, FLAG_TOGGLE);
               else if (FStrEq (arg1, "6"))
                  WaypointChangeFlag (W_FL_RESCUE, FLAG_TOGGLE);
               else if (FStrEq (arg1, "7"))
                  WaypointChangeFlag (W_FL_CAMP, FLAG_TOGGLE);
               else if (FStrEq (arg1, "8"))
                  WaypointChangeFlag (W_FL_NOHOSTAGE, FLAG_TOGGLE);
               else if (FStrEq (arg1, "9"))
                  UTIL_ShowMenu (&menuWpSetTeam);

               RETURN_META (MRES_SUPERCEDE);
            }

            // Waypoint Set Team Menu
            else if (pUserMenu == &menuWpSetTeam)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1"))
               {
                  WaypointChangeFlag (W_FL_TERRORIST, FLAG_SET);
                  WaypointChangeFlag (W_FL_COUNTER, FLAG_CLEAR);
               }
               else if (FStrEq (arg1, "2"))
               {
                  WaypointChangeFlag (W_FL_TERRORIST, FLAG_CLEAR);
                  WaypointChangeFlag (W_FL_COUNTER, FLAG_SET);
               }
               else if (FStrEq (arg1, "3"))
               {
                  WaypointChangeFlag (W_FL_TERRORIST, FLAG_CLEAR);
                  WaypointChangeFlag (W_FL_COUNTER, FLAG_CLEAR);
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Waypoint Add Path Menu
            else if (pUserMenu == &menuWpAddPath)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1"))
                  WaypointCreatePath (PATH_OUTGOING);
               else if (FStrEq (arg1, "2"))
                  WaypointCreatePath (PATH_INCOMING);
               else if (FStrEq (arg1, "3"))
                  WaypointCreatePath (PATH_BOTHWAYS);

               RETURN_META (MRES_SUPERCEDE);
            }

            // Waypoint Delete Path Menu
            else if (pUserMenu == &menuWpDeletePath)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1"))
                  WaypointDeletePath ();

               RETURN_META (MRES_SUPERCEDE);
            }

            // Waypoint Save Menu
            else if (pUserMenu == &menuWpSave)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1"))
               {
                  WaypointSave ();
                  UTIL_HostPrint ("WARNING: Waypoints saved with errors!\n");
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Waypoint Options Menu
            else if (pUserMenu == &menuWpOptions)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1")) // wp on/off
               {
                  g_bWaypointOn ^= TRUE; // switch variable on/off (XOR it)

                  if (g_bWaypointOn)
                  {
                     UTIL_HostPrint ("Waypoints Editing is ON\n");

                     while (!FNullEnt (pSpawnPoint = FIND_ENTITY_BY_STRING (pSpawnPoint, "classname", "info_player_start")))
                        pSpawnPoint->v.effects &= ~EF_NODRAW;
                     while (!FNullEnt (pSpawnPoint = FIND_ENTITY_BY_STRING (pSpawnPoint, "classname", "info_player_deathmatch")))
                        pSpawnPoint->v.effects &= ~EF_NODRAW;
                     while (!FNullEnt (pSpawnPoint = FIND_ENTITY_BY_STRING (pSpawnPoint, "classname", "info_vip_start")))
                        pSpawnPoint->v.effects &= ~EF_NODRAW;
                  }
                  else
                  {
                     UTIL_HostPrint ("Waypoint Editing turned OFF\n");

                     while (!FNullEnt (pSpawnPoint = FIND_ENTITY_BY_STRING (pSpawnPoint, "classname", "info_player_start")))
                        pSpawnPoint->v.effects |= EF_NODRAW;
                     while (!FNullEnt (pSpawnPoint = FIND_ENTITY_BY_STRING (pSpawnPoint, "classname", "info_player_deathmatch")))
                        pSpawnPoint->v.effects |= EF_NODRAW;
                     while (!FNullEnt (pSpawnPoint = FIND_ENTITY_BY_STRING (pSpawnPoint, "classname", "info_vip_start")))
                        pSpawnPoint->v.effects |= EF_NODRAW;

                     if (g_bWaypointsChanged && g_bWaypointsSaved)
                     {
                        UTIL_HostPrint ("The map will restart in 5 seconds!\n");
                        g_fTimeRestartServer = gpGlobals->time + 4.0;
                     }
                     else if (g_bWaypointsChanged)
                        UTIL_HostPrint ("Don't forget to SAVE your waypoints...\n");
                  }
               }
               else if (FStrEq (arg1, "2"))
               {
                  g_bAutoWaypoint ^= TRUE; // Switch Variable on/off (XOR it)
                  UTIL_HostPrint ("Auto-Waypointing is %s\n", (g_bAutoWaypoint ? "ENABLED" : "DISABLED"));
               }
               else if (FStrEq (arg1, "3"))
               {
                  g_bEditNoclip ^= TRUE; // Switch Variable on/off (XOR it)
                  if (g_bEditNoclip)
                     pHostEdict->v.movetype = MOVETYPE_NOCLIP;
                  else
                     pHostEdict->v.movetype = MOVETYPE_WALK;
                  UTIL_HostPrint ("No Clipping Cheat is %s\n", (g_bEditNoclip ? "ENABLED" : "DISABLED"));
               }
               else if (FStrEq (arg1, "4"))
               {
                  g_bIgnoreEnemies ^= TRUE; // Switch Variable on/off (XOR it)
                  UTIL_HostPrint ("Peace Mode is %s (Bots %signore Enemies)\n", (g_bIgnoreEnemies ? "ENABLED" : "DISABLED"), (g_bIgnoreEnemies ? "" : "DON'T "));
               }
               else if (FStrEq (arg1, "5"))
               {
                  g_bShowWpFlags ^= TRUE; // Switch Variable on/off (XOR it)
                  UTIL_HostPrint ("Waypoint Flag display is %s\n", (g_bShowWpFlags ? "ENABLED" : "DISABLED"));
               }
               else if (FStrEq (arg1, "6"))
                  UTIL_ShowMenu (&menuWpAutoPathMaxDistance);
               else if (FStrEq (arg1, "7"))
                  WaypointCache ();

               RETURN_META (MRES_SUPERCEDE);
            }

            // Waypoint AutoPathMaxDistance Menu 
            else if (pUserMenu == &menuWpAutoPathMaxDistance)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               UTIL_DisplayWpMenuWelcomeMessage ();

               if (FStrEq (arg1, "1"))
               {
                  g_fAutoPathMaxDistance = 0;
                  UTIL_HostPrint ("Auto-path disabled\n");
               }
               else if (FStrEq (arg1, "2"))
               {
                  g_fAutoPathMaxDistance = 100;
                  UTIL_HostPrint ("Auto-path Max Distance set to 100\n");
               }
               else if (FStrEq (arg1, "3"))
               {
                  g_fAutoPathMaxDistance = 130;
                  UTIL_HostPrint ("Auto-path Max Distance set to 130\n");
               }
               else if (FStrEq (arg1, "4"))
               {
                  g_fAutoPathMaxDistance = 160;
                  UTIL_HostPrint ("Auto-path Max Distance set to 160\n");
               }
               else if (FStrEq (arg1, "5"))
               {
                  g_fAutoPathMaxDistance = 190;
                  UTIL_HostPrint ("Auto-path Max Distance set to 190\n");
               }
               else if (FStrEq (arg1, "6"))
               {
                  g_fAutoPathMaxDistance = 220;
                  UTIL_HostPrint ("Auto-path Max Distance set to 220\n");
               }
               else if (FStrEq (arg1, "7"))
               {
                  g_fAutoPathMaxDistance = 250;
                  UTIL_HostPrint ("Auto-path Max Distance set to 250\n");
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Main PODBot Menu ?
            else if (pUserMenu == &menuPODBotMain)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if (FStrEq (arg1, "1"))
               {
                  for (i = 0; i < gpGlobals->maxClients; i++)
                     if (!BotCreateTab[i].bNeedsCreation)
                        break;
                  if (i < gpGlobals->maxClients)
                  {
                     memset (&BotCreateTab[i], 0, sizeof (createbot_t));
                     BotCreateTab[i].bot_skill = 101;
                     BotCreateTab[i].bot_personality = 5;
                     BotCreateTab[i].bot_team = 5;
                     BotCreateTab[i].bot_class = 5;
                     BotCreateTab[i].bNeedsCreation = TRUE;
                     botcreation_time = gpGlobals->time;
                  }
                  else
                     UTIL_HostPrint ("Bot creation queue full!\n");
               }
               else if (FStrEq (arg1, "2"))
                  UTIL_ShowMenu (&menuPODBotAddBotSkill);
               else if (FStrEq (arg1, "3"))
                  UserKillAllBots ();
               else if (FStrEq (arg1, "4"))
                  UserNewroundAll ();
               else if (FStrEq (arg1, "5"))
                  UTIL_ShowMenu (&menuPODBotFillServerSkill);
               else if (FStrEq (arg1, "6"))
                  UTIL_ShowMenu (&menuPODBotKickBotTeam);
               else if (FStrEq (arg1, "7"))
                  UserRemoveAllBots ();
               else if (FStrEq (arg1, "8"))
                  UTIL_ShowMenu (&menuPODBotWeaponMode);

               RETURN_META (MRES_SUPERCEDE);
            }

            // Add Bot Skill menu ?
            else if (pUserMenu == &menuPODBotAddBotSkill)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if (FStrEq (arg1, "1"))
               {
                  iStoreAddbotSkill = RANDOM_LONG (0, 19);
                  UTIL_ShowMenu (&menuPODBotAddBotPersonality);
               }
               else if (FStrEq (arg1, "2"))
               {
                  iStoreAddbotSkill = RANDOM_LONG (20, 39);
                  UTIL_ShowMenu (&menuPODBotAddBotPersonality);
               }
               else if (FStrEq (arg1, "3"))
               {
                  iStoreAddbotSkill = RANDOM_LONG (40, 59);
                  UTIL_ShowMenu (&menuPODBotAddBotPersonality);
               }
               else if (FStrEq (arg1, "4"))
               {
                  iStoreAddbotSkill = RANDOM_LONG (60, 79);
                  UTIL_ShowMenu (&menuPODBotAddBotPersonality);
               }
               else if (FStrEq (arg1, "5"))
               {
                  iStoreAddbotSkill = RANDOM_LONG (80, 99);
                  UTIL_ShowMenu (&menuPODBotAddBotPersonality);
               }
               else if (FStrEq (arg1, "6"))
               {
                  iStoreAddbotSkill = 100;
                  UTIL_ShowMenu (&menuPODBotAddBotPersonality);
               }
               else if (FStrEq (arg1, "7"))
               {
                  iStoreAddbotSkill = 101;
                  UTIL_ShowMenu (&menuPODBotAddBotPersonality);
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Add Bot Personality menu ?
            else if (pUserMenu == &menuPODBotAddBotPersonality)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if (FStrEq (arg1, "1"))
               {
                  iStoreAddbotPersonality = 0;
                  UTIL_ShowMenu (&menuPODBotAddBotTeam);
               }
               else if (FStrEq (arg1, "2"))
               {
                  iStoreAddbotPersonality = 1;
                  UTIL_ShowMenu (&menuPODBotAddBotTeam);
               }
               else if (FStrEq (arg1, "3"))
               {
                  iStoreAddbotPersonality = 2;
                  UTIL_ShowMenu (&menuPODBotAddBotTeam);
               }
               else if (FStrEq (arg1, "5"))
               {
                  iStoreAddbotPersonality = 5;
                  UTIL_ShowMenu (&menuPODBotAddBotTeam);
               }


               RETURN_META (MRES_SUPERCEDE);
            }

            // Add Bot Team Select Menu ?
            else if (pUserMenu == &menuPODBotAddBotTeam)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if (FStrEq (arg1, "1"))
               {
                  iStoreAddbotTeam = 1;
                  UTIL_ShowMenu (&menuPODBotAddBotTModel);
               }
               else if (FStrEq (arg1, "2"))
               {
                  iStoreAddbotTeam = 2;
                  UTIL_ShowMenu (&menuPODBotAddBotCTModel);
               }
               else if (FStrEq (arg1, "5"))
               {
                  iStoreAddbotTeam = 5;
                  for (i = 0; i < gpGlobals->maxClients; i++)
                     if (!BotCreateTab[i].bNeedsCreation)
                        break;
                  if (i < gpGlobals->maxClients)
                  {
                     memset (&BotCreateTab[i], 0, sizeof (createbot_t));
                     BotCreateTab[i].bNeedsCreation = TRUE;
                     BotCreateTab[i].bot_skill = iStoreAddbotSkill;
                     BotCreateTab[i].bot_personality = iStoreAddbotPersonality;
                     BotCreateTab[i].bot_team = iStoreAddbotTeam;
                     BotCreateTab[i].bot_class = 5;
                  }
                  else
                     UTIL_HostPrint ("Bot creation queue full!\n");

                  if (botcreation_time == 0.0)
                     botcreation_time = gpGlobals->time;
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Add Bot Model Selection Menu ?
            else if ((pUserMenu == &menuPODBotAddBotTModel)
                     || (pUserMenu == &menuPODBotAddBotCTModel))
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if ((atoi (arg1) >= 1) && (atoi (arg1) <= 5))
               {
                  for (i = 0; i < gpGlobals->maxClients; i++)
                     if (!BotCreateTab[i].bNeedsCreation)
                        break;
                  if (i < gpGlobals->maxClients)
                  {
                     memset (&BotCreateTab[i], 0, sizeof (createbot_t));
                     BotCreateTab[i].bNeedsCreation = TRUE;
                     BotCreateTab[i].bot_skill = iStoreAddbotSkill;
                     BotCreateTab[i].bot_personality = iStoreAddbotPersonality;
                     BotCreateTab[i].bot_team = iStoreAddbotTeam;
                     BotCreateTab[i].bot_class = atoi (arg1);
                  }
                  else
                     UTIL_HostPrint ("Bot creation queue full!\n");

                  if (botcreation_time == 0.0)
                     botcreation_time = gpGlobals->time;
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Fill Server Bot Skill menu ?
            else if (pUserMenu == &menuPODBotFillServerSkill)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if (FStrEq (arg1, "1"))
               {
                  iStoreFillServerSkill = RANDOM_LONG (0, 19);
                  UTIL_ShowMenu (&menuPODBotFillServerPersonality);
               }
               else if (FStrEq (arg1, "2"))
               {
                  iStoreFillServerSkill = RANDOM_LONG (20, 39);
                  UTIL_ShowMenu (&menuPODBotFillServerPersonality);
               }
               else if (FStrEq (arg1, "3"))
               {
                  iStoreFillServerSkill = RANDOM_LONG (40, 59);
                  UTIL_ShowMenu (&menuPODBotFillServerPersonality);
               }
               else if (FStrEq (arg1, "4"))
               {
                  iStoreFillServerSkill = RANDOM_LONG (60, 79);
                  UTIL_ShowMenu (&menuPODBotFillServerPersonality);
               }
               else if (FStrEq (arg1, "5"))
               {
                  iStoreFillServerSkill = RANDOM_LONG (80, 99);
                  UTIL_ShowMenu (&menuPODBotFillServerPersonality);
               }
               else if (FStrEq (arg1, "6"))
               {
                  iStoreFillServerSkill = 100;
                  UTIL_ShowMenu (&menuPODBotFillServerPersonality);
               }
               else if (FStrEq (arg1, "7"))
               {
                  iStoreFillServerSkill = 101;
                  UTIL_ShowMenu (&menuPODBotFillServerPersonality);
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Fill Server Bot Personality menu ?
            else if (pUserMenu == &menuPODBotFillServerPersonality)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if (FStrEq (arg1, "1"))
               {
                  iStoreFillServerPersonality = 0;
                  UTIL_ShowMenu (&menuPODBotFillServerTeam);
               }
               else if (FStrEq (arg1, "2"))
               {
                  iStoreFillServerPersonality = 1;
                  UTIL_ShowMenu (&menuPODBotFillServerTeam);
               }
               else if (FStrEq (arg1, "3"))
               {
                  iStoreFillServerPersonality = 2;
                  UTIL_ShowMenu (&menuPODBotFillServerTeam);
               }
               else if (FStrEq (arg1, "5"))
               {
                  iStoreFillServerPersonality = 5;
                  UTIL_ShowMenu (&menuPODBotFillServerTeam);
               }


               RETURN_META (MRES_SUPERCEDE);
            }

            // Fill Server Team Menu Select ?
            else if (pUserMenu == &menuPODBotFillServerTeam)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if (FStrEq (arg1, "1"))
               {
                  iStoreFillServerTeam = 1;
                  UTIL_ShowMenu (&menuPODBotFillServerTModel);
               }
               else if (FStrEq (arg1, "2"))
               {
                  iStoreFillServerTeam = 2;
                  UTIL_ShowMenu (&menuPODBotFillServerCTModel);
               }
               else if (FStrEq (arg1, "5"))
               {
                  for (i = 0; i < gpGlobals->maxClients; i++)
                     if (!BotCreateTab[i].bNeedsCreation)
                     {
                        memset (&BotCreateTab[i], 0, sizeof (createbot_t));
                        BotCreateTab[i].bNeedsCreation = TRUE;
                        BotCreateTab[i].bot_skill = iStoreFillServerSkill;
                        BotCreateTab[i].bot_personality = iStoreFillServerPersonality;
                        BotCreateTab[i].bot_team = atoi (arg1);
                        BotCreateTab[i].bot_class = 5;
                     }

                  if (botcreation_time == 0.0)
                     botcreation_time = gpGlobals->time;
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Fill Server Model Menu Select ?
            else if ((pUserMenu == &menuPODBotFillServerTModel)
                     || (pUserMenu == &menuPODBotFillServerCTModel))
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if ((atoi (arg1) >= 1) && (atoi (arg1) <= 5))
               {
                  CVAR_SET_STRING ("mp_limitteams", "0");
                  CVAR_SET_STRING ("mp_autoteambalance", "0");

                  for (i = 0; i < gpGlobals->maxClients; i++)
                     if (!BotCreateTab[i].bNeedsCreation)
                     {
                        memset (&BotCreateTab[i], 0, sizeof (createbot_t));
                        BotCreateTab[i].bNeedsCreation = TRUE;
                        BotCreateTab[i].bot_skill = iStoreFillServerSkill;
                        BotCreateTab[i].bot_personality = iStoreFillServerPersonality;
                        BotCreateTab[i].bot_team = iStoreFillServerTeam;
                        BotCreateTab[i].bot_class = atoi (arg1);
                     }

                  if (botcreation_time == 0.0)
                     botcreation_time = gpGlobals->time;
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Kick Bot Team Select Menu ?
            else if (pUserMenu == &menuPODBotKickBotTeam)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if (FStrEq (arg1, "1"))
               {
                  for (i = 0; i < gpGlobals->maxClients; i++)
                     if (bots[i].is_used && !FNullEnt (bots[i].pEdict) && (bots[i].bot_team == TEAM_CS_TERRORIST))  // is this slot used? Terrorist?
                     {
                        sprintf (kick_command, "kick \"%s\"\n", STRING (bots[i].pEdict->v.netname));
                        SERVER_COMMAND (kick_command); // kick the bot using (kick "name")
                        break;
                     }
               }
               else if (FStrEq (arg1, "2"))
               {
                  for (i = 0; i < gpGlobals->maxClients; i++)
                     if (bots[i].is_used && !FNullEnt (bots[i].pEdict) && (bots[i].bot_team == TEAM_CS_COUNTER))  // is this slot used? Counter-Terrorist?
                     {
                        sprintf (kick_command, "kick \"%s\"\n", STRING (bots[i].pEdict->v.netname));
                        SERVER_COMMAND (kick_command); // kick the bot using (kick "name")
                        break;
                     }
               }
               else if (FStrEq (arg1, "5"))
               {
                  for (i = 0; i < gpGlobals->maxClients; i++)
                     if (bots[i].is_used && !FNullEnt (bots[i].pEdict))  // is this slot used?
                     {
                        sprintf (kick_command, "kick \"%s\"\n", STRING (bots[i].pEdict->v.netname));
                        SERVER_COMMAND (kick_command); // kick the bot using (kick "name")
                        break;
                     }
               }

               RETURN_META (MRES_SUPERCEDE);
            }

            // Weapon Mode Selection Menu ?
            else if (pUserMenu == &menuPODBotWeaponMode)
            {
               UTIL_ShowMenu (NULL); // reset menu display

               if ((atoi (arg1) >= 1) && (atoi (arg1) <= 7))
                  UserSelectWeaponMode (atoi (arg1));

               RETURN_META (MRES_SUPERCEDE);
            }
         }

         RETURN_META (MRES_SUPERCEDE);
      }
   }

   // Check Radio Commands
   iClientIndex = ENTINDEX (pEntity) - 1;

   if ((iRadioSelect[iClientIndex] != 0) && FStrEq (pcmd, "menuselect"))
   {
      iRadioCommand = atoi (arg1);

      if (iRadioCommand != 0)
      {
         iRadioCommand += 10 * (iRadioSelect[iClientIndex] - 1);

         if ((iRadioCommand != RADIO_AFFIRMATIVE)
             && (iRadioCommand != RADIO_NEGATIVE)
             && (iRadioCommand != RADIO_REPORTINGIN))
         {
            for (i = 0; i < gpGlobals->maxClients; i++)
            {
               if (bots[i].is_used && (bots[i].bot_team == clients[iClientIndex].iTeam)
                   && (pEntity != bots[i].pEdict))
               {
                  if (bots[i].iRadioOrder == 0)
                  {
                     bots[i].iRadioOrder = iRadioCommand;
                     bots[i].pRadioEntity = pEntity;
                  }
               }
            }
         }

         g_rgfLastRadioTime[clients[iClientIndex].iTeam - 1] = gpGlobals->time;
      }

      iRadioSelect[iClientIndex] = 0;
   }

   else if (strncmp (pcmd, "radio", 5) == 0)
      iRadioSelect[iClientIndex] = atoi (pcmd + 5);
   // End Radio Commands

   RETURN_META (MRES_IGNORED);
}


void ServerActivate (edict_t *pEdictList, int edictCount, int clientMax)
{
   char line_buffer[256];
   int i, c;
   int iChatType;
   replynode_t *pTempReply = NULL;
   replynode_t **pReply;
   char arg0[80];
   char arg1[80];
   int *ptrWeaponPrefs;
   int iParseWeapons;
   int iWeaponPrefsType;
   char *pszStart;
   char *pszEnd;
   STRINGNODE **pNode;
   STRINGNODE *pTempNode;

   // Load & Initialise Botnames from 'Botnames.txt'
   fp = fopen ("cstrike/addons/podbot/botnames.txt", "r");
   if (fp == NULL)
      UTIL_ServerPrint ("POD-Bot couldn't find botnames.txt!\n");
   else
   {
      memset (szBotNames, 0, sizeof (szBotNames));
      iNumBotNames = 0;

      while (fgets (line_buffer, 256, fp) != NULL)
      {
         if ((line_buffer[0] == '#') || (line_buffer[0] == 0) || (line_buffer[0] == '\r') || (line_buffer[0] == '\n'))
            continue; // ignore comments or blank lines

         i = strlen (line_buffer);
         while ((line_buffer[i - 1] == '\n' || line_buffer[i - 1] == ' '
            || line_buffer[i - 1] == '\r' || line_buffer[i - 1] == '\t') && (i > 0))
            line_buffer[--i] = 0;

         strcpy (szBotNames[iNumBotNames], line_buffer);
         iNumBotNames++;
      }

      fclose (fp);
   }
   // End Botnames

   // Load & Initialise Botchats from 'Botchat.txt'
   fp = fopen ("cstrike/addons/podbot/botchat.txt", "r");
   if (fp == NULL)
      UTIL_ServerPrint ("POD-Bot couldn't find botchat.txt!\n");
   else
   {
      memset (szKillChat, 0, sizeof (szKillChat));
      memset (szBombChat, 0, sizeof (szKillChat));
      memset (szDeadChat, 0, sizeof (szKillChat));
      memset (szNoKwChat, 0, sizeof (szKillChat));
      iNumKillChats = 0;
      iNumBombChats = 0;
      iNumDeadChats = 0;
      iNumNoKwChats = 0;

      iChatType = -1;

      while (fgets (line_buffer, 256, fp) != NULL)
      {
         if ((line_buffer[0] == '#') || (line_buffer[0] == 0) || (line_buffer[0] == '\r') || (line_buffer[0] == '\n'))
            continue; // ignore comments or blank lines

         strcpy (arg0, GetField (line_buffer, 0));

         // Killed Chat Section ?
         if (FStrEq (arg0, "[KILLED]"))
         {
            iChatType = 0;
            continue;
         }

         // Bomb Chat Section ?
         else if (FStrEq (arg0, "[BOMBPLANT]"))
         {
            iChatType = 1;
            continue;
         }

         // Dead Chat Section ?
         else if (FStrEq (arg0, "[DEADCHAT]"))
         {
            iChatType = 2;
            continue;
         }

         // Keyword Chat Section ?
         else if (FStrEq (arg0, "[REPLIES]"))
         {
            iChatType = 3;
            pReply = &pChatReplies;
            continue;
         }

         // Unknown Keyword Section ?
         else if (FStrEq (arg0, "[UNKNOWN]"))
         {
            iChatType = 4;
            continue;
         }

         if (iChatType == 0)
         {
//            strcat (line_buffer, "\n");
            line_buffer[79] = 0;

            strcpy (szKillChat[iNumKillChats], line_buffer);
            iNumKillChats++;
         }

         else if (iChatType == 1)
         {
//            strcat (line_buffer, "\n");
            line_buffer[79] = 0;

            strcpy (szBombChat[iNumBombChats], line_buffer);
            iNumBombChats++;
         }

         else if (iChatType == 2)
         {
//            strcat (line_buffer, "\n");
            line_buffer[79] = 0;

            strcpy (szDeadChat[iNumDeadChats], line_buffer);
            iNumDeadChats++;
         }

         else if (iChatType == 3)
         {
            if (strstr (line_buffer, "@KEY") != NULL)
            {
               /*if (pTempReply != NULL)
                  delete (pTempReply);*/
               pTempReply = new (replynode_t);
               *pReply = pTempReply;
               pTempReply->pNextReplyNode = NULL;
               pTempReply->pReplies = NULL;
               pTempReply->cNumReplies = 0;
               pTempReply->cLastReply = 0;
               pNode = &pTempReply->pReplies;
               pTempNode = NULL;
               memset (pTempReply->szKeywords, 0, sizeof (pTempReply->szKeywords));

               c = 0;

               for (i = 0; i < 256; i++)
               {
                  if (line_buffer[i] == '\"')
                  {
                     i++;
                     while (line_buffer[i] != '\"')
                        pTempReply->szKeywords[c++] = line_buffer[i++];
                     pTempReply->szKeywords[c++] = '@';
                  }
                  else if (line_buffer[i] == 0)
                     break;
               }
               pReply = &pTempReply->pNextReplyNode;
            }
            else if (pTempReply)
            {
//               strcat (line_buffer, "\n");
               line_buffer[255] = 0;

               /*if (pTempNode != NULL)
                  delete (pTempNode);*/
               pTempNode = new (STRINGNODE);
               if (pTempNode == NULL)
                  UTIL_ServerPrint ("POD-Bot out of Memory!\n");
               else
               {
                  *pNode = pTempNode;
                  pTempNode->Next = NULL;
                  strcpy (pTempNode->szString, line_buffer);

                  pTempReply->cNumReplies++;
                  pNode = &pTempNode->Next;
               }
            }
         }

         else if (iChatType == 4)
         {
//            strcat (line_buffer, "\n");
            line_buffer[79] = 0;

            strcpy (szNoKwChat[iNumNoKwChats], line_buffer);
            iNumNoKwChats++;
         }
      }

      fclose (fp);
   }
   // End Botchats

   // Load & Initialise Botskill.cfg
   fp = fopen ("cstrike/addons/podbot/botskill.cfg", "r");
   if (fp == NULL)
      UTIL_ServerPrint ("No Botskill.cfg ! Using defaults...\n");
   else
   {
      i = 0;

      while (fgets (line_buffer, 256, fp) != NULL)
      {
         if ((line_buffer[0] == '#') || (line_buffer[0] == 0) || (line_buffer[0] == '\r') || (line_buffer[0] == '\n'))
            continue; // ignore comments or blank lines

         strcpy (arg0, GetField (line_buffer, 0));
         strcpy (arg1, GetField (line_buffer, 1));

         if (FStrEq (arg0, "MIN_DELAY"))
            BotSkillDelays[i].fMinSurpriseDelay = (float) atof (arg1);
         else if (FStrEq (arg0, "MAX_DELAY"))
            BotSkillDelays[i].fMaxSurpriseDelay = (float) atof (arg1);
         else if (FStrEq (arg0, "MIN_TURNSPEED"))
            BotTurnSpeeds[i].fMinTurnSpeed = (float) atof (arg1);
         else if (FStrEq (arg0, "MAX_TURNSPEED"))
            BotTurnSpeeds[i].fMaxTurnSpeed = (float) atof (arg1);
         else if (FStrEq (arg0, "AIM_OFFS_X"))
            BotAimTab[i].fAim_X = (float) atof (arg1);
         else if (FStrEq (arg0, "AIM_OFFS_Y"))
            BotAimTab[i].fAim_Y = (float) atof (arg1);
         else if (FStrEq (arg0, "AIM_OFFS_Z"))
            BotAimTab[i].fAim_Z = (float) atof (arg1);
         else if (FStrEq (arg0, "HEADSHOT_ALLOW"))
            BotAimTab[i].iHeadShot_Frequency = atoi (arg1);
         else if (FStrEq (arg0, "HEAR_SHOOTTHRU"))
            BotAimTab[i].iHeardShootThruProb = atoi (arg1);
         else if (FStrEq (arg0, "SEEN_SHOOTTHRU"))
         {
            BotAimTab[i].iSeenShootThruProb = atoi (arg1);

            if (i < 5)
               i++; // Prevent Overflow if Errors in cfg
         }
      }

      fclose (fp);
   }
   // End Botskill.cgf

   // Load & Initialise BotLogos from BotLogos.cfg
   fp = fopen ("cstrike/addons/podbot/botlogos.cfg", "r");
   if (fp == NULL)
      UTIL_ServerPrint ("No BotLogos.cfg ! Using Defaults...\n");
   else
   {
      g_iNumLogos = 0;

      while (fgets (line_buffer, 256, fp) != NULL)
      {
         if ((line_buffer[0] == '#') || (line_buffer[0] == 0) || (line_buffer[0] == '\r') || (line_buffer[0] == '\n'))
            continue; // ignore comments or blank lines

         strcpy (szSprayNames[g_iNumLogos], GetField (line_buffer, 0));
         g_iNumLogos++;
      }

      fclose (fp);
   }
   // End BotLogos

   // Load & initialise Weapon Stuff from 'BotWeapons.cfg'
   fp = fopen ("cstrike/addons/podbot/botweapons.cfg", "r");
   if (fp == NULL)
      UTIL_ServerPrint ("No BotWeapons.cfg ! Using Defaults...\n");
   else
   {
      iParseWeapons = 0;
      ptrWeaponPrefs = NULL;

      while (fgets (line_buffer, 256, fp) != NULL)
      {
         if ((line_buffer[0] == '#') || (line_buffer[0] == 0) || (line_buffer[0] == '\r') || (line_buffer[0] == '\n'))
            continue; // ignore comments or blank lines

         strcpy (arg0, GetField (line_buffer, 0));

         if (iParseWeapons < 2)
         {
            if (FStrEq (arg0, "[STANDARD]"))
               iWeaponPrefsType = MAP_DE;
            else if (FStrEq (arg0, "[AS]"))
               iWeaponPrefsType = MAP_AS;
            else
            {
               pszStart = &line_buffer[0];
               pszEnd = NULL;

               if (iWeaponPrefsType == MAP_DE)
               {
                  for (i = 0; i < NUM_WEAPONS; i++)
                  {
                     pszEnd = strchr (pszStart, ',');
                     cs_weapon_select[i].iTeamStandard = atoi (pszStart);
                     pszStart = pszEnd + 1;
                  }
               }
               else
               {
                  for (i = 0; i < NUM_WEAPONS; i++)
                  {
                     pszEnd = strchr (pszStart, ',');
                     cs_weapon_select[i].iTeamAS = atoi (pszStart);
                     pszStart = pszEnd + 1;
                  }
               }

               iParseWeapons++;
            }
         }
         else
         {
            if (FStrEq (arg0, "[NORMAL]"))
               ptrWeaponPrefs = &NormalWeaponPrefs[0];
            else if (FStrEq (arg0, "[AGRESSIVE]"))
               ptrWeaponPrefs = &AgressiveWeaponPrefs[0];
            else if (FStrEq (arg0, "[DEFENSIVE]"))
               ptrWeaponPrefs = &DefensiveWeaponPrefs[0];
            else
            {
               pszStart = &line_buffer[0];

               for (i = 0; i < NUM_WEAPONS; i++)
               {
                  pszEnd = strchr (pszStart, ',');
                  *ptrWeaponPrefs++ = atoi (pszStart);
                  pszStart = pszEnd + 1;
               }
            }
         }
      }

      fclose (fp);
   }

   // Load the Waypoints for this Map
   WaypointLoad ();
   InitVisTab ();
   InitExperienceTab ();

   // Initialise the Client Struct for welcoming and keeping track who's connected
   memset (clients, 0, sizeof (clients));

   // Initialize the bots array of structures
   memset (bots, 0, sizeof (bots));

   // Initialize TheFatal's method for calculating the msec value
   msecnum = 0;
   msecdel = 0;
   msecval = 0;

   // open the bot config file
   file_opened = FALSE;
   g_GameRules = TRUE;

   // a new map has started...
   botcreation_time = gpGlobals->time + 5.0;

   // set the respawn time
   g_fTimeRoundStart = gpGlobals->time + CVAR_GET_FLOAT ("mp_freezetime");

   RETURN_META (MRES_IGNORED);
}


void ServerDeactivate (void)
{
   int index;
   int tab_index;

   tab_index = 0;
   for (index = 0; index < gpGlobals->maxClients; index++)
      if (bots[index].is_used)
      {
         BotCreateTab[tab_index].bNeedsCreation = TRUE;
         strcpy (BotCreateTab[tab_index].bot_name, bots[index].name);
         BotCreateTab[tab_index].bot_skill = bots[index].bot_skill;
         BotCreateTab[tab_index].bot_personality = bots[index].bot_personality;
         BotCreateTab[tab_index].bot_team = bots[index].bot_team;
         BotCreateTab[tab_index].bot_class = bots[index].bot_class;
         tab_index++;
      }

   // Save collected Experience on Shutdown
   SaveExperienceTab ();
   SaveVisTab ();

   // Free everything that's freeable
   BotFreeAllMemory ();

   RETURN_META (MRES_IGNORED);
}


// Called each Server frame at the very beginning
void StartFrame (void)
{
   edict_t *pPlayer;
   static int i, player_index, bot_index;
   static FILE *bot_cfg_fp = NULL;
   static char cmd_line[1024];
   static char cmd[128];
   static char arg1[128];
   static char arg2[128];
   static char arg3[128];
   static char arg4[128];
   static char arg5[128];
   static float pause_time;

   // Should the Map restart now ?
   if ((g_fTimeRestartServer > 0) && (g_fTimeRestartServer < gpGlobals->time))
   {
      g_fTimeRestartServer = 0; // don't keep restarting over and over again
      SERVER_COMMAND ("restart\n"); // restart the map
   }

   // Record some Stats of all Players on the Server
   num_players = 0;
   for (player_index = 0; player_index < gpGlobals->maxClients; player_index++)
   {
      pPlayer = INDEXENT (player_index + 1);

      if (!FNullEnt (pPlayer) && (pPlayer->v.flags & FL_CLIENT))
      {
         num_players++;
         clients[player_index].pEdict = pPlayer;
         clients[player_index].IsUsed = TRUE;
         clients[player_index].IsAlive = IsAlive (pPlayer);

         if (clients[player_index].IsAlive)
         {
            clients[player_index].iTeam = UTIL_GetTeam (pPlayer);
            clients[player_index].vOrigin = pPlayer->v.origin;
            SoundSimulateUpdate (player_index);
         }

         // Does Client need to be shocked by the ugly red welcome message ?
         if ((clients[player_index].welcome_time > 0) && (clients[player_index].welcome_time < gpGlobals->time))
         {
            // Real Clients only
            if (!(pPlayer->v.flags & FL_FAKECLIENT))
            {
               // Hacked together Version of HUD_DrawString
               MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, pPlayer);
               WRITE_BYTE (TE_TEXTMESSAGE);
               WRITE_BYTE (1);
               WRITE_SHORT (FixedSigned16 (-1, 1 << 13));
               WRITE_SHORT (FixedSigned16 (0, 1 << 13));
               WRITE_BYTE (2);
               WRITE_BYTE (255);
               WRITE_BYTE (0);
               WRITE_BYTE (0);
               WRITE_BYTE (0);
               WRITE_BYTE (255);
               WRITE_BYTE (255);
               WRITE_BYTE (255);
               WRITE_BYTE (200);
               WRITE_SHORT (FixedUnsigned16 (0.0078125, 1 << 8));
               WRITE_SHORT (FixedUnsigned16 (2, 1 << 8));
               WRITE_SHORT (FixedUnsigned16 (6, 1 << 8));
               WRITE_SHORT (FixedUnsigned16 (0.1, 1 << 8));
               WRITE_STRING (szWelcomeMessage);
               MESSAGE_END ();

               // If this is the Host, scare him even more with a spoken Message
               if (g_bUseSpeech && (pPlayer == pHostEdict))
                  SERVER_COMMAND ((char *) &szSpeechSentences[RANDOM_LONG (0, 15)]);
            }

            clients[player_index].welcome_time = 0.0;
         }

         // Does Client need to be shocked by the ugly yellow waypoint message ?
         if ((clients[player_index].wptmessage_time > 0) && (clients[player_index].wptmessage_time < gpGlobals->time)
             && !(pPlayer->v.flags & FL_SPECTATOR))
         {
            // Real Clients only
            if (!(pPlayer->v.flags & FL_FAKECLIENT))
            {
               if (GET_USER_MSG_ID (PLID, "TextMsg", NULL) == 0)
                  REG_USER_MSG ("TextMsg", -1);

               MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, GET_USER_MSG_ID (PLID, "TextMsg", NULL), NULL, pPlayer);
               WRITE_BYTE (HUD_PRINTCENTER);
               WRITE_STRING (g_szWaypointMessage);
               MESSAGE_END ();
            }

            clients[player_index].wptmessage_time = 0.0;
         }
      }
      else
      {
         clients[player_index].pEdict = NULL;
         clients[player_index].IsUsed = FALSE;
         clients[player_index].IsAlive = FALSE;
      }
   }

   // Estimate next Frame's duration
   EstimateNextFrameDuration ();

   // Go through all active Bots, calling their Think fucntion
   num_bots = 0;
   for (bot_index = 0; bot_index < gpGlobals->maxClients; bot_index++)
   {
      if (bots[bot_index].is_used
          && !FNullEnt (bots[bot_index].pEdict))
      {
         BotThink (&bots[bot_index]);
         num_bots++;
      }
   }

   // Show Waypoints to host if turned on and no dedicated Server
   if (!g_bIsDedicatedServer && g_bWaypointOn && !FNullEnt (pHostEdict))
      WaypointThink ();

   if (g_bMapInitialised && g_bRecalcVis)
      WaypointCalcVisibility ();

   // are we currently spawning bots and is it time to spawn one yet?
   if ((botcreation_time > 0.0) && (botcreation_time < gpGlobals->time))
   {
      // find bot needing to be spawned...
      for (bot_index = 0; bot_index < gpGlobals->maxClients; bot_index++)
         if (BotCreateTab[bot_index].bNeedsCreation)
            break;

      if (bot_index < gpGlobals->maxClients)
      {
         BotCreate (BotCreateTab[bot_index].bot_skill, BotCreateTab[bot_index].bot_personality,
            BotCreateTab[bot_index].bot_team, BotCreateTab[bot_index].bot_class, BotCreateTab[bot_index].bot_name);
         BotCreateTab[bot_index].bNeedsCreation = FALSE;
         botcreation_time = gpGlobals->time + 0.5; // set next spawn time
      }
      else if (num_bots < min_bots)
      {
         BotCreate (101, 5, 5, 5, NULL);
         botcreation_time = gpGlobals->time + 0.5; // set next spawn time
      }
      else
         botcreation_time = 0.0;
   }

   // Executing the .cfg file - most work already done by Botman
   // Command Names are the same as in ClientCommand
   if (g_GameRules)
   {
      if (!file_opened)  // have we open podbot.cfg file yet?
      {
         UTIL_ServerPrint ("Executing podbot.cfg\n");

         bot_cfg_fp = fopen ("cstrike/addons/podbot/podbot.cfg", "r");
         if (bot_cfg_fp == NULL)
            UTIL_ServerPrint ("podbot.cfg File not found\n");

         file_opened = TRUE;
         pause_time = gpGlobals->time;
      }

      // if the bot.cfg file is still open and time to execute command...
      while ((bot_cfg_fp != NULL) && !feof (bot_cfg_fp) && (pause_time < gpGlobals->time))
      {
         if (fgets (cmd_line, 1024, bot_cfg_fp) != NULL)
         {
            if ((cmd_line[0] == '#') || (cmd_line[0] == '\r') || (cmd_line[0] == '\n') || (cmd_line[0] == 0))
               continue; // ignore comments or blank lines

            sprintf (cmd, GetField (cmd_line, 0));
            sprintf (arg1, GetField (cmd_line, 1));
            sprintf (arg2, GetField (cmd_line, 2));
            sprintf (arg3, GetField (cmd_line, 3));
            sprintf (arg4, GetField (cmd_line, 4));
            sprintf (arg5, GetField (cmd_line, 5));

            if (FStrEq (cmd, CONSOLE_CMD_PAUSE))
            {
               pause_time = gpGlobals->time + atoi (arg1);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_ADDBOT))
            {
               // check for empty slots
               for (bot_index = 0; bot_index < gpGlobals->maxClients; bot_index++)
                  if (!BotCreateTab[bot_index].bNeedsCreation)
                     break;

               // add bots to bot creation queue if queue is empty
               if (bot_index < gpGlobals->maxClients)
               {
                  memset (&BotCreateTab[bot_index], 0, sizeof (createbot_t));
                  strcpy (BotCreateTab[bot_index].bot_name, arg5);
                  BotCreateTab[bot_index].bot_skill = atoi (arg1);
                  BotCreateTab[bot_index].bot_personality = atoi (arg2);
                  BotCreateTab[bot_index].bot_team = atoi (arg3);
                  BotCreateTab[bot_index].bot_class = atoi (arg4);
                  BotCreateTab[bot_index].bNeedsCreation = TRUE;
                  if (botcreation_time == 0.0)
                     botcreation_time = gpGlobals->time;
               }
               else
                  UTIL_ServerPrint ("Bot creation queue full!\n");

               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_MINBOTS))
            {
               min_bots = atoi (arg1);
               if (min_bots < 0)
                  min_bots = 0;
               if (min_bots > max_bots)
                  min_bots = max_bots;

               UTIL_ServerPrint ("min_bots set to %d\n", min_bots);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_MAXBOTS))
            {
               max_bots = atoi (arg1);
               if (max_bots > 32)
                  max_bots = 32;
               if (max_bots < min_bots)
                  max_bots = min_bots;

               UTIL_ServerPrint ("max_bots set to %d\n", max_bots);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_MINBOTSKILL))
            {
               g_iMinBotSkill = atoi (arg1);
               if (g_iMinBotSkill < 1)
                  g_iMinBotSkill = 1;
               if (g_iMinBotSkill > g_iMaxBotSkill)
                  g_iMinBotSkill = g_iMaxBotSkill;

               UTIL_ServerPrint ("MinBotSkill set to %d\n", g_iMinBotSkill);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_MAXBOTSKILL))
            {
               g_iMaxBotSkill = atoi (arg1);
               if (g_iMaxBotSkill > 100)
                  g_iMaxBotSkill = 100;
               if (g_iMaxBotSkill < g_iMinBotSkill)
                  g_iMaxBotSkill = g_iMinBotSkill;

               UTIL_ServerPrint ("MaxBotSkill set to %d\n", g_iMaxBotSkill);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_BOTCHAT))
            {
               if (FStrEq (arg1, "on"))
                  g_bBotChat = TRUE;
               else if (FStrEq (arg1, "off"))
                  g_bBotChat = FALSE;

               UTIL_ServerPrint ("BotChat turned %s\n", (g_bBotChat ? "on" : "off"));
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_JASONMODE))
            {
               if (FStrEq (arg1, "on"))
                  g_bJasonMode = TRUE;
               else if (FStrEq (arg1, "off"))
                  g_bJasonMode = FALSE;

               UTIL_ServerPrint ("JasonMode turned %s\n", (g_bJasonMode ? "on" : "off"));
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_WPTFOLDER))
            {
               strcpy (g_szWPTDirname, arg1);

               UTIL_ServerPrint ("Waypoint Folder set to '%s'\n", g_szWPTDirname);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_DETAILNAMES))
            {
               if (FStrEq (arg1, "on"))
                  g_bDetailNames = TRUE;
               else if (FStrEq (arg1, "off"))
                  g_bDetailNames = FALSE;

               UTIL_ServerPrint ("DetailedNames turned %s\n", (g_bDetailNames ? "on" : "off"));
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_INHUMANTURNS))
            {
               if (FStrEq (arg1, "on"))
                  g_bInstantTurns = TRUE;
               else if (FStrEq (arg1, "off"))
                  g_bInstantTurns = FALSE;

               UTIL_ServerPrint ("Inhumanturns turned %s\n", (g_bInstantTurns ? "on" : "off"));
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_MAXBOTSFOLLOW))
            {
               g_iMaxNumFollow = atoi (arg1);
               if (g_iMaxNumFollow < 0)
                  g_iMaxNumFollow = 3;

               UTIL_ServerPrint ("Max. Number of Bots following set to: %d\n", g_iMaxNumFollow);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_MAXWEAPONPICKUP))
            {
               g_iMaxWeaponPickup = atoi (arg1);
               if (g_iMaxWeaponPickup < 0)
                  g_iMaxWeaponPickup = 1;

               UTIL_ServerPrint ("Bots pickup a maximum of %d weapons\n", g_iMaxWeaponPickup);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_COLLECTEXP))
            {
               if (FStrEq (arg1, "on"))
                  g_bUseExperience = TRUE;
               else if (FStrEq (arg1, "off"))
                  g_bUseExperience = FALSE;

               UTIL_ServerPrint ("Experience Collection turned %s\n", (g_bUseExperience ? "on" : "off"));
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_SHOOTTHRU))
            {
               if (FStrEq (arg1, "on"))
                  g_bShootThruWalls = TRUE;
               else if (FStrEq (arg1, "off"))
                  g_bShootThruWalls = FALSE;

               UTIL_ServerPrint ("Shooting thru Walls turned %s\n", (g_bShootThruWalls ? "on" : "off"));
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_TIMESOUND))
            {
               g_fTimeSoundUpdate = atof (arg1);
               if (g_fTimeSoundUpdate < 0.1)
                  g_fTimeSoundUpdate = 0.1;

               UTIL_ServerPrint ("Sound Update Timer set to %f secs\n", g_fTimeSoundUpdate);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_TIMEPICKUP))
            {
               g_fTimePickupUpdate = atof (arg1);
               if (g_fTimePickupUpdate < 0.1)
                  g_fTimePickupUpdate = 0.1;

               UTIL_ServerPrint ("Pickup Update Timer set to %f secs\n", g_fTimePickupUpdate);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_TIMEGRENADE))
            {
               g_fTimeGrenadeUpdate = atof (arg1);
               if (g_fTimeGrenadeUpdate < 0.1)
                  g_fTimeGrenadeUpdate = 0.1;

               UTIL_ServerPrint ("Grenade Check Timer set to %f secs\n", g_fTimeGrenadeUpdate);
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_SPEECH))
            {
               if (FStrEq (arg1, "on"))
                  g_bUseSpeech = TRUE;
               else if (FStrEq (arg1, "off"))
                  g_bUseSpeech = FALSE;

               UTIL_ServerPrint ("POD Speech turned %s\n", (g_bUseSpeech ? "on" : "off"));
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_ALLOWSPRAY))
            {
               if (FStrEq (arg1, "on"))
                  g_bBotSpray = TRUE;
               else if (FStrEq (arg1, "off"))
                  g_bBotSpray = FALSE;

               UTIL_ServerPrint ("Bot Logo spraying turned %s\n", (g_bBotSpray ? "on" : "off"));
               break;
            }
            else if (FStrEq (cmd, CONSOLE_CMD_DANGERFACTOR))
            {
               g_iDangerFactor = atoi (arg1);
               if (g_iDangerFactor < 1)
                  g_iDangerFactor = 1;

               UTIL_ServerPrint ("Pathfinder danger factor set to %d\n", g_iDangerFactor);
               break;
            }
            else
            {
               UTIL_ServerPrint ("executing server command: %s\n", cmd_line);

               SERVER_COMMAND (cmd_line);
               break;
            }
         }
      }

      // if bot.cfg file is open and reached end of file, then close and free it
      if ((bot_cfg_fp != NULL) && feof (bot_cfg_fp))
      {
         fclose (bot_cfg_fp);
         bot_cfg_fp = NULL;
         g_GameRules = FALSE;

         // print the waypoint message to console
         UTIL_ServerPrint (g_szWaypointMessage);
      }
   }

   previous_time = gpGlobals->time;

   RETURN_META (MRES_IGNORED);
}


void FakeClientCommand (edict_t *pFakeClient, const char *fmt, ...)
{
   // the purpose of this function is to provide fakeclients (bots) with the same client
   // command-scripting advantages (putting multiple commands in one line between semicolons)
   // as real players. It is an improved version of botman's FakeClientCommand, in which you
   // supply directly the whole string as if you were typing it in the bot's "console". It
   // is supposed to work exactly like the pfnClientCommand (server-sided client command).

   va_list argptr;
   static char command[256];
   int length, fieldstart, fieldstop, i, index, stringindex = 0;
   int iClientIndex;
   int iRadioCommand;

   if (FNullEnt (pFakeClient))
      return; // reliability check

   // concatenate all the arguments in one string
   va_start (argptr, fmt);
   vsprintf (command, fmt, argptr);
   va_end (argptr);

   if ((command == NULL) || (*command == 0))
      return; // if nothing in the command buffer, return

   isFakeClientCommand = TRUE; // set the "fakeclient command" flag
   length = strlen (command); // get the total length of the command string

   // process all individual commands (separated by a semicolon) one each a time
   while (stringindex < length)
   {
      fieldstart = stringindex; // save field start position (first character)
      while ((stringindex < length) && (command[stringindex] != ';'))
         stringindex++; // reach end of field
      if (command[stringindex - 1] == '\n')
         fieldstop = stringindex - 2; // discard any trailing '\n' if needed
      else
         fieldstop = stringindex - 1; // save field stop position (last character before semicolon or end)
      for (i = fieldstart; i <= fieldstop; i++)
         g_argv[i - fieldstart] = command[i]; // store the field value in the g_argv global string
      g_argv[i - fieldstart] = 0; // terminate the string
      stringindex++; // move the overall string index one step further to bypass the semicolon

      index = 0;
      fake_arg_count = 0; // let's now parse that command and count the different arguments

      // count the number of arguments
      while (index < i - fieldstart)
      {
         while ((index < i - fieldstart) && (g_argv[index] == ' '))
            index++; // ignore spaces

         // is this field a group of words between quotes or a single word ?
         if (g_argv[index] == '"')
         {
            index++; // move one step further to bypass the quote
            while ((index < i - fieldstart) && (g_argv[index] != '"'))
               index++; // reach end of field
            index++; // move one step further to bypass the quote
         }
         else
            while ((index < i - fieldstart) && (g_argv[index] != ' '))
               index++; // this is a single word, so reach the end of field

         fake_arg_count++; // we have processed one argument more
      }

      // Check Radio Commands (fix): do it here since metamod won't call our own ClientCommand()
      iClientIndex = ENTINDEX (pFakeClient) - 1;

      if ((iRadioSelect[iClientIndex] != 0) && (strncmp (g_argv, "menuselect", 10) == 0))
      {
         iRadioCommand = atoi (g_argv + 11);

         if (iRadioCommand != 0)
         {
            iRadioCommand += 10 * (iRadioSelect[iClientIndex] - 1);

            if ((iRadioCommand != RADIO_AFFIRMATIVE)
                && (iRadioCommand != RADIO_NEGATIVE)
                && (iRadioCommand != RADIO_REPORTINGIN))
            {
               for (i = 0; i < gpGlobals->maxClients; i++)
               {
                  if (bots[i].is_used && (bots[i].bot_team == clients[iClientIndex].iTeam)
                      && (pFakeClient != bots[i].pEdict))
                  {
                     if (bots[i].iRadioOrder == 0)
                     {
                        bots[i].iRadioOrder = iRadioCommand;
                        bots[i].pRadioEntity = pFakeClient;
                     }
                  }
               }
            }

            g_rgfLastRadioTime[clients[iClientIndex].iTeam - 1] = gpGlobals->time;
         }

         iRadioSelect[iClientIndex] = 0;
      }
      else if (strncmp (g_argv, "radio", 5) == 0)
         iRadioSelect[iClientIndex] = atoi (g_argv + 5);

      // End Radio Commands

      MDLL_ClientCommand (pFakeClient); // tell now the MOD DLL to execute this ClientCommand...
   }

   g_argv[0] = 0; // when it's done, reset the g_argv field
   isFakeClientCommand = FALSE; // reset the "fakeclient command" flag
   fake_arg_count = 0; // and the argument count
}


const char *GetField (const char *string, int field_number)
{
   // This function gets and returns a particuliar field in a string where several fields are
   // concatenated. Fields can be words, or groups of words between quotes ; separators may be
   // white space or tabs. A purpose of this function is to provide bots with the same Cmd_Argv
   // convenience the engine provides to real clients. This way the handling of real client
   // commands and bot client commands is exactly the same, just have a look in engine.cpp
   // for the hooking of pfnCmd_Argc, pfnCmd_Args and pfnCmd_Argv, which redirects the call
   // either to the actual engine functions (when the caller is a real client), either on
   // our function here, which does the same thing, when the caller is a bot.

   static char field[256];
   int length, i, index = 0, field_count = 0, fieldstart, fieldstop;

   field[0] = 0; // reset field
   length = strlen (string); // get length of string

   while ((length > 0) && ((string[length - 1] == '\n') || (string[length - 1] == '\r')))
      length--; // discard trailing newlines

   // while we have not reached end of line
   while ((index < length) && (field_count <= field_number))
   {
      while ((index < length) && ((string[index] == ' ') || (string[index] == '\t')))
         index++; // ignore spaces or tabs

      // is this field multi-word between quotes or single word ?
      if (string[index] == '"')
      {
         index++; // move one step further to bypass the quote
         fieldstart = index; // save field start position
         while ((index < length) && (string[index] != '"'))
            index++; // reach end of field
         fieldstop = index - 1; // save field stop position
         index++; // move one step further to bypass the quote
      }
      else
      {
         fieldstart = index; // save field start position
         while ((index < length) && ((string[index] != ' ') && (string[index] != '\t')))
            index++; // reach end of field
         fieldstop = index - 1; // save field stop position
      }

      // is this field we just processed the wanted one ?
      if (field_count == field_number)
      {
         for (i = fieldstart; i <= fieldstop; i++)
            field[i - fieldstart] = string[i]; // store the field value in a string
         field[i - fieldstart] = 0; // terminate the string
         break; // and stop parsing
      }

      field_count++; // we have parsed one field more
   }

   return (&field[0]); // returns the wanted field
}


void PODBot_ServerCommand (void)
{
   // This is cleaner than polling a CVAR, eh ? :) -- PM
   char pcmd[128];
   char arg1[128];
   char arg2[128];
   char arg3[128];
   char arg4[128];
   char arg5[128];
   int index;
   int iSelection;

   sprintf (pcmd, CMD_ARGV (1));
   sprintf (arg1, CMD_ARGV (2));
   sprintf (arg2, CMD_ARGV (3));
   sprintf (arg3, CMD_ARGV (4));
   sprintf (arg4, CMD_ARGV (5));
   sprintf (arg5, CMD_ARGV (6));

   // "addbot" adds a bot to creation queue with specified args
   if (FStrEq (pcmd, CONSOLE_CMD_ADDBOT))
   {
      for (index = 0; index < gpGlobals->maxClients; index++)
         if (!BotCreateTab[index].bNeedsCreation)
            break;
      if (index < gpGlobals->maxClients)
      {
         memset (&BotCreateTab[index], 0, sizeof (createbot_t));
         strcpy (BotCreateTab[index].bot_name, arg5);
         BotCreateTab[index].bot_skill = atoi (arg1);
         if ((arg2 != NULL) && (*arg2 != 0))
            BotCreateTab[index].bot_personality = atoi (arg2);
         else
            BotCreateTab[index].bot_personality = 5;
         BotCreateTab[index].bot_team = atoi (arg3);
         BotCreateTab[index].bot_class = atoi (arg4);
         BotCreateTab[index].bNeedsCreation = TRUE;
         if (botcreation_time == 0.0)
            botcreation_time = gpGlobals->time;
      }
      else
         UTIL_HostPrint ("Bot creation queue full!\n");
   }

   // "removebots" removes all bots
   else if (FStrEq (pcmd, CONSOLE_CMD_REMOVEALLBOTS))
      UserRemoveAllBots ();

   // "killbots" kills all bots
   else if (FStrEq (pcmd, CONSOLE_CMD_KILLALLBOTS))
      UserKillAllBots ();

   // "fillserver" Fills the server with bots
   else if (FStrEq (pcmd, "fillserver"))
   {
      if ((arg3 != NULL) && (*arg3 != 0))
         iSelection = atoi (arg3);

      if ((iSelection == 1) || (iSelection == 2))
      {
         CVAR_SET_STRING ("mp_limitteams", "0");
         CVAR_SET_STRING ("mp_autoteambalance", "0");
      }
      else
         iSelection = 5;

      for (index = 0; index < gpGlobals->maxClients; index++)
      {
         if (!BotCreateTab[index].bNeedsCreation)
         {
            memset (&BotCreateTab[index], 0, sizeof (createbot_t));
            BotCreateTab[index].bot_skill = atoi (arg1);
            if ((arg2 != NULL) && (*arg2 != 0))
               BotCreateTab[index].bot_personality = atoi (arg2);
            else
               BotCreateTab[index].bot_personality = 5;
            BotCreateTab[index].bot_team = iSelection;
            BotCreateTab[index].bot_class = atoi (arg4);
            BotCreateTab[index].bNeedsCreation = TRUE;
         }
      }

      if (botcreation_time == 0.0)
         botcreation_time = gpGlobals->time;
   }

   // "debuggoal" Forces Bots to use a specified Waypoint as a Goal
   else if (FStrEq (pcmd, CONSOLE_CMD_DEBUGGOAL))
   {
      g_iDebugGoalIndex = atoi (arg1);

      // Bots ignore Enemies in Goal Debug Mode
      if ((g_iDebugGoalIndex >= 0) && (g_iDebugGoalIndex < g_iNumWaypoints))
         g_bIgnoreEnemies = TRUE;
      else
         g_bIgnoreEnemies = FALSE;

      UTIL_HostPrint ("Goal for Bot set to %d\n", g_iDebugGoalIndex);
   }

   // "experience"
   else if (FStrEq (pcmd, CONSOLE_CMD_EXPERIENCE))
   {
      if (FStrEq (arg1, "save"))
      {
         SaveExperienceTab ();
         SaveVisTab ();
      }
   }

   // "botchat"
   else if (FStrEq (pcmd, CONSOLE_CMD_BOTCHAT))
   {
      if (FStrEq (arg1, "on"))
         g_bBotChat = TRUE;
      else if (FStrEq (arg1, "off"))
         g_bBotChat = FALSE;

      UTIL_HostPrint ("Bot Chat is %s\n", (g_bBotChat ? "ENABLED" : "DISABLED"));
   }

   // "jasonmode" Forces bots to use knives if on
   else if (FStrEq (pcmd, CONSOLE_CMD_JASONMODE))
   {
      if (FStrEq (arg1, "on"))
         g_bJasonMode = TRUE;
      else if (FStrEq (arg1, "off"))
         g_bJasonMode = FALSE;

      UTIL_HostPrint ("Knife Mode is %s\n", (g_bJasonMode ? "ENABLED" : "DISABLED"));
   }

   // "danger_factor"
   else if (FStrEq (pcmd, CONSOLE_CMD_DANGERFACTOR))
   {
      if (atoi (arg1) > 0)
         g_iDangerFactor = atoi (arg1);

      UTIL_HostPrint ("Pathfinder danger factor is %d\n", g_iDangerFactor);
   }

   // If it were none of the above commands tell the user
   else
      UTIL_HostPrint ("\"%s\" is not a Podbot command\n", pcmd);
}


C_DLLEXPORT int GetEntityAPI2 (DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion)
{
   gFunctionTable.pfnGameInit = GameDLLInit;
   gFunctionTable.pfnSpawn = Spawn;
   gFunctionTable.pfnClientConnect = ClientConnect;
   gFunctionTable.pfnClientDisconnect = ClientDisconnect;
   gFunctionTable.pfnClientPutInServer = ClientPutInServer;
   gFunctionTable.pfnClientCommand = ClientCommand;
   gFunctionTable.pfnServerActivate = ServerActivate;
   gFunctionTable.pfnServerDeactivate = ServerDeactivate;
   gFunctionTable.pfnStartFrame = StartFrame;

   memcpy (pFunctionTable, &gFunctionTable, sizeof (DLL_FUNCTIONS));
   return (TRUE);
}


C_DLLEXPORT int GetEntityAPI2_Post (DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion)
{
   gFunctionTable_Post.pfnSpawn = Spawn_Post;

   memcpy (pFunctionTable, &gFunctionTable_Post, sizeof (DLL_FUNCTIONS));
   return (TRUE);
}
