AssaultCube Reloaded Wiki
Advertisement
//
// C++ Implementation: bot_waypoint
//
// Description: Waypoint class for bots
//
//
// Author: Rick <rickhelmus@gmail.com>
//

#include "cube.h"
#include "bot.h"

vec v_debuggoal = g_vecZero;

#if defined AC_CUBE
CACWaypointClass WaypointClass;
#elif defined VANILLA_CUBE
CCubeWaypointClass WaypointClass;
#endif

VAR(xhairwpsel, 0, 1, 1);
// FIXME: multiple selections support ?
// well, TBH I haven't groked this myself yet, but the following curselection segfaults the client, so I'm just putting in a sane-variant until someone groks it ;-) - ft 2011sep28
extern vector<block> sels;
//#define curselection (xhairwpsel ? vec(sels.last().x, sels.last().y, S(sels.last().x, sels.last().y)->floor+2.0f) : vec(player1->o.x, player1->o.y, player1->o.z))
#define curselection (vec(player1->o.x, player1->o.y, player1->o.z))

// Waypoint class begin

CWaypointClass::CWaypointClass(void) : m_bDrawWaypoints(false), m_bDrawWPPaths(true),
                                       m_bAutoWaypoint(false),  m_bAutoPlacePaths(true),
                                       m_bDrawWPText(false), m_vLastCreatedWP(g_vecZero),
                                       m_bFlooding(false), m_bFilteringNodes(false),
                                       m_iFloodStartTime(0), m_iCurFloodX(0),
                                       m_iCurFloodY(0), m_iFloodSize(0),
                                       m_iFilteredNodes(0), m_iWaypointCount(0)
{
     loopi(MAX_MAP_GRIDS)
     {
          loopj(MAX_MAP_GRIDS)
          {
               while(!m_Waypoints[i][j].Empty())
                    delete m_Waypoints[i][j].Pop();
          }
     }

     m_szMapName[0] = 0;
}

void CWaypointClass::Init()
{
     Clear();  // Free previously allocated path memory

     loopi(MAX_MAP_GRIDS)
     {
          loopj(MAX_MAP_GRIDS)
          {
               while(!m_Waypoints[i][j].Empty())
                    delete m_Waypoints[i][j].Pop();
          }
     }

     m_fPathDrawTime = 0.0;
     m_iWaypointCount = 0;
     m_vLastCreatedWP = g_vecZero;
}

void CWaypointClass::Clear()
{
     for(int i=0;i<MAX_MAP_GRIDS;i++)
     {
          for(int j=0;j<MAX_MAP_GRIDS;j++)
          {
               while(!m_Waypoints[i][j].Empty())
               {
                    node_s *p = m_Waypoints[i][j].Pop();
                    p->ConnectedWPs.DeleteAllNodes();
                    p->ConnectedWPsWithMe.DeleteAllNodes();
                    BotManager.DelWaypoint(p);
                    delete p;
               }
          }
     }
}

//fixme
TLinkedList<node_s *>::node_s *testx;

// returns true if waypoints succesfull loaded
bool CWaypointClass::LoadWaypoints()
{
     stream *bfp;
     char szWPFileName[64];
     char filename[256];
     waypoint_header_s header;
     short num, triggernr, yaw;
     int i, j, flags;
     vec from, to;

     strcpy(szWPFileName, m_szMapName);
     strcat(szWPFileName, ".wpt");

     BotManager.MakeBotFileName(szWPFileName, "waypoints", filename);

     bfp = openfile(filename, "rb");

     BotManager.m_sCurrentTriggerNr = -1;

     // if file exists, read the waypoint structure from it
     if (bfp != NULL)
     {
          bfp->read(&header, sizeof(header));

          header.szFileType[sizeof(header.szFileType)-1] = 0;
          if (strcmp(header.szFileType, "cube_bot") == 0)
          {
               header.szMapName[sizeof(header.szMapName)-1] = 0;

               if (strcmp(header.szMapName, m_szMapName) == 0)
               {
                    Init();  // remove any existing waypoints

                    // Check which waypoint file version this is, handle each of them different
                    if (header.iFileVersion == 1)
                    {
                         // First version, works with an big array and has a limit

                         waypoint_version_1_s WPs[1024];
                         int path_index;

                         m_iWaypointCount = header.iWPCount;
                         for (i=0; i < header.iWPCount; i++)
                         {
                              bfp->read(&WPs[i], sizeof(WPs[0]));
                              WPs[i].ConnectedWPs.Reset(); // We get garbage when we read this from
                                                           // a file, so just clear it

                              // Convert to new waypoint structure
                              node_s *pNode = new node_s(WPs[i].v_origin, WPs[i].iFlags, 0);

                              short x, y;
                              GetNodeIndexes(pNode->v_origin, &x, &y);

                              m_Waypoints[x][y].AddNode(pNode);
                              BotManager.AddWaypoint(pNode);
                         }

                         // read and add waypoint paths...
                         for (i=0; i < m_iWaypointCount; i++)
                         {
                              // read the number of paths from this node...
                              bfp->read(&num, sizeof(num));

                              // See which waypoint the current is
                              node_s *pCurrent = GetWaypointFromVec(WPs[i].v_origin);

                              if (!pCurrent)
                              {
                                   conoutf("Error: NULL path in waypoint file");
                                   continue;
                              }

                              for (j=0; j < num; j++)
                              {
                                   bfp->read(&path_index, sizeof(path_index));

                                   // See which waypoint this is
                                   node_s *pTo = GetWaypointFromVec(WPs[path_index].v_origin);

                                   if (!pTo)
                                   {
                                        conoutf("Error: NULL path in waypoint file");
                                        continue;
                                   }

                                   AddPath(pCurrent, pTo);
                              }
                         }
                         conoutf("Old waypoint version(%d) converted to new version(%d)",
                                  header.iFileVersion, WAYPOINT_VERSION);
                         conoutf("Use the wpsave command to convert permently");

                    }
                    else if (header.iFileVersion == 2)
                    {
                         m_iWaypointCount = header.iWPCount;

                         for (i=0; i < header.iWPCount; i++)
                         {
                              bfp->read(&from, sizeof(from)); // Read origin
                              bfp->read(&flags, sizeof(flags)); // Read waypoint flags
                              bfp->read(&triggernr, sizeof(triggernr)); // Read trigger nr
                              bfp->read(&yaw, sizeof(yaw)); // Read target yaw

                              node_s *pNode = new node_s(from, flags, triggernr, yaw);

                              short x, y;
                              GetNodeIndexes(pNode->v_origin, &x, &y);

                              m_Waypoints[x][y].AddNode(pNode);
                              BotManager.AddWaypoint(pNode);

                              if ((BotManager.m_sCurrentTriggerNr == -1) ||
                                  (triggernr < BotManager.m_sCurrentTriggerNr))
                                  BotManager.m_sCurrentTriggerNr = triggernr;
                         }

                         for (i=0; i < header.iWPCount; i++)
                         {
                              // read the number of paths from this node...
                              bfp->read(&num, sizeof(num));
                              bfp->read(&from, sizeof(from));

                              if (!num)
                                   continue;

                              node_s *pCurrent = GetWaypointFromVec(from);

                              if (!pCurrent)
                              {
                                   conoutf("Error: NULL path in waypoint file");

                                   for(j=0;j<num;j++) bfp->read(&to, sizeof(to)); // Read rest of block
                                   continue;
                              }

                              for (j=0; j < num; j++)
                              {
                                   bfp->read(&to, sizeof(to));
                                   node_s *p = GetWaypointFromVec(to);
                                   if (p)
                                        AddPath(pCurrent, p);
                              }
                         }
                    }
                    else if (header.iFileVersion > WAYPOINT_VERSION)
                    {
                         conoutf("Error: Waypoint file is newer then current, upgrade cube bot.");
                         delete bfp;
                         return false;
                    }
                    else
                    {
                         conoutf("Error: Unknown waypoint version for cube bot");
                         delete bfp;
                         return false;
                    }
               }
               else
               {
                    conoutf("Waypoints aren't for map %s but for map %s", m_szMapName,
                                  header.szMapName);
                    delete bfp;
                    return false;
               }
          }
          else
          {
               conoutf("Waypoint file isn't for cube bot");
               //conoutf("Header FileType: %s", int(header.szFileType));

               delete bfp;
               return false;
          }

          delete bfp;

          //RouteInit();
     }
     else
     {
          conoutf("Waypoint file %s does not exist", (filename));
          return false;
     }

     if (BotManager.m_sCurrentTriggerNr == -1)
          BotManager.m_sCurrentTriggerNr = 0;

     ReCalcCosts();

     conoutf("Waypoints for map %s loaded", (m_szMapName));

     testx = m_Waypoints[1][1].pNodeList;

     return true;
}

void CWaypointClass::SaveWaypoints()
{
     char filename[256];
     char mapname[64];
     waypoint_header_s header;
     short int num;
     TLinkedList<node_s *>::node_s *pPath;

     strcpy(header.szFileType, "cube_bot");

     header.iFileVersion = WAYPOINT_VERSION;

     header.iFileFlags = 0;  // not currently used

     header.iWPCount = m_iWaypointCount;

     memset(header.szMapName, 0, sizeof(header.szMapName));
     strncpy(header.szMapName, m_szMapName, sizeof(header.szMapName)-1);
     header.szMapName[sizeof(header.szMapName)-1] = 0;

     strcpy(mapname, m_szMapName);
     strcat(mapname, ".wpt");

     BotManager.MakeBotFileName(mapname, "waypoints", filename);

     stream *bfp = openfile(filename, "wb");

     if (!bfp)
     {
          conoutf("Error writing waypoint file, check if the directory \"bot/waypoints\" exists and "
                       "the right permissions are set");
          return;
     }

     // write the waypoint header to the file...
     bfp->write(&header, sizeof(header));

     // write the waypoint data to the file...
     loopi(MAX_MAP_GRIDS)
     {
          loopj(MAX_MAP_GRIDS)
          {
               TLinkedList<node_s *>::node_s *p = m_Waypoints[i][j].GetFirst();
               while(p)
               {
                    bfp->write(&p->Entry->v_origin, sizeof(p->Entry->v_origin)); // Write origin
                    bfp->write(&p->Entry->iFlags, sizeof(p->Entry->iFlags)); // Write waypoint flags
                    bfp->write(&p->Entry->sTriggerNr, sizeof(p->Entry->sTriggerNr)); // Write trigger nr
                    bfp->write(&p->Entry->sYaw, sizeof(p->Entry->sYaw)); // Write target yaw

                    p = p->next;
               }
          }
     }

     loopi(MAX_MAP_GRIDS)
     {
          loopj(MAX_MAP_GRIDS)
          {
               TLinkedList<node_s *>::node_s *p = m_Waypoints[i][j].GetFirst();
               while(p)
               {
                    // save the waypoint paths...

                    // count the number of paths from this node...
                    pPath = p->Entry->ConnectedWPs.GetFirst();
                    num = p->Entry->ConnectedWPs.NodeCount();

                    bfp->write(&num, sizeof(num));  // write the count
                    bfp->write(&p->Entry->v_origin, sizeof(p->Entry->v_origin)); // write the origin of this path

                    // now write out each path...
                    while (pPath != NULL)
                    {
                         bfp->write(&pPath->Entry->v_origin, sizeof(pPath->Entry->v_origin));
                         pPath = pPath->next;  // go to next node in linked list
                    }
                    p = p->next;
               }
          }
     }

     delete bfp;
}

bool CWaypointClass::LoadWPExpFile()
{
     FILE *bfp;
     char szWPFileName[64];
     char filename[256];
     waypoint_header_s header;
     short int num;
     int i, j;
     vec from, to;

     if (m_iWaypointCount == 0)
          return false;

     strcpy(szWPFileName, m_szMapName);
     strcat(szWPFileName, ".exp");

     BotManager.MakeBotFileName(szWPFileName, "waypoints", filename);

     bfp = fopen(filename, "rb");

     // if file exists, read the waypoint structure from it
     if (bfp != NULL)
     {
          fread(&header, sizeof(header), 1, bfp);

          header.szFileType[sizeof(header.szFileType)-1] = 0;
          if (strcmp(header.szFileType, "cube_bot") == 0)
          {
               header.szMapName[sizeof(header.szMapName)-1] = 0;

               if (strcmp(header.szMapName, m_szMapName) == 0)
               {
                    // Check which waypoint file version this is, handle each of them different
                    if (header.iFileVersion == 1)
                    {
                         for (i=0; i < header.iWPCount; i++)
                         {
                              // read the number of paths from this node...
                              fread(&num, sizeof(num), 1, bfp);
                              fread(&from, sizeof(vec), 1, bfp);

                              if (!num) continue;

                              node_s *pCurrent = GetWaypointFromVec(from);

                              if (!pCurrent)
                              {
                                   conoutf("Error: NULL node in waypoint experience file");
                                   continue;
                              }

                              for (j=0; j < num; j++)
                              {
                                   fread(&to, sizeof(vec), 1, bfp);
                                   node_s *p = GetWaypointFromVec(to);
                                   if (p)
                                   {
                                        pCurrent->FailedGoalList.AddNode(p);
                                   }
                              }
                         }
                    }
                    else if (header.iFileVersion > EXP_WP_VERSION)
                    {
                         conoutf("Error: Waypoint experience file is newer then current, upgrade cube bot.");
                         fclose(bfp);
                         return false;
                    }
                    else
                    {
                         conoutf("Error: Unknown waypoint experience file version for cube bot");
                         fclose(bfp);
                         return false;
                    }
               }
               else
               {
                    conoutf("Waypoint experience file isn't for map %s but for map %s", (m_szMapName),
                                  (header.szMapName));
                    fclose(bfp);
                    return false;
               }
          }
          else
          {
               conoutf("Waypoint experience file isn't for cube bot");
               //conoutf("Header FileType: %s", int(header.szFileType));

               fclose(bfp);
               return false;
          }

          fclose(bfp);

          //RouteInit();
     }
     else
     {
          conoutf("Waypoint experience file %s does not exist", (filename));
          return false;
     }

     conoutf("Waypoint experience file for map %s loaded", (m_szMapName));
     return true;
}

void CWaypointClass::SaveWPExpFile()
{
     if (!m_iWaypointCount)
          return;

     char filename[256];
     char mapname[64];
     waypoint_header_s header;
     short int num;

     strcpy(header.szFileType, "cube_bot");

     header.iFileVersion = EXP_WP_VERSION;

     header.iFileFlags = 0;  // not currently used

     header.iWPCount = m_iWaypointCount;

     memset(header.szMapName, 0, sizeof(header.szMapName));
     strncpy(header.szMapName, m_szMapName, 31);
     header.szMapName[31] = 0;

     strcpy(mapname, m_szMapName);
     strcat(mapname, ".exp");

     BotManager.MakeBotFileName(mapname, "waypoints", filename);

     FILE *bfp = fopen(filename, "wb");

     if (!bfp)
     {
          conoutf("Error writing waypoint experience file, check if the directory \"bot/waypoints\" exists and "
                       "the right permissions are set");
          return;
     }

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

     // save the waypoint experience data...
     loopi(MAX_MAP_GRIDS)
     {
          loopj(MAX_MAP_GRIDS)
          {
               TLinkedList<node_s *>::node_s *p = m_Waypoints[i][j].GetFirst();
               while(p)
               {
                    // count the number of paths from this node...
                    TLinkedList<node_s *>::node_s *p2 = p->Entry->FailedGoalList.GetFirst();
                    num = p->Entry->FailedGoalList.NodeCount();

                    if (!num || !p2) continue;

                    fwrite(&num, sizeof(num), 1, bfp);  // write the count
                    fwrite(&p->Entry->v_origin, sizeof(vec), 1, bfp); // write the origin of this node

                    while (p2 != NULL)
                    {
                         // Write out the node which a bot can't reach with the current node
                         fwrite(&p2->Entry->v_origin, sizeof(vec), 1, bfp);
                         p2 = p2->next;  // go to next node in linked list
                    }
                    p = p->next;
               }
          }
     }

     fclose(bfp);
}

void CWaypointClass::Think()
{
#ifdef WP_FLOOD
     FloodThink();
#endif

     if (m_bAutoWaypoint) // is auto waypoint on?
     {
          // find the distance from the last used waypoint
          float flDistance = GetDistance(m_vLastCreatedWP, player1->o);

          bool NoLastCreatedWP = ((m_vLastCreatedWP.x == 0) && (m_vLastCreatedWP.y == 0) &&
                                                    (m_vLastCreatedWP.z == 0));

          if ((flDistance > 3.0f) || NoLastCreatedWP)
          {
               // check that no other reachable waypoints are nearby...
               if (!GetNearestWaypoint(player1, 10.0f))
               {
                    AddWaypoint(player1->o, true);  // place a waypoint here
               }
          }
     }

     // Draw info for nearest waypoint?
     if (m_bDrawWPText)
     {
          node_s *nearestwp = GetNearestWaypoint(player1, 20.0f);

#ifdef WP_FLOOD
          if (!nearestwp)
               nearestwp = GetNearestFloodWP(player1, 10.0f);
#endif

          if (nearestwp)
          {
               char szWPInfo[256];
               sprintf(szWPInfo, "Distance nearest waypoint: %f", GetDistance(player1->o, nearestwp->v_origin));
               AddScreenText(szWPInfo);

               strcpy(szWPInfo, "Flags: ");
               if (nearestwp->iFlags & W_FL_TELEPORT)
                    strcat(szWPInfo, "Teleport ");
               if (nearestwp->iFlags & W_FL_TELEPORTDEST)
                    strcat(szWPInfo, "Teleport destination ");
               if (nearestwp->iFlags & W_FL_JUMP)
                    strcat(szWPInfo, "Jump ");
               if (nearestwp->iFlags & W_FL_TRIGGER)
               {
                    char sz[32];
                    sprintf(sz, "Trigger(nr %d) ", nearestwp->sTriggerNr);
                    strcat(szWPInfo, sz);
               }
               if (nearestwp->iFlags & W_FL_INTAG)
                    strcat(szWPInfo, "In tagged cube(s) ");
               if (strlen(szWPInfo) == 7)
                    strcat(szWPInfo, "None");

               AddScreenText(szWPInfo);

               if (nearestwp->sYaw != -1)
                    AddScreenText("yaw %d", nearestwp->sYaw);

               sprintf(szWPInfo, "Waypoint has %d connections",
                       nearestwp->ConnectedWPs.NodeCount());
               AddScreenText(szWPInfo);
               AddScreenText("Base Cost: %d", nearestwp->sCost);
          }
     }

     if (m_bDrawWaypoints)  // display the waypoints if turned on...
     {
          DrawNearWaypoints();
     }
}

void CWaypointClass::DrawNearWaypoints()
{
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glDisable(GL_CULL_FACE);

     TLinkedList<node_s *>::node_s *pNode;
     node_s *pNearest = NULL;
     short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceilf(15.0f / MAX_MAP_GRIDS);
     float flNearestDist = 9999.99f, flDist;

     GetNodeIndexes(player1->o, &i, &j);
     MinI = i - Offset;
     MaxI = i + Offset;
     MinJ = j - Offset;
     MaxJ = j + Offset;

     if (MinI < 0)
          MinI = 0;
     if (MaxI > MAX_MAP_GRIDS - 1)
          MaxI = MAX_MAP_GRIDS - 1;
     if (MinJ < 0)
          MinJ = 0;
     if (MaxJ > MAX_MAP_GRIDS - 1)
          MaxJ = MAX_MAP_GRIDS - 1;

     node_s *nearestwp = WaypointClass.GetNearestWaypoint(curselection, 20.0f);

#ifdef WP_FLOOD
     if (!nearestwp)
          nearestwp = GetNearestFloodWP(player1, 20.0f);
#endif

     for (int x=MinI;x<=MaxI;x++)
     {
          for(int y=MinJ;y<=MaxJ;y++)
          {
               pNode = m_Waypoints[x][y].GetFirst();

               while(pNode)
               {
                    flDist = GetDistance(worldpos, pNode->Entry->v_origin);
                    if (flDist <= 15)
                    {
                         vec o = pNode->Entry->v_origin;
                         vec e = o;
                         o.z -= 2;
                         e.z += 2;

                         if (pNode->Entry->iFlags & W_FL_JUMP)
                         {
                              // draw a red waypoint
                              linestyle(2.5f, 0xFF, 0x40, 0x40);
                         }
                         else if (nearestwp == pNode->Entry)
                         {
                              // draw a green waypoint
                              linestyle(2.5f, 0x40, 0xFF, 0x40);
                         }
                         else
                         {
                              // draw a blue waypoint
                              linestyle(2.5f, 0x40, 0x40, 0xFF);
                         }

                         line(int(o.x), int(o.y), int(o.z), int(e.x), int(e.y), int(e.z));

                         if (flNearestDist > flDist)
                         {
                              flNearestDist = flDist;
                              pNearest = pNode->Entry;
                         }
                    }

                    pNode = pNode->next;
               }
          }
     }

     if (pNearest)
     {
          pNode = pNearest->ConnectedWPs.GetFirst();

          linestyle(2.0f, 0xFF, 0xFF, 0xFF);

          while(pNode)
          {
               vec o = pNode->Entry->v_origin;
               vec e = pNearest->v_origin;

               line(int(o.x), int(o.y), int(o.z), int(e.x), int(e.y), int(e.z));

               pNode = pNode->next;
          }

          // Has this waypoint an target yaw?
          if (pNearest->sYaw != -1)
          {
               vec angles(0, pNearest->sYaw, 0);
               vec from = pNearest->v_origin, end = pNearest->v_origin;
               vec forward, right, up;

               from.z = end.z = (pNearest->v_origin.z-1.0f);

               AnglesToVectors(angles, forward, right, up);
               forward.mul(5.0f);
               end.add(forward);

               linestyle(2.0f, 0xFF, 0x40, 0x40);
               line(int(from.x), int(from.y), int(from.z), int(end.x), int(end.y), int(end.z));
          }
     }

#ifndef RELEASE_BUILD
     // check if path waypointing is on...
     if (m_bDrawWPPaths)
     {
          // Draw path from first bot
          if (bots.length() && bots[0] && bots[0]->pBot && bots[0]->pBot->m_pCurrentWaypoint &&
               bots[0]->pBot->m_pCurrentGoalWaypoint)
          {
               CBot *pB = bots[0]->pBot;
               if (!pB->m_bCalculatingAStarPath && !pB->m_AStarNodeList.Empty())
               {
                    TLinkedList<waypoint_s *>::node_s *pNode = pB->m_AStarNodeList.GetFirst(), *pNext;

                    linestyle(2.5f, 0xFF, 0x40, 0x40);

                    line((int)pB->m_pCurrentWaypoint->pNode->v_origin.x,
                           (int)pB->m_pCurrentWaypoint->pNode->v_origin.y,
                           (int)pB->m_pCurrentWaypoint->pNode->v_origin.z,
                           (int)pNode->Entry->pNode->v_origin.x,
                           (int)pNode->Entry->pNode->v_origin.y,
                           (int)pNode->Entry->pNode->v_origin.z);

                    while(pNode && pNode->next)
                    {
                         pNext = pNode->next;
                         vec &v1 = pNode->Entry->pNode->v_origin;
                         vec &v2 = pNext->Entry->pNode->v_origin;

                         line(int(v1.x), int(v1.y), int(v1.z), int(v2.x), int(v2.y), int(v2.z));

                         pNode = pNode->next;
                    }
               }
          }
     }
#endif

    glEnable(GL_CULL_FACE);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

     if (intermission) return;

     /*for(int i=0;i<MAX_STORED_LOCATIONS;i++)
     {
          if (player1->PrevLocations.prevloc[i]==g_vecZero) continue;
          vec v1 = player1->PrevLocations.prevloc[i];
          v1.z -= 1.0f;
          vec v2 = v1;
          v2.z += 2.0f;
          linestyle(2.5f, 0xFF, 0x40, 0x40);
          line(int(v1.x), int(v1.y), int(v1.z), int(v2.x), int(v2.y), int(v2.z));
     }*/
}

// Add waypoint at location o, returns pointer of created wp
node_s *CWaypointClass::AddWaypoint(vec o, bool connectwp)
{
     short x, y;
     int flags = 0;
     if (S((int)o.x, (int)o.y)->tag) flags |= W_FL_INTAG;

     node_s *pNode = new node_s(o, flags, 0);
     m_vLastCreatedWP = o;

     GetNodeIndexes(o, &x, &y);

     m_Waypoints[x][y].AddNode(pNode);
     BotManager.AddWaypoint(pNode);

     m_iWaypointCount++;

     if (connectwp && m_bAutoPlacePaths)
     {
          // Connect new waypoint with other near waypoints.

          loopi(MAX_MAP_GRIDS)
          {
               loopj(MAX_MAP_GRIDS)
               {
                    TLinkedList<node_s *>::node_s *p = m_Waypoints[i][j].GetFirst();

                    while(p)
                    {
                         if (p->Entry == pNode)
                         {
                              p = p->next;
                              continue;  // skip the waypoint that was just added
                         }

                         // check if the waypoint is reachable from the new one (one-way)
                         if (WPIsReachable(o, p->Entry->v_origin))
                              AddPath(pNode, p->Entry); // Add a path from a to b
                         if (WPIsReachable(p->Entry->v_origin, pNode->v_origin))
                              AddPath(p->Entry, pNode); // Add a path from b to a

                         p = p->next;
                    }
               }
          }
     }

     return pNode;
}

void CWaypointClass::DeleteWaypoint(vec v_src)
{
     node_s *pWP;

     pWP = GetNearestWaypoint(v_src, 7.0f);

     if (!pWP)
     {
          conoutf("Error: Couldn't find near waypoint");
          return;
     }

     BotManager.DelWaypoint(pWP);

     // delete any paths that lead to this index...
     DeletePath(pWP);

     short x, y;
     GetNodeIndexes(pWP->v_origin, &x, &y);

     m_Waypoints[x][y].DeleteEntry(pWP);

     pWP->ConnectedWPs.DeleteAllNodes();
     pWP->ConnectedWPsWithMe.DeleteAllNodes();

     m_iWaypointCount--;
     delete pWP;
}

void CWaypointClass::AddPath(node_s *pWP1, node_s *pWP2)
{
     pWP1->ConnectedWPs.AddNode(pWP2);
     pWP2->ConnectedWPsWithMe.AddNode(pWP1);
}

// Deletes all paths connected to the given waypoint
void CWaypointClass::DeletePath(node_s *pWP)
{
     TLinkedList<node_s *>::node_s *p;

     loopi(MAX_MAP_GRIDS)
     {
          loopj(MAX_MAP_GRIDS)
          {
               p = m_Waypoints[i][j].GetFirst();
               while (p)
               {
                    p->Entry->ConnectedWPs.DeleteEntry(pWP);
                    pWP->ConnectedWPsWithMe.DeleteEntry(p->Entry);
                    p = p->next;
               }
          }
     }
}

// Deletes path between 2 waypoints(1 way)
void CWaypointClass::DeletePath(node_s *pWP1, node_s *pWP2)
{
     pWP1->ConnectedWPs.DeleteEntry(pWP2);
     pWP2->ConnectedWPsWithMe.DeleteEntry(pWP1);
}

void CWaypointClass::ManuallyCreatePath(vec v_src, int iCmd, bool TwoWay)
{
     static node_s *waypoint1 = NULL;  // initialized to unassigned
     static node_s *waypoint2 = NULL;  // initialized to unassigned

     if (iCmd == 1)  // assign source of path
     {
          waypoint1 = GetNearestWaypoint(v_src, 7.0f);

          if (!waypoint1)
          {
               conoutf("Error: Couldn't find near waypoint");
               return;
          }

          return;
     }

     if (iCmd == 2)  // assign dest of path and make path
     {
          if (!waypoint1)
          {
               conoutf("Error: First waypoint unset");
               return;
          }

          waypoint2 = GetNearestWaypoint(v_src, 7.0f);

          if (!waypoint2)
          {
               conoutf("Error: Couldn't find near waypoint");
               return;
          }

          AddPath(waypoint1, waypoint2);

          if (TwoWay)
               AddPath(waypoint2, waypoint1);
     }
}

void CWaypointClass::ManuallyDeletePath(vec v_src, int iCmd, bool TwoWay)
{
     static node_s *waypoint1 = NULL;  // initialized to unassigned
     static node_s *waypoint2 = NULL;  // initialized to unassigned

     if (iCmd == 1)  // assign source of path
     {
          waypoint1 = GetNearestWaypoint(v_src, 7.0f);

          if (!waypoint1)
          {
               conoutf("Error: Couldn't find near waypoint");
               return;
          }

          return;
     }

     if (iCmd == 2)  // assign dest of path and delete path
     {
          if (!waypoint1)
          {
               conoutf("Error: First waypoint unset");
               return;
          }

          waypoint2 = GetNearestWaypoint(v_src, 7.0f);

          if (!waypoint2)
          {
               conoutf("Error: Couldn't find near waypoint");
               return;
          }

          DeletePath(waypoint1, waypoint2);

          if (TwoWay)
               DeletePath(waypoint2, waypoint1);
     }
}

bool CWaypointClass::WPIsReachable(vec from, vec to)
{
     traceresult_s tr;
     float curr_height, last_height;

     float distance = GetDistance(from, to);

     // is the destination close enough?
     if (distance < REACHABLE_RANGE)
     {
          if (IsVisible(from, to))
          {
               if (UnderWater(from) && UnderWater(to))
               {
                    // No need to worry about heights in water
                    return true;
               }
/*
               if (to.z > (from.z + JUMP_HEIGHT))
               {
                    vec v_new_src = to;
                    vec v_new_dest = to;

                    v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f);

                    // check if we didn't hit anything, if so then it's in mid-air
                    if (::IsVisible(v_new_src, v_new_dest, NULL))
                    {
                         conoutf("to is in midair");
                         debugbeam(from, to);
                         return false;  // can't reach this one
                    }
               }
*/

               // check if distance to ground increases more than jump height
               // at points between from and to...

               vec v_temp = to;
               v_temp.sub(from);
               vec v_direction = Normalize(v_temp);  // 1 unit long
               vec v_check = from;
               vec v_down = from;

               v_down.z = v_down.z - 100.0f;  // straight down

               TraceLine(v_check, v_down, NULL, false, &tr);

                 // height from ground
               last_height = GetDistance(v_check, tr.end);

               distance = GetDistance(to, v_check);  // distance from goal

               while (distance > 2.0f)
               {
                    // move 2 units closer to the goal
                    v_temp = v_direction;
                    v_temp.mul(2.0f);
                    v_check.add(v_temp);

                    v_down = v_check;
                    v_down.z = v_down.z - 100.0f;

                    TraceLine(v_check, v_down, NULL, false, &tr);

                    curr_height = GetDistance(v_check, tr.end);

                    // is the difference in the last height and the current height
                    // higher that the jump height?
                    if ((last_height - curr_height) >= JUMP_HEIGHT)
                    {
                         // can't get there from here...
                         //conoutf("traces failed to to");
                         return false;
                    }

                    last_height = curr_height;

                    distance = GetDistance(to, v_check);  // distance from goal
               }

               return true;
          }
     }

     return false;
}

node_s *CWaypointClass::GetNearestWaypoint(vec v_src, float flRange)
{
     TLinkedList<node_s *>::node_s *pNode;
     node_s *pNearest = NULL;
     short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS);
     float flNearestDist = 9999.99f, flDist;

     GetNodeIndexes(v_src, &i, &j);
     MinI = i - Offset;
     MaxI = i + Offset;
     MinJ = j - Offset;
     MaxJ = j + Offset;

     if (MinI < 0)
          MinI = 0;
     if (MaxI > MAX_MAP_GRIDS - 1)
          MaxI = MAX_MAP_GRIDS - 1;
     if (MinJ < 0)
          MinJ = 0;
     if (MaxJ > MAX_MAP_GRIDS - 1)
          MaxJ = MAX_MAP_GRIDS - 1;

     for (int x=MinI;x<=MaxI;x++)
     {
          for(int y=MinJ;y<=MaxJ;y++)
          {
               pNode = m_Waypoints[x][y].GetFirst();

               while(pNode)
               {
                    flDist = GetDistance(v_src, pNode->Entry->v_origin);
                    if ((flDist < flNearestDist) && (flDist <= flRange))
                    {
                         if (IsVisible(v_src, pNode->Entry->v_origin, NULL))
                         {
                              pNearest = pNode->Entry;
                              flNearestDist = flDist;
                         }
                    }

                    pNode = pNode->next;
               }
          }
     }
     return pNearest;
}

node_s *CWaypointClass::GetNearestTriggerWaypoint(vec v_src, float flRange)
{
     TLinkedList<node_s *>::node_s *pNode;
     node_s *pNearest = NULL;
     short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS);
     float flNearestDist = 9999.99f, flDist;

     GetNodeIndexes(v_src, &i, &j);
     MinI = i - Offset;
     MaxI = i + Offset;
     MinJ = j - Offset;
     MaxJ = j + Offset;

     if (MinI < 0)
          MinI = 0;
     if (MaxI > MAX_MAP_GRIDS - 1)
          MaxI = MAX_MAP_GRIDS - 1;
     if (MinJ < 0)
          MinJ = 0;
     if (MaxJ > MAX_MAP_GRIDS - 1)
          MaxJ = MAX_MAP_GRIDS - 1;

     for (int x=MinI;x<=MaxI;x++)
     {
          for(int y=MinJ;y<=MaxJ;y++)
          {
               pNode = m_Waypoints[x][y].GetFirst();

               while(pNode)
               {
                    if ((pNode->Entry->iFlags & W_FL_FLOOD) || !(pNode->Entry->iFlags & W_FL_TRIGGER))
                    {
                         pNode = pNode->next;
                         continue;
                    }

                    flDist = GetDistance(v_src, pNode->Entry->v_origin);
                    if ((flDist < flNearestDist) && (flDist <= flRange))
                    {
                         if (IsVisible(v_src, pNode->Entry->v_origin, NULL))
                         {
                              pNearest = pNode->Entry;
                              flNearestDist = flDist;
                         }
                    }

                    pNode = pNode->next;
               }
          }
     }
     return pNearest;
}

node_s *CWaypointClass::GetWaypointFromVec(const vec &v_src)
{
     static TLinkedList<node_s *>::node_s *pNode;
     static short x, y;

     GetNodeIndexes(v_src, &x, &y);

     pNode = m_Waypoints[x][y].GetFirst();

     while(pNode)
     {
          if (pNode->Entry->v_origin==v_src)
               return pNode->Entry;

          pNode = pNode->next;
     }
     return NULL;
}

void CWaypointClass::CalcCost(node_s *pNode)
{
     float flCost = 10.0f;

     // Check nearby cubes...
     int x = int(pNode->v_origin.x);
     int y = int(pNode->v_origin.y);
     int a, b, row;

     for (row=0;row<=1;row++)
     {
          if (row==0) b = y - 6;
          else b = y + 6;

          for (a=(x-6);a<=(x+6);a++)
          {
               if (OUTBORD(a, b)) continue;

               vec to(a, b, GetCubeFloor(a, b) + 1.0f);

               // See if there is a obstacle(cube or mapmodel) nearby
               traceresult_s tr;
               TraceLine(pNode->v_origin, to, NULL, false, &tr);
               if (tr.collided)
               {
                    float flFraction = (GetDistance(pNode->v_origin, tr.end) /
                                        GetDistance(pNode->v_origin, to));
                    flCost += (1.0f-flFraction)*0.5f;
               }

               if ((abs(a) > 4) || (abs(b) > 4)) continue;

               vec from = to;
               to.z -= (JUMP_HEIGHT - 1.0f);
               TraceLine(from, to, NULL, false, &tr);
               if (!tr.collided)
                    flCost += 0.5f;
          }

          if (row==0) a = x - 6;
          else a = x + 6;

          for (b=(y-6);b<=(y+6);b++)
          {
               if (OUTBORD(a, b)) continue;

               vec to(a, b, GetCubeFloor(a, b) + 1.0f);

               // See if there is a obstacle(cube or mapmodel) nearby
               traceresult_s tr;
               TraceLine(pNode->v_origin, to, NULL, false, &tr);
               if (tr.collided)
               {
                    float flFraction = (GetDistance(pNode->v_origin, tr.end) /
                                        GetDistance(pNode->v_origin, to));
                    flCost += (1.0f-flFraction)*0.5f;
               }

               if ((abs(a) > 4) || (abs(b) > 4)) continue;

               vec from = to;
               to.z -= (JUMP_HEIGHT - 1.0f);
               TraceLine(from, to, NULL, false, &tr);
               if (!tr.collided)
                    flCost += 0.5f;
          }
     }

     if (UnderWater(pNode->v_origin)) // Water is annoying
          flCost += 5.0f;

     pNode->sCost = (short)flCost;
}

void CWaypointClass::ReCalcCosts(void)
{
     loopi(MAX_MAP_GRIDS)
     {
          loopj(MAX_MAP_GRIDS)
          {
               TLinkedList<node_s *>::node_s *p = m_Waypoints[i][j].GetFirst();
               while(p)
               {
                    CalcCost(p->Entry);
                    p = p->next;
               }
          }
     }
}

#ifdef WP_FLOOD

void CWaypointClass::StartFlood()
{
     if (m_bFlooding) return;

     conoutf("Starting flood, this may take a while on large maps....");
     m_bFlooding = true;
     m_iFloodStartTime = SDL_GetTicks();
     m_iCurFloodX = m_iCurFloodY = MINBORD;
     m_iFloodSize = 0;
}

void CWaypointClass::FloodThink()
{
     if (!m_bFlooding) return;

     static int x, y, count;
     count = 0;

     if (!m_bFilteringNodes)
     {
          // loop through ALL cubes and check if we should add a waypoint here
          for (x=m_iCurFloodX; x<(ssize-MINBORD); x+=4)
          {
               if (count >= 3)
               {
                    AddScreenText("Flooding map with waypoints... %.2f %%",
                                  ((float)x / float(ssize-MINBORD)) * 100.0f);
                    m_iCurFloodX = x;
                    return;
               }

               count++;

               for (y=MINBORD; y<(ssize-MINBORD); y+=4)
               {
                    vec from(x, y, GetCubeFloor(x, y)+2.0f);
                    bool bFound = CanPlaceNodeHere(from);

                    if (!bFound)
                    {
                         for (int a=x-2;a<=(x+2);a++)
                         {
                              for (int b=y-2;b<=(y+2);b++)
                              {
                                   if (OUTBORD(a, b)) continue;
                                   if ((a==x) && (b==y)) continue;
                                   makevec(&from, a, b, GetCubeFloor(a, b) + 2.0f);
                                   if (CanPlaceNodeHere(from))
                                   {
                                        bFound = true;
                                        break;
                                   }
                              }

                              if (bFound) break;
                         }
                    }

                    if (!bFound) continue;

                    // Add WP
                    int flags = W_FL_FLOOD;
                    if (S((int)from.x, (int)from.y)->tag) flags |= W_FL_INTAG;

                    node_s *pWP = new node_s(from, flags, 0);

                    short i, j;
                    GetNodeIndexes(from, &i, &j);
                    m_Waypoints[i][j].PushNode(pWP);
                    BotManager.AddWaypoint(pWP);
                    m_iFloodSize += sizeof(node_s);
                    m_iWaypointCount++;

                    // Connect with other nearby nodes
                    ConnectFloodWP(pWP);
               }
          }
     }

     if (!m_bFilteringNodes)
     {
          m_bFilteringNodes = true;
          m_iCurFloodX = m_iCurFloodY = 0;
          m_iFilteredNodes = 0;
     }

     count = 0;

     // Filter all nodes which aren't connected to any other nodes
     for (x=m_iCurFloodX;x<MAX_MAP_GRIDS;x++)
     {
          if (count > 3)
          {
               AddScreenText("Filtering useless waypoints and");
               AddScreenText("adding costs... %.2f %%", ((float)x / float(MAX_MAP_GRIDS)) * 100.0f);
               m_iCurFloodX = x;
               return;
          }

          count++;

          for (y=0;y<MAX_MAP_GRIDS;y++)
          {
               TLinkedList<node_s *>::node_s *pNode = m_Waypoints[x][y].GetFirst(), *pTemp;
               while(pNode)
               {
                    if (pNode->Entry->ConnectedWPs.Empty() ||
                         pNode->Entry->ConnectedWPsWithMe.Empty())
                    {
                         BotManager.DelWaypoint(pNode->Entry);
                         pTemp = pNode;
                         pNode = pNode->next;
                         delete pTemp->Entry;
                         m_Waypoints[x][y].DeleteNode(pTemp);
                         m_iFilteredNodes++;
                         m_iFloodSize -= sizeof(node_s);
                         m_iWaypointCount--;
                         continue;
                    }
                    else
                         CalcCost(pNode->Entry);
                    pNode = pNode->next;
               }
          }
     }

     // Done with flooding
     m_bFlooding = false;
     m_bFilteringNodes = false;

     //ReCalcCosts();
     BotManager.PickNextTrigger();

     m_iFloodSize += sizeof(m_Waypoints);
     conoutf("Added %d wps in %d milliseconds", m_iWaypointCount, SDL_GetTicks()-m_iFloodStartTime);
     conoutf("Filtered %d wps", m_iFilteredNodes);

     BotManager.CalculateMaxAStarCount();

     conoutf("Total size: %.2f Kb", float(m_iFloodSize)/1024.0f);
}

bool CWaypointClass::CanPlaceNodeHere(const vec &from)
{
     static short x, y, a, b;
     static traceresult_s tr;
     static vec to, v1, v2;

     x = short(from.x);
     y = short(from.y);

     sqr *s = S(x, y);
     if (SOLID(s))
     {
          return false;
     }

     if (fabs((float)(s->ceil - s->floor)) < player1->radius)
     {
          return false;
     }

     if (GetNearestFloodWP(from, 2.0f, NULL)) return false;

     for (a=(x-1);a<=(x+1);a++)
     {
          for (b=(y-1);b<=(y+1);b++)
          {
               if ((x==a) && (y==b)) continue;
               if (OUTBORD(a, b)) return false;
               makevec(&v1, a, b, from.z);
               v2 = v1;
               v2.z -= 1000.0f;

               TraceLine(v1, v2, NULL, false, &tr, true);
               to = tr.end;

               if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1)))
               {
                    if (!tr.collided || (to.z < (from.z-4.0f)))
                    {
                         return false;
                    }
               }

               to.z += 2.0f;

               if (from.z < (to.z-JUMP_HEIGHT))
               {
                    return false;
               }

               TraceLine(from, to, NULL, false, &tr, true);
               if (tr.collided)
                    return false;
          }
     }

     return true;
}

void CWaypointClass::ConnectFloodWP(node_s *pWP)
{
     if (!pWP) return;

     static float flRange;
     static TLinkedList<node_s *>::node_s *pNode;
     static short i, j, MinI, MaxI, MinJ, MaxJ, x, y, Offset;
     static float flDist;
     static node_s *p;

     // Calculate range, based on distance to nearest node
     p = GetNearestFloodWP(pWP->v_origin, 15.0f, pWP, true);
     if (p)
     {
          flDist = GetDistance(pWP->v_origin, p->v_origin);
          flRange = min(flDist+2.0f, 15.0f);
          if (flRange < 5.0f) flRange = 5.0f;
     }
     else
          return;

     Offset = (short)ceil(flRange / MAX_MAP_GRIDS);

     GetNodeIndexes(pWP->v_origin, &i, &j);
     MinI = i - Offset;
     MaxI = i + Offset;
     MinJ = j - Offset;
     MaxJ = j + Offset;

     if (MinI < 0)
          MinI = 0;
     if (MaxI > MAX_MAP_GRIDS - 1)
          MaxI = MAX_MAP_GRIDS - 1;
     if (MinJ < 0)
          MinJ = 0;
     if (MaxJ > MAX_MAP_GRIDS - 1)
          MaxJ = MAX_MAP_GRIDS - 1;

     for (x=MinI;x<=MaxI;x++)
     {
          for(y=MinJ;y<=MaxJ;y++)
          {
               pNode = m_Waypoints[x][y].GetFirst();

               while(pNode)
               {
                    if (pNode->Entry == pWP)
                    {
                         pNode = pNode->next;
                         continue;
                    }

                    flDist = GetDistance(pWP->v_origin, pNode->Entry->v_origin);
                    if (flDist <= flRange)
                    {
                         if (IsVisible(pWP->v_origin, pNode->Entry->v_origin, NULL, true))
                         {
                              // Connect a with b
                              pWP->ConnectedWPs.AddNode(pNode->Entry);
                              pNode->Entry->ConnectedWPsWithMe.AddNode(pWP);

                              // Connect b with a
                              pNode->Entry->ConnectedWPs.AddNode(pWP);
                              pWP->ConnectedWPsWithMe.AddNode(pNode->Entry);

                              m_iFloodSize += (2 * sizeof(node_s *));
                         }
                    }

                    pNode = pNode->next;
               }
          }
     }
}

node_s *CWaypointClass::GetNearestFloodWP(vec v_origin, float flRange, node_s *pIgnore,
                                          bool SkipTags)
{
     TLinkedList<node_s *>::node_s *p;
     node_s *pNearest = NULL;
     short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS);
     float flNearestDist = 9999.99f, flDist;

     GetNodeIndexes(v_origin, &i, &j);
     MinI = i - Offset;
     MaxI = i + Offset;
     MinJ = j - Offset;
     MaxJ = j + Offset;

     if (MinI < 0)
          MinI = 0;
     if (MaxI > MAX_MAP_GRIDS - 1)
          MaxI = MAX_MAP_GRIDS - 1;
     if (MinJ < 0)
          MinJ = 0;
     if (MaxJ > MAX_MAP_GRIDS - 1)
          MaxJ = MAX_MAP_GRIDS - 1;

     for (int x=MinI;x<=MaxI;x++)
     {
          for(int y=MinJ;y<=MaxJ;y++)
          {
               p = m_Waypoints[x][y].GetFirst();

               while(p)
               {
                    if ((p->Entry == pIgnore) || (!(p->Entry->iFlags & W_FL_FLOOD)))
                    {
                         p = p->next;
                         continue;
                    }

                    flDist = GetDistance(v_origin, p->Entry->v_origin);
                    if ((flDist < flNearestDist) && (flDist <= flRange))
                    {
                         if (IsVisible(v_origin, p->Entry->v_origin, NULL, SkipTags))
                         {
                              pNearest = p->Entry;
                              flNearestDist = flDist;
                         }
                    }

                    p = p->next;
               }
          }
     }
     return pNearest;
}

node_s *CWaypointClass::GetNearestTriggerFloodWP(vec v_origin, float flRange)
{
     TLinkedList<node_s *>::node_s *p;
     node_s *pNearest = NULL;
     short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS);
     float flNearestDist = 9999.99f, flDist;

     GetNodeIndexes(v_origin, &i, &j);
     MinI = i - Offset;
     MaxI = i + Offset;
     MinJ = j - Offset;
     MaxJ = j + Offset;

     if (MinI < 0)
          MinI = 0;
     if (MaxI > MAX_MAP_GRIDS - 1)
          MaxI = MAX_MAP_GRIDS - 1;
     if (MinJ < 0)
          MinJ = 0;
     if (MaxJ > MAX_MAP_GRIDS - 1)
          MaxJ = MAX_MAP_GRIDS - 1;

     for (int x=MinI;x<=MaxI;x++)
     {
          for(int y=MinJ;y<=MaxJ;y++)
          {
               p = m_Waypoints[x][y].GetFirst();

               while(p)
               {
                    if (!(p->Entry->iFlags & (W_FL_FLOOD | W_FL_TRIGGER)))
                    {
                         p = p->next;
                         continue;
                    }

                    flDist = GetDistance(v_origin, p->Entry->v_origin);
                    if ((flDist < flNearestDist) && (flDist <= flRange))
                    {
                         if (IsVisible(v_origin, p->Entry->v_origin, NULL))
                         {
                              pNearest = p->Entry;
                              flNearestDist = flDist;
                         }
                    }

                    p = p->next;
               }
          }
     }
     return pNearest;
}

void CWaypointClass::GetNodeIndexes(const vec &v_origin, short *i, short *j)
{
     // Function code by cheesy and PMB
     //*i = abs((int)((int)(v_origin.x + (2*ssize)) / SECTOR_SIZE));
     //*j = abs((int)((int)(v_origin.y + (2*ssize)) / SECTOR_SIZE));
     //*i = (int)((v_origin.x) / ssize * MAX_MAP_GRIDS);
     //*j = (int)((v_origin.y) / ssize * MAX_MAP_GRIDS);
     *i = abs((int)((v_origin.x) / MAX_MAP_GRIDS));
     *j = abs((int)((v_origin.y) / MAX_MAP_GRIDS));

     if (*i > MAX_MAP_GRIDS - 1)
          *i = MAX_MAP_GRIDS - 1;
     if (*j > MAX_MAP_GRIDS - 1)
          *j = MAX_MAP_GRIDS - 1;
}

#endif // WP_FLOOD
// Waypoint class end

#if defined AC_CUBE

// AC waypoint class begin
void CACWaypointClass::StartFlood()
{
     // UNDONE?
     CWaypointClass::StartFlood();
}

// AC waypoint class end

#elif defined VANILLA_CUBE

// Cube waypoint class begin

void CCubeWaypointClass::StartFlood()
{
     CWaypointClass::StartFlood();

     // Add wps at triggers and teleporters and their destination
     loopv(ents)
     {
          entity &e = ents[i];

          if (OUTBORD(e.x, e.y)) continue;

          if (e.type == TELEPORT)
          {
               vec telepos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }, teledestpos = g_vecZero;

               // Find the teleport destination
               int n = -1, tag = e.attr1, beenhere = -1;
               for(;;)
               {
                    n = findentity(TELEDEST, n+1);
                    if(n==beenhere || n<0) { conoutf("no teleport destination for tag %d", tag); break; };
                    if(beenhere<0) beenhere = n;
                    if(ents[n].attr2==tag)
                    {
                         teledestpos.x = ents[n].x;
                         teledestpos.y = ents[n].y;
                         teledestpos.z = S(ents[n].x, ents[n].y)->floor+player1->eyeheight;
                         break;
                    }
               }

               if (vis(teledestpos, g_vecZero)) continue;

               int flags = (W_FL_FLOOD | W_FL_TELEPORT);
               if (S((int)telepos.x, (int)telepos.y)->tag) flags |= W_FL_INTAG;

               // Add waypoint at teleporter and teleport destination
               node_s *pWP = new node_s(telepos, flags, 0);

               short i, j;
               GetNodeIndexes(telepos, &i, &j);
               m_Waypoints[i][j].PushNode(pWP);
               BotManager.AddWaypoint(pWP);
               m_iFloodSize += sizeof(node_s);
               m_iWaypointCount++;

               flags = (W_FL_FLOOD | W_FL_TELEPORTDEST);
               if (S((int)teledestpos.x, (int)teledestpos.y)->tag) flags |= W_FL_INTAG;

               node_s *pWP2 = new node_s(teledestpos, flags, 0);

               GetNodeIndexes(teledestpos, &i, &j);
               m_Waypoints[i][j].PushNode(pWP2);
               BotManager.AddWaypoint(pWP2);
               m_iFloodSize += sizeof(node_s);
               m_iWaypointCount++;

               // Connect the teleporter waypoint with the teleport-destination waypoint(1 way)
               AddPath(pWP, pWP2);

               // Connect with other nearby nodes
               ConnectFloodWP(pWP);
          }
          else if (e.type == CARROT)
          {
               vec pos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight };

               int flags = (W_FL_FLOOD | W_FL_TRIGGER);
               if (S(e.x, e.y)->tag) flags |= W_FL_INTAG;

               node_s *pWP = new node_s(pos, flags, 0);

               short i, j;
               GetNodeIndexes(pos, &i, &j);
               m_Waypoints[i][j].PushNode(pWP);
               BotManager.AddWaypoint(pWP);
               m_iFloodSize += sizeof(node_s);
               m_iWaypointCount++;

               // Connect with other nearby nodes
               ConnectFloodWP(pWP);
          }
          else if (e.type == MAPMODEL)
          {
               mapmodelinfo &mmi = getmminfo(e.attr2);
               if(!&mmi || !mmi.h || !mmi.rad) continue;

               float floor = (float)(S(e.x, e.y)->floor+mmi.zoff+e.attr3)+mmi.h;

               float x1 = e.x - mmi.rad;
               float x2 = e.x + mmi.rad;
               float y1 = e.y - mmi.rad;
               float y2 = e.y + mmi.rad;

               // UNDONE?
               for (float x=(x1+1.0f);x<=(x2-1.0f);x++)
               {
                    for (float y=(y1+1.0f);y<=(y2-1.0f);y++)
                    {
                         vec from = { x, y, floor+2.0f };
                         if (GetNearestFloodWP(from, 2.0f, NULL)) continue;

                         // Add WP
                         int flags = W_FL_FLOOD;
                         if (S((int)x, (int)y)->tag) flags |= W_FL_INTAG;

                         node_s *pWP = new node_s(from, flags, 0);

                         short i, j;
                         GetNodeIndexes(from, &i, &j);
                         m_Waypoints[i][j].PushNode(pWP);
                         BotManager.AddWaypoint(pWP);
                         m_iFloodSize += sizeof(node_s);
                         m_iWaypointCount++;

                         // Connect with other nearby nodes
                         ConnectFloodWP(pWP);
                    }
               }
          }
     }
     CWaypointClass::StartFlood();
}

void CCubeWaypointClass::CreateWPsAtTeleporters()
{
     loopv(ents)
     {
          entity &e = ents[i];

          if (e.type != TELEPORT) continue;
          if (OUTBORD(e.x, e.y)) continue;

          vec telepos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }, teledestpos = g_vecZero;

          // Find the teleport destination
          int n = -1, tag = e.attr1, beenhere = -1;
          for(;;)
          {
               n = findentity(TELEDEST, n+1);
               if(n==beenhere || n<0) { conoutf("no teleport destination for tag %d", tag); continue; };
               if(beenhere<0) beenhere = n;
               if(ents[n].attr2==tag)
               {
                    teledestpos.x = ents[n].x;
                    teledestpos.y = ents[n].y;
                    teledestpos.z = S(ents[n].x, ents[n].y)->floor+player1->eyeheight;
                    break;
               }
          }

          if (vis(teledestpos, g_vecZero)) continue;

          // Add waypoint at teleporter and teleport destination
          node_s *telewp = AddWaypoint(telepos, false);
          node_s *teledestwp = AddWaypoint(teledestpos, false);

          if (telewp && teledestwp)
          {
               // Connect the teleporter waypoint with the teleport-destination waypoint(1 way)
               AddPath(telewp, teledestwp);

               // Flag waypoints
               telewp->iFlags = W_FL_TELEPORT;
               teledestwp->iFlags = W_FL_TELEPORTDEST;
          }
     }
}

void CCubeWaypointClass::CreateWPsAtTriggers()
{
     loopv(ents)
     {
          entity &e = ents[i];

          if (e.type != CARROT) continue;
          if (OUTBORD(e.x, e.y)) continue;

          vec pos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight };

          node_s *wp = AddWaypoint(pos, false);

          if (wp)
          {
               // Flag waypoints
               wp->iFlags = W_FL_TRIGGER;
          }
     }
}

#endif

// Cube waypoint class end

// Waypoint commands begin

void addwp(int *autoconnect)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.AddWaypoint(curselection, *autoconnect!=0);
}

COMMAND(addwp, "i");

void delwp(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.DeleteWaypoint(curselection);
}

COMMAND(delwp, "");

void wpvisible(int *on)
{
     WaypointClass.SetWaypointsVisible(*on!=0);
}

COMMAND(wpvisible, "i");

void wpsave(void)
{
     WaypointClass.SaveWaypoints();
}

COMMAND(wpsave, "");

void wpload(void)
{
     WaypointClass.LoadWaypoints();
}

COMMAND(wpload, "");

void wpclear(void)
{
    WaypointClass.Clear();
}

COMMAND(wpclear, "");

void autowp(int *on)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.SetAutoWaypoint(*on!=0);
}

COMMAND(autowp, "i");

void wpinfo(int *on)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.SetWPInfo(*on!=0);
}

COMMAND(wpinfo, "i");

void addpath1way1(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.ManuallyCreatePath(curselection, 1, false);
}

COMMAND(addpath1way1, "");

void addpath1way2(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.ManuallyCreatePath(curselection, 2, false);
}

COMMAND(addpath1way2, "");

void addpath2way1(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.ManuallyCreatePath(curselection, 1, true);
}

COMMAND(addpath2way1, "");

void addpath2way2(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.ManuallyCreatePath(curselection, 2, true);
}

COMMAND(addpath2way2, "");

void delpath1way1(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.ManuallyDeletePath(curselection, 1, false);
}

COMMAND(delpath1way1, "");

void delpath1way2(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.ManuallyDeletePath(curselection, 2, false);
}

COMMAND(delpath1way2, "");

void delpath2way1(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.ManuallyDeletePath(curselection, 1, true);
}

COMMAND(delpath2way1, "");

void delpath2way2(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.ManuallyDeletePath(curselection, 2, true);
}

COMMAND(delpath2way2, "");

void setjumpwp(void)
{
     node_s *wp = WaypointClass.GetNearestWaypoint(curselection, 20.0f);
     if (wp)
     {
          WaypointClass.SetWPFlags(wp, W_FL_JUMP);
     }
}

COMMAND(setjumpwp, "");

void unsetjumpwp(void)
{
     node_s *wp = WaypointClass.GetNearestWaypoint(curselection, 20.0f);
     if (wp)
     {
          WaypointClass.UnsetWPFlags(wp, W_FL_JUMP);
     }
}

COMMAND(unsetjumpwp, "");

void setwptriggernr(int *nr)
{
     node_s *wp = WaypointClass.GetNearestWaypoint(curselection, 20.0f);
     if (wp)
     {
          WaypointClass.SetWPTriggerNr(wp, *nr);
     }
}

COMMAND(setwptriggernr, "i");

void setwpyaw(void)
{
     node_s *wp = WaypointClass.GetNearestWaypoint(curselection, 20.0f);
     if (wp)
     {
          WaypointClass.SetWPYaw(wp, short(player1->yaw));
     }
}

COMMAND(setwpyaw, "");

#ifdef WP_FLOOD
void wpflood(void)
{
     WaypointClass.StartFlood();
}

COMMAND(wpflood, "");
#endif

#ifdef VANILLA_CUBE
// Commands specific for cube
void addtelewps(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.CreateWPsAtTeleporters();
}

COMMAND(addtelewps, "");

void addtriggerwps(void)
{
     WaypointClass.SetWaypointsVisible(true);
     WaypointClass.CreateWPsAtTriggers();
}

COMMAND(addtriggerwps, "");
#endif

// Debug functions
#ifdef WP_FLOOD

#ifndef RELEASE_BUILD
void botsheadtome(void)
{
     loopv(bots)
     {
          if (!bots[i] || !bots[i]->pBot) continue;
          bots[i]->pBot->HeadToGoal();
          bots[i]->pBot->GoToDebugGoal(player1->o);
     }
}

COMMAND(botsheadtome, "");

void setdebuggoal(void) { v_debuggoal = player1->o; };
COMMAND(setdebuggoal, "");

void botsheadtodebuggoal(void)
{
     loopv(bots)
     {
          if (!bots[i] || !bots[i]->pBot) continue;

          bots[i]->pBot->GoToDebugGoal(v_debuggoal);
     }
}

COMMAND(botsheadtodebuggoal, "");

#endif // RELEASE_BUILD

#endif // WP_FLOOD

// End debug functions
// Waypoint commands end


// Bot class begin

bool CBot::FindWaypoint()
{
     waypoint_s *wp, *wpselect;
     int index;
     float distance, min_distance[3];
     waypoint_s *min_wp[3];

     for (index=0; index < 3; index++)
     {
          min_distance[index] = 9999.0;
          min_wp[index] = NULL;
     }

     TLinkedList<node_s *>::node_s *pNode = m_pCurrentWaypoint->pNode->ConnectedWPs.GetFirst();

     while (pNode)
     {
          if ((pNode->Entry->iFlags & W_FL_INTAG) &&
            !OUTBORD((int)pNode->Entry->v_origin.x, (int)pNode->Entry->v_origin.y) &&
              SOLID(S((int)pNode->Entry->v_origin.x, (int)pNode->Entry->v_origin.y)))
          {
               pNode = pNode->next;
               continue;
          }

          wp = GetWPFromNode(pNode->Entry);
          if (!wp)
          {
               pNode = pNode->next;
               continue;
          }

          // if index is not a current or recent previous waypoint...
          if ((wp != m_pCurrentWaypoint) &&
              (wp != m_pPrevWaypoints[0]) &&
              (wp != m_pPrevWaypoints[1]) &&
              (wp != m_pPrevWaypoints[2]) &&
              (wp != m_pPrevWaypoints[3]) &&
              (wp != m_pPrevWaypoints[4]))
          {
               // find the distance from the bot to this waypoint
               distance = GetDistance(wp->pNode->v_origin);

               if (distance < min_distance[0])
               {
                    min_distance[2] = min_distance[1];
                    min_wp[2] = min_wp[1];

                    min_distance[1] = min_distance[0];
                    min_wp[1] = min_wp[0];

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

                    min_distance[1] = distance;
                    min_wp[1] = wp;
               }
               else if (distance < min_distance[2])
               {
                    min_distance[2] = distance;
                    min_wp[2] = wp;
               }
          }
          pNode = pNode->next;
     }

     wpselect = NULL;

     // about 20% of the time choose a waypoint at random
     // (don't do this any more often than every 10 seconds)

     if ((RandomLong(1, 100) <= 20) && (m_iRandomWaypointTime <= lastmillis))
     {
          m_iRandomWaypointTime = lastmillis + 10000;

          if (min_wp[2])
               index = RandomLong(0, 2);
          else if (min_wp[1])
               index = RandomLong(0, 1);
          else if (min_wp[0])
               index = 0;
          else
               return false;  // no waypoints found!

          wpselect = min_wp[index];
     }
     else
     {
          // use the closest waypoint that has been recently used
          wpselect = min_wp[0];
     }

     if (wpselect)  // was a waypoint found?
     {
          m_pPrevWaypoints[4] = m_pPrevWaypoints[3];
          m_pPrevWaypoints[3] = m_pPrevWaypoints[2];
          m_pPrevWaypoints[2] = m_pPrevWaypoints[1];
          m_pPrevWaypoints[1] = m_pPrevWaypoints[0];
          m_pPrevWaypoints[0] = m_pCurrentWaypoint;

          SetCurrentWaypoint(wpselect);
          return true;
     }

     return false;  // couldn't find a waypoint
}

bool CBot::HeadToWaypoint()
{
     if (!m_pCurrentWaypoint)
          return false; // Can't head to waypoint

     bool Touching = false;
     float WPDist = GetDistance(m_pCurrentWaypoint->pNode->v_origin);

#ifndef RELEASE_BUILD
     if (m_pCurrentGoalWaypoint && m_vGoal==g_vecZero)
          condebug("Warning: m_vGoal unset");
#endif

     // did the bot run past the waypoint? (prevent the loop-the-loop problem)
     if ((m_fPrevWaypointDistance > 1.0) && (WPDist > m_fPrevWaypointDistance) &&
         (WPDist <= 5.0f))
          Touching = true;
     // bot needs to be close for jump and trigger waypoints
     else if ((m_pCurrentWaypoint->pNode->iFlags & W_FL_JUMP) ||
                (m_pCurrentWaypoint->pNode->iFlags & W_FL_TRIGGER))
          Touching = (WPDist <= 1.5f);
     else if (m_pCurrentWaypoint->pNode->iFlags & W_FL_TELEPORT)
          Touching = (WPDist <= 4.0f);
     // are we close enough to a target waypoint...
     else if (WPDist <= 3.0f)
     {
          if (!m_pCurrentGoalWaypoint || (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) ||
               IsVisible(m_vGoal) || (WPDist <= 1.5f))
               Touching = true;
          // If the bot has a goal check if he can see his next wp
          if (m_pCurrentGoalWaypoint && (m_pCurrentGoalWaypoint != m_pCurrentWaypoint) &&
             !m_AStarNodeList.Empty() && (WPDist >= 1.0f) &&
             !IsVisible(m_AStarNodeList.GetFirst()->Entry->pNode->v_origin))
               Touching = false;
     }

     // save current distance as previous
     m_fPrevWaypointDistance = WPDist;

     // Reached the waypoint?
     if (Touching)
     {
          // Does this waypoint has a targetyaw?
          if (m_pCurrentWaypoint->pNode->sYaw != -1)
          {
               // UNDONE: Inhuman
               m_pMyEnt->yaw = m_pMyEnt->targetyaw = m_pCurrentWaypoint->pNode->sYaw;
          }

          // Reached a jump waypoint?
          if (m_pCurrentWaypoint->pNode->iFlags & W_FL_JUMP)
               m_pMyEnt->jumpnext = true;

          m_fPrevWaypointDistance = 0.0f;

          // Does the bot has a goal?
          if (m_pCurrentGoalWaypoint)
          {
               if (m_pCurrentWaypoint != m_pCurrentGoalWaypoint)
               {
                    if (m_AStarNodeList.Empty())
                    {
                         if (!AStar())
                         {
                              // Bot is calculating a new path, just stand still for now
                              ResetMoveSpeed();
                              m_iWaypointTime += 200;
                              return true;
                         }
                         else
                         {
                              if (m_AStarNodeList.Empty())
                              {
                                   //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time));
                                   return false; // Couldn't get a new wp to go to
                              }
                         }
                    }

                    m_pCurrentWaypoint = m_AStarNodeList.Pop();

                    if (!IsVisible(m_pCurrentWaypoint->pNode->v_origin))
                        //(!(m_pCurrentWaypoint->iFlags & W_FL_TELEPORT)))
                    {
                         //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time));
                         condebug("Next WP not visible");
                         return false;
                    }

                    SetCurrentWaypoint(m_pCurrentWaypoint);
               }
               else
               {
                    // Bot reached the goal waypoint but couldn't reach the goal itself
                    // (otherwise we wouldn't be in this function)
                    //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time));
                    return false;
               }
          }
          else
          {
               short index = 4;
               bool status;

               // try to find the next waypoint
               while (((status = FindWaypoint()) == false) && (index > 0))
               {
                    // if waypoint not found, clear oldest previous index and try again

                    m_pPrevWaypoints[index] = NULL;
                    index--;
               }

               if (status == false)
               {
                    ResetWaypointVars();
                    condebug("Couldn't find new random waypoint");
                    return false;
               }
          }
          m_iWaypointHeadPauseTime = lastmillis + 75;
          m_iWaypointTime += 75;
     }

     // keep turning towards the waypoint...
     /*if (m_pCurrentWaypoint->pNode->iFlags & W_FL_FLOOD)
     {UNDONE?
          vec aim = m_pCurrentWaypoint->pNode->v_origin;
          aim.x+=0.5f;
          aim.y+=0.5f;
          AimToVec(aim);
     }
     else*/
          AimToVec(m_pCurrentWaypoint->pNode->v_origin);

     if (m_fYawToTurn <= 25.0f)
          m_iWaypointHeadLastTurnLessTime = lastmillis;

     // Bot had to turn much for a while?
     if ((m_iWaypointHeadLastTurnLessTime > 0) &&
         (m_iWaypointHeadLastTurnLessTime < (lastmillis - 1000)))
     {
          m_iWaypointHeadPauseTime = lastmillis + 200;
          m_iWaypointTime += 200;
     }

     if (m_iWaypointHeadPauseTime >= lastmillis)
     {
          m_pMyEnt->move = 0;
          //conoutf("Pause in HeadToWaypoint()");
     }
     else
     {
          // Check if bot has to jump
          vec from = m_pMyEnt->o;
          from.z -= (m_pMyEnt->eyeheight - 1.25f);
          float flEndDist;
          if (!IsVisible(from, FORWARD, 3.0f, false, &flEndDist) &&
              (GetDistance(from, m_pCurrentWaypoint->pNode->v_origin) > flEndDist))
          {
               m_pMyEnt->jumpnext = true;
               condebug("Low wall in HeadToWaypoint()");
          }

          // Check if bot has to strafe
          if (m_iStrafeTime > lastmillis)
               SetMoveDir(m_iMoveDir, true);
          else
          {
               m_iStrafeTime = 0;
               m_iMoveDir = DIR_NONE;

               vec forward, up, right;
               AnglesToVectors(GetViewAngles(), forward, right, up);

               float flLeftDist = -1.0f, flRightDist = -1.0f;
               vec dir = right;
               bool bStrafe = false;
               int iStrafeDir = 0;

               dir.mul(m_pMyEnt->radius);

               // Check left
               from = m_pMyEnt->o;
               from.sub(dir);
               if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist))
                    flLeftDist = -1.0f;

               // Check right
               from = m_pMyEnt->o;
               from.add(dir);
               if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist))
                    flRightDist = -1.0f;

               if ((flLeftDist != -1.0f) && (flRightDist != -1.0f))
               {
                    if (flLeftDist < flRightDist)
                    {
                         // Strafe right
                         bStrafe = true;
                         iStrafeDir = RIGHT;
                    }
                    else if (flRightDist < flLeftDist)
                    {
                         // Strafe left
                         bStrafe = true;
                         iStrafeDir = LEFT;
                    }
                    else
                    {
                         // Randomly choose a strafe direction
                         bStrafe = true;
                         if (RandomLong(0, 1))
                              iStrafeDir = LEFT;
                         else
                              iStrafeDir = RIGHT;
                    }
               }
               else if (flLeftDist != -1.0f)
               {
                    // Strafe right
                    bStrafe = true;
                    iStrafeDir = RIGHT;
               }
               else if (flRightDist != -1.0f)
               {
                    // Strafe left
                    bStrafe = true;
                    iStrafeDir = LEFT;
               }

               if (bStrafe)
               {
                    SetMoveDir(iStrafeDir, true);
                    m_iMoveDir = iStrafeDir;
                    m_iStrafeTime = lastmillis + RandomLong(30, 50);
               }
          }
     }

     return true;
}

// returns true when done or failure
bool CBot::HeadToGoal()
{
     // Does the bot has a goal(waypoint)?
     if (m_pCurrentGoalWaypoint)
     {
          if (ReachedGoalWP())
          {
               return false;
          }
          else
          {
               if (CurrentWPIsValid())
               {
                    if (m_bCalculatingAStarPath)
                    {
                         // Bot is calculating a new path, just stand still for now
                         ResetMoveSpeed();
                         m_iWaypointTime += 200;

                         // Done calculating the path?
                         if (AStar())
                         {
                              if (m_AStarNodeList.Empty())
                              {
                                   return false; // Couldn't find a path
                              }
                              //else
                               //    SetCurrentWaypoint(m_AStarNodeList.Pop());
                         }
                         else
                              return true; // else just wait a little bit longer
                    }
                    //ResetMoveSpeed();
                    //return true;
               }
               else
               {
                    // Current waypoint isn't reachable, search new one
                    waypoint_s *pWP = NULL;

#ifdef WP_FLOOD
                    if (m_pCurrentGoalWaypoint->pNode->iFlags & W_FL_FLOOD)
                         pWP = GetNearestFloodWP(8.0f);
                    else
#endif
                         pWP = GetNearestWaypoint(15.0f);

                    if (!pWP || (pWP == m_pCurrentWaypoint))
                         return false;

                    SetCurrentWaypoint(pWP);
                    if (AStar())
                    {
                         if (m_AStarNodeList.Empty()) return false;
                    }
                    else
                    {
                         m_iWaypointTime += 200;
                         ResetMoveSpeed();
                         return true;
                    }
               }

               //debugbeam(m_pMyEnt->o, m_pCurrentWaypoint->pNode->v_origin);
          }
     }
     else
          return false;

     return HeadToWaypoint();
}

// to disable, comment out the above function and uncomment this line
//#define temp_containsLoop(n) false

// return true when done calculating
bool CBot::AStar()
{
     if (!m_pCurrentGoalWaypoint || !m_pCurrentWaypoint)
     {
          if (m_bCalculatingAStarPath)
          {
               m_bCalculatingAStarPath = false;
               BotManager.m_sUsingAStarBotsCount--;
          }
          return true;
     }

     // Ideas by PMB :
     // * Make locals static to speed up a bit
     // * MaxCycles per frame and make it fps dependent

     static int iMaxCycles;
     static int iCurrentCycles;
     static short newg;
     static waypoint_s *n, *n2;
     static TLinkedList<node_s *>::node_s *pPath = NULL;
     static bool bPathFailed;

     iMaxCycles = BotManager.m_iFrameTime / 10;
     if (iMaxCycles < 10) iMaxCycles = 10;
     //condebug("MaxCycles: %d", iMaxCycles);
     iCurrentCycles = 0;
     bPathFailed = false;

     if (!m_bCalculatingAStarPath)
     {
          if ((BotManager.m_sUsingAStarBotsCount+1) > BotManager.m_sMaxAStarBots)
          {
               return true;
          }

          BotManager.m_sUsingAStarBotsCount++;

          CleanAStarLists(false);

          m_pCurrentWaypoint->g[0] = m_pCurrentWaypoint->g[1] = 0;
          m_pCurrentWaypoint->pParent[0] = m_pCurrentWaypoint->pParent[1] = NULL;
          m_pCurrentGoalWaypoint->g[0] = m_pCurrentGoalWaypoint->g[1] = 0;
          m_pCurrentGoalWaypoint->pParent[0] = m_pCurrentGoalWaypoint->pParent[1] = NULL;

          m_AStarNodeList.DeleteAllNodes();

          m_AStarOpenList[0].Clear();
          m_AStarOpenList[1].Clear();
          m_AStarClosedList[0].DeleteAllNodes();
          m_AStarClosedList[1].DeleteAllNodes();

          m_AStarOpenList[0].AddEntry(m_pCurrentWaypoint,
                                      GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin));
          m_AStarOpenList[1].AddEntry(m_pCurrentGoalWaypoint,
                                      GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin));

          m_pCurrentWaypoint->bIsOpen[0] = m_pCurrentGoalWaypoint->bIsOpen[1] = true;
          m_pCurrentWaypoint->bIsOpen[1] = m_pCurrentGoalWaypoint->bIsOpen[0] = false;
          m_pCurrentWaypoint->bIsClosed[0] = m_pCurrentGoalWaypoint->bIsClosed[1] = false;
          m_pCurrentWaypoint->bIsClosed[1] = m_pCurrentGoalWaypoint->bIsClosed[0] = false;
     }

     while(!m_AStarOpenList[0].Empty())
     {
          if (iCurrentCycles >= (iMaxCycles/2))
          {
               m_bCalculatingAStarPath = true;
               break;
          }

          n = m_AStarOpenList[0].Pop();
          n->bIsOpen[0] = false;

          if (n->pNode->FailedGoalList.IsInList(m_pCurrentGoalWaypoint->pNode))
          {
               bPathFailed = true;
               break; // Can't make path to goal
          }

          // Done with calculating
          if (n == m_pCurrentGoalWaypoint)
          {
               m_bCalculatingAStarPath = false;
               BotManager.m_sUsingAStarBotsCount--;

               // ACR: FIXME linked lists should not have loops
               waypoint_s *fast = n;
               while(n)
               {
                    m_AStarNodeList.PushNode(n);
                    n = n->pParent[0];
                    // ACR: temporary cycle detection
                    // loops calling this function seem to be infinite loops
                    // because of cycles in the respective linked lists
                    #define ACR_anti_inf_loop_crash(v, i) \
                    if(fast) \
                    { \
                        fast = fast->pParent[i]; \
                        if(fast == v) \
                            break; \
                        if(fast) \
                        { \
                            fast = fast->pParent[i]; \
                            if(fast == v) \
                                break; \
                        } \
                    }
                    ACR_anti_inf_loop_crash(n, 0);
               }
               n = NULL; // REMOVEME

               CleanAStarLists(false);

               return true;
          }

          pPath = n->pNode->ConnectedWPs.GetFirst();
          while(pPath)
          {
               if ((pPath->Entry->iFlags & W_FL_INTAG) &&
                    SOLID(S((int)pPath->Entry->v_origin.x, (int)pPath->Entry->v_origin.y)))
               {
                    pPath = pPath->next;
                    continue;
               }

               n2 = GetWPFromNode(pPath->Entry);

               if (!n2)
               {
                    pPath = pPath->next;
                    continue;
               }

               if (n2->bIsClosed[1])
               {
                    m_bCalculatingAStarPath = false;
                    BotManager.m_sUsingAStarBotsCount--;

                    // ACR: FIXME linked lists should not have loops
                    waypoint_s *fast = n2;
                    while(n2)
                    {
                         m_AStarNodeList.AddNode(n2);
                         n2 = n2->pParent[1];
                         ACR_anti_inf_loop_crash(n2, 1);
                    }
                    n2 = NULL; // REMOVEME

                    // ACR: FIXME linked lists should not have loops
                    fast = n;
                    while(n)
                    {
                         m_AStarNodeList.PushNode(n);
                         n = n->pParent[0];
                         ACR_anti_inf_loop_crash(n, 0);
                    }
                    n = NULL; // REMOVEME

                    CleanAStarLists(false);

                    return true;
               }

               newg = n->g[0] + (short)AStarCost(n, n2);

               if ((n2->g[0] <= newg) && (n2->bIsOpen[0] || n2->bIsClosed[0]))
               {
                    pPath = pPath->next;
                    continue;
               }

               n2->pParent[0] = n;
               n2->g[0] = newg;
               n2->bIsClosed[0] = false;

               if (!n2->bIsOpen[0])
               {
                    m_AStarOpenList[0].AddEntry(n2, n2->g[1] + GetDistance(n2->pNode->v_origin,
                                                 m_pCurrentGoalWaypoint->pNode->v_origin));
                    n2->bIsOpen[0] = true;
               }
               pPath = pPath->next;
          }

          m_AStarClosedList[0].PushNode(n);
          n->bIsClosed[0] = true;
          iCurrentCycles++;
     }

     if (!bPathFailed)
     {
          while(!m_AStarOpenList[1].Empty())
          {
               if (iCurrentCycles >= iMaxCycles)
               {
                    m_bCalculatingAStarPath = true;
                    return false;
               }

               n = m_AStarOpenList[1].Pop();
               n->bIsOpen[1] = false;

               if (n->pNode->FailedGoalList.IsInList(m_pCurrentWaypoint->pNode))
               {
                    bPathFailed = true;
                    break; // Can't make path to goal
               }

               // Done with calculating
               if (n == m_pCurrentWaypoint)
               {
                    m_bCalculatingAStarPath = false;
                    BotManager.m_sUsingAStarBotsCount--;

                    // this one seems to be OK
                    // // ACR: FIXME linked lists should not have loops
                    // waypoint_s *fast = n;
                    while(n)
                    {
                         m_AStarNodeList.AddNode(n);
                         n = n->pParent[1];
                         // ACR_anti_inf_loop_crash(n, 1);
                    }
                    // n = NULL; // REMOVEME

                    CleanAStarLists(false);

                    return true;
               }

               pPath = n->pNode->ConnectedWPsWithMe.GetFirst();
               while(pPath)
               {
                    if ((pPath->Entry->iFlags & W_FL_INTAG) &&
                        SOLID(S((int)pPath->Entry->v_origin.x, (int)pPath->Entry->v_origin.y)))
                    {
                         pPath = pPath->next;
                         continue;
                    }

                    n2 = GetWPFromNode(pPath->Entry);

                    if (!n2)
                    {
                         pPath = pPath->next;
                         continue;
                    }

                    if (n2->bIsClosed[0])
                    {
                         m_bCalculatingAStarPath = false;
                         BotManager.m_sUsingAStarBotsCount--;

                         // ACR: FIXME linked lists should not have loops
                         waypoint_s *fast = n2;
                         while(n2)
                         {
                              m_AStarNodeList.PushNode(n2);
                              n2 = n2->pParent[0];
                              ACR_anti_inf_loop_crash(n2, 0);
                         }
                         n2 = NULL; // REMOVEME

                         // ACR: FIXME linked lists should not have loops
                         fast = n;
                         while(n)
                         {
                              m_AStarNodeList.AddNode(n);
                              n = n->pParent[1];
                              ACR_anti_inf_loop_crash(n, 1);
                         }
                         n = NULL; // REMOVEME

                         CleanAStarLists(false);

                         return true;
                    }

                    newg = n->g[1] + (short)AStarCost(n, n2);

                    if ((n2->g[1] <= newg) && (n2->bIsOpen[1] || n2->bIsClosed[1]))
                    {
                         pPath = pPath->next;
                         continue;
                    }

                    n2->pParent[1] = n;
                    n2->g[1] = newg;
                    n2->bIsClosed[1] = false;

                    if (!n2->bIsOpen[1])
                    {
                         m_AStarOpenList[1].AddEntry(n2, n2->g[1] + GetDistance(n2->pNode->v_origin,
                                                      m_pCurrentWaypoint->pNode->v_origin));
                         n2->bIsOpen[1] = true;
                    }
                    pPath = pPath->next;
               }

               m_AStarClosedList[1].PushNode(n);
               n->bIsClosed[1] = true;
               iCurrentCycles++;
          }
     }

     // Failed making path
     condebug("Path failed");

     CleanAStarLists(true);
     m_bCalculatingAStarPath = false;
     BotManager.m_sUsingAStarBotsCount--;
     return true;
}

float CBot::AStarCost(waypoint_s *pWP1, waypoint_s *pWP2)
{
     // UNDONE?
     return (GetDistance(pWP1->pNode->v_origin, pWP2->pNode->v_origin) * pWP2->pNode->sCost);
}

void CBot::CleanAStarLists(bool bPathFailed)
{
     while(m_AStarOpenList[0].Empty() == false)
     {
          waypoint_s *p = m_AStarOpenList[0].Pop();
          p->bIsOpen[0] = p->bIsOpen[1] = false;
          p->bIsClosed[0] = p->bIsClosed[1] = false;
          p->pParent[0] = p->pParent[1] = NULL;
          p->g[0] = p->g[1] = 0;
     }

     while(m_AStarOpenList[1].Empty() == false)
     {
          waypoint_s *p = m_AStarOpenList[1].Pop();
          p->bIsOpen[0] = p->bIsOpen[1] = false;
          p->bIsClosed[0] = p->bIsClosed[1] = false;
          p->pParent[0] = p->pParent[1] = NULL;
          p->g[0] = p->g[1] = 0;
     }

     while(m_AStarClosedList[0].Empty() == false)
     {
          waypoint_s *p = m_AStarClosedList[0].Pop();
          p->bIsOpen[0] = p->bIsOpen[1] = false;
          p->bIsClosed[0] = p->bIsClosed[1] = false;
          p->pParent[0] = p->pParent[1] = NULL;
          p->g[0] = p->g[1] = 0;
          if (bPathFailed)
               p->pNode->FailedGoalList.AddNode(m_pCurrentGoalWaypoint->pNode);
     }

     while(m_AStarClosedList[1].Empty() == false)
     {
          waypoint_s *p = m_AStarClosedList[1].Pop();
          p->bIsOpen[0] = p->bIsOpen[1] = false;
          p->bIsClosed[0] = p->bIsClosed[1] = false;
          p->pParent[0] = p->pParent[1] = NULL;
          p->g[0] = p->g[1] = 0;
          if (bPathFailed)
               p->pNode->FailedGoalList.AddNode(m_pCurrentWaypoint->pNode);
     }
}

void CBot::ResetWaypointVars()
{
     m_iWaypointTime = 0;
     m_pCurrentWaypoint = NULL;
     m_pCurrentGoalWaypoint = NULL;
     m_pPrevWaypoints[0] = NULL;
     m_pPrevWaypoints[1] = NULL;
     m_pPrevWaypoints[2] = NULL;
     m_pPrevWaypoints[3] = NULL;
     m_pPrevWaypoints[4] = NULL;
     m_fPrevWaypointDistance = 0;
     m_iWaypointHeadLastTurnLessTime = 0;
     m_iWaypointHeadPauseTime = 0;
     if (m_bCalculatingAStarPath)
     {
          m_bCalculatingAStarPath = false;
          BotManager.m_sUsingAStarBotsCount--;
     }
     m_AStarNodeList.DeleteAllNodes();
     CleanAStarLists(false);
     m_bGoToDebugGoal = false;
}

void CBot::SetCurrentWaypoint(node_s *pNode)
{
     waypoint_s *pWP = GetWPFromNode(pNode);
#ifndef RELEASE_BUILD
     if (!pWP || !pNode) condebug("NULL WP In SetCurrentWP");
#endif

     m_pCurrentWaypoint = pWP;
     m_iWaypointTime = lastmillis;
}

void CBot::SetCurrentWaypoint(waypoint_s *pWP)
{
#ifndef RELEASE_BUILD
     if (!pWP) condebug("NULL WP In SetCurrentWP(2)");
#endif

     m_pCurrentWaypoint = pWP;
     m_iWaypointTime = lastmillis;
}

void CBot::SetCurrentGoalWaypoint(node_s *pNode)
{
     waypoint_s *pWP = GetWPFromNode(pNode);
#ifndef RELEASE_BUILD
     if (!pWP || !pNode) condebug("NULL WP In SetCurrentGoalWP");
#endif

     m_pCurrentGoalWaypoint = pWP;
}

void CBot::SetCurrentGoalWaypoint(waypoint_s *pWP)
{
#ifndef RELEASE_BUILD
     if (!pWP) condebug("NULL WP In SetCurrentGoalWP(2)");
#endif

     m_pCurrentGoalWaypoint = pWP;
}

bool CBot::CurrentWPIsValid()
{
     if (!m_pCurrentWaypoint)
     {
          //condebug("Invalid WP: Is NULL");
          return false;
     }

     // check if the bot has been trying to get to this waypoint for a while...
     if ((m_iWaypointTime + 5000) < lastmillis)
     {
          condebug("Invalid WP: time over");
          return false;
     }

#ifndef RELEASE_BUILD
     if (!IsVisible(m_pCurrentWaypoint->pNode->v_origin))
          condebug("Invalid WP: Not visible");
#endif

     return (IsVisible(m_pCurrentWaypoint->pNode->v_origin));
}

bool CBot::ReachedGoalWP()
{
     if ((!m_pCurrentWaypoint) || (!m_pCurrentGoalWaypoint))
          return false;

     if (m_pCurrentWaypoint != m_pCurrentGoalWaypoint)
          return false;

     return ((GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin) <= 3.0f) &&
             (IsVisible(m_pCurrentGoalWaypoint->pNode->v_origin)));
}

waypoint_s *CBot::GetWPFromNode(node_s *pNode)
{
     if (!pNode) return NULL;

     short x, y;
     WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y);

     TLinkedList<waypoint_s *>::node_s *p = m_WaypointList[x][y].GetFirst();
     while(p)
     {
          if (p->Entry->pNode == pNode)
               return p->Entry;

          p = p->next;
     }

     return NULL;
}

waypoint_s *CBot::GetNearestWaypoint(vec v_src, float flRange)
{
     TLinkedList<waypoint_s *>::node_s *p;
     waypoint_s *pNearest = NULL;
     short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS);
     float flNearestDist = 9999.99f, flDist;

     WaypointClass.GetNodeIndexes(v_src, &i, &j);
     MinI = i - Offset;
     MaxI = i + Offset;
     MinJ = j - Offset;
     MaxJ = j + Offset;

     if (MinI < 0)
          MinI = 0;
     if (MaxI > MAX_MAP_GRIDS - 1)
          MaxI = MAX_MAP_GRIDS - 1;
     if (MinJ < 0)
          MinJ = 0;
     if (MaxJ > MAX_MAP_GRIDS - 1)
          MaxJ = MAX_MAP_GRIDS - 1;

     for (int x=MinI;x<=MaxI;x++)
     {
          for(int y=MinJ;y<=MaxJ;y++)
          {
               p = m_WaypointList[x][y].GetFirst();

               while(p)
               {
                    if (p->Entry->pNode->iFlags & W_FL_FLOOD)
                    {
                         p = p->next;
                         continue;
                    }

                    flDist = GetDistance(v_src, p->Entry->pNode->v_origin);
                    if ((flDist < flNearestDist) && (flDist <= flRange))
                    {
                         if (::IsVisible(v_src, p->Entry->pNode->v_origin, NULL))
                         {
                              pNearest = p->Entry;
                              flNearestDist = flDist;
                         }
                    }

                    p = p->next;
               }
          }
     }
     return pNearest;
}

waypoint_s *CBot::GetNearestTriggerWaypoint(vec v_src, float flRange)
{
     TLinkedList<waypoint_s *>::node_s *p;
     waypoint_s *pNearest = NULL;
     short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS);
     float flNearestDist = 9999.99f, flDist;

     WaypointClass.GetNodeIndexes(v_src, &i, &j);
     MinI = i - Offset;
     MaxI = i + Offset;
     MinJ = j - Offset;
     MaxJ = j + Offset;

     if (MinI < 0)
          MinI = 0;
     if (MaxI > MAX_MAP_GRIDS - 1)
          MaxI = MAX_MAP_GRIDS - 1;
     if (MinJ < 0)
          MinJ = 0;
     if (MaxJ > MAX_MAP_GRIDS - 1)
          MaxJ = MAX_MAP_GRIDS - 1;

     for (int x=MinI;x<=MaxI;x++)
     {
          for(int y=MinJ;y<=MaxJ;y++)
          {
               p = m_WaypointList[x][y].GetFirst();

               while(p)
               {
                    if (!(p->Entry->pNode->iFlags & W_FL_TRIGGER))
                    {
                         p = p->next;
                         continue;
                    }

                    flDist = GetDistance(v_src, p->Entry->pNode->v_origin);
                    if ((flDist < flNearestDist) && (flDist <= flRange))
                    {
                         if (::IsVisible(v_src, p->Entry->pNode->v_origin, NULL))
                         {
                              pNearest = p->Entry;
                              flNearestDist = flDist;
                         }
                    }

                    p = p->next;
               }
          }
     }
     return pNearest;
}

// Makes a waypoint list for this bot based on the list from WaypointClass
void CBot::SyncWaypoints()
{
     short x, y;
     TLinkedList<node_s *>::node_s *p;

     // Clean everything first
     for (x=0;x<MAX_MAP_GRIDS;x++)
     {
          for (y=0;y<MAX_MAP_GRIDS;y++)
          {
               while(!m_WaypointList[x][y].Empty())
                    delete m_WaypointList[x][y].Pop();
          }
     }

     // Sync
     for (x=0;x<MAX_MAP_GRIDS;x++)
     {
          for (y=0;y<MAX_MAP_GRIDS;y++)
          {
               p = WaypointClass.m_Waypoints[x][y].GetFirst();
               while(p)
               {
                    waypoint_s *pWP = new waypoint_s;
                    pWP->pNode = p->Entry;
                    m_WaypointList[x][y].AddNode(pWP);

#ifndef RELEASE_BUILD
                    if (!GetWPFromNode(p->Entry)) condebug("Error adding bot wp!");
#endif

                    p = p->next;
               }
          }
     }
}

#ifdef WP_FLOOD
waypoint_s *CBot::GetNearestFloodWP(vec v_origin, float flRange)
{
     TLinkedList<waypoint_s *>::node_s *p;
     waypoint_s *pNearest = NULL;
     short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS);
     float flNearestDist = 9999.99f, flDist;

     WaypointClass.GetNodeIndexes(v_origin, &i, &j);
     MinI = i - Offset;
     MaxI = i + Offset;
     MinJ = j - Offset;
     MaxJ = j + Offset;

     if (MinI < 0)
          MinI = 0;
     if (MaxI > MAX_MAP_GRIDS - 1)
          MaxI = MAX_MAP_GRIDS - 1;
     if (MinJ < 0)
          MinJ = 0;
     if (MaxJ > MAX_MAP_GRIDS - 1)
          MaxJ = MAX_MAP_GRIDS - 1;

     for (int x=MinI;x<=MaxI;x++)
     {
          for(int y=MinJ;y<=MaxJ;y++)
          {
               p = m_WaypointList[x][y].GetFirst();

               while(p)
               {
                    if (!(p->Entry->pNode->iFlags & W_FL_FLOOD))
                    {
                         p = p->next;
                         continue;
                    }

                    flDist = GetDistance(v_origin, p->Entry->pNode->v_origin);
                    if ((flDist < flNearestDist) && (flDist <= flRange))
                    {
                         if (::IsVisible(v_origin, p->Entry->pNode->v_origin, NULL))
                         {
                              pNearest = p->Entry;
                              flNearestDist = flDist;
                         }
                    }

                    p = p->next;
               }
          }
     }
     return pNearest;
}

waypoint_s *CBot::GetNearestTriggerFloodWP(vec v_origin, float flRange)
{
     TLinkedList<waypoint_s *>::node_s *p;
     waypoint_s *pNearest = NULL;
     short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS);
     float flNearestDist = 9999.99f, flDist;

     WaypointClass.GetNodeIndexes(v_origin, &i, &j);
     MinI = i - Offset;
     MaxI = i + Offset;
     MinJ = j - Offset;
     MaxJ = j + Offset;

     if (MinI < 0)
          MinI = 0;
     if (MaxI > MAX_MAP_GRIDS - 1)
          MaxI = MAX_MAP_GRIDS - 1;
     if (MinJ < 0)
          MinJ = 0;
     if (MaxJ > MAX_MAP_GRIDS - 1)
          MaxJ = MAX_MAP_GRIDS - 1;

     for (int x=MinI;x<=MaxI;x++)
     {
          for(int y=MinJ;y<=MaxJ;y++)
          {
               p = m_WaypointList[x][y].GetFirst();

               while(p)
               {
                    if (!(p->Entry->pNode->iFlags & (W_FL_FLOOD | W_FL_TRIGGER)))
                    {
                         p = p->next;
                         continue;
                    }

                    flDist = GetDistance(v_origin, p->Entry->pNode->v_origin);
                    if ((flDist < flNearestDist) && (flDist <= flRange))
                    {
                         if (::IsVisible(v_origin, p->Entry->pNode->v_origin, NULL))
                         {
                              pNearest = p->Entry;
                              flNearestDist = flDist;
                         }
                    }

                    p = p->next;
               }
          }
     }
     return pNearest;
}

void CBot::GoToDebugGoal(vec o)
{
     ResetWaypointVars();

     node_s *wp = WaypointClass.GetNearestWaypoint(m_pMyEnt, 20.0f);
     node_s *goalwp = WaypointClass.GetNearestWaypoint(player1, 20.0f);

     if (!wp || !goalwp)
     {
          wp = WaypointClass.GetNearestFloodWP(m_pMyEnt, 8.0f);
          goalwp = WaypointClass.GetNearestFloodWP(player1, 5.0f);
     }
     if (!wp || !goalwp) { condebug("No near WP"); return; }

     SetCurrentWaypoint(wp);
     SetCurrentGoalWaypoint(goalwp);
     m_vGoal = o;
     m_bGoToDebugGoal = true;
}
#endif

// Bot class end
Advertisement