// ####################################
// #                                  #
// #       Ping of Death - Bot        #
// #                by                #
// #    Markus Klinge aka Count Floyd #
// #                                  #
// ####################################
//
// Started from the HPB-Bot Alpha Source
// by Botman so Credits for a lot of the basic
// HL Server/Client Stuff goes to him
//
// waypoint.cpp
//
// Features the Waypoint Code (Editor + Bot Navigation)

#include "bot_globals.h"


void WaypointAddPath (short int add_index, short int path_index, float fDistance)
{
   PATH *p;
   int i;
   int count = 0;

   g_bWaypointsChanged = TRUE;

   p = paths[add_index];

   // Don't allow Paths get connected to the same Waypoint
   if (add_index == path_index)
   {
      UTIL_ServerPrint ("Denied path creation from %d to %d (same waypoint)\n", add_index, path_index);
      return;
   }

   // Don't allow Paths get connected twice
   for (i = 0; i < MAX_PATH_INDEX; i++)
      if (p->index[i] == path_index)
      {
         UTIL_ServerPrint ("Denied path creation from %d to %d (path already exists)\n", add_index, path_index);
         return;
      }

   // Check for free space in the Connection indices
   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      if (p->index[i] == -1)
      {
         p->index[i] = path_index;
         p->distance[i] = fabs (fDistance);

         UTIL_ServerPrint ("Path added from %d to %d\n", add_index, path_index);
         return;
      }
   }

   // There wasn't any free space. Try exchanging it with a long-distance path
   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      if (p->distance[i] > fabs (fDistance))
      {
         UTIL_ServerPrint ("Path added from %d to %d\n", add_index, path_index);

         p->index[i] = path_index;
         p->distance[i] = fabs (fDistance);
         return;
      }
   }

   return;
}


// find the nearest waypoint to the player and return the index (-1 if not found)
int WaypointFindNearest (void)
{
   int i, index;
   float distance;
   float min_distance;

   // find the nearest waypoint...
   min_distance = 9999.0;

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

      if (distance < min_distance)
      {
         index = i;
         min_distance = distance;
      }
   }

   // if not close enough to a waypoint then just return
   if (min_distance > 50)
      return (-1);

   return (index);
}


// find the nearest waypoint to that Origin and return the index
int WaypointFindNearestToMove (Vector vOrigin)
{
   int i, index;
   float distance;
   float min_distance;

   min_distance = 9999.0;

   for (i = 0; i < g_iNumWaypoints; i++)
   {
      distance = (paths[i]->origin - vOrigin).Length ();

      if (distance < min_distance)
      {
         index = i;
         min_distance = distance;
      }
   }

   return (index);
}


// Returns all Waypoints within Radius from Position
void WaypointFindInRadius (Vector vecPos, float fRadius, int *pTab, int *iCount)
{
   int i, iMaxCount;
   float distance;

   iMaxCount = *iCount;
   *iCount = 0;

   for (i = 0; i < g_iNumWaypoints; i++)
   {
      distance = (paths[i]->origin - vecPos).Length ();

      if (distance < fRadius)
      {
         *pTab++ = i;
         *iCount++;

         if (*iCount == iMaxCount)
            break;
      }
   }

   *iCount--;
   return;
}


void WaypointDrawBeam (Vector start, Vector end, int width, int red, int green, int blue)
{
   if (FNullEnt (pHostEdict))
      return;

   MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, pHostEdict);
   WRITE_BYTE (TE_BEAMPOINTS);
   WRITE_COORD (start.x);
   WRITE_COORD (start.y);
   WRITE_COORD (start.z);
   WRITE_COORD (end.x);
   WRITE_COORD (end.y);
   WRITE_COORD (end.z);
   WRITE_SHORT (m_spriteTexture);
   WRITE_BYTE (1); // framestart
   WRITE_BYTE (10); // framerate
   WRITE_BYTE (1); // life in 0.1's
   WRITE_BYTE (width); // width
   WRITE_BYTE (0); // noise
   WRITE_BYTE (red); // r, g, b
   WRITE_BYTE (green); // r, g, b
   WRITE_BYTE (blue); // r, g, b
   WRITE_BYTE (255); // brightness
   WRITE_BYTE (0); // speed
   MESSAGE_END ();

   return;
}


void WaypointAdd (int wpt_type)
{
   int index, i;
   float radius = 40;
   float distance;
   PATH *p, *prev;
   bool bPlaceNew = TRUE;
   Vector vecNewWaypoint;
   TraceResult tr;
   float min_distance;
   int iDestIndex;
   int flags;

   g_bWaypointsChanged = TRUE;
   vecNewWaypoint = pHostEdict->v.origin;

   if (wpt_type == WAYPOINT_ADD_CAMP_END)
   {
      index = WaypointFindNearest ();

      if (index != -1)
      {
         p = paths[index];

         if (!(p->flags & W_FL_CAMP))
         {
            UTIL_ServerPrint ("This is no Camping Waypoint !\n");
            return;
         }

         p->fcampendx = pHostEdict->v.v_angle.x;
         p->fcampendy = pHostEdict->v.v_angle.y;
      }

      EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "common/wpn_hudon.wav", 1.0, ATTN_NORM, 0, 100);
      return;
   }

   else if (wpt_type == WAYPOINT_ADD_JUMP_START)
   {
      index = WaypointFindNearest ();

      if (index != -1)
      {
         distance = (paths[index]->origin - pHostEdict->v.origin).Length ();

         if (distance < 50)
         {
            bPlaceNew = FALSE;
            p = paths[index];
            p->origin = (p->origin + vecLearnPos) / 2;
         }
      }
      else
         vecNewWaypoint = vecLearnPos;
   }

   else if (wpt_type == WAYPOINT_ADD_JUMP_END)
   {
      index = WaypointFindNearest ();

      if (index != -1)
      {
         distance = (paths[index]->origin - pHostEdict->v.origin).Length ();

         if (distance < 50)
         {
            bPlaceNew = FALSE;
            p = paths[index];
            flags = 0;

            for (i = 0; i < MAX_PATH_INDEX; i++)
               flags += p->connectflag[i];

            if (flags == 0)
               p->origin = (p->origin + pHostEdict->v.origin) / 2;
         }
      }
   }

   if (bPlaceNew)
   {
      if (g_iNumWaypoints + 1 >= MAX_WAYPOINTS)
      {
         UTIL_HostPrint ("Max. Waypoint reached! Can't add Waypoint!\n");
         EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
         return;
      }

      index = 0;

      // find the next available slot for the new waypoint...
      p = paths[0];
      prev = NULL;

      // find an empty slot for new path_index...
      while (p != NULL)
      {
         prev = p;
         p = p->next;
         index++;
      }

      paths[index] = new PATH;
      if (paths[index] == NULL)
         return; // ERROR ALLOCATING MEMORY!!!

      p = paths[index];

      if (prev)
         prev->next = p;

      // increment total number of waypoints
      g_iNumWaypoints++;
      p->iPathNumber = index;
      p->flags = 0;

      // store the origin (location) of this waypoint
      p->origin = vecNewWaypoint;
      p->fcampstartx = 0;
      p->fcampstarty = 0;
      p->fcampendx = 0;
      p->fcampendy = 0;

      for (i = 0; i < MAX_PATH_INDEX; i++)
      {
         p->index[i] = -1;
         p->distance[i] = 0;
         p->connectflag[i] = 0;
         p->vecConnectVel[i] = g_vecZero;
      }

      p->next = NULL;

      // store the last used waypoint for the auto waypoint code...
      g_vecLastWaypoint = pHostEdict->v.origin;
   }

   if (wpt_type == WAYPOINT_ADD_JUMP_START)
      g_iLastJumpWaypoint = index;

   else if (wpt_type == WAYPOINT_ADD_JUMP_END)
   {
      distance = (paths[g_iLastJumpWaypoint]->origin - pHostEdict->v.origin).Length ();
      WaypointAddPath (g_iLastJumpWaypoint, index, distance);

      for (i = 0; i < MAX_PATH_INDEX; i++)
      {
         if (paths[g_iLastJumpWaypoint]->index[i] == index)
         {
            paths[g_iLastJumpWaypoint]->connectflag[i] |= C_FL_JUMP;
            paths[g_iLastJumpWaypoint]->vecConnectVel[i] = vecLearnVelocity;
            break;
         }
      }

      CalculateWaypointWayzone ();
      EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "weapons/xbow_hit1.wav", 1.0, ATTN_NORM, 0, 100);
      return;
   }

   if (pHostEdict->v.flags & FL_DUCKING)
      p->flags |= W_FL_CROUCH; // set a crouch waypoint

   // *******************************************************
   // look for buttons, lift, ammo, flag, health, armor, etc.
   // *******************************************************

   if (wpt_type == WAYPOINT_ADD_TERRORIST)
      p->flags |= W_FL_TERRORIST;
   else if (wpt_type == WAYPOINT_ADD_COUNTER)
      p->flags |= W_FL_COUNTER;
   else if (wpt_type == WAYPOINT_ADD_LADDER)
      p->flags |= W_FL_LADDER;
   else if (wpt_type == WAYPOINT_ADD_RESCUE)
      p->flags |= W_FL_RESCUE;
   else if (wpt_type == WAYPOINT_ADD_CAMP_START)
   {
      p->flags |= W_FL_CAMP;
      p->fcampstartx = pHostEdict->v.v_angle.x;
      p->fcampstarty = pHostEdict->v.v_angle.y;
   }
   else if (wpt_type == WAYPOINT_ADD_GOAL)
      p->flags |= W_FL_GOAL;

   // Ladder waypoints need careful connections
   if (wpt_type == WAYPOINT_ADD_LADDER)
   {
      min_distance = 9999.0;
      iDestIndex = -1;

      // calculate all the paths to this new waypoint
      for (i = 0; i < g_iNumWaypoints; i++)
      {
         if (i == index)
            continue; // skip the waypoint that was just added

         // Other ladder waypoints should connect to this
         if (paths[i]->flags & W_FL_LADDER)
         {
            // check if the waypoint is reachable from the new one
            TRACE_LINE (vecNewWaypoint, paths[i]->origin, ignore_monsters, pHostEdict, &tr);
            if ((tr.flFraction == 1.0)
                && (fabs (vecNewWaypoint.x - paths[i]->origin.x) < 50)
                && (fabs (vecNewWaypoint.y - paths[i]->origin.y) < 50))
            {
               distance = (paths[i]->origin - vecNewWaypoint).Length ();
               WaypointAddPath (index, i, distance);
               WaypointAddPath (i, index, distance);
            }
         }
         else
         {
            // check if the waypoint is reachable from the new one
            if (WaypointNodeReachable (vecNewWaypoint, paths[i]->origin)
                || WaypointNodeReachable (paths[i]->origin, vecNewWaypoint))
            {
               distance = (paths[i]->origin - vecNewWaypoint).Length ();

               if (distance < min_distance)
               {
                  iDestIndex = i;
                  min_distance = distance;
               }
            }
         }
      }

      if ((iDestIndex > -1) && (iDestIndex < g_iNumWaypoints))
      {
         // check if the waypoint is reachable from the new one (one-way)
         if (WaypointNodeReachable (vecNewWaypoint, paths[iDestIndex]->origin))
         {
            distance = (paths[iDestIndex]->origin - vecNewWaypoint).Length ();
            WaypointAddPath (index, iDestIndex, distance);
         }

         // check if the new one is reachable from the waypoint (other way)
         if (WaypointNodeReachable (paths[iDestIndex]->origin, vecNewWaypoint))
         {
            distance = (paths[iDestIndex]->origin - vecNewWaypoint).Length ();
            WaypointAddPath (iDestIndex, index, distance);
         }
      }
   }
   else
   {
      // calculate all the paths to this new waypoint
      for (i = 0; i < g_iNumWaypoints; i++)
      {
         if (i == index)
            continue; // skip the waypoint that was just added

         // check if the waypoint is reachable from the new one (one-way)
         if (WaypointNodeReachable (vecNewWaypoint, paths[i]->origin))
         {
            distance = (paths[i]->origin - vecNewWaypoint).Length ();
            WaypointAddPath (index, i, distance);
         }

         // check if the new one is reachable from the waypoint (other way)
         if (WaypointNodeReachable (paths[i]->origin, vecNewWaypoint))
         {
            distance = (paths[i]->origin - vecNewWaypoint).Length ();
            WaypointAddPath (i, index, distance);
         }
      }
   }

   CalculateWaypointWayzone ();
   EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "weapons/xbow_hit1.wav", 1.0, ATTN_NORM, 0, 100);
   return;
}


void WaypointDelete (void)
{
   PATH *p_previous = NULL;
   PATH *p;
   int count;
   int wpt_index;
   int i, ix;

   if (g_iNumWaypoints < 1)
      return;

   wpt_index = WaypointFindNearest ();

   if ((wpt_index < 0) || (wpt_index >= g_iNumWaypoints))
   {
      UTIL_HostPrint ("No Waypoint nearby!\n");
      return;
   }

   if ((paths[wpt_index] != NULL) && (wpt_index > 0))
      p_previous = paths[wpt_index - 1];

   count = 0;

   // delete all references to Node
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      p = paths[i];

      for (ix = 0; ix < MAX_PATH_INDEX; ix++)
      {
         if (p->index[ix] == wpt_index)
         {
            p->index[ix] = -1; // unassign this path
            p->connectflag[ix] = 0;
            p->distance[ix] = 0;
            p->vecConnectVel[ix] = g_vecZero;
         }
      }
   }

   // delete all connections to Node
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      p = paths[i];

      // if Pathnumber bigger than deleted Node Number=Number -1
      if (p->iPathNumber > wpt_index)
         p->iPathNumber--;

      for (ix = 0; ix < MAX_PATH_INDEX; ix++)
         if (p->index[ix] > wpt_index)
            p->index[ix]--;
   }

   // free deleted node
   delete paths[wpt_index];
   paths[wpt_index] = NULL;

   // Rotate Path Array down
   for (i = wpt_index; i < g_iNumWaypoints - 1; i++)
      paths[i] = paths[i + 1];

   if (p_previous)
      p_previous->next = paths[wpt_index];

   g_iNumWaypoints--;

   EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "weapons/mine_activate.wav", 1.0, ATTN_NORM, 0, 100);
   g_bWaypointsChanged = TRUE;
   return;
}


// Remember a Waypoint
void WaypointCache (void)
{
   int iNode;

   iNode = WaypointFindNearest ();

   if (iNode == -1)
   {
      g_iCachedWaypoint = -1;
      UTIL_HostPrint ("Cache cleared (no Waypoint nearby!)\n");
      return;
   }

   g_iCachedWaypoint = iNode;
   UTIL_HostPrint ("Waypoint #%d has been put into memory\n");
   return;
}


// change a waypoint's radius
void WaypointChangeRadius (float radius)
{
   int wpt_index;

   wpt_index = WaypointFindNearest ();

   if ((wpt_index < 0) || (wpt_index >= g_iNumWaypoints))
   {
      UTIL_HostPrint ("No Waypoint nearby!\n");
      return;
   }

   paths[wpt_index]->Radius = radius;

   EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "common/wpn_hudon.wav", 1.0, ATTN_NORM, 0, 100);
   g_bWaypointsChanged = TRUE;
   return;
}


// switch a waypoint's flag on/off
void WaypointChangeFlag (int flag, char status)
{
   int wpt_index;

   wpt_index = WaypointFindNearest ();

   if ((wpt_index < 0) || (wpt_index >= g_iNumWaypoints))
   {
      UTIL_HostPrint ("No Waypoint nearby!\n");
      return;
   }

   if (status == FLAG_SET)
      paths[wpt_index]->flags |= flag; // set flag
   else if (status == FLAG_CLEAR)
      paths[wpt_index]->flags &= ~flag; // reset flag
   else if (status == FLAG_TOGGLE)
      paths[wpt_index]->flags ^= flag; // Switch flag on/off (XOR it)

   EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "common/wpn_hudon.wav", 1.0, ATTN_NORM, 0, 100);
   g_bWaypointsChanged = TRUE;
   return;
}


// allow player to manually create a path from one waypoint to another
void WaypointCreatePath (char direction)
{
   int index;
   int iNodeFrom;
   int iNodeTo;
   float distance;
   Vector vToWaypoint;
   Vector vWaypointBound;
   Vector vWaypointAngles;
   float fBestAngle;

   iNodeFrom = WaypointFindNearest ();
   iNodeTo = -1;

   if (iNodeFrom == -1)
   {
      UTIL_HostPrint ("No Waypoint nearby!\n");
      return;
   }

   // find the waypoint the user is pointing at
   fBestAngle = 5;
   for (index = 0; index < g_iNumWaypoints; index++)
   {
      vToWaypoint = paths[index]->origin - pHostEdict->v.origin;
      distance = vToWaypoint.Length ();

      if (distance > g_fAutoPathMaxDistance)
         continue;

      vWaypointAngles = UTIL_VecToAngles (vToWaypoint) - pHostEdict->v.v_angle;
      UTIL_ClampVector (&vWaypointAngles);

      if (fabs (vWaypointAngles.y) > fBestAngle)
         continue;

      fBestAngle = vWaypointAngles.y;

      if (paths[index]->flags & W_FL_CROUCH)
         vWaypointBound = paths[index]->origin - Vector (0, 0, 17);
      else
         vWaypointBound = paths[index]->origin - Vector (0, 0, 34);

      vWaypointAngles = -pHostEdict->v.v_angle;
      vWaypointAngles.x = -vWaypointAngles.x;
      vWaypointAngles = vWaypointAngles + UTIL_VecToAngles (vWaypointBound - pHostEdict->v.origin);
      UTIL_ClampVector (&vWaypointAngles);
      if (vWaypointAngles.x > 0)
         continue;

      if (paths[index]->flags & W_FL_CROUCH)
         vWaypointBound = paths[index]->origin + Vector (0, 0, 17);
      else
         vWaypointBound = paths[index]->origin + Vector (0, 0, 34);

      vWaypointAngles = -pHostEdict->v.v_angle;
      vWaypointAngles.x = -vWaypointAngles.x;
      vWaypointAngles = vWaypointAngles + UTIL_VecToAngles (vWaypointBound - pHostEdict->v.origin);
      UTIL_ClampVector (&vWaypointAngles);
      if (vWaypointAngles.x < 0)
         continue;

      iNodeTo = index;
   }

   if ((iNodeTo < 0) || (iNodeTo >= g_iNumWaypoints))
   {
      if ((g_iCachedWaypoint >= 0) && (g_iCachedWaypoint < g_iNumWaypoints))
         iNodeTo = g_iCachedWaypoint;
      else
      {
         UTIL_HostPrint ("Destination Waypoint not found!\n");
         return;
      }
   }

   if (iNodeTo == iNodeFrom)
   {
      UTIL_HostPrint ("Can't create Path to the same Waypoint!\n");
      return;
   }

   distance = (paths[iNodeTo]->origin - paths[iNodeFrom]->origin).Length ();

   if (direction == PATH_OUTGOING)
      WaypointAddPath (iNodeFrom, iNodeTo, distance);
   else if (direction == PATH_INCOMING)
      WaypointAddPath (iNodeTo, iNodeFrom, distance);
   else
   {
      WaypointAddPath (iNodeFrom, iNodeTo, distance);
      WaypointAddPath (iNodeTo, iNodeFrom, distance);
   }

   EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "common/wpn_hudon.wav", 1.0, ATTN_NORM, 0, 100);
   g_bWaypointsChanged = TRUE;
   return;
}


// allow player to manually remove a path from one waypoint to another
void WaypointDeletePath (void)
{
   int index;
   int iNodeFrom;
   int iNodeTo;
   float distance;
   Vector vToWaypoint;
   Vector vWaypointBound;
   Vector vWaypointAngles;
   float fBestAngle;

   iNodeFrom = WaypointFindNearest ();
   iNodeTo = -1;

   if (iNodeFrom == -1)
   {
      UTIL_HostPrint ("No Waypoint nearby!\n");
      return;
   }

   // find the waypoint the user is pointing at
   fBestAngle = 5;
   for (index = 0; index < g_iNumWaypoints; index++)
   {
      vToWaypoint = paths[index]->origin - pHostEdict->v.origin;
      distance = vToWaypoint.Length ();

      if (distance > 500)
         continue;

      vWaypointAngles = UTIL_VecToAngles (vToWaypoint) - pHostEdict->v.v_angle;
      UTIL_ClampVector (&vWaypointAngles);

      if (fabs (vWaypointAngles.y) > fBestAngle)
         continue;

      fBestAngle = vWaypointAngles.y;

      if (paths[index]->flags & W_FL_CROUCH)
         vWaypointBound = paths[index]->origin - Vector (0, 0, 17);
      else
         vWaypointBound = paths[index]->origin - Vector (0, 0, 34);

      vWaypointAngles = -pHostEdict->v.v_angle;
      vWaypointAngles.x = -vWaypointAngles.x;
      vWaypointAngles = vWaypointAngles + UTIL_VecToAngles (vWaypointBound - pHostEdict->v.origin);
      UTIL_ClampVector (&vWaypointAngles);
      if (vWaypointAngles.x > 0)
         continue;

      if (paths[index]->flags & W_FL_CROUCH)
         vWaypointBound = paths[index]->origin + Vector (0, 0, 17);
      else
         vWaypointBound = paths[index]->origin + Vector (0, 0, 34);

      vWaypointAngles = -pHostEdict->v.v_angle;
      vWaypointAngles.x = -vWaypointAngles.x;
      vWaypointAngles = vWaypointAngles + UTIL_VecToAngles (vWaypointBound - pHostEdict->v.origin);
      UTIL_ClampVector (&vWaypointAngles);
      if (vWaypointAngles.x < 0)
         continue;

      iNodeTo = index;
   }

   if ((iNodeTo < 0) || (iNodeTo >= g_iNumWaypoints))
   {
      if ((g_iCachedWaypoint >= 0) && (g_iCachedWaypoint < g_iNumWaypoints))
         iNodeTo = g_iCachedWaypoint;
      else
      {
         UTIL_HostPrint ("Destination Waypoint not found!\n");
         return;
      }
   }

   for (index = 0; index < MAX_PATH_INDEX; index++)
   {
      if (paths[iNodeFrom]->index[index] == iNodeTo)
      {
         paths[iNodeFrom]->index[index] = -1; // unassign this path
         paths[iNodeFrom]->connectflag[index] = 0;
         paths[iNodeFrom]->vecConnectVel[index] = g_vecZero;
         paths[iNodeFrom]->distance[index] = 0;

         EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "weapons/mine_activate.wav", 1.0, ATTN_NORM, 0, 100);
         g_bWaypointsChanged = TRUE;
         return;
      }
   }

   // not found this way ? check for incoming connections then
   index = iNodeFrom;
   iNodeFrom = iNodeTo;
   iNodeTo = index;

   for (index = 0; index < MAX_PATH_INDEX; index++)
   {
      if (paths[iNodeFrom]->index[index] == iNodeTo)
      {
         paths[iNodeFrom]->index[index] = -1; // unassign this path
         paths[iNodeFrom]->connectflag[index] = 0;
         paths[iNodeFrom]->vecConnectVel[index] = g_vecZero;
         paths[iNodeFrom]->distance[index] = 0;

         EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "weapons/mine_activate.wav", 1.0, ATTN_NORM, 0, 100);
         g_bWaypointsChanged = TRUE;
         return;
      }
   }

   UTIL_HostPrint ("Already no Path to this Waypoint!\n");
   return;
}


// Checks if Waypoint A has a Connection to Waypoint Nr. B
bool ConnectedToWaypoint (int a, int b)
{
   int ix;

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

   return (FALSE);
}


void CalculateWaypointWayzone (void)
{
   PATH *p;
   Vector start;
   Vector vRadiusEnd;
   Vector v_direction;
   TraceResult tr;
   int iScanDistance;
   float fRadCircle;
   bool bWayBlocked;
   int index;
   int x;

   index = WaypointFindNearest ();
   p = paths[index];

   if ((p->flags & W_FL_LADDER)
       || (p->flags & W_FL_GOAL)
       || (p->flags & W_FL_CAMP)
       || (p->flags & W_FL_RESCUE)
       || (p->flags & W_FL_CROUCH)
       || g_bLearnJumpWaypoint)
   {
      p->Radius = 0;
      return;
   }

   for (x = 0; x < MAX_PATH_INDEX; x++)
   {
      if ((p->index[x] != -1) && (paths[p->index[x]]->flags & W_FL_LADDER))
      {
         p->Radius = 0;
         return;
      }
   }

   bWayBlocked = FALSE;

   for (iScanDistance = 32; iScanDistance < 128; iScanDistance += 16)
   {
      start = p->origin;
      MAKE_VECTORS (g_vecZero);
      vRadiusEnd = start + (gpGlobals->v_forward * iScanDistance);
      v_direction = vRadiusEnd - start;
      v_direction = UTIL_VecToAngles (v_direction);
      p->Radius = iScanDistance;

      for (fRadCircle = 0.0; fRadCircle < 360.0; fRadCircle += 20)
      {
         MAKE_VECTORS (v_direction);
         vRadiusEnd = start + (gpGlobals->v_forward * iScanDistance);
         TRACE_LINE (start, vRadiusEnd, ignore_monsters, pHostEdict, &tr);

         if (tr.flFraction < 1.0)
         {
            if (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0)
            {
               p->Radius = 0;
               bWayBlocked = TRUE;
               break;
            }

            bWayBlocked = TRUE;
            p->Radius -= 16;
            break;
         }

         vRadiusEnd.z += 34;
         TRACE_LINE(start, vRadiusEnd, ignore_monsters, pHostEdict, &tr);

         if (tr.flFraction < 1.0)
         {
            bWayBlocked = TRUE;
            p->Radius -= 16;
            break;
         }

         v_direction.y += fRadCircle;
         UTIL_ClampAngle (&v_direction.y);
      }

      if (bWayBlocked)
         break;
   }

   p->Radius -= 16;
   if (p->Radius < 0)
      p->Radius = 0;

   return;
}


void SaveExperienceTab (void)
{
   char filename[256];
   EXPERIENCE_HDR header;
   experiencesave_t *pExperienceSave;
   int iResult;
   int i, j;

   if ((g_iNumWaypoints <= 0) || !g_bUseExperience || g_bWaypointsChanged || g_bWaypointOn)
      return;

   strcpy (header.filetype, "PODEXP!");
   header.experiencedata_file_version = EXPERIENCE_VERSION;
   header.number_of_waypoints = g_iNumWaypoints;

   sprintf (filename, "cstrike/addons/podbot/%s/%s.pxp", g_szWPTDirname, STRING (gpGlobals->mapname));

   pExperienceSave = new experiencesave_t[g_iNumWaypoints * g_iNumWaypoints];

   if (pExperienceSave == NULL)
   {
      UTIL_ServerPrint ("ERROR: Couldn't allocate Memory for saving Experience Data!\n");
      return;
   }

   UTIL_ServerPrint ("Compressing & saving Experience Data...this may take a while!\n");

   for (i = 0; i < g_iNumWaypoints; i++)
   {
      for (j = 0; j < g_iNumWaypoints; j++)
      {
         (pExperienceSave + (i * g_iNumWaypoints) + j)->uTeam0Damage = (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam0Damage >> 3;
         (pExperienceSave + (i * g_iNumWaypoints) + j)->uTeam1Damage = (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam1Damage >> 3;
         (pExperienceSave + (i * g_iNumWaypoints) + j)->cTeam0Value = (pBotExperienceData + (i * g_iNumWaypoints) + j)->wTeam0Value / 8;
         (pExperienceSave + (i * g_iNumWaypoints) + j)->cTeam1Value = (pBotExperienceData + (i * g_iNumWaypoints) + j)->wTeam1Value / 8;
      }
   }

   iResult = Encode (filename, (unsigned char *) &header, sizeof (EXPERIENCE_HDR), (unsigned char *) pExperienceSave, g_iNumWaypoints * g_iNumWaypoints * sizeof (experiencesave_t));

   if (pExperienceSave != NULL)
      delete [](pExperienceSave);
   pExperienceSave = NULL;

   if (iResult == -1)
   {
      UTIL_ServerPrint ("ERROR: Couldn't save Experience Data!\n");
      return;
   }

   UTIL_ServerPrint ("Experience Data saved...\n");
   return;
}


void InitExperienceTab (void)
{
   FILE *bfp = NULL;
   experiencesave_t *pExperienceLoad;
   int iCompare;
   bool bDataLoaded = FALSE;
   bool bExperienceExists = FALSE;
   int i, j;
   EXPERIENCE_HDR header;
   char filename[256];
   char wptfilename[256];

   if (pBotExperienceData != NULL)
      delete [](pBotExperienceData);
   pBotExperienceData = NULL;

   if (!g_bUseExperience)
      return;

   if (g_iNumWaypoints == 0)
      return;

   sprintf (filename, "addons/podbot/%s/%s.pxp", g_szWPTDirname, STRING (gpGlobals->mapname));
   sprintf (wptfilename, "addons/podbot/%s/%s.pwf", g_szWPTDirname, STRING (gpGlobals->mapname));

   pBotExperienceData = new experience_t[g_iNumWaypoints * g_iNumWaypoints];

   if (pBotExperienceData == NULL)
   {
      UTIL_ServerPrint ("ERROR: Couldn't allocate Memory for Experience Data !\n");
      return;
   }

   // Does the Experience File exist & is newer than waypoint file ?
   if (COMPARE_FILE_TIME (filename, wptfilename, &iCompare))
   {
      if (iCompare > 0)
         bExperienceExists = TRUE;
   }

   if (bExperienceExists)
   {
      // Now build the real filename
      sprintf (filename, "cstrike/addons/podbot/%s/%s.pxp", g_szWPTDirname, STRING (gpGlobals->mapname));
      bfp = fopen (filename, "rb");

      // if file exists, read the experience Data from it
      if (bfp != NULL)
      {
         fread (&header, sizeof (EXPERIENCE_HDR), 1, bfp);
         fclose (bfp);

         header.filetype[7] = 0;

         if (strcmp (header.filetype, "PODEXP!") == 0)
         {
            if ((header.experiencedata_file_version == EXPERIENCE_VERSION)
                && (header.number_of_waypoints == g_iNumWaypoints))
            {
               UTIL_ServerPrint ("Loading & decompressing Experience Data\n");

               pExperienceLoad = new experiencesave_t[g_iNumWaypoints * g_iNumWaypoints];

               if (pExperienceLoad == NULL)
               {
                  UTIL_ServerPrint ("ERROR: Couldn't allocate Memory for Experience Data !\n");
                  g_bUseExperience = FALSE; // Turn Experience off to be safe
                  return;
               }

               Decode (filename, sizeof (EXPERIENCE_HDR), (unsigned char *) pExperienceLoad, g_iNumWaypoints * g_iNumWaypoints * sizeof (experiencesave_t));

               for (i = 0; i < g_iNumWaypoints; i++)
               {
                  for (j = 0; j < g_iNumWaypoints; j++)
                  {
                     if (i == j)
                     {
                        (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam0Damage = (unsigned short) ((pExperienceLoad + (i * g_iNumWaypoints) + j)->uTeam0Damage);
                        (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam1Damage = (unsigned short) ((pExperienceLoad + (i * g_iNumWaypoints) + j)->uTeam1Damage);
                     }
                     else
                     {
                        (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam0Damage = (unsigned short) ((pExperienceLoad + (i * g_iNumWaypoints) + j)->uTeam0Damage) << 3;
                        (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam1Damage = (unsigned short) ((pExperienceLoad + (i * g_iNumWaypoints) + j)->uTeam1Damage) << 3;
                     }

                     (pBotExperienceData + (i * g_iNumWaypoints) + j)->wTeam0Value = (signed short) ((pExperienceLoad + (i * g_iNumWaypoints) + j)->cTeam0Value) * 8;
                     (pBotExperienceData + (i * g_iNumWaypoints) + j)->wTeam1Value = (signed short) ((pExperienceLoad + (i * g_iNumWaypoints) + j)->cTeam1Value) * 8;
                  }
               }

               if (pExperienceLoad != NULL)
                  delete [](pExperienceLoad);
               pExperienceLoad = NULL;

               bDataLoaded = TRUE;
            }
         }
      }
   }

   if (!bDataLoaded)
   {
      UTIL_ServerPrint ("No Experience Data File or old one - starting new !\n");

      // initialize table by hand to correct values, and NOT zero it out, got it Markus ? ;)
      for (i = 0; i < g_iNumWaypoints; i++)
      {
         for (j = 0; j < g_iNumWaypoints; j++)
         {
            (pBotExperienceData + (i * g_iNumWaypoints) + j)->iTeam0_danger_index = -1;
            (pBotExperienceData + (i * g_iNumWaypoints) + j)->iTeam1_danger_index = -1;
            (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam0Damage = 0;
            (pBotExperienceData + (i * g_iNumWaypoints) + j)->uTeam1Damage = 0;
            (pBotExperienceData + (i * g_iNumWaypoints) + j)->wTeam0Value = 0;
            (pBotExperienceData + (i * g_iNumWaypoints) + j)->wTeam1Value = 0;
         }
      }
   }
   else
      UTIL_ServerPrint ("Experience Data loaded from File...\n");

   return;
}


void SaveVisTab (void)
{
   char filename[256];
   VISTABLE_HDR header;
   int iResult;

   if ((g_iNumWaypoints <= 0) || !g_bUseExperience || g_bWaypointsChanged || g_bWaypointOn)
      return;

   strcpy (header.filetype, "PODVIS!");
   header.vistable_file_version = VISTABLE_VERSION;
   header.number_of_waypoints = g_iNumWaypoints;

   sprintf (filename, "cstrike/addons/podbot/%s/%s.pvi", g_szWPTDirname, STRING (gpGlobals->mapname));

   UTIL_ServerPrint ("Compressing & saving Visibility Table...this may take a while!\n");

   iResult = Encode (filename, (unsigned char *) &header, sizeof (VISTABLE_HDR), (unsigned char *) g_rgbyVisLUT, MAX_WAYPOINTS * (MAX_WAYPOINTS / 4) * sizeof (unsigned char));

   if (iResult == -1)
   {
      UTIL_ServerPrint ("ERROR: Couldn't save Visibility Table!\n");
      return;
   }

   UTIL_ServerPrint ("Visibility Table saved...\n");
   return;
}


void InitVisTab (void)
{
   FILE *bfp = NULL;
   int iCompare;
   bool bVisTableLoaded = FALSE;
   bool bVisTableExists = FALSE;
   VISTABLE_HDR header;
   char filename[256];
   char wptfilename[256];

   if (!g_bUseExperience)
      return;

   if (g_iNumWaypoints == 0)
      return;

   sprintf (filename, "addons/podbot/%s/%s.pvi", g_szWPTDirname, STRING (gpGlobals->mapname));
   sprintf (wptfilename, "addons/podbot/%s/%s.pwf", g_szWPTDirname, STRING (gpGlobals->mapname));

   // Does the Experience File exist & is newer than waypoint file ?
   if (COMPARE_FILE_TIME (filename, wptfilename, &iCompare))
   {
      if (iCompare > 0)
         bVisTableExists = TRUE;
   }

   if (bVisTableExists)
   {
      // Now build the real filename
      sprintf (filename, "cstrike/addons/podbot/%s/%s.pvi", g_szWPTDirname, STRING (gpGlobals->mapname));
      bfp = fopen (filename, "rb");

      // if file exists, read the experience Data from it
      if (bfp != NULL)
      {
         fread (&header, sizeof (VISTABLE_HDR), 1, bfp);
         fclose (bfp);

         header.filetype[7] = 0;

         if (strcmp (header.filetype, "PODVIS!") == 0)
         {
            if ((header.vistable_file_version == VISTABLE_VERSION)
                && (header.number_of_waypoints == g_iNumWaypoints))
            {
               UTIL_ServerPrint ("Loading & decompressing Visibility Table\n");

               Decode (filename, sizeof (VISTABLE_HDR), (unsigned char *) g_rgbyVisLUT, MAX_WAYPOINTS * (MAX_WAYPOINTS / 4) * sizeof (unsigned char));
               bVisTableLoaded = TRUE;
            }
         }
      }
   }

   if (!bVisTableLoaded)
   {
      UTIL_ServerPrint ("No Visibility Table File or old one - starting new !\n");

      memset (g_rgbyVisLUT, 0, sizeof (g_rgbyVisLUT));

      g_iCurrVisIndex = 0;
      g_bRecalcVis = TRUE;
      g_fTimeDisplayVisTableMsg = gpGlobals->time;
   }
   else
      UTIL_ServerPrint ("Visibility Table loaded from File...\n");

   return;
}


void InitWaypointTypes (void)
{
   int index;

   g_iNumTerrorPoints = 0;
   g_iNumCTPoints = 0;
   g_iNumGoalPoints = 0;
   g_iNumCampPoints = 0;

   memset (g_rgiTerrorWaypoints, 0, sizeof (g_rgiTerrorWaypoints));
   memset (g_rgiCTWaypoints, 0, sizeof (g_rgiCTWaypoints));
   memset (g_rgiGoalWaypoints, 0, sizeof (g_rgiGoalWaypoints));
   memset (g_rgiCampWaypoints, 0, sizeof (g_rgiCampWaypoints));

   for (index = 0; index < g_iNumWaypoints; index++)
   {
      if (paths[index]->flags & W_FL_TERRORIST)
      {
         g_rgiTerrorWaypoints[g_iNumTerrorPoints] = index;
         g_iNumTerrorPoints++;
      }

      else if (paths[index]->flags & W_FL_COUNTER)
      {
         g_rgiCTWaypoints[g_iNumCTPoints] = index;
         g_iNumCTPoints++;
      }

      else if (paths[index]->flags & W_FL_GOAL)
      {
         g_rgiGoalWaypoints[g_iNumGoalPoints] = index;
         g_iNumGoalPoints++;
      }

      else if (paths[index]->flags & W_FL_CAMP)
      {
         g_rgiCampWaypoints[g_iNumCampPoints] = index;
         g_iNumCampPoints++;
      }
   }

   return;
}


bool WaypointLoad (void)
{
   FILE *bfp = NULL;
   char filename[256];
   WAYPOINT_HDR header;
   int index;
   bool bOldWaypointFormat = FALSE;

   g_bMapInitialised = FALSE;
   g_bRecalcVis = FALSE;
   g_fTimeDisplayVisTableMsg = 0;
   g_bWaypointsSaved = FALSE;

   sprintf (filename, "cstrike/addons/podbot/%s/%s.pwf", g_szWPTDirname, STRING (gpGlobals->mapname));

   bfp = fopen (filename, "rb");
   if (bfp == NULL)
   {
      UTIL_ServerPrint ("Waypoint file %s does not exist!\n", filename);
      sprintf (g_szWaypointMessage, "Waypoint file %s does not exist!\n(you can't add Bots!)\n", filename);
      return (FALSE);
   }

   // if file exists, read the waypoint structure from it
   fread (&header, sizeof (header), 1, bfp);
   header.filetype[7] = 0;
   header.mapname[31] = 0;

   if (strcmp (header.filetype, "PODWAY!") != 0)
   {
      UTIL_ServerPrint ("%s is not a POD Bot waypoint file!\n", filename);
      sprintf (g_szWaypointMessage, "Waypoint file %s does not exist!\n", filename);
      fclose (bfp);
      return (FALSE);
   }

   if (header.waypoint_file_version != WAYPOINT_VERSION7)
   {
      if ((header.waypoint_file_version == WAYPOINT_VERSION6)
          || (header.waypoint_file_version == WAYPOINT_VERSION5))
      {
         UTIL_ServerPrint ("Old POD Bot waypoint file version (V%d)!\nTrying to convert...\n", header.waypoint_file_version);
         bOldWaypointFormat = TRUE;
      }
      else
      {
         UTIL_ServerPrint ("%s Incompatible POD Bot waypoint file version!\nWaypoints not loaded!\n",filename);
         sprintf (g_szWaypointMessage, "%s Incompatible POD Bot waypoint file version!\nWaypoints not loaded!\n", filename);
         fclose (bfp);
         return (FALSE);
      }
   }

   if (strcmp (header.mapname, STRING (gpGlobals->mapname)) != 0)
   {
      UTIL_ServerPrint ("%s POD Bot waypoints are not for this map!\n", filename);
      sprintf (g_szWaypointMessage, "%s POD Bot waypoints are not for this map!\n", filename);
      fclose (bfp);
      return (FALSE);
   }

   g_iNumWaypoints = header.number_of_waypoints;

   // read and add waypoint paths...
   for (index = 0; index < g_iNumWaypoints; index++)
   {
      // Oldest Format to convert
      if (header.waypoint_file_version == WAYPOINT_VERSION5)
      {
         PATH5 convpath;

         paths[index] = new PATH;

         // read 1 oldpath
         fread (&convpath, sizeof (PATH5), 1, bfp);

         // Convert old to new
         paths[index]->iPathNumber = convpath.iPathNumber;
         paths[index]->flags = convpath.flags;
         paths[index]->origin = convpath.origin;
         paths[index]->Radius = convpath.Radius;
         paths[index]->fcampstartx = convpath.fcampstartx;
         paths[index]->fcampstarty = convpath.fcampstarty;
         paths[index]->fcampendx = convpath.fcampendx;
         paths[index]->fcampendy = convpath.fcampendy;
         paths[index]->index[0] = convpath.index[0];
         paths[index]->index[1] = convpath.index[1];
         paths[index]->index[2] = convpath.index[2];
         paths[index]->index[3] = convpath.index[3];
         paths[index]->index[4] = -1;
         paths[index]->index[5] = -1;
         paths[index]->index[6] = -1;
         paths[index]->index[7] = -1;
         paths[index]->distance[0] = convpath.distance[0];
         paths[index]->distance[1] = convpath.distance[1];
         paths[index]->distance[2] = convpath.distance[2];
         paths[index]->distance[3] = convpath.distance[3];
         paths[index]->distance[4] = 0;
         paths[index]->distance[5] = 0;
         paths[index]->distance[6] = 0;
         paths[index]->distance[7] = 0;
         paths[index]->connectflag[0] = 0;
         paths[index]->connectflag[1] = 0;
         paths[index]->connectflag[2] = 0;
         paths[index]->connectflag[3] = 0;
         paths[index]->connectflag[4] = 0;
         paths[index]->connectflag[5] = 0;
         paths[index]->connectflag[6] = 0;
         paths[index]->connectflag[7] = 0;
         paths[index]->vecConnectVel[0] = g_vecZero;
         paths[index]->vecConnectVel[1] = g_vecZero;
         paths[index]->vecConnectVel[2] = g_vecZero;
         paths[index]->vecConnectVel[3] = g_vecZero;
         paths[index]->vecConnectVel[4] = g_vecZero;
         paths[index]->vecConnectVel[5] = g_vecZero;
         paths[index]->vecConnectVel[6] = g_vecZero;
         paths[index]->vecConnectVel[7] = g_vecZero;
      }
      else if (header.waypoint_file_version == WAYPOINT_VERSION6)
      {
         PATH6 convpath;

         paths[index] = new PATH;

         // read 1 oldpath
         fread (&convpath, sizeof (PATH6), 1, bfp);

         // Convert old to new
         paths[index]->iPathNumber = convpath.iPathNumber;
         paths[index]->flags = convpath.flags;
         paths[index]->origin = convpath.origin;
         paths[index]->Radius = convpath.Radius;
         paths[index]->fcampstartx = convpath.fcampstartx;
         paths[index]->fcampstarty = convpath.fcampstarty;
         paths[index]->fcampendx = convpath.fcampendx;
         paths[index]->fcampendy = convpath.fcampendy;
         paths[index]->index[0] = convpath.index[0];
         paths[index]->index[1] = convpath.index[1];
         paths[index]->index[2] = convpath.index[2];
         paths[index]->index[3] = convpath.index[3];
         paths[index]->index[4] = convpath.index[4];
         paths[index]->index[5] = convpath.index[5];
         paths[index]->index[6] = convpath.index[6];
         paths[index]->index[7] = convpath.index[7];
         paths[index]->distance[0] = convpath.distance[0];
         paths[index]->distance[1] = convpath.distance[1];
         paths[index]->distance[2] = convpath.distance[2];
         paths[index]->distance[3] = convpath.distance[3];
         paths[index]->distance[4] = convpath.distance[4];
         paths[index]->distance[5] = convpath.distance[5];
         paths[index]->distance[6] = convpath.distance[6];
         paths[index]->distance[7] = convpath.distance[7];
         paths[index]->connectflag[0] = 0;
         paths[index]->connectflag[1] = 0;
         paths[index]->connectflag[2] = 0;
         paths[index]->connectflag[3] = 0;
         paths[index]->connectflag[4] = 0;
         paths[index]->connectflag[5] = 0;
         paths[index]->connectflag[6] = 0;
         paths[index]->connectflag[7] = 0;
         paths[index]->vecConnectVel[0] = g_vecZero;
         paths[index]->vecConnectVel[1] = g_vecZero;
         paths[index]->vecConnectVel[2] = g_vecZero;
         paths[index]->vecConnectVel[3] = g_vecZero;
         paths[index]->vecConnectVel[4] = g_vecZero;
         paths[index]->vecConnectVel[5] = g_vecZero;
         paths[index]->vecConnectVel[6] = g_vecZero;
         paths[index]->vecConnectVel[7] = g_vecZero;
      }
      else
      {
         paths[index] = new PATH;
         fread (paths[index], sizeof (PATH), 1, bfp); // read the number of paths from this node...
      }
   }

   fclose (bfp);

   for (index = 0; index < g_iNumWaypoints; index++)
      paths[index]->next = paths[index + 1];
   paths[index - 1]->next = NULL;

   sprintf (g_szWaypointMessage, "Waypoints created by %s\n", header.creatorname);

   InitWaypointTypes ();
   InitPathMatrix ();

   g_bWaypointsChanged = FALSE;

   return (TRUE);
}


void WaypointSave (void)
{
   FILE *bfp = NULL;
   char filename[256];
   WAYPOINT_HDR header;
   int i;
   PATH *p;

   g_bWaypointsChanged = TRUE;

   strcpy (header.filetype, "PODWAY!");
   header.waypoint_file_version = WAYPOINT_VERSION7;
   header.number_of_waypoints = g_iNumWaypoints;

   memset (header.mapname, 0, sizeof (header.mapname));
   memset (header.creatorname, 0, sizeof (header.creatorname));
   strncpy (header.mapname, STRING (gpGlobals->mapname), 31);
   header.mapname[31] = 0;
   strcpy (header.creatorname, STRING (pHostEdict->v.netname));

   sprintf (filename, "cstrike/addons/podbot/%s/%s.pwf", g_szWPTDirname, header.mapname);

   bfp = fopen (filename, "wb");

   if (bfp == NULL)
   {
      UTIL_ServerPrint ("Error opening .pwf file for writing! Waypoints NOT saved!\n");
      return;
   }

   // write the waypoint header to the file...
   fwrite (&header, sizeof (header), 1, bfp);

   p = paths[0];

   // save the waypoint paths...
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      fwrite (p, sizeof (PATH), 1, bfp);
      p = p->next;
   }

   fclose (bfp);

   g_bWaypointsSaved = TRUE;
   return;
}


// Returns 2D Traveltime to a Position
float GetTravelTime (float fMaxSpeed, Vector vecSource, Vector vecPosition)
{
   float fDistance = (vecPosition - vecSource).Length2D ();
   return (fDistance / fMaxSpeed);
}


bool WaypointReachable (Vector v_src, Vector v_dest, edict_t *pEntity)
{
   TraceResult tr;
   float distance = (v_dest - v_src).Length ();

   // is the destination close enough?
   if (distance < 200)
   {
      // check if this waypoint is "visible"...
      TRACE_LINE (v_src, v_dest, ignore_monsters, pEntity, &tr);

      // if waypoint is visible from current position (even behind head)...
      if (tr.flFraction >= 1.0)
      {
         // are we in water ?
         if ((pEntity->v.waterlevel == 2) || (pEntity->v.waterlevel == 3))
         {
           // is dest waypoint higher than src? (62 is max jump height)
           if (v_dest.z > v_src.z + 62.0)
              return (FALSE); // can't reach this one

           // is dest waypoint lower than src?
           if (v_dest.z < v_src.z - 100.0)
              return (FALSE); // can't reach this one
         }

         return (TRUE);
      }
   }

   return (FALSE);
}


bool WaypointNodeReachable (Vector v_src, Vector v_dest)
{
   TraceResult tr;
   float height, last_height;
   float distance = (v_dest - v_src).Length ();

   // is the destination NOT close enough?
   if (distance > g_fAutoPathMaxDistance)
      return (FALSE);

   // check if we go through a func_illusionary, in which case return FALSE
   TRACE_HULL (v_src, v_dest, ignore_monsters, head_hull, pHostEdict, &tr);
   if (!FNullEnt (tr.pHit) && (strcmp ("func_illusionary", STRING (tr.pHit->v.classname)) == 0))
      return (FALSE); // don't add pathwaypoints through func_illusionaries

   // check if this waypoint is "visible"...
   TRACE_LINE (v_src, v_dest, ignore_monsters, pHostEdict, &tr);

   // if waypoint is visible from current position (even behind head)...
   if ((tr.flFraction >= 1.0) || (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0))
   {
      // If it's a door check if nothing blocks behind
      if (strncmp ("func_door", STRING (tr.pHit->v.classname), 9) == 0)
      {
         Vector vDoorEnd = tr.vecEndPos;

         TRACE_LINE (vDoorEnd, v_dest, ignore_monsters, tr.pHit, &tr);
         if (tr.flFraction < 1.0)
            return (FALSE);
      }

      // check for special case of both waypoints being in water...
      if ((POINT_CONTENTS (v_src) == CONTENTS_WATER)
          && (POINT_CONTENTS (v_dest) == CONTENTS_WATER))
          return (TRUE); // then they're reachable each other

      // check for special case of waypoint being suspended in mid-air...

      // is dest waypoint higher than src? (45 is max jump height)
      if (v_dest.z > v_src.z + 45.0)
      {
         Vector v_new_src = v_dest;
         Vector v_new_dest = v_dest;

         v_new_dest.z = v_new_dest.z - 50; // straight down 50 units

         TRACE_LINE (v_new_src, v_new_dest, ignore_monsters, pHostEdict, &tr);

         // check if we didn't hit anything, if not then it's in mid-air
         if (tr.flFraction >= 1.0)
            return (FALSE); // can't reach this one
      }

      // check if distance to ground drops more than step height at points
      // between source and destination...

      Vector v_direction = (v_dest - v_src).Normalize(); // 1 unit long
      Vector v_check = v_src;
      Vector v_down = v_src;

      v_down.z = v_down.z - 1000.0; // straight down 1000 units

      TRACE_LINE (v_check, v_down, ignore_monsters, pHostEdict, &tr);

      last_height = tr.flFraction * 1000.0; // height from ground

      distance = (v_dest - v_check).Length (); // distance from goal

      while (distance > 10.0)
      {
         // move 10 units closer to the goal...
         v_check = v_check + (v_direction * 10.0);

         v_down = v_check;
         v_down.z = v_down.z - 1000.0; // straight down 1000 units

         TRACE_LINE (v_check, v_down, ignore_monsters, pHostEdict, &tr);

         height = tr.flFraction * 1000.0; // height from ground

         // is the current height greater than the step height?
         if (height < last_height - 18.0)
            return (FALSE); // can't get there without jumping...

         last_height = height;

         distance = (v_dest - v_check).Length (); // distance from goal
      }

      return (TRUE);
   }

   return (FALSE);
}


void WaypointCalcVisibility (void)
{
   TraceResult tr;
   unsigned char byRes;
   unsigned char byShift;
   Vector vecDest;
   int i;

   if ((g_iCurrVisIndex < 0) || (g_iCurrVisIndex > g_iNumWaypoints))
      g_iCurrVisIndex = 0;

   Vector vecSourceDuck = paths[g_iCurrVisIndex]->origin;
   Vector vecSourceStand = paths[g_iCurrVisIndex]->origin;

   if (paths[g_iCurrVisIndex]->flags & W_FL_CROUCH)
   {
      vecSourceDuck.z += 12.0;
      vecSourceStand.z += 18.0 + 28.0;
   }
   else
   {
      vecSourceDuck.z += -18.0 + 12.0;
      vecSourceStand.z += 28.0;
   }

   for (i = 0; i < g_iNumWaypoints; i++)
   {
      // First check ducked Visibility

      vecDest = paths[i]->origin;

      TRACE_LINE (vecSourceDuck, vecDest, ignore_monsters, NULL, &tr);

      // check if line of sight to object is not blocked (i.e. visible)
      if (tr.flFraction != 1.0)
         byRes = 1;
      else
         byRes = 0;
      byRes <<= 1;

      TRACE_LINE (vecSourceStand, vecDest, ignore_monsters, NULL, &tr);

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

      if (byRes != 0)
      {
         vecDest = paths[i]->origin;
         // First check ducked Visibility
         if (paths[i]->flags & W_FL_CROUCH)
            vecDest.z += 18.0 + 28.0;
         else
            vecDest.z += 28.0;

         TRACE_LINE (vecSourceDuck, vecDest, ignore_monsters, NULL, &tr);

         // check if line of sight to object is not blocked (i.e. visible)
         if (tr.flFraction != 1.0)
            byRes |= 2;
         else
            byRes &= 1;

         TRACE_LINE (vecSourceStand, vecDest, ignore_monsters, NULL, &tr);

         // check if line of sight to object is not blocked (i.e. visible)
         if (tr.flFraction != 1.0)
            byRes |= 1;
         else
            byRes &= 2;

      }

      byShift = (i % 4) << 1;
      g_rgbyVisLUT[g_iCurrVisIndex][i >> 2] &= ~(3 << byShift);
      g_rgbyVisLUT[g_iCurrVisIndex][i >> 2] |= byRes << byShift;
   }

   g_iCurrVisIndex++;

   if ((g_fTimeDisplayVisTableMsg > 0) && (g_fTimeDisplayVisTableMsg < gpGlobals->time))
   {
      UTIL_HostPrint ("Visibility Table out of Date. Rebuilding... (%d%%)\n", (g_iCurrVisIndex * 100) / g_iNumWaypoints);
      g_fTimeDisplayVisTableMsg = gpGlobals->time + 1.0;
   }

   if (g_iCurrVisIndex == g_iNumWaypoints)
   {
      g_bRecalcVis = FALSE;
      g_fTimeDisplayVisTableMsg = 0;
   }

   return;
}


bool WaypointIsVisible (int iSourceIndex, int iDestIndex)
{
   unsigned char byRes = g_rgbyVisLUT[iSourceIndex][iDestIndex >> 2];
   byRes >>= (iDestIndex % 4) << 1;

   return (!((byRes & 3) == 3));
}


void WaypointThink (void)
{
   PATH *p;
   float distance, min_distance;
   Vector start, end;
   int i, index;
   char msg[512];
   float fRadCircle;
   Vector vRadiusStart;
   Vector vRadiusEnd;
   Vector v_direction;
   bool isJumpWaypoint;

   if (g_bEditNoclip)
      pHostEdict->v.movetype = MOVETYPE_NOCLIP;

   // is auto waypoint on?
   if (g_bAutoWaypoint)
   {
      // find the distance from the last used waypoint
      distance = (g_vecLastWaypoint - pHostEdict->v.origin).Length ();

      if (distance > 128)
      {
         min_distance = 9999.0;

         // check that no other reachable waypoints are nearby...
         for (i = 0; i < g_iNumWaypoints; i++)
         {
            if (WaypointReachable (pHostEdict->v.origin, paths[i]->origin, pHostEdict))
            {
               distance = (paths[i]->origin - pHostEdict->v.origin).Length ();

               if (distance < min_distance)
                  min_distance = distance;
            }
         }

         // make sure nearest waypoint is far enough away...
         if (min_distance >= 128)
            WaypointAdd (WAYPOINT_ADD_NORMAL); // place a waypoint here
      }
   }

   min_distance = 9999.0;

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

      if (distance < 500)
      {
         if (distance < min_distance)
         {
            index = i; // store index of nearest waypoint
            min_distance = distance;
         }

         if (g_fWPDisplayTime < gpGlobals->time)
         {
            if (paths[i]->flags & W_FL_CROUCH)
            {
               start = paths[i]->origin - Vector (0, 0, 17);
               end = start + Vector (0, 0, 34);
            }
            else
            {
               start = paths[i]->origin - Vector (0, 0, 34);
               end = start + Vector (0, 0, 68);
            }

            if (!FVisible (start, pHostEdict) && !FVisible (end, pHostEdict))
               continue;

            if (paths[i]->flags & W_FL_GOAL)
               WaypointDrawBeam (start, end, 30, 255, 0, 255);

            else if (paths[i]->flags & W_FL_LADDER)
               WaypointDrawBeam (start, end, 30, 255, 0, 255);

            else if (paths[i]->flags & W_FL_RESCUE)
               WaypointDrawBeam (start, end, 30, 255, 255, 255);

            else if (paths[i]->flags & W_FL_CAMP)
            {
               if (paths[i]->flags & W_FL_TERRORIST)
                  WaypointDrawBeam (start, end, 30, 255, 160, 160);
               else if (paths[i]->flags & W_FL_COUNTER)
                  WaypointDrawBeam (start, end, 30, 255, 160, 255);
               else
                  WaypointDrawBeam (start, end, 30, 0, 255, 255);
            }

            else
            {
               if (paths[i]->flags & W_FL_TERRORIST)
                  WaypointDrawBeam (start, end, 30, 255, 0, 0);
               else if (paths[i]->flags & W_FL_COUNTER)
                  WaypointDrawBeam (start, end, 30, 0, 0, 255);
               else
                  WaypointDrawBeam (start, end, 30, 0, 255, 0);
            }

            if (g_bShowWpFlags)
            {
               if (paths[i]->flags & W_FL_NOHOSTAGE)
               {
                  WaypointDrawBeam (end + Vector (-8, 0, 0), end + Vector (8, 0, 0), 30, 255, 0, 0);
                  WaypointDrawBeam (end + Vector (0, -8, 0), end + Vector (0, 8, 0), 30, 255, 0, 0);
               }

               if (paths[i]->flags & W_FL_USE_BUTTON)
               {
                  WaypointDrawBeam (end + Vector (-8, 0, -3), end + Vector (8, 0, -3), 30, 0, 255, 0);
                  WaypointDrawBeam (end + Vector (0, -8, -3), end + Vector (0, 8, -3), 30, 0, 255, 0);
               }
            }
         }
      }
   }

   // check if player is close enough to a waypoint and time to draw path and danger...
   if ((min_distance < 50) && (g_fWPDisplayTime < gpGlobals->time))
   {
      p = paths[index];
      isJumpWaypoint = FALSE;

      for (i = 0; i < MAX_PATH_INDEX; i++)
         if ((p->index[i] != -1) && (p->connectflag[i] & C_FL_JUMP))
            isJumpWaypoint = TRUE;

      sprintf (msg, "\n"
                    "\n"
                    "\n"
                    "\n"
                    "        Index Nr.: %d - Path Node Nr.:%d - Radius: %d\n"
                    "        Flags:%s%s%s%s%s%s%s%s%s%s%s%s\n"
                    "        Extra flags:%s%s\n",
                    index,
                    p->iPathNumber,
                    (int) p->Radius,
                    (p->flags == 0 ? " none" : ""),
                    (p->flags & W_FL_USE_BUTTON ? " | USE_BUTTON" : ""),
                    (p->flags & W_FL_LIFT ? " | LIFT" : ""),
                    (p->flags & W_FL_CROUCH ? " | CROUCH" : ""),
                    (p->flags & W_FL_CROSSING ? " | CROSSING" : ""),
                    (p->flags & W_FL_GOAL ? " | GOAL" : ""),
                    (p->flags & W_FL_LADDER ? " | LADDER" : ""),
                    (p->flags & W_FL_RESCUE ? " | RESCUE" : ""),
                    (p->flags & W_FL_CAMP ? " | CAMP" : ""),
                    (p->flags & W_FL_NOHOSTAGE ? " | NOHOSTAGE" : ""),
                    (p->flags & W_FL_TERRORIST ? " | TERRORIST" : ""),
                    (p->flags & W_FL_COUNTER ? " | COUNTER" : ""),
                    (!isJumpWaypoint ? " none" : ""),
                    (isJumpWaypoint ? " | JUMP" : ""));

         
      MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, pHostEdict);
      WRITE_BYTE (TE_TEXTMESSAGE);
      WRITE_BYTE (1); // channel
      WRITE_SHORT (0); // x coordinates * 8192
      WRITE_SHORT (0); // y coordinates * 8192
      WRITE_BYTE (0); // effect (fade in/out)
      WRITE_BYTE (255); // initial RED
      WRITE_BYTE (255); // initial GREEN
      WRITE_BYTE (255); // initial BLUE
      WRITE_BYTE (1); // initial ALPHA
      WRITE_BYTE (255); // effect RED
      WRITE_BYTE (255); // effect GREEN
      WRITE_BYTE (255); // effect BLUE
      WRITE_BYTE (1); // effect ALPHA
      WRITE_SHORT (0); // fade-in time in seconds * 256
      WRITE_SHORT (0); // fade-out time in seconds * 256
      WRITE_SHORT (256); // hold time in seconds * 256
      WRITE_STRING (msg); // write message
      MESSAGE_END (); // end

      if (p->flags & W_FL_CAMP)
      {
         if (p->flags & W_FL_CROUCH)
            start = p->origin - Vector (0, 0, 17) + Vector (0, 0, 34);
         else
            start = p->origin - Vector (0, 0, 34) + Vector (0, 0, 68);

         MAKE_VECTORS (Vector (p->fcampstartx, p->fcampstarty, 0));
         end = p->origin + gpGlobals->v_forward * 500;
         WaypointDrawBeam (start, end, 30, 255, 0, 0);

         MAKE_VECTORS (Vector (p->fcampendx, p->fcampendy, 0));
         end = p->origin + gpGlobals->v_forward * 500;
         WaypointDrawBeam (start, end, 30, 255, 0, 0);
      }

      for (i = 0; i < MAX_PATH_INDEX; i++)
      {
         if (p->index[i] != -1)
         {
            if (p->connectflag[i] & C_FL_JUMP)
               WaypointDrawBeam (p->origin, paths[p->index[i]]->origin, 10, 255, 0, 0);

            // If 2-way connection draw a yellow line to this index's waypoint
            else if (ConnectedToWaypoint (p->index[i], p->iPathNumber))
               WaypointDrawBeam (p->origin, paths[p->index[i]]->origin, 10, 255, 255, 0);

            // draw a white line to this index's waypoint
            else
               WaypointDrawBeam (p->origin, paths[p->index[i]]->origin, 10, 250, 250, 250);
         }
      }

      // now look for one-way incoming connections
      for (i = 0; i < g_iNumWaypoints; i++)
         if (ConnectedToWaypoint (paths[i]->iPathNumber, p->iPathNumber)
             && !ConnectedToWaypoint (p->iPathNumber, paths[i]->iPathNumber))
            WaypointDrawBeam (p->origin, paths[i]->origin, 10, 96, 48, 0);

      v_direction = g_vecZero;

      for (fRadCircle = 0.0; fRadCircle <= 180.0; fRadCircle += 22.5)
      {
         MAKE_VECTORS (v_direction);
         vRadiusStart = p->origin - (gpGlobals->v_forward * p->Radius);
         vRadiusEnd = p->origin + (gpGlobals->v_forward * p->Radius);
         WaypointDrawBeam (vRadiusStart, vRadiusEnd, 30, 0, 0, 255);

         v_direction.y = fRadCircle;
      }

      if (!g_bWaypointsChanged)
      {
         // draw a red line to this index's danger point
         if (UTIL_GetTeam (pHostEdict) == TEAM_CS_TERRORIST)
         {
            if ((pBotExperienceData + (index * g_iNumWaypoints) + index)->iTeam0_danger_index != -1)
               WaypointDrawBeam (paths[index]->origin,
                                 paths[(pBotExperienceData + (index * g_iNumWaypoints) + index)->iTeam0_danger_index]->origin,
                                 75, 127, 0, 0);
         }
         else
         {
            if ((pBotExperienceData + (index * g_iNumWaypoints) + index)->iTeam1_danger_index != -1)
               WaypointDrawBeam (paths[index]->origin,
                                 paths[(pBotExperienceData + (index * g_iNumWaypoints) + index)->iTeam1_danger_index]->origin,
                                 75, 127, 0, 0);
         }
      }
   }

   if (g_bLearnJumpWaypoint)
   {
      if (!g_bEndJumpPoint)
      {
         if (pHostEdict->v.button & IN_JUMP)
         {
            WaypointAdd (WAYPOINT_ADD_JUMP_START);
            g_fTimeJumpStarted = gpGlobals->time;
            g_bEndJumpPoint = TRUE;
         }
         else
         {
            vecLearnVelocity = pHostEdict->v.velocity;
            vecLearnPos = pHostEdict->v.origin;
         }
      }

      else if (((pHostEdict->v.flags & FL_ONGROUND) || (pHostEdict->v.movetype == MOVETYPE_FLY))
               && (g_fTimeJumpStarted + 0.1 < gpGlobals->time) && g_bEndJumpPoint)
      {
         WaypointAdd (WAYPOINT_ADD_JUMP_END);
         g_bLearnJumpWaypoint = FALSE;
         g_bEndJumpPoint = FALSE;
         UTIL_HostPrint ("Observation Check off\n");
         if (g_bUseSpeech)
            SERVER_COMMAND ("speak \"movement check over\"\n");
      }
   }

   if (g_fWPDisplayTime < gpGlobals->time)
      g_fWPDisplayTime = gpGlobals->time + 0.1;

   return;
}


void DeleteSearchNodes (bot_t *pBot)
{
   PATHNODE *NextNode;

   while (pBot->pWayNodesStart != NULL)
   {
      NextNode = pBot->pWayNodesStart->NextNode;
      delete (pBot->pWayNodesStart);
      pBot->pWayNodesStart = NextNode;
   }
   pBot->pWayNodesStart = NULL;
   pBot->pWaypointNodes = NULL;
}


bool WaypointIsConnected (int iNum)
{
   int i, n;

   for (i = 0; i < g_iNumWaypoints; i++)
      if (i != iNum)
         for (n = 0; n < MAX_PATH_INDEX; n++)
            if (paths[i]->index[n] == iNum)
               return (TRUE);

   return (FALSE);
}


bool WaypointNodesValid (void)
{
   TraceResult tr;
   PATHNODE *pStartNode, *pDelNode;
   bool bPathValid;
   int iTPoints = 0;
   int iCTPoints = 0;
   int iGoalPoints = 0;
   int iRescuePoints = 0;
   int iConnections;
   int i, n, x;

   for (i = 0; i < g_iNumWaypoints; i++)
   {
      iConnections = 0;

      for (n = 0; n < MAX_PATH_INDEX; n++)
      {
         if (paths[i]->index[n] != -1)
         {
            iConnections++;
            break;
         }
      }

      if (iConnections == 0)
      {
         if (!WaypointIsConnected (i))
         {
            UTIL_HostPrint ("Node %d not connected to any Waypoint!\n", i);
            EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
            TRACE_HULL (paths[i]->origin + Vector (0, 0, 32),
                        paths[i]->origin + Vector (0, 0, -32),
                        ignore_monsters, human_hull, pHostEdict, &tr);
            SET_ORIGIN (pHostEdict, tr.vecEndPos);
            return (FALSE);
         }
      }

      if (paths[i]->iPathNumber != i)
      {
         UTIL_HostPrint ("Node %d Pathnumber differs from Index!\n", i);
         EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
         TRACE_HULL (paths[i]->origin + Vector (0, 0, 32),
                     paths[i]->origin + Vector (0, 0, -32),
                     ignore_monsters, human_hull, pHostEdict, &tr);
         SET_ORIGIN (pHostEdict, tr.vecEndPos);
         return (FALSE);
      }

      if ((paths[i]->next == NULL) && (i != g_iNumWaypoints - 1))
      {
         UTIL_HostPrint ("Node %d not connected!\n", i);
         EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
         TRACE_HULL (paths[i]->origin + Vector (0, 0, 32),
                     paths[i]->origin + Vector (0, 0, -32),
                     ignore_monsters, human_hull, pHostEdict, &tr);
         SET_ORIGIN (pHostEdict, tr.vecEndPos);
         return (FALSE);
      }

      if (paths[i]->flags & W_FL_CAMP)
      {
         if ((paths[i]->fcampstartx == 0.0) && (paths[i]->fcampstarty == 0.0))
         {
            UTIL_HostPrint ("Node %d Camp Start Position not set!\n", i);
            EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
            TRACE_HULL (paths[i]->origin + Vector (0, 0, 32),
                        paths[i]->origin + Vector (0, 0, -32),
                        ignore_monsters, human_hull, pHostEdict, &tr);
            SET_ORIGIN (pHostEdict, tr.vecEndPos);
            return (FALSE);
         }

         else if ((paths[i]->fcampendx == 0.0) && (paths[i]->fcampendy == 0.0))
         {
            UTIL_HostPrint ("Node %d Camp End Position not set !\n", i);
            EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
            TRACE_HULL (paths[i]->origin + Vector (0, 0, 32),
                        paths[i]->origin + Vector (0, 0, -32),
                        ignore_monsters, human_hull, pHostEdict, &tr);
            SET_ORIGIN (pHostEdict, tr.vecEndPos);
            return (FALSE);
         }
      }

      else if (paths[i]->flags & W_FL_TERRORIST)
         iTPoints++;
      else if (paths[i]->flags & W_FL_COUNTER)
         iCTPoints++;
      else if (paths[i]->flags & W_FL_GOAL)
         iGoalPoints++;
      else if (paths[i]->flags & W_FL_RESCUE)
         iRescuePoints++;

      for (x = 0; x < MAX_PATH_INDEX; x++)
      {
         if (paths[i]->index[x] != -1)
         {
            if ((paths[i]->index[x] >= g_iNumWaypoints) || (paths[i]->index[x] < -1))
            {
               UTIL_HostPrint ("Node %d - Path Index %d out of Range!\n", i, x);
               EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
               TRACE_HULL (paths[i]->origin + Vector (0, 0, 32),
                           paths[i]->origin + Vector (0, 0, -32),
                           ignore_monsters, human_hull, pHostEdict, &tr);
               SET_ORIGIN (pHostEdict, tr.vecEndPos);
               return (FALSE);
            }

            else if (paths[i]->index[x] == paths[i]->iPathNumber)
            {
               UTIL_HostPrint ("Node %d - Path Index %d points to itself!\n", i, x);
               EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
               TRACE_HULL (paths[i]->origin + Vector (0, 0, 32),
                           paths[i]->origin + Vector (0, 0, -32),
                           ignore_monsters, human_hull, pHostEdict, &tr);
               SET_ORIGIN (pHostEdict, tr.vecEndPos);
               return (FALSE);
            }
         }
      }
   }

   if (g_iMapType & MAP_CS)
   {
      if (iRescuePoints == 0)
      {
         UTIL_HostPrint ("You didn't set a Rescue Point!\n");
         EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
         return (FALSE);
      }
   }

   if (iTPoints == 0)
   {
      UTIL_HostPrint ("You didn't set any Terrorist Point!\n");
      EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
      return (FALSE);
   }

   else if (iCTPoints == 0)
   {
      UTIL_HostPrint ("You didn't set any Counter Point!\n");
      EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
      return (FALSE);
   }

   else if (iGoalPoints == 0)
   {
      UTIL_HostPrint ("You didn't set any Goal Points!\n");
      EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
      return (FALSE);
   }

   // Init Floyd Matrix to use Floyd Pathfinder
   InitPathMatrix ();

   // Now check if each and every Path is valid
   for (i = 0; i < g_iNumWaypoints; i++)
   {
      for (n = 0; n < g_iNumWaypoints; n++)
      {
         if (i == n)
            continue;

         pStartNode = FindShortestPath (i, n, &bPathValid);

         while (pStartNode != NULL)
         {
            pDelNode = pStartNode->NextNode;
            delete pStartNode;
            pStartNode = pDelNode;
         }

         pStartNode = NULL;
         pDelNode = NULL;

         // No path from A to B ?
         if (!bPathValid)
         {
            UTIL_HostPrint ("No Path from %d to %d! One or both are unconnected!\n", i, n);
            EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "debris/bustglass1.wav", 1.0, ATTN_NORM, 0, 100);
            TRACE_HULL (paths[n]->origin + Vector (0, 0, 32),
                        paths[n]->origin + Vector (0, 0, -32),
                        ignore_monsters, human_hull, pHostEdict, &tr);
            SET_ORIGIN (pHostEdict, tr.vecEndPos);
            return (FALSE);
         }
      }
   }

   EMIT_SOUND_DYN2 (pHostEdict, CHAN_WEAPON, "weapons/he_bounce-1.wav", 1.0, ATTN_NORM, 0, 100);
   return (TRUE);
}


void InitPathMatrix (void)
{
   int i, j, k;
   PATH *p;

   g_pFloydDistanceMatrix = new int[g_iNumWaypoints * g_iNumWaypoints];
   g_pFloydPathMatrix = new int[g_iNumWaypoints * g_iNumWaypoints];

   for (i = 0; i < g_iNumWaypoints; i++)
   {
      for (j = 0; j < g_iNumWaypoints; j++)
      {
         *(g_pFloydDistanceMatrix + (i * g_iNumWaypoints) + j) = 999999;
         *(g_pFloydPathMatrix + (i * g_iNumWaypoints) + j) = -1;
      }
   }

   for (i = 0; i < g_iNumWaypoints; i++)
   {
      if (paths[i]->index[0] != -1)
      {
         *(g_pFloydDistanceMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[0])) = paths[i]->distance[0];
         *(g_pFloydPathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[0])) = paths[i]->index[0];
      }

      if (paths[i]->index[1] != -1)
      {
         *(g_pFloydDistanceMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[1])) = paths[i]->distance[1];
         *(g_pFloydPathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[1])) = paths[i]->index[1];
      }

      if (paths[i]->index[2] != -1)
      {
         *(g_pFloydDistanceMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[2])) = paths[i]->distance[2];
         *(g_pFloydPathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[2])) = paths[i]->index[2];
      }

      if (paths[i]->index[3] != -1)
      {
         *(g_pFloydDistanceMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[3])) = paths[i]->distance[3];
         *(g_pFloydPathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[3])) = paths[i]->index[3];
      }

      if (paths[i]->index[4] != -1)
      {
         *(g_pFloydDistanceMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[4])) = paths[i]->distance[4];
         *(g_pFloydPathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[4])) = paths[i]->index[4];
      }

      if (paths[i]->index[5] != -1)
      {
         *(g_pFloydDistanceMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[5])) = paths[i]->distance[5];
         *(g_pFloydPathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[5])) = paths[i]->index[5];
      }

      if (paths[i]->index[6] != -1)
      {
         *(g_pFloydDistanceMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[6])) = paths[i]->distance[6];
         *(g_pFloydPathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[6])) = paths[i]->index[6];
      }

      if (paths[i]->index[7] != -1)
      {
         *(g_pFloydDistanceMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[7])) = paths[i]->distance[7];
         *(g_pFloydPathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[7])) = paths[i]->index[7];
      }
   }

   for (i = 0; i < g_iNumWaypoints; i++)
      *(g_pFloydDistanceMatrix + (i * g_iNumWaypoints) + i) = 0;

   for (k = 0; k < g_iNumWaypoints; k++)
   {
      for (i = 0; i < g_iNumWaypoints; i++)
      {
         for (j = 0; j < g_iNumWaypoints; j++)
         {
            if (*(g_pFloydDistanceMatrix + (i * g_iNumWaypoints) + k) + *(g_pFloydDistanceMatrix + (k * g_iNumWaypoints) + j)
                < (*(g_pFloydDistanceMatrix + (i * g_iNumWaypoints) + j)))
            {
               *(g_pFloydDistanceMatrix + (i * g_iNumWaypoints) + j) = *(g_pFloydDistanceMatrix + (i * g_iNumWaypoints) + k) + *(g_pFloydDistanceMatrix + (k * g_iNumWaypoints) + j);
               *(g_pFloydPathMatrix + (i * g_iNumWaypoints) + j) = *(g_pFloydPathMatrix + (i * g_iNumWaypoints) + k);
            }
         }
      }
   }

   if (g_iMapType & MAP_CS)
   {
      g_pWithHostageDistMatrix = new int[g_iNumWaypoints * g_iNumWaypoints];
      g_pWithHostagePathMatrix = new int[g_iNumWaypoints * g_iNumWaypoints];

      for (i = 0; i < g_iNumWaypoints; i++)
      {
         for (j = 0; j < g_iNumWaypoints; j++)
         {
            *(g_pWithHostageDistMatrix + (i * g_iNumWaypoints) + j) = 999999;
            *(g_pWithHostagePathMatrix + (i * g_iNumWaypoints) + j) = -1;
         }
      }

      for (i = 0; i < g_iNumWaypoints; i++)
      {
         if (paths[i]->index[0] != -1)
         {
            p = paths[paths[i]->index[0]];

            if ((p->flags & W_FL_NOHOSTAGE) == 0)
            {
               *(g_pWithHostageDistMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[0])) = paths[i]->distance[0];
               *(g_pWithHostagePathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[0])) = paths[i]->index[0];
            }
         }

         if (paths[i]->index[1] != -1)
         {
            p = paths[paths[i]->index[1]];

            if ((p->flags & W_FL_NOHOSTAGE) == 0)
            {
               *(g_pWithHostageDistMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[1])) = paths[i]->distance[1];
               *(g_pWithHostagePathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[1])) = paths[i]->index[1];
            }
         }

         if (paths[i]->index[2] != -1)
         {
            p = paths[paths[i]->index[2]];

            if ((p->flags & W_FL_NOHOSTAGE) == 0)
            {
               *(g_pWithHostageDistMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[2])) = paths[i]->distance[2];
               *(g_pWithHostagePathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[2])) = paths[i]->index[2];
            }
         }

         if (paths[i]->index[3] != -1)
         {
            p = paths[paths[i]->index[3]];

            if ((p->flags & W_FL_NOHOSTAGE) == 0)
            {
               *(g_pWithHostageDistMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[3])) = paths[i]->distance[3];
               *(g_pWithHostagePathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[3])) = paths[i]->index[3];
            }
         }

         if (paths[i]->index[4] != -1)
         {
            p = paths[paths[i]->index[4]];

            if ((p->flags & W_FL_NOHOSTAGE) == 0)
            {
               *(g_pWithHostageDistMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[4])) = paths[i]->distance[4];
               *(g_pWithHostagePathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[4])) = paths[i]->index[4];
            }
         }

         if (paths[i]->index[5] != -1)
         {
            p = paths[paths[i]->index[5]];

            if ((p->flags & W_FL_NOHOSTAGE) == 0)
            {
               *(g_pWithHostageDistMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[5])) = paths[i]->distance[5];
               *(g_pWithHostagePathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[5])) = paths[i]->index[5];
            }
         }

         if (paths[i]->index[6] != -1)
         {
            p = paths[paths[i]->index[6]];
            if ((p->flags & W_FL_NOHOSTAGE) == 0)
            {
               *(g_pWithHostageDistMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[6])) = paths[i]->distance[6];
               *(g_pWithHostagePathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[6])) = paths[i]->index[6];
            }
         }

         if (paths[i]->index[7] != -1)
         {
            p = paths[paths[i]->index[7]];

            if ((p->flags & W_FL_NOHOSTAGE) == 0)
            {
               *(g_pWithHostageDistMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[7])) = paths[i]->distance[7];
               *(g_pWithHostagePathMatrix + (paths[i]->iPathNumber * g_iNumWaypoints) + (paths[i]->index[7])) = paths[i]->index[7];
            }
         }
      }

      for (i = 0; i < g_iNumWaypoints; i++)
         *(g_pWithHostageDistMatrix + (i * g_iNumWaypoints) + i) = 0;

      for (k = 0; k < g_iNumWaypoints; k++)
      {
         for (i = 0; i < g_iNumWaypoints; i++)
         {
            for (j = 0; j < g_iNumWaypoints; j++)
            {
               if (*(g_pWithHostageDistMatrix + (i * g_iNumWaypoints) + k) + *(g_pWithHostageDistMatrix + (k * g_iNumWaypoints) + j)
                   < (*(g_pWithHostageDistMatrix + (i * g_iNumWaypoints) + j)))
               {
                  *(g_pWithHostageDistMatrix + (i * g_iNumWaypoints) + j) = *(g_pWithHostageDistMatrix + (i * g_iNumWaypoints) + k) + *(g_pWithHostageDistMatrix + (k * g_iNumWaypoints) + j);
                  *(g_pWithHostagePathMatrix + (i * g_iNumWaypoints) + j) = *(g_pWithHostagePathMatrix + (i * g_iNumWaypoints) + k);
               }
            }
         }
      }
   }

   // Free up the hostage distance matrix
   if (g_pWithHostageDistMatrix != NULL)
      delete [](g_pWithHostageDistMatrix);
   g_pWithHostageDistMatrix = NULL;

   g_cKillHistory = 0;

   return;
}


int GetPathDistance (int iSourceWaypoint, int iDestWaypoint)
{
   return (*(g_pFloydDistanceMatrix + (iSourceWaypoint * g_iNumWaypoints) + iDestWaypoint));
}


// Return the most distant waypoint which is seen from the Bot to
// the Target and is within iCount
int GetAimingWaypoint (bot_t *pBot, Vector vecTargetPos, int iCount)
{
   if (pBot->curr_wpt_index == -1)
      pBot->curr_wpt_index = WaypointFindNearestToMove (pBot->pEdict->v.origin);

   int iSourceIndex = pBot->curr_wpt_index;
   int iDestIndex = WaypointFindNearestToMove (vecTargetPos);
   int iCurrCount = 0;
   int iBestIndex = iSourceIndex;
   PATHNODE *pStartNode, *pNode;

   pNode = new PATHNODE;
   pNode->iIndex = iDestIndex;
   pNode->NextNode = NULL;
   pStartNode = pNode;

   while (iDestIndex != iSourceIndex)
   {
      iDestIndex = *(g_pFloydPathMatrix + (iDestIndex * g_iNumWaypoints) + iSourceIndex);

      if (iDestIndex < 0)
         break;

      pNode->NextNode = new PATHNODE;
      pNode = pNode->NextNode;
      pNode->iIndex = iDestIndex;
      pNode->NextNode = NULL;

      if (WaypointIsVisible (pBot->curr_wpt_index, iDestIndex))
      {
         iBestIndex = iDestIndex;
         break;
      }
   }

   while (pStartNode != NULL)
   {
      pNode = pStartNode->NextNode;
      delete pStartNode;
      pStartNode = pNode;
   }

   return (iBestIndex);
}


PATHNODE *FindShortestPath (int iSourceIndex, int iDestIndex, bool *bValid)
{
   PATHNODE *StartNode, *Node;
   Node = new PATHNODE;
   Node->iIndex = iSourceIndex;
   Node->NextNode = NULL;
   StartNode = Node;
   *bValid = FALSE;

   while (iSourceIndex != iDestIndex)
   {
      iSourceIndex = *(g_pFloydPathMatrix + (iSourceIndex * g_iNumWaypoints) + iDestIndex);

      if (iSourceIndex < 0)
         return (StartNode);

      Node->NextNode = new PATHNODE;
      Node = Node->NextNode;
      Node->iIndex = iSourceIndex;
      Node->NextNode = NULL;
   }

   *bValid = TRUE;

   return (StartNode);
}


// Test function to view the calculated A* Path
void TestAPath (bot_t *pBot, int iSourceIndex, int iDestIndex, unsigned char byPathType)
{
   PATHNODE *root;
   PATHNODE *path, *p;

   g_iSearchGoalIndex = iDestIndex;

   // allocate and setup the root node
   root = new PATHNODE;

   root->iIndex = iSourceIndex;
   root->parent = NULL;
   root->NextNode = NULL;
   root->prev = NULL;
   root->g = 0;
   root->h = hfunctionSquareDist (root);
   root->f = root->g + root->h;
   root->id = 0;
   root->depth = 0;
   root->state = OPEN;

   if (byPathType == 1)
   {
      if (pBot->bot_team == TEAM_CS_TERRORIST)
         path = AStarSearch (root, gfunctionKillsDistT, hfunctionSquareDist, goal, makeChildren, nodeEqual);
      else if (BotHasHostage (pBot))
         path = AStarSearch (root, gfunctionKillsDistCTWithHostage, hfunctionSquareDist, goal, makeChildren, nodeEqual);
      else
         path = AStarSearch (root, gfunctionKillsDistCT, hfunctionSquareDist, goal, makeChildren, nodeEqual);
   }
   else
   {
      if (pBot->bot_team == TEAM_CS_TERRORIST)
         path = AStarSearch (root, gfunctionKillsT, hfunctionSquareDist, goal, makeChildren, nodeEqual);
      else if (BotHasHostage (pBot))
         path = AStarSearch (root, gfunctionKillsCTWithHostage, hfunctionSquareDist, goal, makeChildren, nodeEqual);
      else
         path = AStarSearch (root, gfunctionKillsCT, hfunctionSquareDist, goal, makeChildren, nodeEqual);
   }

   // A* returned failure
   if (path == NULL)
      UTIL_HostPrint ("NO PATH FOUND!\n");

   // otherwise, we had a successful search
   p = path;

   Vector vecSource;
   Vector vecDest;

   while (p != NULL)
   {
      vecSource = paths[p->iIndex]->origin;
      p = (PATHNODE *) p->NextNode;

      if ((p != NULL) && !FNullEnt (pBot->pEdict))
      {
         vecDest = paths[p->iIndex]->origin;
         MESSAGE_BEGIN (MSG_ONE_UNRELIABLE, SVC_TEMPENTITY, NULL, pBot->pEdict);
         WRITE_BYTE (TE_SHOWLINE);
         WRITE_COORD (vecSource.x);
         WRITE_COORD (vecSource.y);
         WRITE_COORD (vecSource.z);
         WRITE_COORD (vecDest.x);
         WRITE_COORD (vecDest.y);
         WRITE_COORD (vecDest.z);
         MESSAGE_END ();
      }
   }

   // delete the nodes on the path (which should delete root as well)
   while (path != NULL)
   {
      p = (PATHNODE *) path->NextNode;
      delete (path);
      path = p;
   }

   return;
}


PATHNODE *FindLeastCostPath (bot_t *pBot, int iSourceIndex, int iDestIndex)
{
   PATHNODE *root;
   PATHNODE *path;

   if ((iDestIndex > g_iNumWaypoints - 1) || (iDestIndex < 0))
      return (NULL);
   else if ((iSourceIndex > g_iNumWaypoints - 1) || (iSourceIndex < 0))
      return (NULL);

   g_iSearchGoalIndex = iDestIndex;

   // allocate and setup the root node
   root = new PATHNODE;

   root->iIndex = iSourceIndex;
   root->parent = NULL;
   root->NextNode = NULL;
   root->prev = NULL;
   root->g = 0;
   root->h = hfunctionSquareDist (root);
   root->f = root->g + root->h;
   root->id = 0;
   root->depth = 0;
   root->state = OPEN;

   if (pBot->byPathType == 1)
   {
      if (pBot->bot_team == TEAM_CS_TERRORIST)
         path = AStarSearch (root, gfunctionKillsDistT, hfunctionSquareDist, goal, makeChildren, nodeEqual);
      else if (BotHasHostage (pBot))
         path = AStarSearch (root, gfunctionKillsDistCTWithHostage, hfunctionSquareDist, goal, makeChildren, nodeEqual);
      else
         path = AStarSearch (root, gfunctionKillsDistCT, hfunctionSquareDist, goal, makeChildren, nodeEqual);
   }
   else
   {
      if (pBot->bot_team == TEAM_CS_TERRORIST)
         path = AStarSearch (root, gfunctionKillsT, hfunctionSquareDist, goal, makeChildren, nodeEqual);
      else if (BotHasHostage (pBot))
         path = AStarSearch (root, gfunctionKillsCTWithHostage, hfunctionSquareDist, goal, makeChildren, nodeEqual);
      else
         path = AStarSearch (root, gfunctionKillsCT, hfunctionSquareDist, goal, makeChildren, nodeEqual);
   }

   // A* returned failure
   if (path == NULL)
      UTIL_HostPrint ("Waypoint Problem! No path found!\n");

   // otherwise, we had a successful search
   return (path);
}


// This is a general A* function, so the user defines all of the
// necessary function calls for calculating path costs and generating
// the children of a particular node.
//
// root: Node*, root node from which to begin the search.
//
// gcalc: Takes a Node* as argument and returns a double indicating
// the g value (cost from initial state).
//
// hcalc: Takes a Node* as argument and returns a double indicating
// the h value (estimated cost to the goal).
//
// goalNode: Takes a Node* as argument and returns 1 if the node is
// the goal node, 0 otherwise.
//
// children: Takes a Node* as argument and returns a linked list of
// the children of that node, or NULL if there are none.
//
// nodeEqual: Takes two Node* as arguments and returns a 1 if the
// nodes are equivalent states, 0 otherwise.
//
// The function returns a Node*, which is the root node of a linked
// list (follow the "next" fields) that specifies the path from the
// root node to the goal node.

PATHNODE *AStarSearch (PATHNODE *root, int (*gcalc) (PATHNODE *), int (*hcalc) (PATHNODE *),
                       int (*goalNode) (PATHNODE *), PATHNODE * (*children) (PATHNODE *),
                       int (*nodeEqual) (PATHNODE *, PATHNODE *))
{
   PATHNODE *openList;
   PATHNODE *closedList;
   PATHNODE *current;
   PATHNODE *childList;
   PATHNODE *curChild;
   PATHNODE *p, *q;
   PATHNODE *path;
   static int gblID = 1;
   static int gblExpand = 0;

   // generate the open list
   openList = NULL;

   // generate the closed list
   closedList = NULL;

   // put the root node on the open list
   root->NextNode = NULL;
   root->prev = NULL;
   openList = root;

   while (openList != NULL)
   {
      // remove the first node from the open list, as it will always be sorted

      current = openList;
      openList = (PATHNODE *) openList->NextNode;

      if (openList != NULL)
         openList->prev = NULL;

      gblExpand++;

      // is the current node the goal node?
      if (goalNode (current))
      {
         // build the complete path to return
         current->NextNode = NULL;
         path = current;
         p = (PATHNODE *) current->parent;

         //printf ("Goal state reached with %d nodes created and %d nodes expanded\n", gblID, gblExpand);

         while (p != NULL)
         {
            // remove the parent node from the closed list (where it has to be)
            if (p->prev != NULL)
               ((PATHNODE *) p->prev)->NextNode = p->NextNode;

            if (p->NextNode != NULL)
               ((PATHNODE *) p->NextNode)->prev = p->prev;

            // check if we're romoving the top of the list
            if (p == closedList)
               closedList = (PATHNODE *)p->NextNode;

            // set it up in the path
            p->NextNode = path;
            path = p;

            p = (PATHNODE *) p->parent;
         }

         // now delete all nodes on OPEN
         while (openList != NULL)
         {
            p = (PATHNODE *) openList->NextNode;
            delete (openList);
            openList = p;
         }

         // now delete all nodes on CLOSED
         while (closedList != NULL)
         {
            p = (PATHNODE *) closedList->NextNode;
            delete (closedList);
            closedList = p;
         }

         // now return the path
         return (path);
      }

      // now expand the current node
      childList = children (current);

      // insert the children into the OPEN list according to their f values
      while (childList != NULL)
      {
         curChild = childList;
         childList = (PATHNODE *) childList->NextNode;

         // set up the rest of the child node
         curChild->parent = current;
         curChild->state = OPEN;
         curChild->depth = current->depth + 1;
         curChild->id = gblID++;
         curChild->NextNode = NULL;
         curChild->prev = NULL;

         // calculate the f value as f = g + h
         curChild->g = gcalc (curChild);
         curChild->h = hcalc (curChild);
         curChild->f = curChild->g + curChild->h;

         // forbidden value for g ?
         if (curChild->g == -1)
         {
            curChild->g = 9e+99;
            curChild->h = 9e+99;
            curChild->f = 9e+99; // max out all the costs for this child
         }

         // test whether the child is in the closed list (already been there)
         if (closedList != NULL)
         {
            p = closedList;

            while (p != NULL)
            {
               if (nodeEqual (p, curChild))
               {
                  // if so, check if the f value is lower
                  if (p->f <= curChild->f)
                  {
                     // if the f value of the older node is lower, delete the new child
                     delete (curChild);
                     curChild = NULL;
                     break;
                  }
                  else
                  {
                     // the child is a shorter path to this point, delete p from the closed list
                     // This works so long as the new child is put in the OPEN list.
                     // Another solution is to just update all of the descendents of this node
                     // with the new f values.
                     if (p->prev != NULL)
                        ((PATHNODE *) p->prev)->NextNode = p->NextNode;

                     if (p->NextNode != NULL)
                        ((PATHNODE *) p->NextNode)->prev = p->prev;

                     if (p == closedList)
                        closedList = (PATHNODE *) p->NextNode;

                     delete (p);
                     break;
                  }
               }

               p = (PATHNODE *) p->NextNode;
            }
         }

         if (curChild != NULL)
         {
            // check if the child is already on the open list
            p = openList;

            while (p != NULL)
            {
               if (nodeEqual (p, curChild))
               {
                  // child is on the OPEN list
                  if (p->f <= curChild->f)
                  {
                     // child is a longer path to the same place so delete it
                     delete (curChild);
                     curChild = NULL;
                     break;
                  }
                  else
                  {
                     // child is a shorter path to the same place, remove the duplicate node
                     if (p->prev != NULL)
                        ((PATHNODE *) p->prev)->NextNode = p->NextNode;

                     if (p->NextNode != NULL)
                        ((PATHNODE *) p->NextNode)->prev = p->prev;

                     if (p == openList)
                        openList = (PATHNODE *) p->NextNode;

                     break;
                  }
               }

               p = (PATHNODE *)p->NextNode;
            }

            if (curChild != NULL)
            {
               // now insert the child into the list according to the f values
               p = openList;
               q = p;

               while (p != NULL)
               {
                  if (p->f >= curChild->f)
                  {
                     // insert before p
                     // test head case

                     if (p == openList)
                        openList = curChild;

                     // insert the node
                     curChild->NextNode = p;
                     curChild->prev = p->prev;
                     p->prev = curChild;

                     if (curChild->prev != NULL)
                        ((PATHNODE *) curChild->prev)->NextNode = curChild;
                     break;
                  }

                  q = p;
                  p = (PATHNODE *)p->NextNode;
               }

               if (p == NULL)
               {
                  // insert at the end
                  if (q != NULL)
                  {
                     q->NextNode = curChild;
                     curChild->prev = q;
                  }
                  else
                     openList = curChild; // insert at the beginning
               }
            } // end if child is not NULL (better duplicate on OPEN list)
         } // end if child is not NULL (better duplicate on CLOSED list)
      } // end of child list loop

      // put the current node onto the closed list
      current->NextNode = closedList;
      if (closedList != NULL)
         closedList->prev = current;
      closedList = current;

      current->prev = NULL;
      current->state = CLOSED;
   }

   // if we got here, then there is no path to the goal

   // delete all nodes on CLOSED since OPEN is now empty
   while (closedList != NULL)
   {
      p = (PATHNODE *) closedList->NextNode;
      delete (closedList);
      closedList = p;
   }

   return (NULL);
}



// Least Kills and Number of Nodes to Goal for a Team
int gfunctionKillsDistT (PATHNODE* p)
{
   int i;

   if (p == NULL)
      return (-1);

   int iThisIndex = p->iIndex;
   int iCost = (pBotExperienceData + (iThisIndex * g_iNumWaypoints) + iThisIndex)->uTeam0Damage + g_cKillHistory;
   int iNeighbour;

   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      iNeighbour = paths[iThisIndex]->index[i];

      if (iNeighbour != -1)
         iCost += (float) (pBotExperienceData + (iNeighbour * g_iNumWaypoints) + iNeighbour)->uTeam0Damage * 0.3;
   }

   if (paths[iThisIndex]->flags & W_FL_CROUCH)
      iCost *= 1.5;

   return (iCost * (g_iDangerFactor * 2 / 3));
}


int gfunctionKillsDistCT (PATHNODE *p)
{
   int i;

   if (p == NULL)
      return (-1);

   int iThisIndex = p->iIndex;
   int iCost = (pBotExperienceData + (iThisIndex * g_iNumWaypoints) + iThisIndex)->uTeam1Damage + g_cKillHistory;
   int iNeighbour;

   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      iNeighbour = paths[iThisIndex]->index[i];

      if (iNeighbour != -1)
         iCost += (float) (pBotExperienceData + (iNeighbour * g_iNumWaypoints) + iNeighbour)->uTeam1Damage * 0.3;
   }

   if (paths[iThisIndex]->flags & W_FL_CROUCH)
      iCost *= 1.5;

   return (iCost * (g_iDangerFactor * 2 / 3));
}


int gfunctionKillsDistCTWithHostage (PATHNODE *p)
{
   int i;

   if (p == NULL)
      return (-1);

   int iThisIndex = p->iIndex;

   if (paths[iThisIndex]->flags & W_FL_NOHOSTAGE)
      return (-1);

   int iCost = (pBotExperienceData + (iThisIndex * g_iNumWaypoints) + iThisIndex)->uTeam1Damage + g_cKillHistory;
   int iNeighbour;

   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      iNeighbour = paths[iThisIndex]->index[i];

      if (iNeighbour != -1)
         iCost += (float) (pBotExperienceData + (iNeighbour * g_iNumWaypoints) + iNeighbour)->uTeam1Damage * 0.3;
   }

   if (paths[iThisIndex]->flags & W_FL_CROUCH)
      iCost *= 1.5;

   return (iCost * g_iDangerFactor);
}


// Least Kills to Goal for a Team
int gfunctionKillsT (PATHNODE* p)
{
   int i;

   if (p == NULL)
      return (-1);

   int iThisIndex = p->iIndex;
   int iCost = (pBotExperienceData + (iThisIndex * g_iNumWaypoints) + iThisIndex)->uTeam0Damage;
   int iNeighbour;

   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      iNeighbour = paths[iThisIndex]->index[i];

      if (iNeighbour != -1)
         iCost += (float) (pBotExperienceData + (iNeighbour * g_iNumWaypoints) + iNeighbour)->uTeam0Damage * 0.3;
   }

   if (paths[iThisIndex]->flags & W_FL_CROUCH)
      iCost *= 1.5;

   return (iCost * (g_iDangerFactor * 2 / 3));
}


int gfunctionKillsCT (PATHNODE *p)
{
   int i;

   if (p == NULL)
      return (-1);

   int iThisIndex = p->iIndex;
   int iCost = (pBotExperienceData + (iThisIndex * g_iNumWaypoints) + iThisIndex)->uTeam1Damage;
   int iNeighbour;

   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      iNeighbour = paths[iThisIndex]->index[i];

      if (iNeighbour != -1)
         iCost += (float) (pBotExperienceData + (iNeighbour * g_iNumWaypoints) + iNeighbour)->uTeam1Damage * 0.3;
   }

   if (paths[iThisIndex]->flags & W_FL_CROUCH)
      iCost *= 1.5;

   return (iCost * (g_iDangerFactor * 2 / 3));
}


int gfunctionKillsCTWithHostage (PATHNODE *p)
{
   int i;

   if (p == NULL)
      return (-1);

   int iThisIndex = p->iIndex;

   if (paths[iThisIndex]->flags & W_FL_NOHOSTAGE)
      return (-1);

   int iCost = (pBotExperienceData + (iThisIndex * g_iNumWaypoints) + iThisIndex)->uTeam1Damage;
   int iNeighbour;

   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      iNeighbour = paths[iThisIndex]->index[i];

      if (iNeighbour != -1)
         iCost += (float) (pBotExperienceData + (iNeighbour * g_iNumWaypoints) + iNeighbour)->uTeam1Damage * 0.3;
   }

   if (paths[iThisIndex]->flags & W_FL_CROUCH)
      iCost *= 1.5;

   return (iCost * g_iDangerFactor);
}


// No heurist (greedy) !!
int hfunctionNone (PATHNODE *p)
{
   if (p == NULL)
      return (-1);

   return (0);
}


// Square Distance Heuristic
int hfunctionSquareDist (PATHNODE *p)
{
   int deltaX = abs ((int) paths[g_iSearchGoalIndex]->origin.x - (int) paths[p->iIndex]->origin.x);
   int deltaY = abs ((int) paths[g_iSearchGoalIndex]->origin.y - (int) paths[p->iIndex]->origin.y);
   int deltaZ = abs ((int) paths[g_iSearchGoalIndex]->origin.z - (int) paths[p->iIndex]->origin.z);

   return (deltaX + deltaY + deltaZ);
}


// define the goalNode function
int goal (PATHNODE *p)
{
   if (p->iIndex == g_iSearchGoalIndex)
      return (1);

   return (0);
}


// define the children function
PATHNODE *makeChildren (PATHNODE *parent)
{
   int i;
   PATHNODE *p, *q;

   // initialize the return list
   q = NULL;
   int iParentIndex = parent->iIndex;

   for (i = 0; i < MAX_PATH_INDEX; i++)
   {
      if (paths[iParentIndex]->index[i] != -1)
      {
         p = new PATHNODE;
         p->iIndex = paths[iParentIndex]->index[i];
         p->parent = parent;
         p->NextNode = q;
         q = p;
      }
   }

   return (q);
}


// Test for node equality
int nodeEqual (PATHNODE *a, PATHNODE *b)
{
   if ((a == NULL) && (b == NULL))
      return (1);
   else if (a == NULL)
      return (0);
   else if (b == NULL)
      return (0);

   return (a->iIndex == b->iIndex);
}
