Putting BitchBots into your mod

Title: Putting BitchBots into your Mod
Author: Maj.Bitch
Date: Feb 8, 2000
Difficulty: Not too bad but tedious..
Email: peblair@hotmail.com
Note: Please give credit where credit is due.
=====================================================

First off, alot of this bot is Maj.Bitch's modification
of the code developed by Ponpoko for his 3zb2 bot and much
credit goes to him/her for their absolutely brilliant work!

Okay.. It seems like alot of code but with cutting and
pasting you should be able to have this ALL added to
your source and compiled and running in around 10-15
minutes.. Be patient.. Follow the tutorial step-by-step
and be methodical so you don't leave anything out.

The bots use the *.chn file format used by the 3zb2 bots.
I decided to stick with this file format because there
are a ton of route files already in existence which you
can download from the web so it saves everybody alot of
time. Goto: http://www.btinternet.com/~RsFHQ/ and ENTER
the site. Then goto the "3rd Zigock" pull-down menu across
the top of the site and click on "info & download". Work
over to the Route Database. There, you'll find a route
file for ALOT of maps. Any of these maps will work with
this bot.

The bot source will be looking for the route files in
"C:\Quake2\3zb2\chdtm\" directory (or you can change the
path in the bot.c file).. Set it up anyway you like so
long as the bot can access the *.chn route files..

Compile your gamex86.dll and run it just like you would do
normally. At the console, type "sv addbot 5" to add 5
bots to the game (for example).. I've got the source
set up for 50 different bots you can put into the game
even at the same time if you want absolute total mayhem!
The source will randomly select bots from the list of 50
so feel free to change the names/skins to whatever you like..

Spawn around 40 bots and then go into spectator mode and
roam around and watch the CHAOS!

Also, the code will automatically respawn the same bots back
into the game as your next map cycles thru.

As always, you should feel free to modify the bot's chat
and insult sayings to fit your particular mod.. Just
change the text strings in bot.c (easy to do) and the
bots will say the things that you want them too.. I've
got it setup so that you can have the bots say things
when they accidentally kill themselves, or when they
frag somebody, or just random chatter!

Okay.. About this tutorial...

Simply open the appropriate file and find the EXACT spot
that I'm referencing and then cut the source immediately
after the Maj++ and stop cutting at the next dashed line..

Okay, Let's get started!

=====================================================
Open your g_local.h file and make these changes..
<C&NBSP;CODE>
#define IT_KEY      16
#define IT_POWERUP  32
// Maj++ ----------------------------- - START CUT HERE
#define IT_PACK       64
#define IT_HEALTH    128
#define IT_NODE      256
//------------------------------------ - END CUT HERE
</C&NBSP;CODE>

Further down in this same file after the gitem_t struct:
<C&NBSP;CODE>
  char    *precaches; 
} gitem_t;

// Maj++----------------------------------
// Item Ammo (IT_AMMO)
gitem_t *item_shells,
        *item_cells,
        *item_rockets,
        *item_grenades,
        *item_slugs,
        *item_bullets,
// Item Weapons (IT_WEAPON)
        *item_blaster,
        *item_shotgun,
        *item_machinegun,
        *item_supershotgun,
        *item_chaingun,
        *item_handgrenade,
        *item_grenadelauncher,
        *item_rocketlauncher,
        *item_hyperblaster,
        *item_railgun,
        *item_bfg,
// Item Armor (IT_ARMOR)
        *item_jacketarmor,
        *item_combatarmor,
        *item_bodyarmor,
        *item_armorshard,
        *item_powerscreen,
        *item_powershield,
// Item Health (IT_HEALTH)
        *item_adrenaline,
        *item_health,
        *item_stimpak,
        *item_health_large,
        *item_health_mega,
// Item Powerup (IT_POWERUP)
        *item_quad,
        *item_invulnerability,
        *item_silencer,
        *item_breather,
        *item_enviro,
// Item Pak (IT_PACK)
        *item_pack,
        *item_bandolier,
// Item Nodes (IT_NODE)
        *item_navi1,
        *item_navi2,
        *item_navi3;
//-------------------------------------
</C&NBSP;CODE>

Further, at the bottom of your client_persistant_t struct add:
<C&NBSP;CODE>
  qboolean  spectator;
//Maj++-------------------------
  int     botindex;
  int     routeindex;
//------------------------------
} client_persistant_t;
</C&NBSP;CODE>

Further, at the bottom of your gclient_t struct add:
<C&NBSP;CODE>
  edict_t    *chase_target;    // player we are chasing
  qboolean  update_chase;    // need to update chase info?

//Maj++ --------------------------------------
  float    changetime;       //next time to alter bot's parameters..
  float    chattime;         //time of next chat
  float    insulttime;       //time of next insult
  float    taunttime;        //time of next taunting;
  int      movestate;        //movement state
  int      combatstate;      //combat state
  float    duckedtime;       //next ducked time
  edict_t *current_enemy;    //current enemy target
  edict_t *prev_enemy;       //old enemy
  edict_t *waiting_obj;      //ent bot is waiting on (elevators, etc).
  int      ground_contents;  //contents under bot
  float    ground_slope;     //slope under bot
  qboolean havetarget;       //got a target? (yes/no)
  int      enemysearchcnt;   //enemy search count
  int      waterstate;       //current water state (0..2)
  qboolean routetrace;       //
  int      routeindex;       //route[] index
  float    routelocktime;    //route[] locking time
  float    routereleasetime; //route[] release time
  vec3_t   targ_old_origin;  //target's old origin
  int      battlemode;       //current battle mode - see FIRE_*
  int      battlecount;      //temp battle count
  int      battlesubcnt;     //subcount
  int      battleduckcnt;    //ducktime during battle
  int      enemy_routeindex; //index of target
  float    targetlock;       //enemy locking time
  vec3_t   movtarget_pt;     //moving target waiting point
  float    nextcheck;        //checking time
  vec3_t   my_old_origin;    //bot's old origin
  qboolean objshot;          //button has been activated
  float    moveyaw;          //temp moving yaw
  vec3_t   vtemp;            //temp storage vec
  float    camptime;         //time spent camping
  gitem_t *campitem;         //item camping near
  vec3_t   lastorigin;       //origin of camping spot
//----------------------------------------------------
};
</C&NBSP;CODE>

Further, at the bottom of your edict_t struct add:
<C&NBSP;CODE>
  // common data blocks
  moveinfo_t     moveinfo;
  monsterinfo_t  monsterinfo;

//Maj++ ----------------------------------
  qboolean isabot;
  edict_t *union_ent;
  edict_t *trainteam;
//----------------------------------------
};
</C&NBSP;CODE>

At the very bottom of your g_local.h file add:
<C&NBSP;CODE>
#include "bot.h" // Maj++
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Now, let's open your q_shared.h file and add:
<C&NBSP;CODE>
#define  MASK_OPAQUE  (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA)
#define  MASK_SHOT    (CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEADMONSTER)
//Maj++------------------
#define MASK_BOTSOLID  (CONTENTS_SOLID|CONTENTS_LADDER|CONTENTS_WINDOW|CONTENTS_MONSTER)
#define MASK_BOTSOLIDX (CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_MONSTER)
//----------------------
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your g_combat.c and add:
<C&NBSP;CODE>
void T_Damage (edict_t *targ, edict_t *inflictor, edict_t *attacker, vec3_t dir, vec3_t point, vec3_t normal, int damage, int knockback, int dflags, int mod)
{
  gclient_t  *client;
  int      take;
  int      save;
  int      asave;
  int      psave;
  int      te_sparks;

  if (!targ->takedamage)
    return;

  CheckBotCrushed(targ,inflictor,mod); // Maj++ - Add this line here
</C&NBSP;CODE>

Further down add:
<C&NBSP;CODE>
  if ((targ != attacker) && ((deathmatch->value && ((int)(dmflags->value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop->value))
  {
    if (OnSameTeam (targ, attacker))
    {
      if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
        damage = 0;
      else
        mod |= MOD_FRIENDLY_FIRE;
    }
// Maj++ ---------------------------------------
  else
    if (targ->client && !targ->isabot)
      if (attacker->client)
        targ->client->current_enemy = attacker;
//----------------------------------------------------------
  }
  meansOfDeath = mod;
</C&NBSP;CODE>

Further down add:
<C&NBSP;CODE>
// do the damage
  if (take)
  {
    if ((targ->svflags & SVF_MONSTER) || (client)) { // Maj++ - left brace
      SpawnDamage (TE_BLOOD, point, normal, take);
      BotCheckEnemy(client,attacker,targ,mod); } // Maj++
    else
      SpawnDamage (te_sparks, point, normal, take);

    targ->health = targ->health - take;
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your g_func.c and add this:

At the bottom of your SP_Func_Plat() add:
<C&NBSP;CODE>
  ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav");
  ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav");
  ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");

  bFuncPlat(ent); // Maj++ - Add this line here
}
</C&NBSP;CODE>

At the bottom of your SP_Func_Button() add:
<C&NBSP;CODE>
  VectorCopy (ent->s.angles, ent->moveinfo.end_angles);

  gi.linkentity (ent);

  bFuncButton(ent); // Maj++ - Add this line here
}
</C&NBSP;CODE>

In the middle of your door_blocked() function, add:
<C&NBSP;CODE>
  T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH);

  bDoorBlocked(self); // Maj++ - Add this line here

  if (self->spawnflags & DOOR_CRUSHER)
    return;
</C&NBSP;CODE>

At the bottom of your SP_func_door() add:
<C&NBSP;CODE>
  ent->nextthink = level.time + FRAMETIME;
  if (ent->health || ent->targetname)
    ent->think = Think_CalcMoveSpeed;
  else
    ent->think = Think_SpawnDoorTrigger;

  bFuncDoor(ent); // Maj++ - Add this line here
}
</C&NBSP;CODE>

Near the bottom of your SP_func_train() add:
<C&NBSP;CODE>
  gi.linkentity(self);

  bFuncTrain(self); // Maj++ - Add this line here.

  if (self->target)
  {
    // start trains on the second frame, to make sure their targets have had
    // a chance to spawn
    self->nextthink = level.time + FRAMETIME;
    self->think = func_train_find;
  }
}
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your g_misc.c file and add this stuff:

At the top of your teleporter_touch(), add:
<C&NBSP;CODE>
void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
  edict_t    *dest;
  int      i;

  if (!other->client)
    return;
  dest = G_Find (NULL, FOFS(targetname), self->target);
  if (!dest)
  {
    return;
  }

  ForceRouteReset(other); // Maj++ - Add this line here
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your g_monster.c file and add this:

Comment out (or delete) your M_CheckGround() function and
substitute this one in its place OR you can make the
indicated changes... Your choice.
<C&NBSP;CODE>
void M_CheckGround (edict_t *ent)
{
  vec3_t    point;
  trace_t    trace;

  if (ent->flags & (FL_SWIM|FL_FLY))
    return;

  ResetGroundSlope(ent); // Maj++

  if (ent->velocity[2] > 100)
  {
    ent->groundentity = NULL;
    return;
  }

// if the hull point one-quarter unit down is solid the entity is on ground
  point[0] = ent->s.origin[0];
  point[1] = ent->s.origin[1];
  point[2] = ent->s.origin[2] - 0.25;

  trace = gi.trace (ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_BOTSOLID); // Maj++ - Change mask

  // check steepness
  if ( trace.plane.normal[2] < 0.7 && !trace.startsolid)
  {
    ent->groundentity = NULL;
    return;
  }

/* Maj++ - Comment out this stuff here.
  if (!trace.startsolid && !trace.allsolid)
  {
    VectorCopy (trace.endpos, ent->s.origin);
    ent->groundentity = trace.ent;
    ent->groundentity_linkcount = trace.ent->linkcount;
    ent->velocity[2] = 0;
  }
*/

  TraceAllSolid(ent,point,trace); // Maj++ - Add this line here
}
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your g_phys.c file and make these changes..

In the middle of your SV_Push() function, add:
<C&NBSP;CODE>
// see if any solid entities are inside the final position
  check = g_edicts+1;
  for (e = 1; e < globals.num_edicts; e++, check++)
  {
    if (!check->inuse)
      continue;
//Maj++ ---------------------------------------------------------
    if (!check->solid) continue;
    if (check->classname[0] == 'R')
      if (check->item != item_navi2) continue;
//-----------------------------------------------------------------
    if (check->movetype == MOVETYPE_PUSH
    || check->movetype == MOVETYPE_STOP
    || check->movetype == MOVETYPE_NONE
    || check->movetype == MOVETYPE_NOCLIP)
      continue;
</C&NBSP;CODE>

At the top of your SV_Physics_Step() function add:
<C&NBSP;CODE>
void SV_Physics_Step (edict_t *ent)
{
  qboolean  wasonground;
  qboolean  hitsound = false;
  float    *vel;
  float    speed, newspeed, control;
  float    friction;
  edict_t    *groundentity;
  int      mask;

  // airborn monsters should always check for ground
  if (!ent->groundentity)
    M_CheckGround (ent);
//Maj++ --------------------------------
  else if (ent->isabot)
    M_CheckGround (ent); // bots must check ground
//--------------------------------------
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your g_save.c file and make this one change:
<C&NBSP;CODE>
void InitGame (void)
{
  gi.dprintf ("==== InitGame ====\n");

  srand(time(NULL)); // Maj++ - Init random generator here

  gun_x = gi.cvar ("gun_x", "0", 0);
  gun_y = gi.cvar ("gun_y", "0", 0);
  gun_z = gi.cvar ("gun_z", "0", 0);
</C&NBSP;CODE>

At the very bottom of this same function add:

<C&NBSP;CODE>
  LoadBotNames(); // Maj++ - Add this line here
</C&NBSP;CODE>


Okay, we're done with this file now. Close it out

=====================================================
Open your g_spawn.c file and make this one change:

At the bottom of you SpawnEntities() function, add;
<C&NBSP;CODE>
  gi.dprintf ("%i entities inhibited\n", inhibit);

  G_FindTeams ();

  G_FindTrainTeam(); // Maj++ - Add these lines here
  ReadRouteFile();   // Maj++
  RespawnAllBots();  // Maj++
}
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your g_svcmds.c file and make this one change:

At the bottom of your ServerCommand() function add:
<C&NBSP;CODE>
//Maj++ -------------------------------
  if (!strcmp(gi.argv(1),"addbot"))
    SpawnNumBots(atoi(gi.argv(2)));
  else if (!strcmp(gi.argv(1),"clearall"))
    RemoveAllBots();
//-------------------------------------
  else
    gi_cprintf (NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd);
}
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your g_utils.c file and add these new functions:
<C&NBSP;CODE>
// Maj++ - BEFORE you paste in these new printf functions you 
// MUST do a global substitution for these function names thru
// your entire source!  We need to preven bots from getting
// printed messages sent to them.. 
//
// Change all gi.cprintf to gi_cprintf.
// Change all gi.centerprintf to gi_centerprintf
// Change all gi,bprintf to gi_bprintf
//
// THEN PASTE THESE FUNCTIONS INTO YOUR SOURCE

//==============================================
void gi_cprintf(edict_t *ent, int printlevel, char *fmt, ...) {
char bigbuffer[0x10000];
va_list argptr;
int len;

  if (!ent || !ent->inuse || !ent->client) return;

  if (ent->isabot) return; // Maj++ - bots don't get messages

  va_start(argptr,fmt);
  len = vsprintf(bigbuffer,fmt,argptr);
  va_end(argptr);

  gi.cprintf(ent, printlevel, bigbuffer);
}

//===================================================
void gi_centerprintf(edict_t *ent, char *fmt, ...) {
char bigbuffer[0x10000];
va_list argptr;
int len;

  if (!ent || !ent->inuse || !ent->client) return;

  if (ent->isabot) return; // Maj++ - bots don't get messages

  va_start(argptr,fmt);
  len = vsprintf(bigbuffer,fmt,argptr);
  va_end(argptr);

  gi.centerprintf(ent, bigbuffer);
}

//===================================================
void gi_bprintf(int printlevel, char *fmt, ...) {
char bigbuffer[0x10000];
int i, len;
va_list argptr;
edict_t *ent;

  va_start(argptr,fmt);
  len = vsprintf(bigbuffer,fmt,argptr);
  va_end(argptr);

  if (dedicated->value)
    gi_cprintf(NULL, printlevel, bigbuffer);

  for (i=0; i<game.maxclients; i++) {
    ent=g_edicts+1+i;
    gi_cprintf(ent, printlevel, bigbuffer); }
}
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your p_hud.c file and make these changes:

At the bottom of your MoveClientToIntermission(), add:
<C&NBSP;CODE>
  ent->s.sound = 0;
  ent->solid = SOLID_NOT;

  if (ent->isabot) return; // Maj++ - don't unicast() to bots!

  // add the layout

  if (deathmatch->value || coop->value)
  {
    DeathmatchScoreboardMessage (ent, NULL);
    gi.unicast (ent, true);
  }
}
</C&NBSP;CODE>

Add this to your DeathmatchScoreboard() function:
<C&NBSP;CODE>
void DeathmatchScoreboard (edict_t *ent)
{
  if (ent->isabot) return; // Maj++ - Don't unicast() to bots!
  DeathmatchScoreboardMessage (ent, ent->enemy);
  gi.unicast (ent, true);
}
</C&NBSP;CODE>

At the top of your Cmd_Help_f() function add:
<C&NBSP;CODE>
void Cmd_Help_f (edict_t *ent)
{
  if (ent->isabot) return; // Maj++ - bots don't need help..

  // this is for backwards compatability
  if (deathmatch->value)
  {
    Cmd_Score_f (ent);
    return;
  }
</C&NBSP;CODE>

At the top of your G_CheckChaseStats() function add:
<C&NBSP;CODE>
void G_CheckChaseStats (edict_t *ent)
{
  int i;
  gclient_t *cl;

  if (ent->isabot) return; // Maj++ - bots don't chasecam
</C&NBSP;CODE>

At the top of your G_SetSpectatorStats() add:
<C&NBSP;CODE>
void G_SetSpectatorStats (edict_t *ent)
{
  gclient_t *cl = ent->client;

  if (ent->isabot) return; // Maj++ - bots don't spectate

  if (!cl->chase_target)
    G_SetStats (ent);
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your p_view.c file and make these changes:

In the middle of your ClientEndServerFrame() function add:
<C&NBSP;CODE>
  // burn from lava, etc
  P_WorldEffects ();

  //
  // set model angles from view angles so other things in
  // the world can tell which direction you are looking
  //
  if (!ent->isabot) { // Maj++ - Add this line here
    if (ent->client->v_angle[0] > 180)
      ent->s.angles[0] = (-360+ent->client->v_angle[0])*0.33;
    else
      ent->s.angles[0] = ent->client->v_angle[0]*0.33;
    ent->s.angles[1] = ent->client->v_angle[1];
    ent->s.angles[2] = SV_CalcRoll(ent->s.angles,ent->velocity)*4;
    } // Maj++ - Add this end brace here
</C&NBSP;CODE>

Further down in this same function make this change:
<C&NBSP;CODE>
  //
  // calculate speed and cycle to be used for
  // all cyclic walking effects
  //
  xyspeed = sqrt(ent->velocity[0]*ent->velocity[0] + ent->velocity[1]*ent->velocity[1]);

  SetBotXYSpeed(ent,&xyspeed); // Maj++ - Add this line here
</C&NBSP;CODE>

At the bottom of this same function add:
<C&NBSP;CODE>
  if (ent->isabot) return; // Maj++ - don't unicast to bots!

  // if the scoreboard is up, update it
  if (ent->client->showscores && !(level.framenum & 31) )
  {
    DeathmatchScoreboardMessage (ent, ent->enemy);
    gi.unicast (ent, false);
  }
}
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your p_weapon.c file and make this change:

At the top of your Pickup_Weapon() function add:
<C&NBSP;CODE>
qboolean Pickup_Weapon (edict_t *ent, edict_t *other)
{
  int      index;
  gitem_t    *ammo;

  index = ITEM_INDEX(ent->item);

  CheckCampSite(ent,other); // Maj++ - Add this line here
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your p_client.c file and make these changes:

At the top of your ClientObituary() function add:
<C&NBSP;CODE>
void ClientObituary (edict_t *self, edict_t *inflictor, edict_t *attacker)
{
  int      mod;
  char *message=NULL; // Maj++ - BugFix
  char *message2="";  // Maj++ - BugFix
  qboolean  ff;
</C&NBSP;CODE>

Now, at the very bottom of this same function add:
<C&NBSP;CODE>
        gi_bprintf (PRINT_MEDIUM,"%s %s %s%s\n", self->client->pers.netname, message, attacker->client->pers.netname, message2);
        if (deathmatch->value)
        {
          TauntVictim(attacker,self);  // Maj++ - Add this line here
          InsultVictim(attacker,self); // Maj++ - Add this line here
          if (ff)
            attacker->client->resp.score--;
          else
            attacker->client->resp.score++;
        }
</C&NBSP;CODE>

At the top of your LookAtKiller() function add:
<C&NBSP;CODE>
void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker)
{
  vec3_t    dir;

  if (self->isabot) return; // Maj++ - Add this line here
</C&NBSP;CODE>

In the middle of your player_die() function change:
<C&NBSP;CODE>
  self->svflags |= SVF_DEADMONSTER;

  if (!self->deadflag)
  {
    self->client->respawn_time=level.time + 1.0;
// Maj++ --------------------------------------------
    if (self->isabot)
      self->client->respawn_time += 4.0; // Increase to 4+1=5 for bots
//---------------------------------------------------
    LookAtKiller (self, inflictor, attacker);
    self->client->ps.pmove.pm_type = PM_DEAD;
    ClientObituary(self, inflictor, attacker);
</C&NBSP;CODE>

Further down in this same function add:
<C&NBSP;CODE>
  self->client->breather_framenum = 0;
  self->client->enviro_framenum = 0;
  self->flags &= ~FL_POWER_ARMOR;

  if (self->health < -40 || !self->isabot) // Maj++ - Make this quick change
  {  // gib
    gi.sound (self, CHAN_BODY, gi.soundindex ("misc/udeath.wav"), 1, ATTN_NORM, 0);
    for (n= 0; n < 4; n++)
      ThrowGib (self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC);
</C&NBSP;CODE>

At the bottom of your SelectSpawnPoint() function add:
<C&NBSP;CODE>
  VectorCopy (spot->s.origin, origin);
  origin[2] += 9;
  if (ent->isabot) origin[2] += 21; // Maj++ - Add this line here
  VectorCopy (spot->s.angles, angles);
}
</C&NBSP;CODE>

At the top of your respawn() function, add this:
<C&NBSP;CODE>
void respawn (edict_t *self)
{
  if (deathmatch->value || coop->value)
  {
    // spectator's don't leave bodies
    if (self->movetype != MOVETYPE_NOCLIP)
      CopyToBodyQue (self);
    self->svflags &= ~SVF_NOCLIENT;

    if (self->isabot) return; // Maj++ - Add this line here

    PutClientInServer (self);
</C&NBSP;CODE>

In the middle of your PutClientInServer() function add:
<C&NBSP;CODE>
  ent->s.angles[PITCH] = 0;
  ent->s.angles[YAW] = spawn_angles[YAW];
  ent->s.angles[ROLL] = 0;
  VectorCopy (ent->s.angles, client->ps.viewangles);
  VectorCopy (ent->s.angles, client->v_angle);

  SetBotThink(ent); // Maj++ - Add this line here

  // spawn a spectator
  if (client->pers.spectator) {
    client->chase_target = NULL;
    client->resp.spectator = true;
</C&NBSP;CODE>

Comment out your ClientThink() function and substitute
this function for it OR make the indicated change.
<C&NBSP;CODE>
// Maj++ - This ClientThink allows chasecaming of bots
// Original Source did not. ??  Haven't taken time to
// see exactly what diff was between the two.  But...

void ClientThink(edict_t *ent, usercmd_t *ucmd){
edict_t *other;
int i, j=0;
pmove_t pm;

  level.current_entity = ent;

  if (level.intermissiontime) {
    ent->client->ps.pmove.pm_type = PM_FREEZE;
    if (level.time > level.intermissiontime+5.0)
      if (ucmd->buttons & BUTTON_ANY)
        level.exitintermission = true;
    return; }

  if (ent->isabot) return; // Maj++ - Add this line here

  pm_passent = ent;

  if (ent->client->chase_target) {
    ent->client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
    ent->client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
    ent->client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]); }
  else {
    memset(&pm, 0, sizeof(pm));
    if (ent->movetype == MOVETYPE_NOCLIP)
      ent->client->ps.pmove.pm_type = PM_SPECTATOR;
    else if (ent->s.modelindex != 255)
      ent->client->ps.pmove.pm_type = PM_GIB;
    else if (ent->deadflag)
      ent->client->ps.pmove.pm_type = PM_DEAD;
    else
      ent->client->ps.pmove.pm_type = PM_NORMAL;
    ent->client->ps.pmove.gravity = 800;
    pm.s = ent->client->ps.pmove;
    for (i=0; i<3; i++){
      pm.s.origin[i] = ent->s.origin[i]*8;
      pm.s.velocity[i] = ent->velocity[i]*8;}
    if (memcmp(&ent->client->old_pmove, &pm.s, sizeof(pm.s)))
      pm.snapinitial = true;
    pm.cmd = *ucmd;
    pm.trace = PM_trace;
    pm.pointcontents = gi.pointcontents;
    gi.Pmove(&pm);
    ent->client->ps.pmove = pm.s;
    ent->client->old_pmove = pm.s;
    for (i=0; i<3; i++){
      ent->s.origin[i] = pm.s.origin[i]*0.125;
      ent->velocity[i] = pm.s.velocity[i]*0.125;}
    VectorCopy(pm.mins, ent->mins);
    VectorCopy(pm.maxs, ent->maxs);
    ent->client->resp.cmd_angles[0] = SHORT2ANGLE(ucmd->angles[0]);
    ent->client->resp.cmd_angles[1] = SHORT2ANGLE(ucmd->angles[1]);
    ent->client->resp.cmd_angles[2] = SHORT2ANGLE(ucmd->angles[2]);
    if (ent->groundentity && !pm.groundentity)
      if ((pm.cmd.upmove >= 10) && (pm.waterlevel == 0))
        gi.sound(ent, 2, gi.soundindex("*jump1.wav"), 1, 1, 0);
    ent->viewheight = pm.viewheight;
    ent->waterlevel = pm.waterlevel;
    ent->watertype = pm.watertype;
    ent->groundentity = pm.groundentity;
    if (pm.groundentity)
      ent->groundentity_linkcount = pm.groundentity->linkcount;
    if (ent->deadflag){
      ent->client->ps.viewangles[2] = 40;
      ent->client->ps.viewangles[0] = -15;
      ent->client->ps.viewangles[1] = ent->client->killer_yaw;}
    else{
      VectorCopy(pm.viewangles, ent->client->v_angle);
      VectorCopy(pm.viewangles, ent->client->ps.viewangles);}
    gi.linkentity(ent);
    if (ent->movetype != MOVETYPE_NOCLIP)
      G_TouchTriggers(ent);
    for (i=0; i<pm.numtouch; i++){
      other = pm.touchents[i];
      for (j=0; j<i; j++)
        if (pm.touchents[j] == other) break;
      if (j != i) continue;
      if (!other->touch) continue;
      other->touch(other, ent, NULL, NULL);}}

  ent->client->oldbuttons = ent->client->buttons;
  ent->client->buttons = ucmd->buttons;
  ent->client->latched_buttons |= ent->client->buttons & ~ent->client->oldbuttons;

  if (ent->client->latched_buttons & BUTTON_ATTACK){
    if (ent->client->resp.spectator) {
      ent->client->latched_buttons = 0;
      if (ent->client->chase_target) {
        ent->client->chase_target = NULL;
        ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;}
      else
        GetChaseTarget(ent);}
    else if (!ent->client->weapon_thunk) {
      ent->client->weapon_thunk = true;
      Think_Weapon(ent);}}

  if (ent->client->resp.spectator) {
    if (ucmd->upmove >= 10) {
      if (!(ent->client->ps.pmove.pm_flags & PMF_JUMP_HELD)) {
        ent->client->ps.pmove.pm_flags |= PMF_JUMP_HELD;
        if (ent->client->chase_target)
          ChaseNext(ent);
        else
          GetChaseTarget(ent);}}
    else
      ent->client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD;}

  for (i=1; i <= game.maxclients; i++) {
    other = g_edicts+i;
    if (other->inuse && ent->client->chase_target == other)
      UpdateChaseCam(ent);}
}
</C&NBSP;CODE>

In the middle of your ClientBeginServerFrame() add:
<C&NBSP;CODE>
  if (deathmatch->value &&
    client->pers.spectator != client->resp.spectator &&
    (level.time - client->respawn_time) >= 5) {
    spectator_respawn(ent);
    return;
  }

  if (ent->isabot) SV_Physics_Step(ent); // Maj++ - Add this line here

  // run weapon animations if it hasn't been done by a ucmd_t
  if (!client->weapon_thunk && !client->resp.spectator)
    Think_Weapon (ent);
  else
    client->weapon_thunk = false;
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Open your g_items.c file and make these changes:

Near the bottom of your DoRespawn() function add:
<C&NBSP;CODE>
  ent->svflags &= ~SVF_NOCLIENT;
  ent->solid = SOLID_TRIGGER;
  gi.linkentity (ent);

  if (ent->classname[0] == 'R') return; // Maj++ - Add this line here

  // send an effect
  ent->s.event = EV_ITEM_RESPAWN;
}
</C&NBSP;CODE>

In the middle of your Pickup_Health() function add:
<C&NBSP;CODE>
  if (ent->style & HEALTH_TIMED)
  {
    CheckCampSite(ent,other); // Maj++ - Add this line here..
    ent->think = MegaHealth_think;
    ent->nextthink = level.time + 5;
    ent->owner = other;
</C&NBSP;CODE>

In the middle of your Touch_Item() function add:
<C&NBSP;CODE>
    // change selected item
    if (ent->item->use)
      other->client->pers.selected_item = other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item);

    CheckPrimaryWeapon(ent,other); // Maj++ - Add this line right here

    if (ent->classname[0] != 'R') // Maj++ - Add this line here to nest if statement
    if (ent->item->pickup == Pickup_Health)
    {
</C&NBSP;CODE>

At the top of your itemlist[] array add:
<C&NBSP;CODE>
gitem_t  itemlist[] =
{
  {
    NULL
  },  // leave index 0 alone

//Maj++ ----------------------------------------
  //
  // NAVIGATION NODES
  //
  {
   "R_navi1",
    Pickup_Navi,
    NULL,
    NULL,
    NULL,
    NULL,
    "models/items/keys/spinner/tris.md2", 0,
    NULL,
    NULL,
    "Roam Navi1",
    2,
    10,
    NULL,
    IT_NODE,
    0,
    NULL,
    0,
  ""
  },

  {
    "R_navi2",
    Pickup_Navi,
    NULL,
    NULL,
    NULL,
    NULL,
    "models/items/keys/spinner/tris.md2", 0,
    NULL,
    NULL,
    "Roam Navi2",
    2,
    30,
    NULL,
    IT_NODE,
    0,
    NULL,
    0,
  ""
  },
  {
    "R_navi3",
    Pickup_Navi,
    NULL,
    NULL,
    NULL,
    NULL,
    "models/items/keys/spinner/tris.md2", 0,
    NULL,
    NULL,
    "Roam Navi3",
    2,
    20,
    NULL,
    IT_NODE,
    0,
    NULL,
    0,
  ""
  },
//---------------------------------------------------
</C&NBSP;CODE>

Further in your itemlist[] array make these changes too..
<C&NBSP;CODE>
/* icon */    "p_adrenaline",
/* pickup */  "Adrenaline",
/* width */    2,
    60,
    NULL,
    IT_HEALTH, // Maj++ - Change to this flag
    0,
    NULL,
    0,
    ""
  },


/* icon */    "p_bandolier",
/* pickup */  "Bandolier",
/* width */    2,
    60,
    NULL,
    IT_PACK, // Maj++ - Change to this flag
    0,
    NULL,
    0,
    ""
  },

/* icon */    "i_pack",
/* pickup */  "Ammo Pack",
/* width */    2,
    180,
    NULL,
    IT_PACK, // Maj++ - Change to this flag
    0,
    NULL,
    0,
    ""
  },


/* icon */    "i_health",
/* pickup */  "Health",
/* width */    3,
    0,
    NULL,
    IT_HEALTH, // Maj++ - Change to this flag here..
    0,
    NULL,
    0,
    "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav"
  },

  // end of list marker
  {NULL}
};
</C&NBSP;CODE>

Now, in your SetItemNames() function add:
<C&NBSP;CODE>
void SetItemNames (void)
{
  int    i;
  gitem_t  *it;

  for (i=0 ; i<game.num_items ; i++)
  {
    it = &itemlist[i];
    gi.configstring (CS_ITEMS+i, it->pickup_name);
  }

  InitAllItems(); // Maj++ - put this here...
</C&NBSP;CODE>

Okay, we're done with this file now. Close it out

=====================================================
Now, CREATE a new file called bot.h and paste this:
<C&NBSP;CODE>
//Maj++--------------------------------------------
#define MAXNODES  10000

typedef struct {
  vec3_t Pt;
  union {
    vec3_t Tcourner;
    unsigned short linkpod[6]; };
  edict_t *ent;
  short index;
  short state;
} route_t;

extern route_t Route[MAXNODES];
extern int TotalRouteNodes;

#define MAXBOTS 51 // Number of name/skin entries

typedef  struct {
  char netname[21]; //netname
  char skin[64];    //skin
  int  ingame;      //spawned
  int  skill[6];    //parameters
} botinfo_t;

extern botinfo_t Bot[MAXBOTS+1];

void gi_cprintf(edict_t *ent, int printlevel, char *fmt, ...);
void gi_centerprintf(edict_t *ent, char *fmt, ...);
void gi_bprintf(int printlevel, char *fmt, ...);

void Use_Plat(edict_t *ent, edict_t *other, edict_t *activator);
void train_use(edict_t *self, edict_t *other, edict_t *activator);
void button_use(edict_t *self, edict_t *other, edict_t *activator);
void door_use(edict_t *self, edict_t *other, edict_t *activator);
void rotating_use(edict_t *self, edict_t *other, edict_t *activator);
void trigger_relay_use(edict_t *self, edict_t *other, edict_t *activator);
void plat_go_up(edict_t *ent);
void SV_Physics_Step(edict_t *ent);
void path_corner_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
void DoRespawn(edict_t *ent);
void Cmd_Kill_f(edict_t *ent);
void ClientUserinfoChanged(edict_t *ent, char *userinfo);
void ClientDisconnect(edict_t *ent);
void Use_Item(edict_t *ent, edict_t *other, edict_t *activator);

//weapon
int WeapIndex(int weap);
void Weapon_Blaster (edict_t *ent);
void Weapon_Shotgun (edict_t *ent);
void Weapon_SuperShotgun (edict_t *ent);
void Weapon_Machinegun (edict_t *ent);
void Weapon_Chaingun (edict_t *ent);
void Weapon_HyperBlaster (edict_t *ent);
void Weapon_RocketLauncher (edict_t *ent);
void Weapon_Grenade (edict_t *ent);
void Weapon_GrenadeLauncher (edict_t *ent);
void Weapon_Railgun (edict_t *ent);
void Weapon_BFG (edict_t *ent);

void LoadBotNames(void);
int GetKindWeapon(gitem_t *it);
void ReadRouteFile(void);
void droptofloor2(edict_t *ent);
void SpawnNumBots(int num);
void RespawnAllBots(void);
void TauntVictim(edict_t *ent, edict_t *victim);
void InsultVictim(edict_t *ent, edict_t *victim);
qboolean Pickup_Navi(edict_t *ent, edict_t *other);
void BotThink(edict_t *ent);
void CheckCampSite(edict_t *ent,edict_t *other);
void bFuncPlat(edict_t *ent);
void bFuncButton(edict_t *ent);
void bDoorBlocked(edict_t *ent);
void bFuncDoor(edict_t *ent);
void bFuncTrain(edict_t *ent);
void ResetGroundSlope(edict_t *ent);
void TraceAllSolid(edict_t *ent, vec3_t point, trace_t tr);
void CheckPrimaryWeapon(edict_t *ent, edict_t *other);
void G_FindTrainTeam(void);
void ForceRouteReset(edict_t *other);
void InitAllItems(void);
float SetBotXYSpeed(edict_t *ent, float *xyspeed);
void SetBotThink(edict_t *ent);
void CheckBotCrushed(edict_t *targ,edict_t *inflictor,int mod);
void BotCheckEnemy(gclient_t *client, edict_t *attacker, edict_t *targ, int mod);
void RemoveAllBots(void);
void BotCheckGrapple(edict_t *ent);
//---------------------------------------------------
</C&NBSP;CODE>

Save this new file...

=====================================================
LASTLY!! CREATE a new file called bot.c and paste:

<C&NBSP;CODE>
//Maj++----------------------------------------------
#include "g_local.h"
#include "m_player.h"

// Special thanks to Ponpoko for his brilliant work upon
// which this work is based.

#define MaxOf(x,y) ((x)>(y)?(x):(y))

#define DEG2RAD(a) ((a)*(M_PI/180.0F))

//skill[]
#define AIMACCURACY    0 // 0..9 aiming accuracy
#define AGGRESSION     1 // 0..9
#define COMBATSKILL    2 // 0..9
#define VRANGEVIEW     3 // 60-120 deg - Vertical view range
#define HRANGEVIEW     4 // 60-120 deg - Horizontal view range
#define PRIMARYWEAP    5 // 1..11  - 1=Blaster, 11=BFG

//=====================================================
//=====================================================

vec3_t zvec = {0,0,0};

float myrandom=0.5F;

int TotalRouteNodes=0;
route_t Route[MAXNODES];

botinfo_t Bot[MAXBOTS+1];

qboolean pickup_priority=false;
int trace_priority=0;
float JumpMax=0.0F;

int NumBotsInGame=0; // [1..MAXBOTS]

int SkillLevel[10] = {
  0x00001000|0x10000000|0x00000002|0x00000004,
  0x00001000|0x10000000|0x00000002|0x00000004,
  0x00001000|0x10000000|0x00000002,
  0x00001000|0x10000000|0x00000002,
  0x00001000|0x10000000,
  0x00001000|0x10000000,
  0x00001000|0x10000000,
  0x00001000|0x00000010,
  0x10000000|0x00000010,
  0x10000000|0x00000010
};

typedef char cfg[64];
typedef cfg cfg_t[2];
cfg_t gbot[] = { // ENTRIES MUST == MAXBOTS, else kabooom!
{  "Tyr574",   "cyborg/tyr574"   },
{  "Razor",    "male/razor"      },
{  "Cobalt",   "female/cobalt"   },
{  "Scout",    "male/scout"      },
{  "ps9000",   "cyborg/ps9000"   },
{  "Brianna",  "female/brianna"  },
{  "Recon",    "male/recon"      },
{  "Viper",    "male/viper"      },
{  "oni911",   "cyborg/oni911"   },
{  "Flak",     "male/flak"       },

{  "Venus",    "female/venus"    },
{  "Pointman", "male/pointman"   },
{  "Stiletto", "female/stiletto" },
{  "Claymore", "male/claymore"   },
{  "Jezebel",  "female/jezebel"  },
{  "Cypher",   "male/cypher"     },
{  "Athena",   "female/athena"   },
{  "Major",    "male/major"      },
{  "Jungle",   "female/jungle"   },
{  "Howitzer", "male/howitzer"   },

{  "Ensign",   "female/ensign"   },
{  "NightOps", "male/nightops"   },
{  "Psycho",   "male/psycho"     },
{  "Voodoo",   "female/voodoo"   },
{  "Rampage",  "male/rampage"    },
{  "Brazen",   "cyborg/tyr574"   },
{  "Zeroid",   "male/razor"      },
{  "Busted",   "female/cobalt"   },
{  "Lotus",    "female/lotus"    },
{  "Grunt",    "male/grunt"      },

{  "Flyte",    "female/venus"    },
{  "Avenger",  "male/pointman"   },
{  "WetSpot",  "female/stiletto" },
{  "Bomber",   "male/claymore"   },
{  "Shaved",   "female/jezebel"  },
{  "BadBoy",   "male/cypher"     },
{  "Nailz",    "female/athena"   },
{  "Sarge",    "male/major"      },
{  "Hairless", "female/jungle"   },
{  "PlowBoy",  "male/howitzer"   },

{  "T1000",    "cyborg/tyr574"   },
{  "Copper",   "male/razor"      },
{  "Queen",    "female/cobalt"   },
{  "Private",  "male/scout"      },
{  "z111x",    "cyborg/ps9000"   },
{  "Virgin",   "female/brianna"  },
{  "Sniper",   "male/recon"      },
{  "Violator", "male/viper"      },
{  "rs3434",   "cyborg/oni911"   },
{  "Metal",    "male/flak"       }, // Count=50

{  "Killer",   "male/grunt"      }, // 1 extra for safety
};
//===================================
//===================================

//======================================================
//========== BASIC BOT UTILITY FUNCTIONS ===============
//======================================================

//======================================================
qboolean G_EntExists(edict_t *ent) {
  return ((ent) && (ent->client) && (ent->inuse));
}

//======================================================
qboolean G_ClientNotDead(edict_t *ent) {
qboolean buried=true;
qboolean b1=(ent->client->ps.pmove.pm_type!=PM_DEAD);
qboolean b2=(ent->deadflag==DEAD_NO);
qboolean b3=(ent->health > 0);
  return ((b3 || b2 || b1) && buried);
}

//======================================================
qboolean G_ClientInGame(edict_t *ent) {
  if (!G_EntExists(ent)) return false;
  if (!G_ClientNotDead(ent)) return false;
  if (ent->client->pers.spectator) return false;
  return (ent->client->respawn_time + 5.0 < level.time);
}

//==============================================
float Get_yaw(vec3_t vec) {
vec3_t out;
double yaw;
  VectorCopy(vec,out);
  out[2] = 0;
  VectorNormalize(out);
  yaw = (double)(acos((double)out[0]))/M_PI*180;
  if (asin((double)out[1])<0) yaw *= -1;
  return (float)yaw;
}

//==============================================
float Get_pitch(vec3_t vec) {
vec3_t out;
float pitch;
  VectorCopy(vec,out);
  VectorNormalize(out);
  pitch = (((float)(acos((double)out[2])))/M_PI*180)-90;
  return (float)((pitch<-180)?(pitch+360):pitch);
}

//======================================================
void AdjustAngle(edict_t *ent, vec3_t targaim, float aim) {

  VectorSet(ent->s.angles,(Get_pitch(targaim)),(Get_yaw(targaim)),0.0F);

  ent->s.angles[1] += aim*0.70*(myrandom-0.5);
  if (ent->s.angles[1] > 180)
    ent->s.angles[1] -= 360;
  else
  if (ent->s.angles[1] < -180)
    ent->s.angles[1] += 360;

  ent->s.angles[0] += aim*0.70*(myrandom-0.5);
  if (ent->s.angles[0] > 90)
    ent->s.angles[0] = 90;
  else
  if (ent->s.angles[0] < -90)
    ent->s.angles[0] = -90;
}

//=============================================
qboolean BankCheck(edict_t *ent, vec3_t pos) {
trace_t tr;
vec3_t end;
  VectorCopy(pos,end);
  end[2] -= 4096;
  tr = gi.trace(pos, ent->mins, ent->maxs, end, ent, MASK_BOTSOLIDX);
  return !(tr.startsolid || tr.allsolid || tr.plane.normal[2] < 0.8);
}

//=============================================
qboolean HazardCheck(edict_t *ent, vec3_t pos) {
trace_t tr;
vec3_t end;
int contents;
  VectorCopy(pos,end);
  end[2] -= 8192;
  contents = (ent->client->enviro_framenum>level.framenum)?CONTENTS_LAVA:(CONTENTS_LAVA|CONTENTS_SLIME);
  tr = gi.trace(pos, ent->mins, ent->maxs, end, ent, MASK_OPAQUE);
  return !(tr.contents & contents);
}

//==============================================
void SetBotAnim(edict_t *ent) {
  gi.sound(ent, CHAN_VOICE, gi.soundindex("*jump1.wav"), 1, ATTN_NORM, 0);
  if (ent->client->anim_priority >= ANIM_JUMP) return;
  ent->s.frame = FRAME_jump1-1;
  ent->client->anim_end = FRAME_jump6;
}

//============================================================
qboolean Get_FlyingSpeed(float bottom,float block,float dist,float *speed) {
float tdist;

  if (bottom >= 40) {
    if (block > 4) return false;
    tdist = (dist*block)*.250; }
  else if (bottom >= 35) {
    if (block > 5) return false;
    tdist = (dist*block)*.200; }
  else if (bottom >= 30) {
    if (block > 6) return false;
    tdist = (dist*block)*.167; }
  else if (bottom >= 20) {
    if (block > 7) return false;
    tdist = (dist*block)*.143; }
  else if (bottom >= -5) {
    if (block > 8) return false;
    tdist = (dist*block)*.125; }
  else if (bottom >= -20) {
    if (block > 9) return false;
    tdist = (dist*block)*.143; }
  else if (bottom >= -35) {
    if (block > 10) return false;
    tdist = (dist*block)*.167; }
  else if (bottom >= -52) {
    if (block > 11) return false;
    tdist = (dist*block)*.200; }
  else if (bottom >= -75) {
    if (block > 12) return false;
    tdist = (dist*block)*.250; }
  else if (bottom >= -95) {
    if (block > 13) return false;
    tdist = (dist*block)*.333; }
  else if (bottom >=-125) {
    if (block > 14) return false;
    tdist = (dist*block)*.500; }
  else {
    if (block > 15) return false;
    tdist = (dist*block)*.500; }

  *speed = tdist/30;

  return true;
}

//==========================================
float SetBotXYSpeed(edict_t *ent, float *xyspeed) {

  if (!ent->isabot) return *xyspeed;

  if (ent->groundentity && ent->client->movestate & (0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800)) {
    *xyspeed = (VectorLength(ent->groundentity->velocity)<1)?300:0;
    if (*xyspeed)
      ent->client->movestate |= 0x00000010; } // Don't move
  else
    *xyspeed = (ent->client->camptime > level.time)?0:300;

  return *xyspeed;
}

//==========================================
void SetBotThink(edict_t *ent) {

  if (!ent->isabot) return;

  ent->client->chattime = level.time+60+(20*rand()%6);
  ent->client->ping = atoi(Info_ValueForKey(ent->client->pers.userinfo,"ping"));
  ent->think = BotThink;
  ent->nextthink = level.time+0.1;
}

//==========================================
void ForceRouteReset(edict_t *other) {

  if (!other->isabot) return;

  if (!other->client->routetrace) return;

  if (other->client->pers.routeindex < TotalRouteNodes) {
    if (Route[other->client->pers.routeindex].state == 2)
      other->client->pers.routeindex++;
    if (other->client->pers.routeindex < TotalRouteNodes)
      if (Route[other->client->pers.routeindex].state == 22)
        other->client->pers.routeindex++; }
}

//==========================================
void G_FindTrainTeam(void) {
edict_t *e;
int i;

  e = &g_edicts[(int)maxclients->value+1];
  for (i=maxclients->value+1; i<globals.num_edicts; i++, e++) {
    if (e->inuse && e->classname) {
      if (e->touch == path_corner_touch && e->targetname && e->target) {
        qboolean findteam = false;
        char *currtarget = e->target;         //target
        char *currtargetname = e->targetname; //targetname
        int k,lc=0,loopindex=0;
        edict_t *teamlist[1025];
        char *targethist[1024];
        memset(&teamlist,0,sizeof(teamlist));
        memset(&targethist,0,sizeof(targethist));
        targethist[0] = e->targetname;
        while (lc < MAX_EDICTS) {
          int j;
          edict_t *p,*t=&g_edicts[(int)maxclients->value+1];
          for (j=maxclients->value+1; j<globals.num_edicts; j++, t++)
            if (t->inuse && t->classname && !t->trainteam)
              if (t->use == train_use)
                if (!stricmp(t->target,currtargetname))
                  for (k=0;k<lc;k++)
                    if (teamlist[k] == t)
                      break;
          if (k == lc) {
            teamlist[lc] = t;
            lc++; }
          p = G_PickTarget(currtarget);
          if (!p) break; // exit while
          currtarget = p->target;
          currtargetname = p->targetname;
          if (!p->target) break; // exit while
          for (k=0;k<loopindex;k++)
            if (!stricmp(targethist[k],currtargetname))
              break;
          if (k < loopindex) {
            findteam = true;
            break; }
          targethist[loopindex] = currtargetname;
          loopindex++; }
        if (findteam && lc > 0)
          for (k=0;k<lc;k++) {
            if (!teamlist[k+1]) {
              teamlist[k]->trainteam = teamlist[0];
              break; }
            teamlist[k]->trainteam = teamlist[k+1]; } } } }
}

//==============================================
void droptofloor2(edict_t *ent) {
vec3_t trmin,trmax,min,mins,maxs;
float i,j=0,yaw;
trace_t tr;
vec3_t v,dest;

  VectorSet(ent->mins,-15,-15,-15);
  VectorSet(ent->maxs,8,8,15);

  if (ent->union_ent && ent->item == item_navi2) {
    dest[0] = (ent->union_ent->s.origin[0]+ent->union_ent->mins[0]+ent->union_ent->s.origin[0]+ent->union_ent->maxs[0])*0.5;
    dest[1] = (ent->union_ent->s.origin[1]+ent->union_ent->mins[1]+ent->union_ent->s.origin[1]+ent->union_ent->maxs[1])*0.5;
    for (i=ent->union_ent->s.origin[2]+ent->union_ent->mins[2]; i <= ent->union_ent->s.origin[2]+ent->union_ent->maxs[2] +16; i++) {
      dest[2] = i;
      tr = gi.trace(dest,ent->mins,ent->maxs,dest,ent,MASK_SOLID);
      if ((!tr.startsolid && !tr.allsolid) && j==1) {
        j = 2; break; }
      else
      if ((tr.startsolid || tr.allsolid) && (!j) && tr.ent == ent->union_ent)
        j = 1; }
    VectorCopy(dest,ent->s.origin);
    VectorSubtract(ent->s.origin,ent->union_ent->s.origin,ent->moveinfo.dir); }

  ent->s.modelindex = 0;
  ent->solid = (ent->item == item_navi3)?SOLID_NOT:SOLID_TRIGGER;
  ent->movetype = MOVETYPE_TOSS;
  ent->touch = Touch_Item;
  ent->use = NULL;

  VectorSet(v,0,0,-128);
  VectorAdd(ent->s.origin, v, dest);

  tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
  if (tr.startsolid && ent->item == item_navi1) {
    G_FreeEdict(ent);
    return; }

  VectorCopy(tr.endpos, ent->s.origin);

  if (ent->team) {
    ent->flags &= ~FL_TEAMSLAVE;
    ent->chain = ent->teamchain;
    ent->teamchain = NULL;
    ent->svflags |= SVF_NOCLIENT;
    ent->solid = SOLID_NOT;
    if (ent == ent->teammaster) {
      ent->nextthink = level.time+FRAMETIME;
      ent->think = DoRespawn; } }

  if (ent->spawnflags & 2) {
    ent->solid = SOLID_BBOX;
    ent->touch = NULL;
    ent->s.effects &= ~EF_ROTATE;
    ent->s.renderfx &= ~RF_GLOW; }

  if (ent->spawnflags & 1) {
    ent->svflags |= SVF_NOCLIENT;
    ent->solid = SOLID_NOT;
    ent->use = Use_Item; }

  gi.linkentity(ent);

  VectorCopy(ent->s.origin,min);
  VectorSet(mins, -15, -15, -15);
  VectorSet(maxs, 8, 8, 0);
  min[2] -= 128;

  for (i=0;i<8;i++) {
    if (i<4) {
      yaw = DEG2RAD(90*i-180);
      for (j=32;j<80;j+=2) {
        trmin[0] = ent->s.origin[0]+cos(yaw)*j;
        trmin[1] = ent->s.origin[1]+sin(yaw)*j;
        trmin[2] = ent->s.origin[2];
        VectorCopy(trmin,trmax);
        trmax[2] -= 128;
        tr = gi.trace(trmin, mins, maxs, trmax, ent, MASK_PLAYERSOLID);
        if (tr.endpos[2] < ent->s.origin[2]-16 && tr.endpos[2] > min[2])
          if (!tr.allsolid && !tr.startsolid) {
            min[2] = tr.endpos[2];
            min[0] = ent->s.origin[0]+cos(yaw)*(j+16);
            min[1] = ent->s.origin[1]+sin(yaw)*(j+16);
            break; } } }
    else {
      yaw = DEG2RAD(90*(i-4)-135);
      for (j=32;j<80;j+=2) {
        trmin[0] = ent->s.origin[0]+cos(yaw)*46;
        trmin[1] = ent->s.origin[1]+sin(yaw)*46;
        trmin[2] = ent->s.origin[2];
        VectorCopy(trmin,trmax);
        trmax[2] -= 128;
        tr = gi.trace(trmin, NULL, NULL, trmax, ent, MASK_PLAYERSOLID);
        if (tr.endpos[2] < ent->s.origin[2]-16 && tr.endpos[2] > min[2])
          if (!tr.allsolid && !tr.startsolid) {
            VectorCopy(tr.endpos,min);
            break; } } } }

  VectorCopy(min,ent->moveinfo.start_origin);
}

//==============================================
void TraceAllSolid(edict_t *ent, vec3_t point, trace_t tr) {

  if (tr.allsolid) {
    vec3_t stp,v1,v2;
    trace_t tracep;
    VectorSet(v1,-16,-16,-24);
    VectorSet(v2,16,16,4);
    VectorCopy(ent->s.origin,stp);
    stp[2] += 24;
    tracep = gi.trace(stp, v1, v2, point, ent, MASK_BOTSOLID);
    if (tracep.ent && !tracep.allsolid)
      if (tracep.ent->classname[0] == 'f') {
        VectorCopy(tracep.endpos,ent->s.origin);
        ent->groundentity = tracep.ent;
        ent->groundentity_linkcount = tracep.ent->linkcount;
        gi.linkentity(ent);
        return; } }
  else {
    if (ent->client) {
      ent->client->ground_contents = tr.contents;
      ent->client->ground_slope = tr.plane.normal[2]; }
    VectorCopy(tr.endpos, ent->s.origin);
    ent->groundentity = tr.ent;
    ent->groundentity_linkcount = tr.ent->linkcount; }

  gi.linkentity(ent);
}

//==============================================
void ResetGroundSlope(edict_t *ent) {
  if (!ent->isabot) return;
  ent->client->ground_slope = 1.0;
}

//==============================================
void SpawnItem3(edict_t *it_ent, gitem_t *item) {
  it_ent->item = item;
  it_ent->s.effects = 0;
  it_ent->s.renderfx = 0;
  it_ent->s.modelindex = 0;
  it_ent->nextthink = level.time+0.2;
  it_ent->think = droptofloor2;
}

//===============================================
void bFuncTrain(edict_t *self) {
gitem_t *it = item_navi1;
edict_t *it_ent = G_Spawn();

  VectorAdd(self->s.origin,self->mins,self->monsterinfo.last_sighting);
  it_ent->classname = it->classname;
  it_ent->union_ent = self;
  self->union_ent = it_ent;
  SpawnItem3(it_ent,it);
}

//===============================================
void bFuncDoor(edict_t *ent) {

  VectorAdd(ent->s.origin,ent->mins,ent->monsterinfo.last_sighting);
  if (fabs(ent->moveinfo.start_origin[2]-ent->moveinfo.end_origin[2]) > 20) {
    gitem_t *it=item_navi3;
    edict_t *it_ent=G_Spawn();
    it_ent->classname = it->classname;
    it_ent->union_ent = ent;
    ent->union_ent = it_ent;
    SpawnItem3(it_ent,it); }
}

//===============================================
void bDoorBlocked(edict_t *self) {
int i;
  for (i=1; i<=maxclients->value; i++) {
    edict_t *ent = &g_edicts[i];
    if (!ent->isabot) continue;
    if (ent->client->waiting_obj != self) continue;
    if (!ent->client->movestate) continue;
    ent->client->movestate |= 0x00000010; }
}

//===============================================
void bFuncButton(edict_t *ent) {
vec3_t tdir,tdir2;
vec3_t abs_movedir;
float dist=1;
gitem_t *it=item_navi2;
edict_t *it_ent=G_Spawn();

  VectorAdd(ent->s.origin,ent->mins,ent->monsterinfo.last_sighting);
  it_ent->classname = it->classname;
  VectorCopy(ent->s.origin,it_ent->s.origin);
  it_ent->s.origin[0] = (ent->absmin[0]+ent->absmax[0])*0.5;
  it_ent->s.origin[1] = (ent->absmin[1]+ent->absmax[1])*0.5;
  it_ent->s.origin[2] = (ent->absmin[2]+ent->absmax[2])*0.5;
  it_ent->union_ent = ent;
  ent->union_ent = it_ent;
  VectorSubtract(ent->moveinfo.start_origin, ent->moveinfo.end_origin, abs_movedir);
  VectorNormalize(abs_movedir);
  while (dist < 500) {
    VectorScale(abs_movedir, dist, tdir);
    VectorAdd(it_ent->s.origin,tdir,tdir2);
    if (!(gi.pointcontents(tdir2) & CONTENTS_SOLID)) break;
    dist++; }
  VectorScale(abs_movedir,(dist+20), tdir);
  VectorAdd(it_ent->s.origin,tdir,tdir2);
  VectorCopy(tdir2,it_ent->s.origin);
  it_ent->item = it;
  it_ent->s.effects = 0;
  it_ent->s.renderfx = 0;
  it_ent->s.modelindex  = 0;
  it_ent->solid = SOLID_TRIGGER;
  it_ent->movetype = MOVETYPE_NONE;
  it_ent->touch = Touch_Item;
  gi.linkentity(it_ent);
}

//===============================================
void bFuncPlat(edict_t *ent) {
gitem_t *it=item_navi1;
edict_t *it_ent=G_Spawn();

  VectorAdd(ent->s.origin,ent->mins,ent->monsterinfo.last_sighting);
  it_ent->classname = it->classname;
  it_ent->union_ent = ent;
  ent->union_ent = it_ent;
  SpawnItem3(it_ent,it);
}

//===============================================
void CheckBotCrushed(edict_t *targ,edict_t *inflictor,int mod) {

  if (!targ->isabot || mod != MOD_CRUSH) return;

  if ((targ->client->waiting_obj == inflictor && targ->client->movestate) || targ->groundentity == inflictor)
    targ->client->movestate |= 0x00000010;
}

//===============================================
void CheckPrimaryWeapon(edict_t *ent, edict_t *other) {

  if (!other->isabot) return;

  if (ent->item->use) {
    // Switch weapon if picked up primary
    int k = GetKindWeapon(ent->item);
    if (k != WEAP_BLASTER) {
      if (Bot[other->client->pers.botindex].skill[6] == k)
        ent->item->use(other,ent->item); } }
}

//===============================================
void BotCheckEnemy(gclient_t *client, edict_t *attacker, edict_t *targ, int mod) {

  if (client && targ->isabot && attacker) {
    if (client->battlemode & 0x00000008)
      client->battlemode &= ~0x00000008;
    if (mod & MOD_RAILGUN|MOD_ROCKET|MOD_BLASTER|MOD_HYPERBLASTER) {
      if (attacker->client && !client->current_enemy)
        if (!OnSameTeam(targ, attacker))
          client->current_enemy = attacker; } }
}

//===============================================
void CheckCampSite(edict_t *ent, edict_t *other) {

  if (!other->isabot) return;

  if (ent->item != item_health_mega && ent->item != item_railgun) return;

  if (ent->item == item_health_mega && other->client->pers.weapon == item_blaster)
    return;

  if (other->client->quad_framenum > level.framenum) return;

  if (other->client->camptime >= level.time) return;

  if (random() > 0.60) return; // camp 60% of time

  other->client->camptime = level.time+20+rand()%10; // 20..30
  other->client->chattime = level.time+10+rand()%15; // 10..25
  other->client->taunttime = other->client->camptime+10; // turn OFF!
  VectorCopy(ent->s.origin, other->client->lastorigin);
  other->client->lastorigin[2] += 16;
  other->client->campitem = ent->item; //camping near this item
}

//======================================================
//======== ROUTE FILE AND BOT CONFIGURATION ============
//======================================================

//===============================================
void RandomizeParameters(int i) {
  Bot[i].skill[AIMACCURACY] = 5+rand()%4;     // [5..9]
  Bot[i].skill[AGGRESSION]  = 6+rand()%3;     // [6..9]
  Bot[i].skill[COMBATSKILL] = 3+rand()%6;     // [3..9]
  Bot[i].skill[PRIMARYWEAP] = 3+rand()%8;     // [3..11]
  Bot[i].skill[VRANGEVIEW]  = 60+10*rand()%6; // [60..120]
  Bot[i].skill[HRANGEVIEW]  = 60+10*rand()%6; // [60..120]
}

//==============================================
void LoadBotNames(void) {
int i;
  for (i=0;i<MAXBOTS;i++) {
    memset(&Bot[i],0,sizeof(botinfo_t));
    sprintf(Bot[i].netname,"%s",gbot[i][0]);
    sprintf(Bot[i].skin,"%s",gbot[i][1]);
    RandomizeParameters(i); }
}

//==============================================
//==============================================

int CurrentIndex=0;

//==================================================
qboolean RTJump_Chk(vec3_t apos,vec3_t tpos) {
float x,l,vel,yori;
vec3_t v,vv;
int mf = 0;

  vel = 300;
  yori = apos[2];

  VectorSubtract(tpos,apos,v);

  for(x = 1;x <= 60;++x ) {
    vel -= sv_gravity->value * 0.1;
    yori += vel * 0.1;
    if(vel > 0) {
      if(mf == 0) {
        if(tpos[2] < yori) mf = 2; } }
    else if(x > 1) {
      if(mf == 0) {
        if(tpos[2] < yori) mf = 2; }
      else if(mf == 2) {
        if(tpos[2] >= yori) {
          mf = 1;
          break; } } } }

  VectorCopy(v,vv);
  vv[2] = 0;
  l = VectorLength(vv);
  if(x > 1) l /= (x - 1);

  return (l < 32 && mf==1);
}

//==============================================
void G_FindRouteLink(edict_t *ent) {
trace_t  rs_trace;
qboolean tpbool;
int i,j,k,l,total = 0;
vec3_t v;
float x;

  if (JumpMax == 0) {
    x = 300-ent->gravity*sv_gravity->value*0.1;
    JumpMax = 0;
    while (1) {
      JumpMax += x*0.1;
      x -= ent->gravity*sv_gravity->value*0.1;
      if (x < 0) break; } }

  for(i = 0;i < CurrentIndex;i++) {
    if(Route[i].state == 0) {
      for(j=0;j<CurrentIndex;j++) {
        if(abs(i-j) <= 50 || j==i || Route[j].state != 0) continue;
        VectorSubtract(Route[j].Pt,Route[i].Pt,v);
        if(v[2] > JumpMax || v[2] < -500) continue;
        v[2] = 0;
        if(VectorLength(v) > 200) continue;
        if(fabs(v[2]) > 20 || VectorLength(v) > 64)
          if(!RTJump_Chk(Route[i].Pt,Route[j].Pt))
            continue;
        tpbool = false;
        for(l = -5;l < 6;l++) {
          if((i+l)<0 || (i+l)>=CurrentIndex) continue;
          for(k = 0;k < 5;k++) {
            if(!Route[i + l].linkpod[k]) break;
            if(abs(Route[i + l].linkpod[k] - j) < 50) {
              tpbool = true;
              break; } }
          if(tpbool) break; }
      if(tpbool) continue;
      rs_trace = gi.trace(Route[j].Pt,NULL,NULL,Route[i].Pt,ent,MASK_SOLID);
      if(!rs_trace.startsolid && !rs_trace.allsolid && rs_trace.fraction == 1.0) {
        for(k = 0;k < 5;k++) {
          if(!Route[i].linkpod[k]) {
            Route[i].linkpod[k] = j;
            total++;
            break; } } } } } }

  G_FreeEdict (ent);
}

//==================================================
void ReadRouteFile(void) {
int i,j;
vec3_t v;
edict_t *e;
FILE *fp;
char name[256];

  TotalRouteNodes = 0;

  sprintf(name,"c:/quake2/3zb2/chdtm/%s.chn",level.mapname);
  if (fp=fopen(name,"rb")) {
    char code[8];
    unsigned int size;
    CurrentIndex=0;
    memset(Route,0,sizeof(route_t));
    memset(code,0,8);
    fread(code,sizeof(char),8,fp);
    fread(&CurrentIndex,sizeof(int),1,fp);
    size = (unsigned int)CurrentIndex*sizeof(route_t);
    fread(Route,size,1,fp);
    fclose(fp);
    TotalRouteNodes = CurrentIndex;
    gi.dprintf("%d Total Route Nodes\n",TotalRouteNodes); }

  if (TotalRouteNodes==0) {
    gi.dprintf("NO ROUTE FILE LOADED\n");
    return; }

  for(i = 0;i < TotalRouteNodes;i++) {
    if((Route[i].state > 2 && Route[i].state <= 7)
   || Route[i].state == -10 || Route[i].state == -11) {
    edict_t *other = &g_edicts[(int)maxclients->value+1];
    for (j=maxclients->value+1 ; j<globals.num_edicts ; j++, other++) {
      if (other && other->inuse) {
        if(Route[i].state == 4
         || Route[i].state == 5
         || Route[i].state == 7
         || Route[i].state == 6) {
         VectorAdd(other->s.origin,other->mins,v);
         if(VectorCompare (Route[i].Pt,v)) {
           if(Route[i].state == 4 && !Q_stricmp(other->classname, "func_plat")) {
             Route[i].ent = other;
             break; }
           else if(Route[i].state == 5 && !Q_stricmp(other->classname, "func_train")) {
             Route[i].ent = other;
             break; }
           else if(Route[i].state == 7 && !Q_stricmp(other->classname, "func_button")) {
             Route[i].ent = other;
             break; }
           else if(Route[i].state == 6 && !Q_stricmp(other->classname, "func_door")) {
             Route[i].ent = other;
             break; } } }
      else
        if(Route[i].state == 3 || Route[i].state == -10 || Route[i].state == -11) {
          if(VectorCompare(Route[i].Pt,other->monsterinfo.last_sighting)) {
            Route[i].ent = other;
            break; } } } }
  if(j >= globals.num_edicts)
    Route[i].state = 0; } }

  e = G_Spawn();
  e->nextthink = level.time + FRAMETIME * 2;
  e->think = G_FindRouteLink;
}

//======================================================
//============= SPAWNING BOTS INTO THE GAME ============
//======================================================

//======================================================
char *Random_IP(void) {
static char ipstr[16];
int ip1;

  do {
    ip1 = 128+(rand()%128);
  } while (ip1 == 192 || ip1 == 172);

  sprintf(ipstr, "%d.%d.%d.%d", ip1, (int)(rand()%256), (int)(rand()%256), (int)(rand()%256));

  return ipstr;
}

//=============================================
int GetFreeEdict(void) {
int i;

  for (i=(int)(game.maxclients-1); i>=0; i--) {
    edict_t *ent = g_edicts+i+1;
    if (!ent->inuse) {
      G_InitEdict(ent);
      return i; } }

  return -1; // refuse connection
}

//======================================================
void G_MuzzleFlash(short rec_no, vec3_t origin, int flashnum) {
  gi.WriteByte(svc_muzzleflash);
  gi.WriteShort(rec_no);
  gi.WriteByte(flashnum);
  gi.multicast(origin, MULTICAST_PVS);
}

//=============================================
qboolean SpawnBot(int i) {
edict_t *ent;
int clientnum;
char userinfo[512];

  if (i<0 || i>(MAXBOTS-1)) return false;

  clientnum = GetFreeEdict();
  if (clientnum < 0) {
    gi.dprintf("Server is full. Increase maxclients!!\n");
    return false; }

  ent = g_edicts+clientnum+1;
  ent->client = &game.clients[clientnum];

  ent->isabot = true;

  InitClientResp(ent->client);
  InitClientPersistant(ent->client);

  ent->client->pers.botindex = i;
  ent->client->pers.routeindex = 0;

  sprintf(userinfo,"\\name\\%s\\skin\\%s\\fov\\90\\hand\\2\\ip\\%s\\ping\\%3d", Bot[i].netname, Bot[i].skin, Random_IP(), (int)(100+(rand()%128)));  // Maj++ - We store ping in userinfo string. hehe
  ClientUserinfoChanged(ent,userinfo);

  PutClientInServer(ent);

  G_MuzzleFlash((short)(ent-g_edicts), ent->s.origin, (int)(MZ_LOGIN));

  gi_bprintf(2,"%s entered the game\n",Bot[i].netname);

  ClientEndServerFrame(ent);

  return true;
}

//==================================================
void RemoveAllBots(void) {
int i;

  for (i=1;i<=game.maxclients;i++) {
    edict_t *ent = &g_edicts[i];
    if (!ent || !ent->isabot) continue;
    Bot[ent->client->pers.botindex].ingame = 0;
    ent->isabot=false;
    ClientDisconnect(ent); }

  NumBotsInGame = 0; // Must Reset
}

//==================================================
void SpawnNumBots(int numbots) {
int j,k;

  for (k=1;k<=numbots;k++) {
    do {
      j=(int)(rand()%(MAXBOTS-1));
    } while (Bot[j].ingame);
    if (!SpawnBot(j)) return; // No free edicts!!
    Bot[j].ingame = 1;
    NumBotsInGame++; }

  if (NumBotsInGame >= MAXBOTS)
    gi_cprintf(NULL,2,"Max Bots Spawned.\n");
}

//=============================================
void PutBotInGame(edict_t *ent) {

  if (ent->count >= MAXBOTS) {
    G_FreeEdict(ent);
    return; }

  if (Bot[ent->count].ingame)
    SpawnBot(ent->count);

  ent->count += 1;
  ent->nextthink = level.time + 0.2; // Interval setting
}

//=============================================
// Temp edict to put bots into game 
//=============================================
void RespawnAllBots(void) {
  edict_t *ent = G_Spawn();
  ent->svflags |= SVF_NOCLIENT;
  ent->solid = SOLID_NOT;
  ent->count = 0;
  ent->nextthink = level.time + 0.1;
  ent->think = PutBotInGame;
}

 
//=======================================================
//============ TAUNTING/CHATTING/INSULTING ==============
//=======================================================

//==============================================
void InsultVictim(edict_t *ent, edict_t *victim) {

  if (!ent->isabot) return;

  if (!victim || !victim->client) return;

  if (ent->client->insulttime > level.time) return;

  // if bot just killed self then...
  if (victim == ent) {
    if (myrandom < 0.40)
      switch (rand()%7) {
        case 0: gi_bprintf(3,"%s: OUCH!!!\n", ent->client->pers.netname); break;
        case 1: gi_bprintf(3,"%s: I hate that!\n", ent->client->pers.netname); break;
        case 2: gi_bprintf(3,"%s: shit!\n", ent->client->pers.netname); break;
        case 3: gi_bprintf(3,"%s: not again!\n", ent->client->pers.netname); break;
        case 4: gi_bprintf(3,"%s: Ugghhhh!\n", ent->client->pers.netname); break;
        case 5: gi_bprintf(3,"%s: wtf!\n", ent->client->pers.netname); break;
        case 6: gi_bprintf(3,"%s: Ohhhhh!!!!\n", ent->client->pers.netname); } }
  else
  // Otherwise, chat
  if (myrandom < 0.80)
    switch (rand()%19) {
      case 0: gi_bprintf(3,"%s: You REALLY suck!\n", ent->client->pers.netname); break;
      case 1: gi_bprintf(3,"%s: YOU SUCK!\n", ent->client->pers.netname); break;
      case 2: gi_bprintf(3,"%s: Suck THIS!\n", ent->client->pers.netname); break;
      case 3: gi_bprintf(3,"%s: This sucks!\n", ent->client->pers.netname); break;
      case 4: gi_bprintf(3,"%s: Eat Me!\n", ent->client->pers.netname); break;
      case 5: gi_bprintf(3,"%s: You ALL suck!\n", ent->client->pers.netname); break;
      case 6: gi_bprintf(3,"%s: Suck THAT\n", ent->client->pers.netname); break;
      case 7: gi_bprintf(3,"%s: Muhhhhaahhhaaa\n", ent->client->pers.netname); break;
      case 8: gi_bprintf(3,"%s: Muhaaaaaaaaa!!\n", ent->client->pers.netname); break;
      case 9: gi_bprintf(3,"%s: Huuuhhhaaaaaa!\n", ent->client->pers.netname); break;
      case 10:gi_bprintf(3,"%s: Muhhhhhhaaaaa!!!\n", ent->client->pers.netname); break;
      case 11:gi_bprintf(3,"%s: Whoooooaaaaa!\n", ent->client->pers.netname); break;
      case 12:gi_bprintf(3,"%s: Your sister!!\n", ent->client->pers.netname); break;
      case 13:gi_bprintf(3,"%s: Your daughter!!!!\n", ent->client->pers.netname); break;
      case 14:gi_bprintf(3,"%s: Your mama!\n", ent->client->pers.netname); break;
      case 15:gi_bprintf(3,"%s: Arggggghhhh!\n", ent->client->pers.netname); break;
      case 16:gi_bprintf(3,"%s: Your daddy!!!\n", ent->client->pers.netname); break;
      case 17:gi_bprintf(3,"%s: Bite Me!\n", ent->client->pers.netname); break;
      case 18:gi_bprintf(3,"%s: HeeeeHaaaaa\n", ent->client->pers.netname); }

  ent->client->insulttime = level.time+60+(10*(rand()%6));
}

//==============================================
void TauntVictim(edict_t *ent, edict_t *victim) {
vec3_t vtmp;

  if (!ent->isabot || ent == victim) return;

  if (!victim || !victim->client) return;

  if (ent->client->taunttime > level.time) return;

  // Taunt only if near victim (don't reset timer)
  VectorSubtract(ent->s.origin, victim->s.origin, vtmp);
  if (VectorLength(vtmp) > 250) return;

  switch (rand()%3) {
    case 0:ent->s.frame = FRAME_flip01-1;
           ent->client->anim_end = FRAME_flip12; break;
    case 1:ent->s.frame = FRAME_salute01-1;
           ent->client->anim_end = FRAME_salute11; break;
    case 2:ent->s.frame = FRAME_taunt01-1;
           ent->client->anim_end = FRAME_taunt17; }

  ent->client->taunttime = level.time+30+(10*(rand()%6));
}

//==============================================
void RandomChat(edict_t *ent) {

  if (ent->client->chattime > level.time) return;

  if (ent->client->camptime > level.time) {
    if (myrandom < 0.50) { // Camp and Chat about 50% of the time
      if (ent->client->campitem == item_railgun)
        switch (rand()%6) {
        case 0: gi_bprintf(3,"%s: Bring firewood nexttime!!\n", ent->client->pers.netname); break;
        case 1: gi_bprintf(3,"%s: Want a roasted weener?\n", ent->client->pers.netname); break;
        case 2: gi_bprintf(3,"%s: Want a beer with that??\n", ent->client->pers.netname); break;
        case 3: gi_bprintf(3,"%s: Don't ya just love it?\n", ent->client->pers.netname); break;
        case 4: gi_bprintf(3,"%s: Get the camper at railgun!!\n", ent->client->pers.netname); break;
        case 5: gi_bprintf(3,"%s: There's a camper at railgun!\n", ent->client->pers.netname); break; }
      else
      if (ent->client->campitem == item_health_mega)
        switch (rand()%6) {
        case 0: gi_bprintf(3,"%s: Kill the megahealth camper!!!\n", ent->client->pers.netname); break;
        case 1: gi_bprintf(3,"%s: Want marshmellows?\n", ent->client->pers.netname); break;
        case 2: gi_bprintf(3,"%s: Got hotdogs?\n", ent->client->pers.netname); break;
        case 3: gi_bprintf(3,"%s: fuckin campers\n", ent->client->pers.netname); break;
        case 4: gi_bprintf(3,"%s: Get the camper by megahealth!!\n", ent->client->pers.netname); break;
        case 5: gi_bprintf(3,"%s: Camper at megahealth!\n", ent->client->pers.netname); break; }
      ent->client->chattime = level.time+20;
      return; } }
  else
  if (myrandom < 0.10) // Do this random chatting very rarely
    switch (rand()%6) {
      case 0: gi_bprintf(3,"%s: Bunch of Chicken Shits!\n", ent->client->pers.netname); break;
      case 1: gi_bprintf(3,"%s: Come and get it!!!\n", ent->client->pers.netname); break;
      case 2: gi_bprintf(3,"%s: Who wants a piece of me?\n", ent->client->pers.netname); break;
      case 3: gi_bprintf(3,"%s: Where'd everybody go?\n", ent->client->pers.netname); break;
      case 4: gi_bprintf(3,"%s: This server sucks!\n", ent->client->pers.netname); break;
      case 5: gi_bprintf(3,"%s: Only pussies on this server!\n", ent->client->pers.netname); break; }

  ent->client->chattime = level.time+60+(20*(rand()%6));
}

//=====================================================
//=========== BASIC TRACING ALGORITHMS ================
//=====================================================

//==============================================
qboolean InSight(edict_t *ent, edict_t *other) {
vec3_t start,end;
trace_t tr;

  if (other->client && !G_ClientInGame(other))
    return false;

  VectorCopy(ent->s.origin,start);
  start[2] += ent->viewheight-8;

  VectorCopy(other->s.origin,end);
  end[2] += other->viewheight-8;

  if ((gi.pointcontents(start) & CONTENTS_WATER) && !other->waterlevel) {
    tr = gi.trace(end, NULL, NULL, start, ent, CONTENTS_WINDOW|MASK_OPAQUE|CONTENTS_WATER);
    if (tr.surface && tr.surface->flags & SURF_WARP) return false;
    tr = gi.trace(start, NULL, NULL, end, ent, CONTENTS_WINDOW|MASK_OPAQUE);
    return (tr.fraction == 1.0); }

  if ((gi.pointcontents(start) & CONTENTS_WATER) && other->waterlevel) {
    VectorCopy(other->s.origin,end);
    end[2] -= 16;
    tr = gi.trace(start, NULL, NULL, end, ent, CONTENTS_SOLID|CONTENTS_WINDOW);
    return (tr.fraction == 1.0); }

  if (other->waterlevel) {
    VectorCopy(other->s.origin,end);
    end[2] += 32;
    tr = gi.trace(start, NULL, NULL, end, ent, CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_WATER);
    if (tr.surface && tr.surface->flags & SURF_WARP) return false; }

  return (gi.trace(start, NULL, NULL, end, ent, CONTENTS_WINDOW|MASK_OPAQUE).fraction == 1.0);
}

//==============================================
qboolean Bot_trace2(edict_t *ent,vec3_t ttz) {
vec3_t ttx;
  VectorCopy(ent->s.origin,ttx);
  ttx[2] += (ent->maxs[2]>=32)?24:-12;
  return (gi.trace(ttx, NULL, NULL, ttz ,ent, MASK_OPAQUE).fraction == 1.0);
}

//==============================================
qboolean Bot_trace(edict_t *ent, edict_t *other) {
trace_t tr;
vec3_t ttx,tty;

  VectorCopy(ent->s.origin,ttx);
  VectorCopy(other->s.origin,tty);
  if (ent->maxs[2] >=32) {
    if (tty[2] > ttx[2])
      tty[2] += 16;
    ttx[2] += 30; }
  else
    ttx[2] -= 12;

  tr = gi.trace(ttx,NULL,NULL,tty,ent,CONTENTS_WINDOW|MASK_OPAQUE);
  if (tr.fraction == 1.0 && !tr.allsolid && !tr.startsolid) return true;

  if (ent->maxs[2] < 32) return false;

  if (tr.ent && tr.ent->use == door_use)
    if (!tr.ent->targetname)
      return true;

  if (ent->s.origin[2] < other->s.origin[2] || ent->s.origin[2]-24 > other->s.origin[2])
    return false;

  ttx[2] -= 36;
  tr = gi.trace(ttx, NULL, NULL, other->s.origin, ent, CONTENTS_WINDOW|MASK_OPAQUE);
  return (tr.fraction == 1.0 && !tr.allsolid && !tr.startsolid);
}

//==================================================
qboolean TraceX(edict_t *ent,vec3_t p2) {
trace_t tr;
vec3_t v1,v2;
int contents;

  contents = (CONTENTS_SOLID|CONTENTS_WINDOW);

  if (ent->svflags & ~SVF_MONSTER) {
    if (ent->client->waterstate) {
      VectorCopy(ent->mins,v1);
      VectorCopy(ent->maxs,v2); }
    else
    if (ent->client->ps.pmove.pm_flags & ~PMF_DUCKED) {
      VectorSet(v1,-16,-16,-4);
      VectorSet(v2,16,16,32); }
    else {
      VectorSet(v1,-4,-4,-4);
      VectorSet(v2,4,4,4); } }
  else {
    VectorClear(v1);
    VectorClear(v2);
    contents |= (CONTENTS_LAVA|CONTENTS_SLIME); }

  tr = gi.trace(ent->s.origin, v1, v2, p2, ent, contents);
  if (tr.fraction == 1.0 && !tr.allsolid && !tr.startsolid)
    return true;

  if (ent->client->routetrace)
    if (ent->svflags & SVF_MONSTER)
      if (tr.ent && tr.ent->use == door_use)
        return (tr.ent->moveinfo.state == 2);

  return false;
}

//============================================================
void Get_RouteOrigin(int index,vec3_t pos) {
edict_t *e;

  if (Route[index].state <= 3 || Route[index].state >= 20)
    if (Route[index].state == 3) {
      VectorCopy(Route[index].ent->s.origin,pos);
      pos[2] += 8; }
    else
      VectorCopy(Route[index].Pt,pos);

  switch (Route[index].state) {
    case 4:
      VectorCopy(Route[index].ent->union_ent->s.origin,pos);
      pos[2] += 8;
      return;
    case 5:
      if (!Route[index].ent->trainteam) {
        VectorCopy(Route[index].ent->union_ent->s.origin,pos);
        pos[2] += 8;
        return; }
      if (Route[index].ent->target_ent)
        if (VectorCompare(Route[index].Tcourner, Route[index].ent->target_ent->s.origin)) {
          VectorCopy(Route[index].ent->union_ent->s.origin,pos);
          pos[2] += 8;
          return; }
      e = Route[index].ent->trainteam;
      while (1) {
        if (e == Route[index].ent)
          break;
        if (e->target_ent)
          if (VectorCompare(Route[index].Tcourner, e->target_ent->s.origin)) {
            VectorCopy(e->union_ent->s.origin,pos);
            pos[2] += 8;
            Route[index].ent = e;
            return; }
      e = e->trainteam; }
      VectorCopy(Route[index].ent->union_ent->s.origin,pos);
      pos[2] += 8;
      return;
    case 6:
      if (Route[index].ent->union_ent) {
        VectorCopy(Route[index].ent->union_ent->s.origin,pos);
        pos[2] += 8; }
      else
      if (index+1 < TotalRouteNodes) {
        if (Route[index+1].state <= 3) {
          VectorCopy(Route[index+1].Pt,pos);
          pos[2] += 8; }
        else
        if (Route[index+1].state <= 5) {
          VectorCopy(Route[index+1].ent->union_ent->s.origin,pos);
          pos[2] += 8; }
        else
        if (Route[index+1].state == 7) {
          VectorCopy(Route[index+1].ent->union_ent->s.origin,pos);
          pos[2] += 8; }
        else
          VectorCopy(Route[index+1].Pt,pos); }
      else {
        pos[0] = (Route[index].ent->absmin[0]+Route[index].ent->absmax[0])*0.5;
        pos[1] = (Route[index].ent->absmin[1]+Route[index].ent->absmax[1])*0.5;
        pos[2] =  Route[index].ent->absmax[2]; }
      return;
    case 7:
      VectorCopy(Route[index].ent->union_ent->s.origin,pos); }
}

//==============================================
void GetAimAngle(edict_t *ent, float aim, float dist) {
vec3_t targaim;
trace_t tr;
int weapon;

  weapon = GetKindWeapon(ent->client->pers.weapon);

  switch (weapon) {
    case WEAP_BLASTER:
    case WEAP_SHOTGUN:
    case WEAP_SUPERSHOTGUN:
    case WEAP_MACHINEGUN:
    case WEAP_CHAINGUN:
    case WEAP_HYPERBLASTER:
    case WEAP_RAILGUN:
      if (ent->client->current_enemy != ent->client->prev_enemy) {
        if (ent->client->current_enemy->isabot)
          VectorSubtract(ent->client->current_enemy->s.old_origin, ent->client->current_enemy->s.origin, targaim);
        else {
          VectorCopy(ent->client->current_enemy->velocity, targaim);
          VectorInverse(targaim); }
        VectorNormalize(targaim);
        VectorMA(ent->client->current_enemy->s.origin, 5*aim*myrandom, targaim, targaim); }
      else {
        VectorSubtract(ent->client->targ_old_origin, ent->client->current_enemy->s.origin, targaim);
        VectorMA(ent->client->current_enemy->s.origin, aim*myrandom, targaim, targaim); }
      VectorSubtract(targaim, ent->s.origin, targaim);
      AdjustAngle(ent, targaim, aim);
      return;

    case WEAP_GRENADES:
    case WEAP_GRENADELAUNCHER:
    case WEAP_ROCKETLAUNCHER:
      if (ent->client->current_enemy != ent->client->prev_enemy) {
        if (ent->client->current_enemy->isabot)
          VectorSubtract(ent->client->current_enemy->s.origin, ent->client->current_enemy->s.old_origin, targaim);
        else {
          VectorCopy(ent->client->current_enemy->velocity, targaim);
          VectorScale(targaim, 32, targaim); }
        VectorNormalize(targaim);
        VectorMA(ent->client->current_enemy->s.origin, (11-aim)*dist/25, targaim, targaim); }
      else {
        VectorSubtract(ent->client->current_enemy->s.origin, ent->client->targ_old_origin, targaim);
        targaim[2] *= 0.5;
        VectorMA(ent->client->current_enemy->s.origin, -aim*myrandom+dist/75, targaim, targaim); }
      tr = gi.trace(ent->client->current_enemy->s.origin, NULL, NULL, targaim, ent->client->current_enemy, MASK_SHOT);
      VectorCopy(tr.endpos, targaim);
      if (weapon & WEAP_GRENADELAUNCHER|WEAP_ROCKETLAUNCHER) {
        if (targaim[2] < (ent->s.origin[2]+JumpMax)) {
          vec3_t vtmp;
          targaim[2] -= 24;
          VectorCopy(ent->s.origin, vtmp);
          vtmp[2] += ent->viewheight-8;
          tr = gi.trace(vtmp, NULL, NULL, targaim, ent, MASK_SHOT);
          if (tr.fraction != 1.0)
            targaim[2] += 24; }
        else
          if (targaim[2] >(ent->s.origin[2]+JumpMax))
            targaim[2] += 5; }
      VectorSubtract(targaim, ent->s.origin, targaim);
      AdjustAngle(ent, targaim, aim);
      return;

    default: // BFG
      VectorCopy(ent->client->vtemp, targaim);
      VectorSubtract(targaim, ent->s.origin, targaim);
      VectorSet(ent->s.angles,(Get_pitch(targaim)),(Get_yaw(targaim)),0.0F); }
}

//=================================================
qboolean HasAmmoForWeapon(edict_t *self, gitem_t *weapon) {
  if (weapon==item_blaster) return true;
  if ((int)dmflags->value & DF_INFINITE_AMMO) return true;
  return (self->client->pers.inventory[ITEM_INDEX(FindItem(weapon->ammo))] >= weapon->quantity);
}

//========================================================
gitem_t *GetWeaponType(int weapnum) {

  switch (weapnum) {
    case WEAP_SHOTGUN:         return item_shotgun;
    case WEAP_SUPERSHOTGUN:    return item_supershotgun;
    case WEAP_MACHINEGUN:      return item_machinegun;
    case WEAP_CHAINGUN:        return item_chaingun;
    case WEAP_GRENADES:        return item_grenades;
    case WEAP_GRENADELAUNCHER: return item_grenadelauncher;
    case WEAP_ROCKETLAUNCHER:  return item_rocketlauncher;
    case WEAP_HYPERBLASTER:    return item_hyperblaster;
    case WEAP_RAILGUN:         return item_railgun;
    case WEAP_BFG:             return item_bfg; }

  return item_blaster;
}

//==============================================
qboolean CanUseWeapon(edict_t *ent, int weapnum) {
gitem_t *weapon;

  weapon = GetWeaponType(weapnum);

  if (ent->client->pers.inventory[ITEM_INDEX(weapon)])
    if (HasAmmoForWeapon(ent,weapon))
      if (ent->client->weaponstate & WEAPON_READY|WEAPON_FIRING) {
        if (ent->client->pers.weapon != weapon) {
          ent->client->newweapon = weapon;
          ChangeWeapon(ent); }
        return true; } // true whether switched or not

  return false;
}

//==============================================
qboolean Pickup_Navi(edict_t *ent, edict_t *other) {
int i;

  if (!(ent->spawnflags & DROPPED_ITEM))
    if (ent->item->quantity)
      SetRespawn(ent, ent->item->quantity);

  //on door (up & down)
  if (ent->item == item_navi3 && ent->union_ent) {
    int j,k;
    qboolean flg=false;
    if (ent->target_ent == other) {
      other->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
      other->client->waiting_obj = ent->union_ent;
      if (ent->union_ent->spawnflags & 32) {
        if (ent->union_ent->moveinfo.state & 3|1)
          other->client->movestate |= 0x00000100;
        else
          other->client->movestate |= 0x00000080; }
      else {
        if (ent->union_ent->moveinfo.state & 3|0)
          other->client->movestate |= 0x00000100;
        else
        if (ent->union_ent->moveinfo.state & 2|1)
          other->client->movestate |= 0x00000080; }
      for (i=-10;i<10;i++) {
        if (i <= 0)
          j = other->client->pers.routeindex-(10-i);
        else
          j = other->client->pers.routeindex+i;
        if (j < 0) continue;
        if (j >= TotalRouteNodes) continue;
        if ((Route[j].state == 6 && Route[j].ent == ent->union_ent) || Route[j].state == 7) {
          vec3_t v;
          k = 1;
          flg = false;
          while (1) {
            if ((j+k) >= TotalRouteNodes) break;
            if ((j+k) >= other->client->pers.routeindex) {
              Get_RouteOrigin(j+k,v);
              if (fabs(v[2]-other->s.origin[2]) > JumpMax) {
                flg = true;
                break; } }
            k++; }
          if ((j+k) < TotalRouteNodes && flg) {
            other->client->pers.routeindex = j+k;
            break; } } }
      if (!flg)
        other->client->movestate |= 0x00000010;
      ent->target_ent = NULL; }
    SetRespawn(ent, 1000000);
    ent->solid = SOLID_NOT; }
  else
  if (ent->item == item_navi2)
    for (i=0;i<10;i++) {
      if ((other->client->pers.routeindex+i) >= TotalRouteNodes) break;
      if (!Route[other->client->pers.routeindex+i].index) break;
      if (Route[other->client->pers.routeindex+i].state != 7) continue;
      if (Route[other->client->pers.routeindex+i].ent == ent->union_ent) {
        other->client->pers.routeindex += i+1;
        break; } }

  return true;
}

//==============================================
qboolean B_UseWeapon(edict_t *ent, float aim, float distance, int weap) {

  if (!CanUseWeapon(ent,weap)) return false;

  if (weap != WEAP_GRENADES && weap != WEAP_GRENADELAUNCHER)
    if (!InSight(ent,ent->client->current_enemy))
      return false;

  GetAimAngle(ent,aim,distance);
  ent->client->buttons |= BUTTON_ATTACK;
  trace_priority = MaxOf(trace_priority,2);

  return true;
}

//======================================================
//=========== BOT FIGHTING/COMBAT FUNCTIONS ============
//======================================================

//==============================================
void Combat_LevelX(edict_t *ent,float distance) {
vec3_t vdir;

  if (ent->client->battlemode & 0x00000040) {

    int k=0;
    float aim = 10.0 - Bot[ent->client->pers.botindex].skill[AIMACCURACY];

    //Rocket Launcher
    if (distance > 200 && distance < 1000)
      if (GetKindWeapon(ent->client->pers.weapon) == WEAP_ROCKETLAUNCHER)
        if (B_UseWeapon(ent,aim,distance,WEAP_ROCKETLAUNCHER))
          k = 1;

    //Grenade Launcher
    if (!k && distance > 100 && distance < 400)
      if (GetKindWeapon(ent->client->pers.weapon) == WEAP_GRENADELAUNCHER)
        if ((ent->client->current_enemy->s.origin[2]-ent->s.origin[2]) < 150)
          if (B_UseWeapon(ent,aim,distance,WEAP_GRENADELAUNCHER))
            k = 1;

    //Hand Grenade
    if (!k && distance > 200 && distance < 800)
      if (GetKindWeapon(ent->client->pers.weapon) == WEAP_GRENADES)
        if (B_UseWeapon(ent,aim,distance,WEAP_GRENADES))
          k = 1;

    VectorSubtract(ent->client->vtemp,ent->s.origin,vdir);
    ent->s.angles[YAW] = Get_yaw(vdir);
    ent->s.angles[PITCH] = Get_pitch(vdir);
    trace_priority = (k)?4:2;
    return; }

  VectorSubtract(ent->client->current_enemy->s.origin,ent->s.origin,vdir);
  ent->s.angles[YAW] = Get_yaw(vdir);
  ent->s.angles[PITCH] = Get_pitch(vdir);
  trace_priority = 2; // Use this angle
}

qboolean Bot_Fall(edict_t *ent,vec3_t pos,float dist);

//==============================================
void Combat_Normal(edict_t *ent,float distance) {
edict_t *target;
int i,j,k;
vec3_t v,vv,v1,v2;
trace_t tr;
int enewep;
float f,aim;

  target = ent->client->current_enemy;
  aim = 10.0 - Bot[ent->client->pers.botindex].skill[AIMACCURACY];

  // NOTE:  skill = Bot[ent->client->pers.botindex].skill[COMBATSKILL];

  //================================================

  // Fire_Chicken -------------------------
  if (ent->client->battlemode == 0x00000008)
    aim *= 0.7;

  //================================================

  // Fire_Shift -------------------------
  if (ent->client->battlemode & (0x00020000|0x00040000)) {
    GetAimAngle(ent,aim,distance);
    if (--ent->client->battlesubcnt > 0) {
      if (ent->groundentity) {
        if (ent->client->battlemode & 0x00020000) {
          ent->client->moveyaw = ent->s.angles[YAW]+90;
          if (ent->client->moveyaw > 180)
            ent->client->moveyaw -= 360; }
        else {
          ent->client->moveyaw = ent->s.angles[YAW]-90;
          if (ent->client->moveyaw < -180)
            ent->client->moveyaw += 360; }
        trace_priority = 3; } }
    else
      ent->client->battlemode &= ~(0x00020000|0x00040000); }

  //================================================

  // always try to dodge ---------------------------
  if (ent->groundentity && !ent->waterlevel) {
    AngleVectors(target->client->v_angle, v, NULL, NULL);
    VectorScale(v,300,v);
    VectorSet(vv,0,0,target->viewheight-8);
    VectorAdd(target->s.origin,vv,vv);
    VectorAdd(vv,v,v);
    VectorSet(v1, -4, -4,-4);
    VectorSet(v2, 4, 4, 4);
    tr = gi.trace(vv,v1,v2,v,target,MASK_SHOT);
    if (tr.ent == ent)
      if ((tr.endpos[2] > (ent->s.origin[2]+4)) && (random() < 0.4)) { // BUG!
        ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
        ent->client->battleduckcnt = 2+8*random(); }
      else
      if (tr.endpos[2] < (ent->s.origin[2]+JumpMax-24))
        if (ent->client->routetrace) {
          if (Bot_Fall(ent,ent->s.origin,0))
            trace_priority = 3;; }
        else {
          ent->moveinfo.speed = 0.5;
          ent->velocity[2] = 300;
          ent->client->anim_priority = ANIM_JUMP; } }

  //================================================

  // Fire_Ignore -------------------------
  if (ent->client->battlemode & 0x10000000) {
    if (--ent->client->battlecount > 0)
      if (ent->client->current_enemy == ent->client->prev_enemy)
        return;
    ent->client->battlemode = 0x00000000; }

  //================================================

  // Fire_PreStayFire -------------------------
  if (ent->client->battlemode & 0x00000002) {
    if (--ent->client->battlecount > 0) {
      GetAimAngle(ent,aim,distance);
      if (ent->groundentity)
        if (target->client->weaponstate == WEAPON_FIRING)
          ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
      trace_priority = 4;
      return; }
    if (!(ent->client->battlemode & (0x00020000|0x00040000)))
      ent->client->battlemode = 0x00000004;
    ent->client->battlecount = 5+(int)(20*random()); }

  //================================================

  // Fire_StayFire -------------------------
  if (ent->client->battlemode & 0x00000004) {
    if (--ent->client->battlecount > 0) {
      CanUseWeapon(ent,WEAP_BFG);
      aim *= 0.95;
      GetAimAngle(ent,aim,distance);
      if (ent->groundentity)
        if (target->client->weaponstate == WEAPON_FIRING)
          ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
      if (!(ent->client->battlemode & (0x00020000|0x00040000)))
        trace_priority = 4;
      if ((GetKindWeapon(ent->client->pers.weapon) & WEAP_BFG|WEAP_GRENADELAUNCHER) || InSight(ent,target))
        ent->client->buttons |= BUTTON_ATTACK;
      return; }
    ent->client->battlemode = 0x00000000; }

  //================================================

  if (ent->client->battlemode & 0x00000010) {
    if (--ent->client->battlecount > 0) {
      CanUseWeapon(ent,WEAP_BFG);
      aim *= 0.95;
      GetAimAngle(ent,aim,distance);
      if (ent->groundentity)
        if (target->client->weaponstate == WEAPON_FIRING) {
          if (GetKindWeapon(ent->client->pers.weapon) == WEAP_BFG) {
            if (target->s.origin[2] > ent->s.origin[2])
              ent->client->ps.pmove.pm_flags |= PMF_DUCKED; }
          else
            ent->client->ps.pmove.pm_flags |= PMF_DUCKED; }
      trace_priority = 3;
      ent->client->moveyaw = ent->s.angles[YAW];
      if ((GetKindWeapon(ent->client->pers.weapon) & WEAP_BFG|WEAP_GRENADELAUNCHER) || InSight(ent,target))
        ent->client->buttons |= BUTTON_ATTACK;
      return; }
    ent->client->battlemode = 0x00000000; }

  //================================================

  // Always avoid explosions -------------------------
  if (--ent->client->battlecount > 0) {
    CanUseWeapon(ent,WEAP_BFG);
    aim *= 0.95;
    GetAimAngle(ent,aim,distance);
    if (ent->groundentity)
      if (target->client->weaponstate == WEAPON_FIRING) {
        if (GetKindWeapon(ent->client->pers.weapon) == WEAP_BFG) {
          if (target->s.origin[2] > ent->s.origin[2])
            ent->client->ps.pmove.pm_flags |= PMF_DUCKED; }
        else
          ent->client->ps.pmove.pm_flags |= PMF_DUCKED; }
    trace_priority = 3;
    ent->client->moveyaw = ent->s.angles[YAW]+180;
    if (ent->client->moveyaw > 180)
      ent->client->moveyaw -= 360;
      if ((GetKindWeapon(ent->client->pers.weapon) & WEAP_BFG|WEAP_GRENADELAUNCHER) || InSight(ent,target))
        ent->client->buttons |= BUTTON_ATTACK;
    return; }

  //================================================

  // Fire_BFG -------------------------
  if (ent->client->battlemode & 0x00010000) {
    if (--ent->client->battlecount > 0) {
      CanUseWeapon(ent,WEAP_BFG);
      aim *= 0.95;
      GetAimAngle(ent,aim,distance);
      if (ent->groundentity)
        if (target->client->weaponstate == WEAPON_FIRING)
          ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
      trace_priority = 2;
      if ((GetKindWeapon(ent->client->pers.weapon) & WEAP_BFG|WEAP_GRENADELAUNCHER) || InSight(ent,target))
        ent->client->buttons |= BUTTON_ATTACK;
      return; }
    ent->client->battlemode = 0x00000000; }

  //================================================

  // Fire_SeekRefuge -------------------------
  if (ent->client->battlemode & 0x00001000) {
    if (--ent->client->battlecount > 0) {
      aim *= 0.95;
      GetAimAngle(ent,aim,distance);
      if (ent->groundentity)
        if (target->client->weaponstate == WEAPON_FIRING)
          if (GetKindWeapon(ent->client->pers.weapon) == WEAP_BFG)
            ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
      trace_priority = 2;
      if ((GetKindWeapon(ent->client->pers.weapon) & WEAP_BFG|WEAP_GRENADELAUNCHER) || InSight(ent,target))
        ent->client->buttons |= BUTTON_ATTACK;
      return; }
    ent->client->battlemode = 0x00000000;
    ent->client->pers.routeindex -= 2; }

  //================================================

  if (ent->client->combatstate & ~0x00000001)
    if (ent->client->movestate & (0x00000100|0x00000080|0x00000040|0x00000200))
      if (abs(target->s.origin[2]-ent->s.origin[2]) < 300) {
        // GrenadeLauncher
        if (CanUseWeapon(ent,WEAP_GRENADELAUNCHER)) {
          GetAimAngle(ent,aim,distance);
          if (ent->groundentity)
            if ((target->client->weaponstate == WEAPON_FIRING) || (ent->client->movestate & (0x00000100|0x00000080|0x00000040|0x00000200)))
              ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
          ent->client->buttons |= BUTTON_ATTACK;
          trace_priority = 2;
          return; }
        // Handgrenades
        if (CanUseWeapon(ent,WEAP_GRENADES)) {
          GetAimAngle(ent,aim,distance);
          if (ent->groundentity)
            if (target->client->weaponstate == WEAPON_FIRING)
              ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
          if (ent->client->weaponstate == WEAPON_READY)
            ent->client->buttons |= BUTTON_ATTACK;
          trace_priority = 2;
          return; } }


  //================================================

  k=0;
  if (ent->client->battlemode & ~(0x00020000|0x00040000))
    if (Bot[ent->client->pers.botindex].skill[COMBATSKILL] > (random()*Bot[ent->client->pers.botindex].skill[COMBATSKILL]))
      if ((30*random()) < Bot[ent->client->pers.botindex].skill[AGGRESSION]) {
        enewep = GetKindWeapon(target->client->pers.weapon);
        if (ent->client->routetrace && enewep != WEAP_RAILGUN) {
          for (i=ent->client->pers.routeindex; i<(ent->client->pers.routeindex+10); i++) {
            if (i >= TotalRouteNodes) break;
            if (Route[i].state == 3)
              if (Route[i].ent->solid == SOLID_TRIGGER) {
                k = 1; break; } }
          if (!k) {
            GetAimAngle(ent,aim,distance);
            f =target->s.angles[YAW]-ent->s.angles[YAW];
            if (f > 180)  f = -(360-f);
            if (f < -180) f = -(f+360);
            if (f <= -160) {
              ent->client->battlemode |= 0x00040000; // strafe left
              ent->client->battlesubcnt = 5+(int)(16*random()); }
            else
            if (f >= 160) {
              ent->client->battlemode |= 0x00020000; // strafe right
              ent->client->battlesubcnt = 5+(int)(16*random()); } } } }

  //================================================

  // Always avoid invincible enemy!
  if (target->client->invincible_framenum > level.framenum) {
    GetAimAngle(ent,aim,distance);
    trace_priority = 3;
    ent->client->moveyaw = ent->s.angles[YAW]+180; // Turn around!
    if (ent->client->moveyaw > 180)
      ent->client->moveyaw -= 360;
    return; }

  //================================================

  // Always use the quad
  if (ent->client->quad_framenum > level.framenum)
    //SuperShotgun, HyperBlaster, Chaingun
    if (CanUseWeapon(ent,WEAP_SUPERSHOTGUN)
    || (CanUseWeapon(ent,WEAP_HYPERBLASTER))
    || (CanUseWeapon(ent,WEAP_CHAINGUN))) {
      GetAimAngle(ent,aim,distance);
      if ((GetKindWeapon(ent->client->pers.weapon) & WEAP_BFG|WEAP_GRENADELAUNCHER) || InSight(ent,target))
        ent->client->buttons |= BUTTON_ATTACK;
      trace_priority = 2;
      enewep = GetKindWeapon(target->client->pers.weapon);
      if (enewep < WEAP_MACHINEGUN || enewep == WEAP_GRENADES) {
        ent->client->battlemode |= 0x00000010;
        ent->client->battlecount = 8+(int)(10*random()); }
      return; }

  //================================================

  // Fire Refuge
  if (SkillLevel[Bot[ent->client->pers.botindex].skill[COMBATSKILL]] & 0x00001000)
    if (ent->client->battlemode == 0x00000000)
      if (ent->client->routetrace && ent->client->pers.routeindex > 1) {
        enewep = GetKindWeapon(target->client->pers.weapon);
        j = (enewep >= WEAP_CHAINGUN && enewep != WEAP_GRENADES)?1:0;
        Get_RouteOrigin(ent->client->pers.routeindex-2,v);
        if (fabs(v[2]-ent->s.origin[2]) < JumpMax && j==1) {
          if (GetKindWeapon(ent->client->pers.weapon) & WEAP_GRENADELAUNCHER|WEAP_ROCKETLAUNCHER|WEAP_BFG) {
            ent->client->battlemode |= 0x00001000;
            ent->client->battlecount = 8+(int)(10*random());
            trace_priority = 4;
            return; } } }

  if (!ent->client->routetrace && distance < 100) {
    ent->client->battlecount = 4+(int)(8*random());
    trace_priority = 4; }

  //BFG
  if (distance > 400)
    if (B_UseWeapon(ent,aim,distance,WEAP_BFG))
      goto FIRED;

  //Hyper Blaster
  if (distance < 1200)
    if (B_UseWeapon(ent,aim,distance,WEAP_HYPERBLASTER))
      goto FIRED;

  //Rocket
  if ((distance > 100 && distance < 1200))
    if (B_UseWeapon(ent,aim,distance,WEAP_ROCKETLAUNCHER))
      goto FIRED;

  //Railgun
  if (distance < 1200)
    if (B_UseWeapon(ent,aim,distance,WEAP_RAILGUN))
      goto FIRED;

  //Grenade Launcher
  if (distance > 100 && distance < 400)
    if ((target->s.origin[2]-ent->s.origin[2]) < 200)
      if (B_UseWeapon(ent,aim,distance,WEAP_GRENADELAUNCHER))
        goto FIRED;

  //Chain Gun
  if (distance < 1200)
    if (B_UseWeapon(ent,aim,distance,WEAP_CHAINGUN))
      goto FIRED;

  //Machine Gun
  if (distance < 600)
    if (B_UseWeapon(ent,aim,distance,WEAP_MACHINEGUN))
      goto FIRED;

  //SuperShotgun
  if (distance < 800)
    if (B_UseWeapon(ent,aim,distance,WEAP_SUPERSHOTGUN))
      goto FIRED;

  // Should be firing?
  if (ent->groundentity && distance >= 400)
    if (SkillLevel[Bot[ent->client->pers.botindex].skill[COMBATSKILL]] & 0x10000000)
      if (ent->client->movestate & ~(0x00000020|0x00000040|0x00000080|0x00000400|0x00000100|0x00000200)) {
        ent->client->battlemode = 0x10000000;
        ent->client->battlecount = 5+(int)(10*random()); }

  //Shotgun
  if (distance < 800)
    if (B_UseWeapon(ent,aim,distance,WEAP_SHOTGUN))
      goto FIRED;

  //Hand Grenade
  if (distance < 400)
    if (B_UseWeapon(ent,aim,distance,WEAP_GRENADES))
      goto FIRED;

  //Blaster
  if (B_UseWeapon(ent,aim,distance,WEAP_BLASTER))
    goto FIRED;

  return;

FIRED: // Shoot the weapon

  if (ent->client->battlemode == 0x00000008) {
    if (--ent->client->battlesubcnt > 0)
      if (ent->groundentity && ent->waterlevel < 2) {
        f =target->s.angles[YAW]-ent->s.angles[YAW];
        if (f > 180)  f = -(360-f);
        if (f < -180) f = -(f+360);
        if (fabs(f) >= 150)
          ent->client->battlemode = 0x00000000;
        else {
          if (ent->client->weaponstate != WEAPON_READY)
            if (target->s.origin[2] < ent->s.origin[2]) {
              if (GetKindWeapon(ent->client->pers.weapon) & WEAP_ROCKETLAUNCHER|WEAP_GRENADELAUNCHER|WEAP_RAILGUN)
                ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
              else
              if (Bot[ent->client->pers.botindex].skill[COMBATSKILL] >= 7) {
                if (GetKindWeapon(ent->client->pers.weapon) & WEAP_SHOTGUN|WEAP_SUPERSHOTGUN|WEAP_BLASTER)
                  ent->client->ps.pmove.pm_flags |= PMF_DUCKED; } }
          trace_priority = 4; }
        return; }
    else
      ent->client->battlemode = 0x00000000;
  else
  if (ent->client->battlemode == 0x00000000 && distance > 200)
    if (ent->groundentity && ent->waterlevel < 2)
      if (9*random() > Bot[ent->client->pers.botindex].skill[AGGRESSION]) {
        if (GetKindWeapon(ent->client->pers.weapon) > WEAP_BLASTER && target->client->current_enemy != ent) {
          f =target->s.angles[YAW]-ent->s.angles[YAW];
          if (f > 180)  f = -(360-f);
          if (f < -180) f = -(f+360);
          if (fabs(f) < 150) {
            ent->client->battlemode = 0x00000008;
            ent->client->battlesubcnt = 5+(int)(random()*8);
            trace_priority = 4; } } } }
}

//============================================================
void Set_Combatstate(edict_t *ent) {
vec3_t vtmp;
float distance;

  if (ent->client->movestate & 0x00000001) return;

  if (!G_ClientInGame(ent->client->current_enemy)) {
    ent->client->battleduckcnt = 0;
    ent->client->current_enemy = NULL;
    ent->client->combatstate &= ~0x00000001;
    return; }

  if (!Bot_trace(ent,ent->client->current_enemy)) {
    if (ent->client->targetlock <= level.time) {
      ent->client->current_enemy = NULL;
      return; }
    ent->client->combatstate |= 0x00000001; }
  else {
    ent->client->targetlock = level.time+1.2;
    ent->client->combatstate &= ~0x00000001;
    ent->client->battlemode &= ~0x00000040; }

  VectorSubtract(ent->client->current_enemy->s.origin,ent->s.origin,vtmp);
  distance = VectorLength(vtmp);

  if (!(ent->client->combatstate & 0x00000001))
    Combat_Normal(ent,distance);
  else
  if (ent->client->combatstate & 0x00001000)
    Combat_Normal(ent,distance);
  else
    Combat_LevelX(ent,distance);

  if (ent->client->current_enemy) {
    ent->client->prev_enemy = ent->client->current_enemy;
    VectorCopy(ent->client->current_enemy->s.origin, ent->client->targ_old_origin); }
}

//====================================================
//============ BOT ENEMY SEARCHING ROUTINES ==========
//====================================================

//====================================================
void Bot_SearchEnemy(edict_t *ent) {
qboolean tmpflg=false;
edict_t *target=NULL;
edict_t *trent;
int i,j;
vec3_t vdir;

  if (ent->client->current_enemy)
    if (Bot_trace(ent,ent->client->current_enemy))
      tmpflg = true;

  j = (random() < 0.5)?0:-1;

  for (i=1; i <= maxclients->value && !target; i++) {
    if (j)
      trent = &g_edicts[i];
    else
      trent = &g_edicts[(int)(maxclients->value)-i+1];
    if (!trent->inuse || ent == trent || trent->deadflag) continue;
    if (ent->client->current_enemy == trent) continue;
    if (ent->client->current_enemy && ent->client->current_enemy->health < 1) continue; // raven - added deadflag check
    if (trent->movetype != MOVETYPE_NOCLIP) {
      if (InSight(ent,trent)) {
        VectorSubtract(trent->s.origin, ent->s.origin, vdir);
        if (!tmpflg && !target) {
          float vr = (float)Bot[ent->client->pers.botindex].skill[VRANGEVIEW];
          float hr = (float)Bot[ent->client->pers.botindex].skill[HRANGEVIEW];
          float pitch = fabs(Get_pitch(vdir)-ent->s.angles[PITCH]);
          if (pitch > 180) pitch = 360-pitch;
          if (pitch <= vr) {
            float yaw = Get_yaw(vdir);
            yaw = fabs(yaw-ent->s.angles[YAW]);
            if (yaw > 180) yaw = 360-yaw;
            if (yaw <= hr || (ent->client->movestate & (0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800)))
              target = trent; } }
        if (!tmpflg && !target && trent->mynoise && trent->mynoise2) {
          if (trent->mynoise->teleport_time >= (level.time-FRAMETIME)) {
            VectorSubtract(trent->mynoise->s.origin, ent->s.origin, vdir);
            if (VectorLength(vdir) < 300) {
              if ((9*random()) < 1+rand()%8)
                target = trent; } }
            if (!target && trent->mynoise2->teleport_time >= (level.time-FRAMETIME)) {
              VectorSubtract(trent->mynoise->s.origin, ent->s.origin, vdir);
              if (VectorLength(vdir) < 100) {
                if ((9*random()) < 1+rand()%8)
                  target = trent; } } } }
      else
      if (!tmpflg && trent->mynoise)
        if (trent->mynoise->teleport_time >= (level.time-FRAMETIME)) {
          trace_t tr;
          AngleVectors(trent->client->v_angle, vdir, NULL, NULL);
          VectorScale(vdir,200,vdir);
          VectorAdd(trent->s.origin,vdir,vdir);
          tr = gi.trace(trent->s.origin,NULL,NULL,vdir,trent,MASK_SHOT);
          VectorSubtract(ent->s.origin, tr.endpos, vdir);
          if (VectorLength(vdir) < 500) {
            VectorCopy(tr.endpos,vdir);
            tr = gi.trace(ent->s.origin,NULL,NULL,vdir,ent,MASK_SHOT);
            if (tr.fraction == 1.0 && (9*random()) < 1+rand()%8) {
              target = trent;
              ent->client->battlemode |= 0x00000040;
              VectorCopy(vdir,ent->client->vtemp); } } } } }

  if (target && !tmpflg)
    ent->client->current_enemy = target;
  else
  if (target && ent->client->current_enemy)
    if (GetKindWeapon(target->client->pers.weapon) > GetKindWeapon(ent->client->current_enemy->client->pers.weapon))
      ent->client->current_enemy = target;
}

//==============================================
//============ ITEM HANDLING ROUTINES ==========
//==============================================

//==============================================
void InitAllItems(void) {

  // set IT_ARMOR lookups
  item_jacketarmor = FindItem("Jacket Armor");
  item_combatarmor = FindItem("Combat Armor");
  item_bodyarmor   = FindItem("Body Armor");
  item_armorshard  = FindItem("Armor Shard");
  item_powerscreen = FindItem("Power Screen");
  item_powershield = FindItem("Power Shield");

  // set IT_AMMO lookups
  item_shells = FindItem("shells");
  item_cells = FindItem("cells");
  item_bullets = FindItem("bullets");
  item_rockets = FindItem("rockets");
  item_slugs = FindItem("slugs");
  item_grenades = FindItem("grenades");

  // set IT_WEAPON lookups
  item_blaster = FindItem("blaster");
  item_shotgun = FindItem("shotgun");
  item_supershotgun = FindItem("super shotgun");
  item_handgrenade = FindItem("grenades");
  item_machinegun = FindItem("machinegun");
  item_chaingun = FindItem("chaingun");
  item_grenadelauncher = FindItem("grenade launcher");
  item_rocketlauncher = FindItem("rocket launcher");
  item_railgun = FindItem("railgun");
  item_hyperblaster = FindItem("hyperblaster");
  item_bfg = FindItem("bfg10k");

  // set IT_HEALTH lookups
  item_adrenaline = FindItem("Adrenaline");
  item_health = FindItem("Health");
  item_stimpak = FindItem("Health");
  item_health_large = FindItem("Health");
  item_health_mega = FindItem("Health");

  // set IT_POWERUP lookups
  item_quad = FindItem("Quad Damage");
  item_invulnerability = FindItem("Invulnerability");
  item_silencer = FindItem("Silencer");
  item_breather = FindItem("Rebreather");
  item_enviro = FindItem("Environment Suit");

  // set IT_PACK lookups
  item_pack = FindItem("Ammo Pack");
  item_bandolier = FindItem("Bandolier");

  // set IT_NODE lookups
  item_navi1 = FindItem("Roam Navi1");
  item_navi2 = FindItem("Roam Navi2");
  item_navi3 = FindItem("Roam Navi3");
}

//==============================================
int GetKindWeapon(gitem_t *it) {

  if (it->weaponthink == Weapon_Shotgun)         return WEAP_SHOTGUN;
  if (it->weaponthink == Weapon_SuperShotgun)    return WEAP_SUPERSHOTGUN;
  if (it->weaponthink == Weapon_Machinegun)      return WEAP_MACHINEGUN;
  if (it->weaponthink == Weapon_Chaingun)        return WEAP_CHAINGUN;
  if (it->weaponthink == Weapon_Grenade)         return WEAP_GRENADES;
  if (it->weaponthink == Weapon_GrenadeLauncher) return WEAP_GRENADELAUNCHER;
  if (it->weaponthink == Weapon_RocketLauncher)  return WEAP_ROCKETLAUNCHER;
  if (it->weaponthink == Weapon_HyperBlaster)    return WEAP_HYPERBLASTER;
  if (it->weaponthink == Weapon_Railgun)         return WEAP_RAILGUN;
  if (it->weaponthink == Weapon_BFG)             return WEAP_BFG;

  return WEAP_BLASTER;
}


//====================================================
//============ BOT MOVEMENT TESTING ROUTINES =========
//====================================================

//============================================================
int Bot_TestMove(edict_t *ent,float ryaw,vec3_t pos,float dist,float *bottom) {
float yaw;
vec3_t trstart,trend;
vec3_t trmin,trmax,v,vv;
trace_t tr;
float tracelimit;
int contents;

  if (ent->waterlevel >= 1)
    tracelimit = 75;
  else
  if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
    tracelimit = 26;
  else
    tracelimit = JumpMax+5;

  VectorSet(trmin,-16,-16,-24);
  VectorSet(trmax,16,16,3);

  if (ent->client->routetrace)
    VectorSet(vv,16,16,0);
  else
    VectorSet(vv,16,16,3);

  if (ent->client->routetrace)
    if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
      if (ent->waterlevel < 2) {
        Get_RouteOrigin(ent->client->pers.routeindex,v);
        if ((v[2]-ent->s.origin[2]) > 20)
          trmax[2] = 31; }

  yaw = DEG2RAD(ryaw);
  trend[0] = cos(yaw)*dist;
  trend[1] = sin(yaw)*dist;
  trend[2] = 0;
  VectorAdd(trend, ent->s.origin, trstart);

  VectorCopy(trstart,trend);
  trend[2] += 1;
  tr = gi.trace(trstart, trmin, trmax, trend, ent, MASK_BOTSOLIDX);
  trmax[2] += 1;
  if (tr.allsolid || tr.startsolid || tr.fraction != 1.0) {
    float i;
    qboolean moveok = false;
    VectorCopy(trstart, trend);
    for (i = 4; i <(tracelimit+4); i += 4) {
      trstart[2] = ent->s.origin[2]+i;
      tr = gi.trace(trstart, trmin, vv, trend, ent, MASK_BOTSOLIDX);
      if (!tr.allsolid && !tr.startsolid && tr.fraction > 0) {
        moveok = true;
        break; } }
    if (!moveok) return 0;
    *bottom = tr.endpos[2]-ent->s.origin[2];
    if (!ent->client->routetrace) {
      if (tr.plane.normal[2] < 0.7 && (!ent->client->waterstate && ent->groundentity))
        return 0; }
    else {
      Get_RouteOrigin(ent->client->pers.routeindex,v);
      if (tr.plane.normal[2] < 0.7 && v[2] < ent->s.origin[2])
        return 0; }
    if (*bottom >tracelimit-5) return 0;
    VectorCopy(tr.endpos,pos);
    if (trmax[2] == 32) return 1;
    VectorCopy(pos,trend);
    trend[2] += 28;
    tr = gi.trace(pos, trmin, trmax, trend, ent, MASK_BOTSOLIDX);
    return (!tr.allsolid && !tr.startsolid && tr.fraction == 1.0)?1:2; }
  else {
    VectorCopy(trstart,pos);
    VectorCopy(trstart, trend);
    trstart[2] = trend[2]-8190;
    tr = gi.trace(trend, trmin, trmax, trstart, ent, MASK_BOTSOLIDX|MASK_OPAQUE);
    *bottom = tr.endpos[2]-ent->s.origin[2];
    contents = 0;
    if (!ent->waterlevel) {
      if (ent->client->enviro_framenum > level.framenum)
        contents = CONTENTS_LAVA;
      else
        contents = (CONTENTS_LAVA|CONTENTS_SLIME); }
    if (tr.contents & contents)
      *bottom = -9999;
    else
    if (tr.surface->flags & SURF_SKY)
      *bottom = -9999;
    if (!ent->waterlevel && !ent->groundentity)
      if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
        if (ent->velocity[2] > 10 && trmax[2] == 4)
          return 2;
    if (trmax[2] == 32) return 1;
    VectorCopy(pos,trend);
    trend[2] += 28;
    tr = gi.trace(pos, trmin, trmax, trend, ent, MASK_BOTSOLIDX);
    return (!tr.allsolid && !tr.startsolid && tr.fraction == 1.0)?1:2; }
}

//============================================================
qboolean Bot_Watermove(edict_t *ent, vec3_t pos, float dist, float upd) {
trace_t tr;
vec3_t trmin,trmax,touchmin;
float i,j,max,vec;

  VectorCopy(ent->s.origin,trmax);

  trmax[2] += upd;
  tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, trmax, ent, MASK_BOTSOLIDX);
  if (!tr.allsolid && !tr.startsolid && tr.fraction > 0) {
    VectorCopy(tr.endpos,pos);
    return true; }

  VectorCopy(ent->s.origin,trmin);
  trmin[2] += upd;
  vec = -1;
  max = 0;
  for (i=0; i<360; i+=10) {
    if (i && upd > -13 && upd < 0) break;
    if (i > 60 && i < 300) continue;
    j = ent->client->moveyaw+i;
    if (j > 180) j -= 360;
    else if (j < -180) j += 360;
    else j = i;
    touchmin[0] = cos(j)*24;
    touchmin[1] = sin(j)*24;
    touchmin[2] = 0;
    VectorAdd(trmin,touchmin,trmax);
    tr = gi.trace(trmax, ent->mins, ent->maxs, trmin, ent, MASK_BOTSOLIDX);
    if (!tr.allsolid && !tr.startsolid) {
      VectorAdd(tr.endpos,touchmin,trmax);
      tr = gi.trace(trmax, ent->mins, ent->maxs, trmax, ent, MASK_BOTSOLIDX);
      if (!tr.allsolid && !tr.startsolid) {
        vec = i; break; } } }

  if (vec == -1) return false;

  VectorCopy(trmax,pos);
  if (upd < 0)
    ent->velocity[2] = 0;

  return true;
}

//============================================================
qboolean Bot_moveW(edict_t *ent, float ryaw, vec3_t pos, float dist, float *bottom) {
float yaw;
vec3_t trstart,trend;
trace_t tr;
int contents;

  if (ent->client->enviro_framenum > level.framenum)
    contents = CONTENTS_LAVA;
  else
    contents = (CONTENTS_LAVA|CONTENTS_SLIME);

  yaw = DEG2RAD(ryaw);
  trend[0] = cos(yaw)*dist;
  trend[1] = sin(yaw)*dist;
  trend[2] = 0;
  VectorAdd(trend, ent->s.origin, trstart);

  VectorCopy(trstart,pos);
  VectorCopy(trstart,trend);

  trstart[2] = trend[2]-8190;
  tr = gi.trace(trend, ent->mins, ent->maxs, trstart,ent, MASK_BOTSOLIDX|CONTENTS_WATER);
  if ((trend[2]-tr.endpos[2]) >= 95) return false;
  if (tr.contents & contents) return false;
  if (!(tr.contents & CONTENTS_WATER)) return false;

  *bottom = tr.endpos[2]-ent->s.origin[2];

  return true;
}

//============================================================
qboolean Bot_Jump(edict_t *ent, vec3_t pos, float dist) {
float x,yaw,tdist,bottom,speed;
vec3_t temppos;

  yaw = ent->client->moveyaw;

  Bot_TestMove(ent,yaw,temppos,dist,&bottom);
  if (bottom > -JumpMax) return false;

  for (x=2; x<=16; x++) {
    tdist = dist*x;
    if (Bot_TestMove(ent,yaw,temppos,tdist,&bottom)) {
      if (bottom <= JumpMax && bottom > -JumpMax)
        if (Get_FlyingSpeed(bottom,x,dist,&speed)) {
          speed *= 1.5;
          if (speed > 1.2) speed = 1.2;
          ent->moveinfo.speed = speed;
          ent->velocity[2] = 300;
          SetBotAnim(ent);
          return true; }
      continue; }
    else
      return false; }

  return false;
}

//============================================================
qboolean Bot_Fall(edict_t *ent,vec3_t pos,float dist) {
float x,n,speed,vel,yori,ypos;
vec3_t vdir,vv;
int mf = 0;
int mode = 0;

  if (ent->client->routetrace) {
    mode = 2;
    Get_RouteOrigin(ent->client->pers.routeindex,vv);
    ypos = vv[2];
    if (!HazardCheck(ent,vv)) {
      if (++ent->client->pers.routeindex >= TotalRouteNodes)
        ent->client->pers.routeindex = 0;
      return false; }
    yori = pos[2];
    VectorSubtract(vv,pos,vdir);
    if (vdir[2] >= 0) goto JUMPCATCH;
    vel = ent->velocity[2];
    n = 1.0;
    for (x=1; x<=30; ++x,n+=x) {
      vel -= (ent->gravity*sv_gravity->value*FRAMETIME);
      yori += vel*0.1;
      if (ypos >= yori) {
        mf = 1;
        break; } }
    VectorCopy(vdir,vv);
    vv[2] = 0;
    if (Route[ent->client->pers.routeindex].state == 5) {
      vv[0] += 0.1*Route[ent->client->pers.routeindex].ent->velocity[0]*x;
      vv[1] += 0.1*Route[ent->client->pers.routeindex].ent->velocity[1]*x; }
    speed = VectorLength(vv)/x;
    if (speed <= 30 && mf) {
      ent->moveinfo.speed = speed/30;
      VectorCopy(pos,ent->s.origin);
      return true; }
    goto JUMPCATCH; }

  goto JMPCHK;

JUMPCATCH:

  vel = 300;
  yori = pos[2];
  mf = 0;

  for (x=1; x<=30; ++x) {
    vel -= (ent->gravity*sv_gravity->value*FRAMETIME);
    yori += vel*0.1;
    if (vel > 0) {
      if (mf == 0) {
        if (ypos < yori)
          mf = 2; } }
    else if (x > 1) {
      if (mf == 0) {
        if (ypos < yori)
          mf = 2; }
      else if (mf == 2) {
        if (ypos >= yori) {
          mf = 1;
          break; } } } }

  VectorCopy(vdir,vv);
  vv[2] = 0;
  if (mode == 2)
    if (Route[ent->client->pers.routeindex].state == 5) {
      vv[0] += 0.1*Route[ent->client->pers.routeindex].ent->velocity[0]*x;
      vv[1] += 0.1*Route[ent->client->pers.routeindex].ent->velocity[1]*x; }

  n = VectorLength(vv);
  if (x > 1) n /= (x-1);

  if (n < 30 && mf) {
    ent->moveinfo.speed = n/30;
    VectorCopy(pos,ent->s.origin);
    ent->velocity[2] = 300;
    SetBotAnim(ent);
    return true; }

JMPCHK:

  if (Bot_Jump(ent,pos,dist)) return true;

  return false;
}

//============================================================
qboolean TargetJump(edict_t *ent,vec3_t tpos) {
float x,n,jvel,vel,yori;
vec3_t vdir,vv;
int mf=0;

  jvel = vel = 300;
  yori = ent->s.origin[2];

  if (!HazardCheck(ent,tpos)) return false;

  VectorSubtract(tpos,ent->s.origin,vdir);

  for (x=1; x<=60; ++x) {
    vel -= (ent->gravity*sv_gravity->value*0.1);
    yori += vel*0.1;
    if (vel > 0) {
      if (mf == 0) {
        if (tpos[2] < yori)
          mf = 2; } }
    else if (x > 1) {
      if (mf == 0) {
        if (tpos[2] < yori)
          mf = 2; }
      else if (mf == 2) {
        if (tpos[2] >= yori) {
          mf = 1;
          break; } } } }

  VectorCopy(vdir,vv);
  vv[2] = 0;

  n = VectorLength(vv);
  if (x > 1) n /= (x-1);

  if (n < 30 && mf) {
    ent->moveinfo.speed = n/30;
    ent->velocity[2] = jvel;
    SetBotAnim(ent);
    return true; }

  return false;
}

//============================================================
qboolean TargetJump_Chk(edict_t *ent,vec3_t tpos,float defvel) {
float x,n,vel,yori;
vec3_t vdir,vv;
int mf = 0;

  vel = defvel+300;
  yori = ent->s.origin[2];

  if (!HazardCheck(ent,tpos)) return false;

  VectorSubtract(tpos,ent->s.origin,vdir);

  for (x=1; x<=60; ++x) {
    vel -= (ent->gravity*sv_gravity->value*0.1);
    yori += vel*0.1;
    if (vel > 0) {
      if (mf == 0) {
        if (tpos[2] < yori)
          mf = 2; } }
    else if (x > 1) {
      if (mf == 0) {
        if (tpos[2] < yori)
          mf = 2; }
      else if (mf == 2) {
        if (tpos[2] >= yori) {
          mf = 1;
          break; } } } }

  VectorCopy(vdir,vv);
  vv[2] = 0;

  n = VectorLength(vv);
  if (x > 1) n /= (x-1);

  return (n<30 && mf!=0);
}

//============================================================
void Get_WaterState(edict_t *ent) {

  ent->watertype = gi.pointcontents(ent->s.origin);
  ent->waterlevel = (ent->watertype & MASK_WATER)?1:0;

  if (ent->waterlevel) {
    float x;
    trace_t tr;
    vec3_t trmin,trmax;
    VectorCopy(ent->s.origin,trmax);
    VectorCopy(ent->s.origin,trmin);
    trmax[2] -= 24;
    trmin[2] += 8;
    tr = gi.trace(trmin, NULL, NULL, trmax, ent, MASK_WATER);
    x = trmin[2]-tr.endpos[2];
    if (tr.allsolid || tr.startsolid || (x < 4.0))
      ent->client->waterstate = 2;
    else
      ent->client->waterstate = (x >= 4.0 && x <= 12.0)?1:0; }
  else
    ent->client->waterstate = 0;
}

//============================================================
void Search_NearbyPod(edict_t *ent) {

  if (Route[ent->client->pers.routeindex].state >= 3) return;

  if ((ent->client->pers.routeindex+1) < TotalRouteNodes)
    if (Route[ent->client->pers.routeindex+1].state < 3) {
      vec3_t v;
      Get_RouteOrigin(ent->client->pers.routeindex+1,v);
      if (TraceX(ent,v)) {
        vec3_t v1,v2;
        float x;
        VectorSubtract(v,ent->s.origin,v1);
        Get_RouteOrigin(ent->client->pers.routeindex,v);
        VectorSubtract(v,ent->s.origin,v2);
        x = fabs(v1[2]);
        if (VectorLength(v1) < VectorLength(v2) && x <= JumpMax && Route[ent->client->pers.routeindex].state <= 1)
          ent->client->pers.routeindex++;
        else {
          if (ent->client->waterstate==0)
            if (v2[2] > JumpMax)
              if (fabs(v1[2]) < JumpMax)
                ent->client->pers.routeindex++; } } }
}

//========================================================
void BotAI(edict_t *ent) {
float dist,x,yaw,iyaw,f1,f2,f3,bottom;
int tempflag,i,j,k;
edict_t *it_ent,*touch[1024],*trent;
vec3_t touchmin,touchmax,v,vv;
vec3_t temppos,trmin,trmax;
qboolean ladderdrop,canrocj,waterjumped;
gitem_t *it;
edict_t *front,*left,*right,*e;
char *str;
cplane_t plane;
trace_t tr;

  myrandom=random();

  if (ent->client->changetime < level.time) {
    RandomizeParameters(ent->client->pers.botindex);
    ent->client->changetime = level.time+30+10*(int)rand()%9; }

  trace_priority = 1;
  ent->client->objshot = false;
  ent->client->buttons &= ~BUTTON_ATTACK;

  if (VectorCompare(ent->s.origin,ent->s.old_origin))
    if (!ent->groundentity && !ent->waterlevel) {
      VectorCopy(ent->s.origin,v);
      v[2] -= 1.0;
      tr = gi.trace(ent->s.origin,ent->mins,ent->maxs,v,ent,MASK_BOTSOLIDX);
      if (!tr.allsolid && !tr.startsolid)
        ent->groundentity = tr.ent; }

  if (JumpMax == 0) {
    x = 300-ent->gravity*sv_gravity->value*0.1;
    JumpMax = 0;
    while (1) {
      JumpMax += x*0.1;
      x -= ent->gravity*sv_gravity->value*0.1;
      if (x < 0) break; } }

  if (!ent->client->havetarget && ent->client->routetrace) {
    j = Bot[ent->client->pers.botindex].skill[PRIMARYWEAP];
    if (j && !ent->client->pers.inventory[j]) {
      it = &itemlist[j];
      if (ent->client->enemy_routeindex < ent->client->pers.routeindex || ent->client->enemy_routeindex >= TotalRouteNodes)
        ent->client->enemy_routeindex = ent->client->pers.routeindex;
      for (i = ent->client->enemy_routeindex+1;i<(ent->client->enemy_routeindex+50);i++) {
        if (i > TotalRouteNodes) break;
        if (Route[i].state == 3) {
          if (Route[i].ent->item == it) {
            ent->client->havetarget = true;
            break; }
          else
          if (Route[i].ent->solid == SOLID_TRIGGER) {
            if (Route[i].ent->item == &itemlist[j]) {
              ent->client->havetarget = true;
              break; } } } }
      ent->client->enemy_routeindex = i; }
    else
      if (j=ITEM_INDEX(item_quad)) {
        it = &itemlist[j];
        if (ent->client->enemy_routeindex < ent->client->pers.routeindex || ent->client->enemy_routeindex >= TotalRouteNodes)
          ent->client->enemy_routeindex = ent->client->pers.routeindex;
        for (i = ent->client->enemy_routeindex+1;i<(ent->client->enemy_routeindex+25);i++) {
          if (i > TotalRouteNodes) break;
          if (Route[i].state == 3) {
            if (Route[i].ent->item == it) {
              if (Route[i].ent->solid == SOLID_TRIGGER) {
                ent->client->havetarget = true;
                break; } } } }
        ent->client->enemy_routeindex = i; } }
  else
  if (ent->client->havetarget)
    if (ent->client->enemy_routeindex < ent->client->pers.routeindex) {
      ent->client->havetarget = false;
      ent->client->enemy_routeindex = ent->client->pers.routeindex; }

  canrocj = (ent->client->pers.inventory[ITEM_INDEX(item_rocketlauncher)] && ent->client->pers.inventory[ITEM_INDEX(item_rockets)] > 0);

  if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) {
    if (ent->client->battleduckcnt > 0)
      if (ent->groundentity)
        goto DCHCANC;
    VectorSet(v,16,16,32);
    VectorCopy(ent->s.origin,v);
    v[2] += 28;
    tr = gi.trace(ent->s.origin,ent->mins,ent->maxs,v,ent,MASK_BOTSOLIDX);
    if (!tr.startsolid && !tr.allsolid && tr.fraction == 1.0) {
      ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
      ent->maxs[2] = 32; } }
  else
  if (ent->velocity[2] > 10 && !ent->groundentity)
    if (!(ent->client->movestate & (0x00000008|0x00000002|0x00000004))) {
      VectorSet(v,16,16,40);
      tr = gi.trace(ent->s.origin,ent->mins,v,ent->s.origin,ent,MASK_BOTSOLIDX);
      if (tr.startsolid || tr.allsolid) {
        ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
        ent->maxs[2] = 4; } }

DCHCANC:

  if (ent->groundentity || ent->waterlevel) {
    if (ent->waterlevel) {
      if (!(ent->client->movestate & 0x00000004))
        ent->client->movestate &= ~(0x00000008|0x00000002|0x00000004); }
    else
      ent->client->movestate &= ~(0x00000008|0x00000002|0x00000004);
    if (ent->groundentity && !ent->waterlevel)
      ent->moveinfo.speed = 1.0;
    else
    if (ent->waterlevel && ent->velocity[2] <= 1)
      ent->moveinfo.speed = 1.0; }

  if ((ent->client->ps.pmove.pm_flags & PMF_DUCKED) && ent->groundentity)
    dist = 10*ent->moveinfo.speed;
  else {
    dist = 30*ent->moveinfo.speed;
    if (ent->groundentity)
      dist *= ent->client->ground_slope; }

  Get_WaterState(ent);

  ent->client->enemysearchcnt += 2;
  if (ent->client->enemysearchcnt >= 10) {
    Bot_SearchEnemy(ent);
    ent->client->enemysearchcnt = 1+rand()%8;
    if (ent->client->enemysearchcnt > 10)
      ent->client->enemysearchcnt = 10;
    if (ent->client->enemysearchcnt < 0)
      ent->client->enemysearchcnt = 0; }

  Set_Combatstate(ent);

  if (trace_priority == 4)
    goto VCHCANSEL;

  if (ent->client->routetrace) {
    if (Route[ent->client->pers.routeindex].state >= 0)
      Search_NearbyPod(ent);
    Get_RouteOrigin(ent->client->pers.routeindex,v);
    if (ent->client->movestate & (0x00000020|0x00000040|0x00000080|0x00000400|0x00000100|0x00000200))
      ent->client->routelocktime = level.time+1.5;
    else
    if (Route[ent->client->pers.routeindex].state <= 3 && ((v[2]-ent->s.origin[2]) > JumpMax && !ent->client->waterstate) && !(ent->client->movestate & 0x00000001)) {
      if (ent->client->routelocktime <= level.time) {
        ent->client->routetrace = false;
        ent->client->routereleasetime = level.time+2.0; } }
    else
    if (!TraceX(ent,v)) {
      k=0;
      if (ent->groundentity) {
        if (ent->groundentity->use == train_use) {
          ent->client->routelocktime = level.time+1.5;
          k = 1; } }
      if (ent->client->routelocktime <= level.time && !k) {
        ent->client->routetrace = false;
        ent->client->routereleasetime = level.time+2.0; } }
    else
      ent->client->routelocktime = level.time+1.5; }

  if (trace_priority == 4)
    goto VCHCANSEL;

  if (ent->client->movestate & 0x00000001) {
    ent->velocity[2] = 200;
    VectorCopy(ent->mins,trmin);
    trmin[2] += 20;
    yaw = DEG2RAD(ent->client->moveyaw);
    touchmin[0] = cos(yaw)*32;
    touchmin[1] = sin(yaw)*32;
    touchmin[2] = 0;
    VectorAdd(ent->s.origin,touchmin,touchmax);
    tr = gi.trace(ent->s.origin, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID);
    plane = tr.plane;
    if (!(tr.contents & CONTENTS_LADDER) && !tr.allsolid) {
      if (ent->velocity[2] <= 200)
        if (!ent->waterlevel)
          ent->velocity[2] = 200;
      ent->client->movestate &= ~0x00000001;
      ent->moveinfo.speed = 0.25;
      if (ent->client->routetrace) {
        Get_RouteOrigin(ent->client->pers.routeindex,v);
        if (VectorLength(v) > 32) {
          VectorSubtract(v,ent->s.origin,v);
          ent->client->moveyaw = Get_yaw(v);
          if (trace_priority < 2)
            ent->s.angles[YAW] = ent->client->moveyaw; }
        else
          ent->client->pers.routeindex++; } }
    else {
      if (!tr.allsolid)
        VectorCopy(tr.endpos,ent->s.origin);
      VectorCopy(ent->s.origin,touchmin);
      touchmin[2] += 8;
      tr = gi.trace(ent->s.origin, ent->mins,ent->maxs, touchmin,ent, MASK_BOTSOLID);
      x = tr.endpos[2]-ent->s.origin[2];
      ent->s.origin[2] += x;
      e = tr.ent;
      if (x == 0) {
        x = Get_yaw(plane.normal);
        VectorCopy(ent->s.origin,v);
        yaw = x+90;
        if (yaw > 180) yaw -= 360;
        yaw = DEG2RAD(yaw);
        touchmin[0] = cos(yaw)*48;
        touchmin[1] = sin(yaw)*48;
        touchmin[2] = 0;
        VectorAdd(ent->s.origin,touchmin,trmin);
        VectorCopy(trmin,trmax);
        trmin[2] += 32;
        trmax[2] += 64;
        tr = gi.trace(trmin,NULL,NULL,trmax,ent,MASK_BOTSOLID);
        f1 = tr.fraction;
        VectorCopy(ent->s.origin,v);
        iyaw = x -90;
        if (iyaw < 180) iyaw += 360;
        iyaw = DEG2RAD(iyaw);
        touchmin[0] = cos(iyaw)*48;
        touchmin[1] = sin(iyaw)*48;
        touchmin[2] = 0;
        VectorAdd(ent->s.origin,touchmin,trmin);
        VectorCopy(trmin,trmax);
        trmin[2] += 32;
        trmax[2] += 64;
        tr = gi.trace(trmin,NULL,NULL,trmax,ent,MASK_BOTSOLID);
        f2 = tr.fraction;
        x = 0.0;
        if (f1 == 1.0 && f2 != 1.0)
          x = yaw;
        else
          if (f1 != 1.0 && f2 == 1.0)
            x = iyaw;
        if (x != 0.0) {
          touchmin[0] = cos(x)*4;
          touchmin[1] = sin(x)*4;
          touchmin[2] = 0;
          VectorAdd(ent->s.origin,touchmin,trmin);
          tr = gi.trace(ent->s.origin,ent->mins,ent->maxs,trmin,ent,MASK_BOTSOLID);
          if (tr.startsolid || tr.allsolid)
            x = 0;
          else
            VectorCopy(tr.endpos,ent->s.origin); }
        if (x == 0.0) {
          k = 0;
          if (e) {
            if (e->use == door_use) {
              if (e->moveinfo.state == 2)
                k = 1; } }
          if (!k) {
            ent->client->moveyaw += 180;
            if (ent->client->moveyaw > 180)
              ent->client->moveyaw -= 360;
            ent->client->movestate &= ~0x00000001;
            ent->moveinfo.speed = 0.25; } } } }

    if (ent->client->movestate & 0x00000001) {
      if (ent->client->routetrace) {
        Get_RouteOrigin(ent->client->pers.routeindex,v);
        if (v[2] < ent->s.origin[2]) {
          VectorSubtract(ent->s.origin,v,vv);
          vv[2] = 0;
          if (VectorLength(vv) < 32)
            ent->client->pers.routeindex++; } }
      ent->velocity[0] = 0;
      ent->velocity[1] = 0;
      goto VCHCANSEL_L; } }

  if (ent->groundentity && ent->waterlevel <= 1)
    if (trace_priority < 2)
      ent->s.angles[PITCH] = 0;

  if (ent->groundentity && !ent->client->routetrace) {
    if (trace_priority < 3)
      ent->client->moveyaw = ent->s.angles[YAW]; }
  else
  if (trace_priority < 2)
    ent->s.angles[YAW] = ent->client->moveyaw;

  if (!ent->client->routetrace && ent->client->routereleasetime <= level.time) {
    if (ent->client->pers.routeindex >= TotalRouteNodes)
      ent->client->pers.routeindex = 0;
    for (i=0; i<TotalRouteNodes && i<12;i++) {
      if (Route[ent->client->pers.routeindex].state == 21) {
        while (1) {
            ++ent->client->pers.routeindex;
            if (ent->client->pers.routeindex >= TotalRouteNodes) {
              i = TotalRouteNodes;
              break; }
            if (Route[ent->client->pers.routeindex].state == 22) {
              ++ent->client->pers.routeindex;
              break; } }
          continue; }
        else
        if (Route[ent->client->pers.routeindex].state == 22) {
          ++ent->client->pers.routeindex; continue; }
        Get_RouteOrigin(ent->client->pers.routeindex,v);
        if (Route[ent->client->pers.routeindex].state <= 3 && TraceX(ent,v)) {
          if (fabs(v[2]-ent->s.origin[2]) <= JumpMax || ent->client->waterstate == 2) {
            ent->client->routetrace = true;
            ent->client->routelocktime = level.time+1.5;
            break; } }
        if (++ent->client->pers.routeindex >= TotalRouteNodes)
          ent->client->pers.routeindex = 0; } }
    else
    if (ent->client->routetrace) {
      if (Route[ent->client->pers.routeindex].state == 6) {
          it_ent = Route[ent->client->pers.routeindex].ent;
          if (ent->client->pers.routeindex+1 < TotalRouteNodes) {
            Get_RouteOrigin(ent->client->pers.routeindex+1,v);
            ent->client->routetrace = false;
            j = TraceX(ent,v);
            ent->client->routetrace = true;
            if ((!j ||(v[2]-ent->s.origin[2]) > JumpMax)&& it_ent->union_ent) {
              k = ((it_ent->union_ent->s.origin[2]-ent->s.origin[2]) > JumpMax)?1:0;
              VectorSubtract(it_ent->union_ent->s.origin,ent->s.origin,temppos);
              yaw = Get_yaw(temppos);
              if (trace_priority < 2) {
                ent->s.angles[PITCH] = Get_pitch(temppos);
                ent->s.angles[YAW] = yaw; }
              temppos[2] = 0;
              x = VectorLength(temppos);
              if (x == 0 || k) {
                if (it_ent->nextthink >= level.time)
                  ent->client->routelocktime = level.time+1.5;
                goto VCHCANSEL; }
              if (x < dist)
                dist = x;
              if (it_ent->nextthink > level.time)
                ent->client->routelocktime = it_ent->nextthink+1.5;
              else
                ent->client->routelocktime = level.time+1.5;
              if (trace_priority < 3)
                ent->client->moveyaw = yaw;
              goto GOMOVE; } }
        ent->client->pers.routeindex++; }

      if (ent->client->pers.routeindex < TotalRouteNodes) {
        Get_RouteOrigin(ent->client->pers.routeindex,v);
        k = 0;
        if (Route[ent->client->pers.routeindex].state == 7) {
          it_ent = Route[ent->client->pers.routeindex].ent;
          if (it_ent->health && (it_ent->takedamage || it_ent->moveinfo.state != 0))
            k = 2;
          else
          if (it_ent->health) {
            ent->client->pers.routeindex++;
            if (ent->client->pers.routeindex < TotalRouteNodes)
              Get_RouteOrigin(ent->client->pers.routeindex,v); } }
        else {
          VectorSet(touchmax,16,16,4);
          VectorSet(touchmin,-16,-16,0);
          tr = gi.trace(ent->s.origin,touchmin,touchmax,v,ent,MASK_SHOT);
          if (tr.fraction != 1.0 && tr.ent) {
            if (tr.ent->health || tr.ent->takedamage)
              if (tr.ent->classname[0] != 'p' && tr.ent->classname[0] != 'b') {
                ent->client->routelocktime = level.time+1.5;
                it_ent = tr.ent;
                k = 1; } } }
        if (k && !(ent->client->buttons & BUTTON_ATTACK)) {
          trmin[0] = (it_ent->absmin[0]+it_ent->absmax[0])*0.5;
          trmin[1] = (it_ent->absmin[1]+it_ent->absmax[1])*0.5;
          trmin[2] = (it_ent->absmin[2]+it_ent->absmax[2])*0.5;
          if (k == 2) {
            VectorSet(touchmin, 0, 0, ent->viewheight-8);
            VectorAdd(ent->s.origin,touchmin,touchmin);
            tr = gi.trace(it_ent->union_ent->s.origin,NULL,NULL,trmin,it_ent->union_ent,MASK_SHOT);
            VectorSubtract(tr.endpos,ent->s.origin,trmax); }
          else
            VectorSubtract(v,ent->s.origin,trmax);
          if (!ent->client->current_enemy && it_ent->takedamage) {
            ent->client->newweapon = item_blaster;
            ChangeWeapon(ent);
            ent->client->pers.weapon->use(ent,item_blaster); }
          if (!ent->client->current_enemy || it_ent->takedamage) {
            ent->s.angles[YAW] = Get_yaw(trmax);
            ent->s.angles[PITCH] = Get_pitch(trmax); }
          if (it_ent->takedamage)
            ent->client->buttons |= BUTTON_ATTACK;
          if (k == 2) {
            if (it_ent->moveinfo.state != 0)
              goto VCHCANSEL; }
          else {
            if (!TraceX(ent,v))
              goto VCHCANSEL; } }

        if (Route[ent->client->pers.routeindex].state == 5 && !ent->client->waterstate) {
          Get_RouteOrigin(ent->client->pers.routeindex -1 ,trmin);
          if ((trmin[2]-ent->s.origin[2]) > JumpMax && (v[2]-ent->s.origin[2]) > JumpMax && ent->waterlevel < 3) {
            ent->client->routetrace = false; } }
        f2=(ent->client->waterstate == 2)?20:(ent->groundentity)?-8:0;
        if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
          f1 = -16;
        else {
          if (ent->client->waterstate == 2)
            f1 = 24;
          else
          if (ent->waterlevel && ent->waterlevel < 3) {
            f1 = ((v[0] == ent->s.origin[0] && v[1] == ent->s.origin[1])?-300:(-(JumpMax+64))); }
          else
            f1 = -(JumpMax+64); }
        yaw = (Route[ent->client->pers.routeindex].state == 1)?-48:12;
        if (v[0] <= (ent->absmax[0]-yaw) && v[0] >= (ent->absmin[0]+yaw)) {
          if (v[1] <= (ent->absmax[1]-yaw) && v[1] >= (ent->absmin[1]+yaw)) {
            if ((v[2] <= (ent->absmax[2]-f1) && v[2] >= (ent->absmin[2]+f2))
              || Route[ent->client->pers.routeindex].state == 1) {
              if (ent->client->pers.routeindex < TotalRouteNodes) {
                if (Route[ent->client->pers.routeindex].state <= 3) {
                  if (ent->client->havetarget) {
                    for (i = 0;i <(6);i++) {
                      if (!(k=Route[ent->client->pers.routeindex].linkpod[i])) break;
                      if (k > ent->client->pers.routeindex && k < ent->client->enemy_routeindex) {
                        ent->client->pers.routeindex = k;
                        break; } } }
                  else
                  if (random() < 0.2) {
                    for (i = 0;i <(6);i++) {
                      if (!(k=Route[ent->client->pers.routeindex].linkpod[i])) break;
                      if (k > ent->client->pers.routeindex && k < ent->client->enemy_routeindex) {
                        if (random() < 0.5) {
                          ent->client->pers.routeindex = k;
                          break; } } } } }
                ent->client->pers.routeindex++;
                if (!(ent->client->pers.routeindex < TotalRouteNodes))
                 ent->client->pers.routeindex = 0; } } } }

        if (ent->client->pers.routeindex < TotalRouteNodes && trace_priority) {
          if (1) {
            Get_RouteOrigin(ent->client->pers.routeindex,v);
            VectorSubtract(v,ent->s.origin,temppos);
            if (trace_priority < 2)
              ent->s.angles[PITCH] = Get_pitch(temppos);
            k = 0;
            if (ent->groundentity  || ent->waterlevel) {
              yaw = temppos[2];
              temppos[2] = 0;
              x = VectorLength(temppos);
              if (1) {
                k = 0;
                if (trace_priority < 3)
                  ent->client->moveyaw = Get_yaw(temppos);
                if ((ent->groundentity || ent->waterlevel) && trace_priority < 2) {
                  ent->s.angles[YAW] = ent->client->moveyaw;
                  k = 1; }
                if (x < dist && fabs(yaw) < 20 && k) {
                  iyaw = Get_yaw(temppos);
                  i = Bot_TestMove(ent,iyaw,temppos,x,&bottom);
                  tr = gi.trace(v,ent->mins,ent->maxs,v,ent,MASK_BOTSOLIDX);
                  if (Route[ent->client->pers.routeindex].state == 3 && !i) {
                    if (x < 30) ent->client->pers.routeindex++; }
                  else
                  if ((Route[ent->client->pers.routeindex].state == 3
                    || Route[ent->client->pers.routeindex].state == 0)
                    && !tr.allsolid && !tr.startsolid
                    && HazardCheck(ent,v)
                    && fabs(bottom) < 20 && i && !ent->waterlevel) {
                    if ((v[2] < ent->s.origin[2] && bottom < 0) || (v[2] >= ent->s.origin[2] && bottom >= 0)) {
                      VectorCopy(temppos,ent->s.origin);
                      VectorCopy(v,trmin);
                      dist -= x;
                      if (Route[ent->client->pers.routeindex].state <= 3) {
                        if (ent->client->havetarget) {
                          for (i = 0;i <(6);i++) {
                            if (!(j=Route[ent->client->pers.routeindex].linkpod[i])) break;
                            if (j > ent->client->pers.routeindex && j < ent->client->enemy_routeindex) {
                              ent->client->pers.routeindex = j;
                              break; } } } }
                      ent->client->pers.routeindex++;
                      if (i == 2)
                        ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
                      Get_RouteOrigin(ent->client->pers.routeindex,v);
                      VectorSubtract(v,ent->s.origin,temppos);
                      if (trace_priority < 2)
                        ent->s.angles[PITCH] = Get_pitch(temppos);
                      if (trace_priority < 3)
                        ent->client->moveyaw = Get_yaw(temppos);
                      if (k && trace_priority < 2)
                        ent->s.angles[YAW] = ent->client->moveyaw; } }
                  else
                  if ((Route[ent->client->pers.routeindex].state == 3
                    || Route[ent->client->pers.routeindex].state == 0)
                    && fabs(bottom) < 20 && ent->waterlevel) {
                    if ((v[2] < ent->s.origin[2] && bottom < 0) || (v[2] >= ent->s.origin[2] && bottom >= 0)) {
                      VectorCopy(temppos,ent->s.origin);
                      VectorCopy(v,trmin);
                      dist -= x;
                      ent->client->pers.routeindex++;
                      Get_RouteOrigin(ent->client->pers.routeindex,v);
                      VectorSubtract(v,ent->s.origin,temppos);
                      if (trace_priority < 2)
                        ent->s.angles[PITCH] = Get_pitch(temppos);
                      if (trace_priority < 3)
                        ent->client->moveyaw = Get_yaw(temppos);
                      if (k && trace_priority < 2)
                        ent->s.angles[YAW] = ent->client->moveyaw; }
                    else dist = x; }
                  else dist = x; }
                else
                  if (x < dist) dist = x; }

              k = 0;
              if ((ent->client->pers.routeindex-1) >= 0 &&
                (Route[ent->client->pers.routeindex].state == 4
                || Route[ent->client->pers.routeindex].state == 5)) {
                Get_RouteOrigin(ent->client->pers.routeindex-1,v);
                if (fabs(v[2]-ent->s.origin[2]) <= JumpMax) {
                  if (ent->client->waterstate < 2 && Route[ent->client->pers.routeindex].ent->nextthink > level.time)
                    k = 1; } }
              if (k && !(ent->client->movestate & (0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800))) {
                if ((ent->client->pers.routeindex+1) < TotalRouteNodes) {
                  Get_RouteOrigin(ent->client->pers.routeindex+1,v);
                  if ((v[2]-ent->s.origin[2]) > JumpMax) {
                    if ((Route[ent->client->pers.routeindex].ent->union_ent->s.origin[2] - ent->s.origin[2]) > JumpMax) {
                      ent->client->waiting_obj = Route[ent->client->pers.routeindex].ent;
                      ent->client->movestate |= 0x00000400;
                      k = 0;
                      for (i = 1;i <=3;i++) {
                        if (ent->client->pers.routeindex-i >= 0) {
                          Get_RouteOrigin(ent->client->pers.routeindex-i,v);
                          if (ent->client->waiting_obj->absmax[0] < (v[0]+ent->mins[0])) k = 1;
                          else if (ent->client->waiting_obj->absmax[1] < (v[1]+ent->mins[1])) k = 1;
                          else if (ent->client->waiting_obj->absmin[0] > (v[0]+ent->maxs[0])) k = 1;
                          else if (ent->client->waiting_obj->absmin[1] > (v[1]+ent->maxs[1])) k = 1;
                          if (k) break; } }
                      if (k)
                        VectorCopy(v,ent->client->movtarget_pt);
                      else
                        Get_RouteOrigin(ent->client->pers.routeindex-1,ent->client->movtarget_pt);
                      goto VCHCANSEL; } } } } } } }
        else
        if (ent->client->pers.routeindex >= TotalRouteNodes) {
          ent->client->pers.routeindex = 0;
          ent->client->routetrace = false; } }
      else {
        ent->client->pers.routeindex = 0;
        ent->client->routetrace = false; } }

  if (!(ent->client->movestate & 0x00000020) && (!ent->groundentity || ent->groundentity != ent->client->waiting_obj))
    if (!(ent->client->waiting_obj && ent->client->waiting_obj->use == door_use)) {
      ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
      ent->client->waiting_obj = NULL; }

  if (ent->groundentity && !(ent->client->movestate & (0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800))) {
    it_ent = ent->groundentity;
    if (it_ent->classname[0] == 'f') {
      if (it_ent->use == Use_Plat) {
        if (it_ent->pos1[2] > it_ent->pos2[2]
          && ((it_ent->moveinfo.state == 2 && it_ent->velocity[2] > 0) || it_ent->moveinfo.state == 1)) {
          ent->client->waiting_obj = it_ent;
          ent->client->movestate |= 0x00000040;
          if (ent->client->routetrace) {
            if (Route[ent->client->pers.routeindex].ent == ent->client->waiting_obj)
              if (Route[ent->client->pers.routeindex].state == 4) {
                if (ent->client->waiting_obj->union_ent->s.origin[2] >(ent->s.origin[2]+32)) {
                  ent->client->movestate &= ~0x00000040;
                  ent->client->movestate |= 0x00000400; }
                else
                  ent->client->pers.routeindex++; } } } }
      else
      if (it_ent->use == train_use
        && it_ent->nextthink >= level.time
        && ((it_ent->s.origin[2]-it_ent->s.old_origin[2]) > 0 || ent->client->routetrace)) {
        if (ent->client->routetrace && ent->client->pers.routeindex > 0) {
          j = 0;
          k = ent->client->pers.routeindex-1;
          for (i=0;i<3;i++) {
            if ((k+i) < TotalRouteNodes) {
              if (Route[k+i].state == 5) {
                if (Route[k+i].ent == it_ent)
                  j = 1;
                else
                if (it_ent->trainteam) {
                  e = it_ent->trainteam;
                  while (1) {
                    if (e == it_ent) break;
                    if (e == Route[k+i].ent) {
                      j = 1;
                      it_ent = e;
                      Route[k+i].ent = e;
                      break; }
                    e = e->trainteam; } }
                else
                if (it_ent->target_ent) {
                  if (VectorCompare(Route[k+i].Tcourner,it_ent->target_ent->s.origin)) {
                    j = 1;
                    break; } }
                if (j) break; } }
            else break; }
          if (j) {
            ent->client->movestate |= 0x00000200;
            ent->client->waiting_obj = it_ent;
            ent->client->pers.routeindex = k+i+1; } }
        else {
          if ((it_ent->s.origin[2]-it_ent->s.old_origin[2]) > 0) {
            ent->client->movestate |= 0x00000200;
            ent->client->waiting_obj = it_ent; }
          else
          if ((it_ent->s.origin[2]-it_ent->s.old_origin[2]) > -2
            && trace_priority) {
            ent->client->movestate |= 0x00000200;
            ent->client->waiting_obj = it_ent; }
          else
            ent->client->movestate |= 0x00000010; } } } }

  if ((ent->client->movestate & 0x00000010) && ent->groundentity) {
    if (ent->client->movestate & 0x00000040) {
      if (ent->groundentity->use == Use_Plat) {
        ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
        ent->client->waiting_obj = NULL; } }
    else
    if (ent->client->movestate & 0x00000200) {
      if (ent->groundentity->use == train_use) {
        ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
        ent->client->waiting_obj = NULL; } }
    else
    if (ent->client->movestate & (0x00000080|0x00000100)) {
      if (ent->groundentity->use == door_use) {
        ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
        ent->client->waiting_obj = NULL; } }
    else {
      ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
      ent->client->waiting_obj = NULL; } }
  else
  if ((ent->client->movestate & (0x00000040|0x00000400|0x00000080|0x00000100))
   && !(ent->client->movestate & 0x00000010)) {
    k = 0;
    if (ent->client->movestate & (0x00000080|0x00000100)) {
      if (ent->client->movestate & 0x00000080) {
        if (ent->client->waiting_obj->moveinfo.state & 2|1)
          k = 1; }
      else {
        if (ent->client->waiting_obj->moveinfo.state & 0|3)
          k = 1; } }
    else
    if (ent->client->movestate & 0x00000400) {
      if (Route[ent->client->pers.routeindex].state == 5) {
        if (!TraceX(ent,Route[ent->client->pers.routeindex].ent->union_ent->s.origin))
          k = 1;
        if ((Route[ent->client->pers.routeindex].ent->union_ent->s.origin[2]+8-ent->s.origin[2]) > JumpMax)
          k = 1; }
      else {
        if ((ent->client->waiting_obj->union_ent->s.origin[2] - ent->s.origin[2]) > JumpMax)
          k = 1; }
      if (ent->client->pers.routeindex-1 > 0 && ent->client->waterstate < 2) {
        Get_RouteOrigin(ent->client->pers.routeindex -1 ,trmin);
        if ((trmin[2]-ent->s.origin[2]) > JumpMax && (v[2]-ent->s.origin[2]) > JumpMax)
          k = 0; } }
    else {
      if (ent->client->waiting_obj->moveinfo.state & 2|1)
        k = 1;
      if (ent->client->waiting_obj->moveinfo.state == 1)
        plat_go_up(ent->client->waiting_obj);
      if (ent->client->routetrace) {
        Get_RouteOrigin(ent->client->pers.routeindex,v);
        if (ent->s.origin[2] > v[2])
          k = 2; } }

    if (k != 1) {
      if (k == 2)
        ent->client->movestate |= 0x00000010;
      else {
        ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
        ent->client->waiting_obj = NULL; } }
    else {
      if (ent->client->movestate & 0x00000400) {
        k = 0;
        if (ent->client->pers.routeindex-1 > 0) {
          VectorCopy(ent->client->movtarget_pt,trmax);
          trmax[2] = 0;
          k = 1; }
        if (!k)
          goto VCHCANSEL; }
      else {
        trmax[0] = (ent->client->waiting_obj->absmin[0]+ent->client->waiting_obj->absmax[0])*0.5;
        trmax[1] = (ent->client->waiting_obj->absmin[1]+ent->client->waiting_obj->absmax[1])*0.5;
        trmax[2] = 0; }
      VectorSubtract(trmax,ent->s.origin,temppos);
      yaw = temppos[2];
      temppos[2] = 0;
      x = VectorLength(temppos);
      if (x == 0)
        goto VCHCANSEL;
      if (x < dist)
        dist = x;
      if (trace_priority < 3)
        ent->client->moveyaw = Get_yaw(temppos); } }

  else
  if (ent->client->movestate & 0x00000200) {
    i = 0;
    if (ent->client->routetrace) {
      Get_RouteOrigin(ent->client->pers.routeindex,v);
      if ((ent->client->pers.routeindex-1) >= 0) {
        if (Route[ent->client->pers.routeindex-1].state != 5)
          i = 1; }
      else
        i = 1;
      if (TraceX(ent,v)) {
        if ((v[2]-ent->s.origin[2]) <= JumpMax)
          i = 1;
        else
          ent->client->routelocktime = level.time+1.5; }
      else
        ent->client->routelocktime = level.time+1.5; }
    else
    if (j ||(ent->client->waiting_obj->s.origin[2]-ent->client->waiting_obj->s.old_origin[2]) <= 0) {
      ent->client->movestate |= 0x00000010;
      ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800); }
    else {
      k = 0;
      if (ent->client->routetrace) {
        tr = gi.trace(ent->s.origin,NULL,NULL,v,ent,MASK_BOTSOLIDX);
        if (tr.ent == ent->client->waiting_obj) {
          tr = gi.trace(v,NULL,NULL,ent->s.origin,ent,MASK_BOTSOLIDX);
          if (tr.ent == ent->client->waiting_obj) {
            VectorSubtract(v,ent->s.origin,temppos);
            k = 1; } } }
      if (!k) {
        VectorCopy(ent->client->waiting_obj->union_ent->s.origin,trmax);
        trmax[2] += 8;
        VectorSubtract(trmax,ent->s.origin,temppos);
        yaw = temppos[2];
        temppos[2] = 0;
        x = VectorLength(temppos);
        if (x < dist)
          dist = x; }
      if (trace_priority < 3)
        ent->client->moveyaw = Get_yaw(temppos); }
    goto GOMOVE; }

  else
  if (ent->client->movestate & 0x00000020) {
    if (!trace_priority || ent->client->waiting_obj->moveinfo.state == 0) {
      ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
      ent->client->waiting_obj = NULL; }
    else
    if (ent->client->waiting_obj->moveinfo.state & 1|2) {
      VectorSubtract(ent->client->movtarget_pt,ent->s.origin,temppos);
      temppos[2] = 0;
      dist *= 0.25;
      if (VectorLength(temppos) < 10 || VectorCompare(ent->s.origin,ent->client->movtarget_pt)) {
        if (!ent->client->waiting_obj->union_ent) {
          trmin[0] = (ent->client->waiting_obj->absmin[0]+ent->client->waiting_obj->absmax[0])*0.5;
          trmin[1] = (ent->client->waiting_obj->absmin[1]+ent->client->waiting_obj->absmax[1])*0.5;
          trmin[2] = (ent->client->waiting_obj->absmin[2]+ent->client->waiting_obj->absmax[2])*0.5; }
        else
          VectorCopy(ent->client->waiting_obj->union_ent->s.origin,trmin);
        trmin[2] += 8;
        VectorSubtract(trmin,ent->s.origin,temppos);
        if (trace_priority < 3)
          ent->client->moveyaw = Get_yaw(temppos);
        if (trace_priority < 2) {
          ent->s.angles[YAW] = ent->client->moveyaw;
          ent->s.angles[PITCH] = Get_pitch(temppos); }
        goto VCHCANSEL; }
      else {
        if (trace_priority < 3)
          ent->client->moveyaw = Get_yaw(temppos);
        if (!ent->client->waiting_obj->union_ent) {
          trmin[0] = (ent->client->waiting_obj->absmin[0]+ent->client->waiting_obj->absmax[0])*0.5;
          trmin[1] = (ent->client->waiting_obj->absmin[1]+ent->client->waiting_obj->absmax[1])*0.5;
          trmin[2] = (ent->client->waiting_obj->absmin[2]+ent->client->waiting_obj->absmax[2])*0.5; }
        else
          VectorCopy(ent->client->waiting_obj->union_ent->s.origin,trmin);
        trmin[2] += 8;
        VectorSubtract(trmin,ent->s.origin,temppos);
        if (trace_priority < 2) {
          ent->s.angles[YAW] = Get_yaw(temppos);
          ent->s.angles[PITCH] = Get_pitch(temppos); } } } }


GOMOVE:

  if (!ent->groundentity && !ent->waterlevel) {
    if (ent->velocity[2] > 300 && !(ent->client->movestate & (0x00000008|0x00000002|0x00000004)))
      ent->velocity[2] = 300;
    k = (ent->client->ps.pmove.pm_flags & PMF_DUCKED)?1:0;
    for (x = 0; x < 90; x += 10) {
      dist = 30*ent->moveinfo.speed;
      yaw = ent->client->moveyaw+x;
      if (yaw > 180) yaw -= 360;
      if (Bot_TestMove(ent,yaw,temppos,dist,&bottom)) {
        if (bottom <= 24 && bottom > 0)
          if (ent->velocity[2] <= 10) {
            VectorCopy(temppos,ent->s.origin);
            break; }
        if (!ent->waterlevel && ent->s.origin[2] > ent->s.old_origin[2]
          && ent->client->routetrace
          && !(ent->client->movestate & 0x00000001|(0x00000008|0x00000002|0x00000004))
          && (ent->client->pers.routeindex+1) < TotalRouteNodes
          && ent->velocity[2] >= 100
          && ent->velocity[2] < (100+ent->gravity*sv_gravity->value*0.1)) {
          Get_RouteOrigin(ent->client->pers.routeindex,v);
          Get_RouteOrigin(ent->client->pers.routeindex+1,vv);
          k = 0;
          j = Bot_TestMove(ent,yaw,trmin,16,&f1);
          VectorSubtract(v,ent->s.origin,trmin);
          if ((vv[2]-v[2]) > JumpMax)
            k = 1;
          else
          if ((v[2]-ent->s.origin[2]) > JumpMax)
            k = 2;
          else
          if (!TargetJump_Chk(ent,vv,0) && VectorLength(trmin) < 64) {
            if (TargetJump_Chk(ent,vv,ent->velocity[2]))
              k = 1; }
          if (!j)
            k = 0;
          else
            if (f1 > 10 && f1 < -10)
              k = 0;
          if (k) {
            if (k == 2)
              VectorCopy(v,vv);
            if (TargetJump(ent,vv)) {
              VectorSubtract(vv,ent->s.origin,v);
              ent->client->moveyaw = Get_yaw(v);
              if (ent->velocity[2] > 300)
                ent->client->movestate |= 0x00000008;
              if (k == 1)
                ent->client->pers.routeindex++;
              break; } } }
        if (bottom <= 0) {
          VectorCopy(temppos,ent->s.origin);
          if (i == 2)
            ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
          else
            ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
          break; }
        else
          ent->moveinfo.speed = 0.3; }
      else {
        ent->moveinfo.speed = 0.3; }
      if (x == 0) continue;
      yaw = ent->client->moveyaw-x;
      if (yaw < -180) yaw += 360;
      if (Bot_TestMove(ent,yaw,temppos,dist,&bottom)) {
        if (bottom <= 24 && bottom >0  && ent->velocity[2] <= 10) {
          VectorCopy(temppos,ent->s.origin);
          break; }
        if (!ent->waterlevel && ent->s.origin[2] > ent->s.old_origin[2]
          && ent->client->routetrace
          && !(ent->client->movestate & 0x00000001|(0x00000008|0x00000002|0x00000004))
          && (ent->client->pers.routeindex+1) < TotalRouteNodes
          && ent->velocity[2] >= 100
          && ent->velocity[2] <(100+ent->gravity*sv_gravity->value*0.1)) {
          Get_RouteOrigin(ent->client->pers.routeindex ,v);
          Get_RouteOrigin(ent->client->pers.routeindex+1,vv);
          k = 0;
          j = Bot_TestMove(ent,yaw,trmin,16,&f1);
          VectorSubtract(v,ent->s.origin,trmin);
          if ((vv[2]-v[2]) > JumpMax)
            k = 1;
          else
            if ((v[2]-ent->s.origin[2]) > JumpMax)
              k = 2;
          else
            if (!TargetJump_Chk(ent,vv,0) && VectorLength(trmin) < 64) {
              if (TargetJump_Chk(ent,vv,ent->velocity[2]))
                k = 1; }
          if (!j)
            k = 0;
          else
            if (f1 > 10 && f1 < -10)
              k = 0;
          if (k) {
            if (k == 2)
              VectorCopy(v,vv);
            if (TargetJump(ent,vv)) {
              VectorSubtract(vv,ent->s.origin,v);
              ent->client->moveyaw = Get_yaw(v);
              if (ent->velocity[2] > 300)
                ent->client->movestate |= 0x00000008;
              if (k == 1)
                ent->client->pers.routeindex++;
              break; } } }
        if (bottom <= 0) {
          VectorCopy(temppos,ent->s.origin);
          if (i == 2)
            ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
          else
            ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
          break; }
        else
          ent->moveinfo.speed = 0.3; }
      else
        ent->moveinfo.speed = 0.3; }
    if (x >= 90) {
      if (trace_priority < 2)
        ent->s.angles[YAW] += ((random()-0.5)*360);
      if (ent->s.angles[YAW]>180)
        ent->s.angles[YAW] -= 360;
      else
      if (ent->s.angles[YAW]< -180)
        ent->s.angles[YAW] += 360; }
    goto VCHCANSEL; }

  waterjumped = false;
  if (ent->groundentity || ent->waterlevel) {
    if (ent->groundentity && ent->waterlevel <= 0)
      k = 1;
    else
    if (ent->waterlevel) {
      k = 2;
      if (ent->client->routetrace) {
        Get_RouteOrigin(ent->client->pers.routeindex,v);
        VectorSubtract(v,ent->s.origin,vv);
        vv[2] = 0;
        if (v[2] < ent->s.origin[2] && VectorLength(vv) < 24)
          k = 0; }
      if (ent->waterlevel == 3)
        k = 0; }
    else
      if (ent->waterlevel)
        k = 0;
    else
      k = 1;
    if (k)
      if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
        k = 0;
    f1 = (ent->client->waterstate)?-8192:-JumpMax;
    if (ent->client->nextcheck < (level.time+1.0)) {
      VectorSubtract(ent->client->my_old_origin,ent->s.origin,temppos);
      if (VectorLength(temppos) < 64) {
        if (ent->client->routetrace) {
          ent->client->routetrace = false;
          ent->client->pers.routeindex++; }
        else
          f1 = -300; }
      if (ent->client->nextcheck < level.time) {
        VectorCopy(ent->s.origin,ent->client->my_old_origin);
        ent->client->nextcheck = level.time+4.0; } }
    f3 = 20;
    if (ent->client->routetrace)
      Get_RouteOrigin(ent->client->pers.routeindex,v);
    if (ent->waterlevel && ent->client->routetrace) {
      if (v[2]+20 <= ent->s.origin[2]) {
        f2 = 20; f3 = 0; }
      else
        f2 = JumpMax; }
    else
      f2 = JumpMax;

    ladderdrop = true;
    for (x = 0; x <= 180 && dist != 0; x += 10) {
      yaw = ent->client->moveyaw+x;
      if (yaw > 180) yaw -= 360;
      if (j = Bot_TestMove(ent,yaw,temppos,dist,&bottom)) {
        if (x == 0 && !ent->waterlevel && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) {
          if (ent->client->routetrace) {
            if ((v[2] -(ent->s.origin[2]+bottom)) > f2 || (bottom > 20 && v[2] > ent->s.origin[2])) {
              ladderdrop = false;
              if (Bot_Fall(ent,temppos,dist) && !ent->client->waterstate) {
                ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
                break; }
              if ((v[2]-ent->s.origin[2]) <= JumpMax) {
                if (Route[ent->client->pers.routeindex].state == 5 && ent->client->waterstate < 2) break;
                if (ent->client->pers.routeindex > 0)
                  if (Route[ent->client->pers.routeindex-1].state == 5
                    && Route[ent->client->pers.routeindex-1].ent == ent->groundentity) break; } }
            else
            if (ent->groundentity) {
              if (ent->groundentity->use == rotating_use) {
                if (Bot_Fall(ent,temppos,dist)) {
                  ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
                  break; } }
              else
              if (Route[ent->client->pers.routeindex].state == 1) {
                if (!TraceX(ent,v)) break;
                if (!HazardCheck(ent,v)) break;
                if (!BankCheck(ent,v)) break;
                if (Bot_Fall(ent,temppos,dist)) {
                  ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
                  break; } } } } }
        if (bottom > 20 && bottom <= f2 && j == 1 && k && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) {
          ent->moveinfo.speed = 0.15;
          if (k == 1)
            ent->velocity[2] = 300;
          else {
            ent->moveinfo.speed = 0.1;
            if (ent->velocity[2] < 300 || VectorCompare(ent->s.origin,ent->s.old_origin)) {
              ent->velocity[2] = 300;
              ent->client->movestate |= 0x00000004; }
            goto VCHCANSEL; }
          SetBotAnim(ent);
          ent->client->moveyaw = yaw;
          ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
          break; }
        else
        if (bottom <= f3 && (bottom >= f1 || ent->waterlevel)) {
          if (bottom < 0 && !ent->client->waterstate) {
            f2 = 0.1*(ent->velocity[2]-ent->gravity*sv_gravity->value*0.1);
            if (bottom >= f2 && ent->velocity[2] < 0)
              temppos[2] += bottom;
            else
              temppos[2] += f2; }
          VectorCopy(temppos,ent->s.origin);
          if (f1 > -52)
            ent->moveinfo.speed = 0.25;
          if (j != 1)
            ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
          else
            ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
          if (x > 30 || !ent->client->routetrace) {
            f2 = ent->client->moveyaw;
            ent->client->moveyaw = yaw;
            if (f2 == ent->s.angles[YAW] && trace_priority < 2)
              ent->s.angles[YAW] = yaw; }
          break; }
        else
        if (bottom < f1 && !ent->client->waterstate && x <= 30) {
          if (ladderdrop && bottom != -9999)
            if (ent->client->ground_contents & CONTENTS_LADDER) {
              VectorCopy(temppos,ent->s.origin);
              ent->client->moveyaw = yaw;
              ent->moveinfo.speed = 0.2;
              goto VCHCANSEL; }
          if (ladderdrop &&  bottom < 0)
            if (!ent->client->waterstate) {
              if (Bot_moveW(ent,yaw,temppos,dist,&bottom)) {
                iyaw = -41;
                if (bottom > -20 && iyaw < -40) {
                  VectorCopy(temppos,ent->s.origin);
                  break; } } }
          if (Bot_Fall(ent,temppos,dist))
            break; } }

      if (x == 0 && (ent->client->battlemode & (0x00020000|0x00040000)))
        ent->client->battlemode &= ~(0x00020000|0x00040000);

      if (x == 0 || x == 180) continue;

      yaw = ent->client->moveyaw-x;
      if (yaw < -180) yaw += 360;
      if (j = Bot_TestMove(ent,yaw,temppos,dist,&bottom)) {
        f2 = (ent->client->waterstate == 1)?100:JumpMax;
        if (bottom > 20 && bottom <= f2 && j == 1 && k && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) {
          ent->moveinfo.speed = 0.15;
          if (k == 1)
            ent->velocity[2] = 300;
          else {
            ent->moveinfo.speed = 0.1;
            if (ent->velocity[2] < 300 || VectorCompare(ent->s.origin,ent->s.old_origin)) {
              ent->velocity[2] = 300;
              ent->client->movestate |= 0x00000004; }
            goto VCHCANSEL; }
          SetBotAnim(ent);
          ent->client->moveyaw = yaw;
          ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
          break; }
        else
        if (bottom <= f3 && (bottom >= f1 || ent->waterlevel)) {
          if (bottom < 0 && !ent->client->waterstate) {
            f2 = 0.1*(ent->velocity[2]-ent->gravity*sv_gravity->value*0.1);
            if (bottom >= f2 && ent->velocity[2] < 0)
              temppos[2] += bottom;
            else
              temppos[2] += f2; }
          VectorCopy(temppos,ent->s.origin);
          if (f1 > -52)
            ent->moveinfo.speed = 0.25;
          if (j != 1)
            ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
          else
            ent->client->ps.pmove.pm_flags &= ~PMF_DUCKED;
          if (x > 30 || !ent->client->routetrace) {
            f2 = ent->client->moveyaw;
            ent->client->moveyaw = yaw;
            if (f2 == ent->s.angles[YAW] && trace_priority < 2)
              ent->s.angles[YAW] = yaw; }
          break; }
        else
        if (bottom < f1 && !ent->client->waterstate && x <= 30) {
          if (ladderdrop && ent->client->ground_contents & CONTENTS_LADDER && bottom != -9999) {
            VectorCopy(temppos,ent->s.origin);
            ent->client->moveyaw = yaw;
            ent->moveinfo.speed = 0.2;
            goto VCHCANSEL; }
          if (ladderdrop && bottom < 0 && !ent->client->waterstate) {
            if (Bot_moveW(ent,yaw,temppos,dist,&bottom)) {
              iyaw = -41;
              if (bottom > -54 && iyaw < -40) {
                VectorCopy(temppos,ent->s.origin);
                break; } } }
          if (Bot_Fall(ent,temppos,dist))
            break; } } }

    if (!ent->client->routetrace && !ent->client->current_enemy)
      if (trace_priority < 2)
        ent->s.angles[YAW] = yaw;

    if (ent->waterlevel && !waterjumped) {
      k = 0;
      VectorCopy(ent->s.origin,temppos);
      if (ent->client->routetrace) {
        Get_RouteOrigin(ent->client->pers.routeindex,v);
        k = 2;
        x = v[2]-ent->s.origin[2];
        if (x > 13) x = 13;
        else if (x < -13) x = -13;
        if (x < 0) {
          if (Bot_Watermove(ent,temppos,dist,x)) {
            VectorCopy(temppos,ent->s.origin);
            k = 1; } }
        else
        if (x > 0 && ent->client->waterstate == 2)
          if (!(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) {
            if (ent->velocity[2] < -10)
              ent->velocity[2] = 0;
            if (Bot_Watermove(ent,temppos,dist,x)) {
              VectorCopy(temppos,ent->s.origin);
              k = 1; } } }
      else
      if (ent->air_finished-2.0 < level.time)
        if (ent->client->waterstate == 2) {
          if (Bot_Watermove(ent,temppos,dist,13)) {
            VectorCopy(temppos,ent->s.origin);
            k = 1; }
          else
            k = 2; }
      if (k == 1)
        Get_WaterState(ent);
      if (ent->client->routetrace)
        if (v[2] == ent->s.origin[2])
          k = 3;
      if ((!ent->groundentity && !ent->client->waterstate && k && ent->velocity[2] < 1)
        ||(ent->client->waterstate == 2 && (ent->client->ps.pmove.pm_flags & PMF_DUCKED))) {
        if (Bot_Watermove(ent,temppos,dist,-7) && k != 3)
          VectorCopy(temppos,ent->s.origin); }
      if (ent->client->waterstate == 2)
        ent->moveinfo.decel = level.time;
      else
      if (!k) {
        if ((level.time-ent->moveinfo.decel) > 4.0)
          if (!ent->client->routetrace) {
            ent->velocity[2] = -200;
            ent->moveinfo.decel = level.time; } }
      if (ent->groundentity && ent->waterlevel == 1) {
        VectorSubtract(ent->s.origin,ent->s.old_origin,temppos);
        if (!temppos[0] && !temppos[1] && !temppos[2])
          ent->velocity[2] += 80; } }
    else
    if (ent->client->routetrace && !dist) {
      Get_RouteOrigin(ent->client->pers.routeindex,v);
      if (v[2] <(ent->s.origin[2]-20))
        if (Bot_Watermove(ent,temppos,dist,-20))
          VectorCopy(temppos,ent->s.origin); } }

  if (!ent->client->routetrace && trace_priority && random() < 0.2) {
    VectorCopy(ent->s.origin,v);
    VectorCopy(ent->mins,touchmin);
    touchmin[2] += 16;
    VectorCopy(ent->maxs,touchmax);
    if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
      touchmax[2] = 0;
    else
      v[2] += 20;
    if (random() < 0.5) {
      f1 = ent->client->moveyaw+90;
      if (f1 > 180) iyaw -= 360;
      f2 = ent->client->moveyaw+135;
      if (f2 > 180) iyaw -= 360; }
    else {
      f1 = ent->client->moveyaw-90;
      if (f1 < 180) iyaw += 360;
      f2 = ent->client->moveyaw-135;
      if (f2 < 180) iyaw += 360; }
    yaw = DEG2RAD(f1);
    trmin[0] = cos(yaw)*128;
    trmin[1] = sin(yaw)*128;
    trmin[2] = 0;
    VectorAdd(v,trmin,trmax);
    tr = gi.trace(v, NULL, NULL, trmax, ent, MASK_BOTSOLIDX);
    x = tr.fraction;

    yaw = DEG2RAD(f2);
    trmin[0] = cos(yaw)*128;
    trmin[1] = sin(yaw)*128;
    trmin[2] = 0;
    VectorAdd(v,trmin,trmax);
    tr = gi.trace(v, NULL, NULL, trmax, ent, MASK_BOTSOLIDX);
    if (x > tr.fraction && x > 0.5)
      ent->client->moveyaw = f1; }

  it_ent = NULL;
  k = 0;
  VectorCopy(ent->absmin, touchmin);
  VectorCopy(ent->absmax, touchmax);
  touchmin[0] -= 48;
  touchmin[1] -= 48;
  touchmin[2] -= 5;
  touchmax[0] += 48;
  touchmax[1] += 48;
  if (i=gi.BoxEdicts(touchmin ,touchmax,touch,1024,1)) {
    for (j=i-1;j>=0;j--) {
      trent = touch[j];
      if (trent->classname) {
        if (trent->use == button_use) {
          k = 1;
          it_ent = trent;
          break; }
        else
        if (trent->use == door_use || trent->use == rotating_use) {
          if (!trent->targetname && !trent->takedamage)
            if (ent->groundentity != trent) {
              k = 2;
              it_ent = trent;
              break; } } } } }

  if (it_ent && k == 1) {
    if (it_ent->use && it_ent->moveinfo.state == 1 && !it_ent->health) {
      k = 0;
      if (ent->client->routetrace && ent->client->pers.routeindex-1 > 0) {
        k = 1;
        i = ent->client->pers.routeindex;
        if (Route[i].state == 7)
          k = 0;
        else
          if (Route[--i].state == 7)
            k = 0;
        if (!k && Route[i].ent == it_ent)
          ent->client->pers.routeindex = i+1;
        else
          k = 1; }
      if (!k && it_ent->target) {
        str = it_ent->target;
        e = &g_edicts[(int)maxclients->value+1];
        for (i=maxclients->value+1; i<globals.num_edicts; i++, e++) {
          if (!e->inuse || !e->targetname) continue;
          if (!stricmp(str, e->targetname)) {
            if (e->classname[0] == 't') {
              if (e->use == trigger_relay_use) {
                if (e->target) {
                  str = e->target;
                  e = &g_edicts[(int)maxclients->value];
                  i=maxclients->value;
                  continue; } } }
            else
            if (e->classname[0] == 'f') {
              it_ent->use(it_ent,ent,it_ent);
              if (e->use == door_use || e->use == rotating_use) {
                k = 0;
                if (!ent->client->routetrace) {
                  v[0] = (it_ent->absmin[0]+it_ent->absmax[0])*0.5;
                  v[1] = (it_ent->absmin[1]+it_ent->absmax[1])*0.5;
                  v[2] = (it_ent->absmin[2]+it_ent->absmax[2])*0.5;
                  VectorSubtract(it_ent->union_ent->s.origin,v,temppos);
                  VectorScale(temppos, 3, v);
                  VectorAdd(ent->s.origin,v,ent->client->movtarget_pt); }
                else
                  VectorCopy(ent->s.origin,ent->client->movtarget_pt);
                if (fabs(e->moveinfo.start_origin[2]-e->moveinfo.end_origin[2]) > JumpMax) {
                  if (!e->union_ent) {
                    it = item_navi3;
                    trent = G_Spawn();
                    trent->classname = it->classname;
                    trent->s.origin[0] = (e->absmin[0]+e->absmax[0])*0.5;
                    trent->s.origin[1] = (e->absmin[1]+e->absmax[1])*0.5;
                    trent->s.origin[2] = e->absmax[2]+16;
                    trent->union_ent = e;
                    e->union_ent = trent;
                    SpawnItem3(trent, it); }
                  else {
                    trent = e->union_ent;
                    trent->solid = SOLID_TRIGGER;
                    trent->svflags &= ~SVF_NOCLIENT; }
                  trent->target_ent = ent;
                  if (e->spawnflags & 32) {
                    f1 = e->moveinfo.start_origin[2]-e->moveinfo.end_origin[2];
                    k = 1; }
                  else {
                    f1 = e->moveinfo.start_origin[2]-e->moveinfo.end_origin[2];
                    if (f1 > 0) {
                      if (e->moveinfo.state & 1|2) {
                        if (fabs(trent->s.origin[2]-ent->s.origin[2]) < JumpMax)
                          k = 1; } }
                    else {
                      if (e->moveinfo.state & 1|2) {
                        if (fabs(trent->s.origin[2]-ent->s.origin[2]) < JumpMax)
                          k = 1; } } } }
                if (!k) {
                  ent->client->waiting_obj = e;
                  ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
                  ent->client->movestate |= 0x00000020; }
                else {
                  if ((e->union_ent->s.origin[2]+8-ent->s.origin[2]) > JumpMax) {
                    ent->client->routetrace = false;
                    ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800); } }
                break; } } } } }
      else
        if (!k)
          it_ent->use(it_ent,ent,it_ent); } }
  else
  if (it_ent && k == 2) {
    if (it_ent->moveinfo.state == 1) {
      if (it_ent->flags & FL_TEAMSLAVE)
        it_ent->teammaster->use(it_ent->teammaster,ent,it_ent->teammaster);
      else
        it_ent->use(it_ent,ent,it_ent); }
    if (it_ent->moveinfo.state == 1) {
      VectorCopy(ent->s.origin,ent->client->movtarget_pt);
      ent->client->waiting_obj = it_ent;
      ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
      ent->client->movestate |= 0x00000020;
      if (it_ent->flags & FL_TEAMSLAVE) {
        trmin[0] = (it_ent->teammaster->absmin[0]+it_ent->teammaster->absmax[0])*0.5;
        trmin[1] = (it_ent->teammaster->absmin[1]+it_ent->teammaster->absmax[1])*0.5;
        trmax[0] = (it_ent->absmin[0]+it_ent->absmax[0])*0.5;
        trmax[1] = (it_ent->absmin[1]+it_ent->absmax[1])*0.5;
        temppos[0] = (trmin[0]+trmax[0])*0.5;
        temppos[1] = (trmin[1]+trmax[1])*0.5;
        if (trace_priority < 2)
          ent->s.angles[YAW] = Get_yaw(temppos); }
      else {
        trmax[0] = (it_ent->absmin[0]+it_ent->absmax[0])*0.5;
        trmax[1] = (it_ent->absmin[1]+it_ent->absmax[1])*0.5;
        VectorSubtract(trmax,ent->s.origin,temppos);
        if (trace_priority < 2)
          ent->s.angles[YAW] = Get_yaw(temppos); } }
    else
    if (it_ent->moveinfo.state == 2) {
      VectorCopy(ent->s.origin,ent->client->movtarget_pt);
      ent->client->waiting_obj = it_ent;
      ent->client->movestate &= ~(0x00000010|0x00000020|0x00000400|0x00000040|0x00000080|0x00000100|0x00000200|0x00000800);
      ent->client->movestate |= 0x00000020; } }

VCHCANSEL:

  front = left = right = NULL;
  k = 0;
  if (ent->client->routetrace)
    if (ent->client->pers.routeindex+1 < TotalRouteNodes) {
      Get_RouteOrigin(ent->client->pers.routeindex+1,v);
      if (v[2]-ent->s.origin[2] >= 32)
        k = 1; }
  if (k && trace_priority && !(ent->client->ps.pmove.pm_flags & PMF_DUCKED)) {
    tempflag = 0;
    VectorCopy(ent->mins,trmin);
    VectorCopy(ent->maxs,trmax);
    trmin[2] += 20;
    iyaw = ent->client->moveyaw;
    yaw = DEG2RAD(iyaw);
    touchmin[0] = cos(yaw)*32;
    touchmin[1] = sin(yaw)*32;
    touchmin[2] = 0;
    VectorAdd(ent->s.origin,touchmin,touchmax);
    tr = gi.trace(ent->s.origin, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID);
    front = tr.ent;
    if (tr.contents & CONTENTS_LADDER)
      tempflag = 1;
    if (!tempflag && !ent->client->waterstate) {
      trmax[2] += 32;
      tr = gi.trace(ent->s.origin, trmin,trmax, touchmax,ent, MASK_BOTSOLID);
      if (tr.contents & CONTENTS_LADDER)
        tempflag = 2; }
    if (!tempflag && ent->groundentity) {
      Get_RouteOrigin(ent->client->pers.routeindex,v);
      v[2] = ent->s.origin[2];
      tr = gi.trace(ent->s.origin, trmin,ent->maxs, v,ent, MASK_BOTSOLID);
      if (tr.contents & CONTENTS_LADDER)
        tempflag = 3; }
    if (tempflag==0) {
      iyaw = ent->client->moveyaw+90;
      if (iyaw > 180) iyaw -= 360;
      yaw = DEG2RAD(iyaw);
      touchmin[0] = cos(yaw)*32;
      touchmin[1] = sin(yaw)*32;
      touchmin[2] = 0;
      VectorAdd(ent->s.origin,touchmin,touchmax);
      tr = gi.trace(ent->s.origin, trmin,ent->maxs, touchmax,ent,  MASK_BOTSOLID);
      right = tr.ent;
      if (tr.contents & CONTENTS_LADDER)
        tempflag = 1; }
    if (tempflag==0) {
      iyaw = ent->client->moveyaw-90;
      if (iyaw < -180) iyaw += 360;
      yaw = DEG2RAD(iyaw);
      touchmin[0] = cos(yaw)*32;
      touchmin[1] = sin(yaw)*32;
      touchmin[2] = 0;
      VectorAdd(ent->s.origin,touchmin,touchmax);
      tr = gi.trace(ent->s.origin, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID);
      left = tr.ent;
      if (tr.contents & CONTENTS_LADDER)
        tempflag = 1; }
    if (tempflag) {
      VectorCopy(tr.endpos,trmax);
      VectorCopy(trmax,touchmax);
      touchmax[2] += 8192;
      tr = gi.trace(trmax, trmin,ent->maxs, touchmax,ent, MASK_SOLID);
      e = tr.ent;
      k = 0;
      VectorCopy(tr.endpos,temppos);
      VectorAdd(tr.endpos,touchmin,touchmax);
      tr = gi.trace(temppos, trmin,ent->maxs, touchmax,ent, MASK_BOTSOLID);
      if (e && e->use == door_use) k = 1;
      if ((!(tr.contents & CONTENTS_LADDER) || k)) {
        ent->velocity[0] = 0;
        ent->velocity[1] = 0;
        if (ent->client->moveyaw == iyaw || ent->client->routetrace) {
          if (ent->client->moveyaw != iyaw)
            ent->client->moveyaw = iyaw;
          ent->s.angles[YAW] = ent->client->moveyaw;
          if (tempflag != 3)
            VectorCopy(trmax,ent->s.origin);
          ent->client->movestate |= 0x00000001;
          ent->s.angles[YAW] = ent->client->moveyaw;
          ent->s.angles[PITCH] = -29;
          if (tempflag == 2) {
            ent->velocity[2] = 300;
            SetBotAnim(ent);
            ent->client->movestate |= (0x00000008|0x00000002|0x00000004);
            ent->moveinfo.speed = 0; }
          else
          if (tempflag == 3) {
            ent->velocity[2] = 300;
            SetBotAnim(ent);
            ent->client->movestate |= (0x00000008|0x00000002|0x00000004);
            ent->moveinfo.speed = 30; }
          ent->velocity[2] = 200; }
        else {
          ent->client->moveyaw = iyaw;
          ent->s.angles[YAW] = ent->client->moveyaw; } } } }

VCHCANSEL_L:

  if (ent->client->battleduckcnt > 0)
    if (ent->groundentity)
      if (ent->velocity[2] < 10) {
        ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
        ent->client->battleduckcnt--; }

  if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) {
    ent->client->duckedtime = 0;
    ent->maxs[2] = 4;
    ent->viewheight = -2; }
  else {
    if (ent->client->duckedtime < 1)
      ent->client->duckedtime += 0.1;
    ent->maxs[2] = 32;
    ent->viewheight = 22; }

  VectorCopy(ent->s.angles,ent->client->v_angle);
  if (ent->s.angles[PITCH] < -29)
    ent->s.angles[PITCH] = -29;
  else
  if (ent->s.angles[PITCH] > 29)
    ent->s.angles[PITCH] = 29;

  gi.linkentity(ent);
  G_TouchTriggers(ent);

  RandomChat(ent);
}

//==============================================
void BotThink(edict_t *ent) {

  // Reset bot's enemy info after enemy died
  if (ent->client->current_enemy)
    if (!G_ClientInGame(ent->client->current_enemy)) {
      ent->client->battleduckcnt = 0;
      ent->client->current_enemy = NULL;
      ent->client->combatstate &= ~0x00000001;
      ent->client->battlemode = 0x00000000; }

  // bot dead? put'em back into game
  if (!G_ClientNotDead(ent)) {
    ent->s.modelindex2 = 0;
    ent->client->routetrace = false;
    if (ent->client->respawn_time <= level.time) {
      ent->client->respawn_time = level.time;
      PutClientInServer(ent); }
    ent->nextthink = level.time+FRAMETIME;
    return; }

  BotAI(ent);

  // Quit camping if health below 10%
  if (ent->health < 10)
    ent->client->camptime = level.time;

  // Bots will tend to camp at Railgun
  if (ent->client->camptime > level.time) {
    VectorCopy(ent->client->lastorigin,ent->s.origin);
    if (ent->client->campitem == item_railgun) {
      // Force railgun use if camping at rail else stop camping here
      if (ent->client->pers.weapon != item_railgun && HasAmmoForWeapon(ent,item_railgun)) {
        ent->client->newweapon = item_railgun;
        ChangeWeapon(ent); }
      else
        ent->client->camptime = level.time; }
    else
    // Bots will tend to camp at MegaHealth
    if (ent->client->campitem == item_health_mega) {
      if (random() < 0.85)
        ent->client->ps.pmove.pm_flags |= PMF_DUCKED;
      // Prefer using grenadelauncher if camping at megahealth
      if (ent->client->pers.weapon != item_grenadelauncher && HasAmmoForWeapon(ent,item_grenadelauncher)) {
        ent->client->newweapon = item_grenadelauncher;
        ChangeWeapon(ent); } } }
  else
  if (ent->client->quad_framenum > level.framenum)
    // Force SuperShotgun use if quad enabled!
    if (ent->client->pers.weapon != item_supershotgun)
      if (HasAmmoForWeapon(ent,item_supershotgun)) {
        ent->client->newweapon = item_supershotgun;
        ChangeWeapon(ent); }

  ent->nextthink = level.time+FRAMETIME;
}
//---------------------------------------------------
</C&NBSP;CODE>

Save this new file!
=====================================================
=====================================================

Okay, you MADE IT!! Now add your new bot.c file to your
project and sit back, smile and compile!

Now, spawn a bunch of bots into the game and get out
there and KILL THE BASTARDS!!

Have Fun!!

Maj.Bitch