/*
 * Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU GPL v2 license, you may redistribute it
 * and/or modify it under version 2 of the License, or (at your option), any later version.
 */

#include "TravelMgr.h"

#include <iomanip>
#include <numeric>

#include "CellImpl.h"
#include "ChatHelper.h"
#include "MMapFactory.h"
#include "MapMgr.h"
#include "PathGenerator.h"
#include "Playerbots.h"
#include "StrategyContext.h"
#include "TransportMgr.h"
#include "VMapFactory.h"
#include "VMapMgr2.h"
#include "Corpse.h"

WorldPosition::WorldPosition(std::string const str)
{
    std::vector<std::string> tokens = split(str, '|');
    if (tokens.size() == 5)
    {
        try
        {
            m_mapId = std::stoi(tokens[0]);
            m_positionX = std::stof(tokens[1]);
            m_positionY = std::stof(tokens[2]);
            m_positionZ = std::stof(tokens[3]);
            m_orientation = std::stof(tokens[4]);
        }
        catch (const std::exception&)
        {
            m_mapId = 0;
            m_positionX = 0.0f;
            m_positionY = 0.0f;
            m_positionZ = 0.0f;
            m_orientation = 0.0f;
        }
    }
}

WorldPosition::WorldPosition(uint32 mapId, const Position& pos)
    : WorldLocation(mapId, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation())
{
}

WorldPosition::WorldPosition(WorldObject const* wo)
{
    if (wo)
    {
        set(WorldLocation(wo->GetMapId(), wo->GetPositionX(), wo->GetPositionY(), wo->GetPositionZ(),
                          wo->GetOrientation()));
    }
}

WorldPosition::WorldPosition(std::vector<WorldPosition*> list, WorldPositionConst conType)
{
    uint32 size = list.size();
    if (!size)
        return;

    if (size == 1)
        set(*list.front());
    else if (conType == WP_RANDOM)
        set(*list[urand(0, size - 1)]);
    else if (conType == WP_CENTROID)
    {
        set(std::accumulate(list.begin(), list.end(), WorldLocation(list[0]->getMapId(), 0, 0, 0, 0),
                            [size](WorldLocation i, WorldPosition* j)
                            {
                                i.m_positionX += j->getX() / size;
                                i.m_positionY += j->getY() / size;
                                i.m_positionZ += j->getZ() / size;
                                i.NormalizeOrientation(i.m_orientation += j->getO() / size);
                                return i;
                            }));
    }
    else if (conType == WP_MEAN_CENTROID)
    {
        WorldPosition pos = WorldPosition(list, WP_CENTROID);
        set(*pos.closestSq(list));
    }
}

WorldPosition::WorldPosition(std::vector<WorldPosition> list, WorldPositionConst conType)
{
    uint32 size = list.size();
    if (!size)
        return;

    if (size == 1)
        set(list.front());
    else if (conType == WP_RANDOM)
        set(list[urand(0, size - 1)]);
    else if (conType == WP_CENTROID)
    {
        set(std::accumulate(list.begin(), list.end(), WorldLocation(list[0].getMapId(), 0, 0, 0, 0),
                            [size](WorldLocation i, WorldPosition& j)
                            {
                                i.m_positionX += j.getX() / size;
                                i.m_positionY += j.getY() / size;
                                i.m_positionZ += j.getZ() / size;
                                i.NormalizeOrientation(i.m_orientation += j.getO() / size);
                                return i;
                            }));
    }
    else if (conType == WP_MEAN_CENTROID)
    {
        WorldPosition pos = WorldPosition(list, WP_CENTROID);
        set(pos.closestSq(list));
    }
}

WorldPosition::WorldPosition(uint32 mapid, GridCoord grid)
    : WorldLocation(mapid, (int32(grid.x_coord) - CENTER_GRID_ID - 0.5) * SIZE_OF_GRIDS + CENTER_GRID_OFFSET,
                    (int32(grid.y_coord) - CENTER_GRID_ID - 0.5) * SIZE_OF_GRIDS + CENTER_GRID_OFFSET, 0, 0)
{
}

WorldPosition::WorldPosition(uint32 mapid, CellCoord cell)
    : WorldLocation(
          mapid, (int32(cell.x_coord) - CENTER_GRID_CELL_ID - 0.5) * SIZE_OF_GRID_CELL + CENTER_GRID_CELL_OFFSET,
          (int32(cell.y_coord) - CENTER_GRID_CELL_ID - 0.5) * SIZE_OF_GRID_CELL + CENTER_GRID_CELL_OFFSET, 0, 0)
{
}

WorldPosition::WorldPosition(uint32 mapid, mGridCoord grid)
    : WorldLocation(mapid, (32 - grid.first) * SIZE_OF_GRIDS, (32 - grid.second) * SIZE_OF_GRIDS, 0, 0)
{
}

void WorldPosition::set(const WorldLocation& pos) { WorldRelocate(pos); }

void WorldPosition::set(const WorldPosition& pos)
{
    WorldRelocate(pos.m_mapId, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation());
}

void WorldPosition::set(const WorldObject* pos)
{
    WorldRelocate(pos->GetMapId(), pos->GetPositionX(), pos->GetPositionY(), pos->GetPositionZ(), pos->GetOrientation());
}

void WorldPosition::setMapId(uint32 id)
{
    m_mapId = id;
}

void WorldPosition::setX(float x) { m_positionX = x; }

void WorldPosition::setY(float y) { m_positionY = y; }

void WorldPosition::setZ(float z) { m_positionZ = z; }

void WorldPosition::setO(float o) { m_orientation = o; }

WorldPosition::operator bool() const
{
    return GetMapId() != 0 || GetPositionX() != 0 || GetPositionY() != 0 || GetPositionZ() != 0;
}

bool operator==(WorldPosition const& p1, const WorldPosition& p2)
{
    return p1.GetMapId() == p2.GetMapId() && p2.GetPositionX() == p1.GetPositionX() &&
           p2.GetPositionY() == p1.GetPositionY() && p2.GetPositionZ() == p1.GetPositionZ() &&
           p2.GetOrientation() == p1.GetOrientation();
}

bool operator!=(WorldPosition const& p1, const WorldPosition& p2) { return !(p1 == p2); }

WorldPosition& WorldPosition::operator+=(WorldPosition const& p1)
{
    m_positionX += p1.GetPositionX();
    m_positionY += p1.GetPositionY();
    m_positionZ += p1.GetPositionZ();
    return *this;
}

WorldPosition& WorldPosition::operator-=(WorldPosition const& p1)
{
    m_positionX -= p1.GetPositionX();
    m_positionY -= p1.GetPositionY();
    m_positionZ -= p1.GetPositionZ();
    return *this;
}

uint32 WorldPosition::getMapId() { return GetMapId(); }

float WorldPosition::getX() { return GetPositionX(); }

float WorldPosition::getY() { return GetPositionY(); }

float WorldPosition::getZ() { return GetPositionZ(); }

float WorldPosition::getO() { return GetOrientation(); }

bool WorldPosition::isOverworld()
{
    return GetMapId() == 0 || GetMapId() == 1 || GetMapId() == 530 || GetMapId() == 571;
}

bool WorldPosition::isInWater()
{
    return getMap() ? getMap()->IsInWater(PHASEMASK_NORMAL, GetPositionX(), GetPositionY(), GetPositionZ(),
                                          DEFAULT_COLLISION_HEIGHT)
                    : false;
};

bool WorldPosition::isUnderWater()
{
    return getMap() ? getMap()->IsUnderWater(PHASEMASK_NORMAL, GetPositionX(), GetPositionY(), GetPositionZ(),
                                             DEFAULT_COLLISION_HEIGHT)
                    : false;
};

WorldPosition WorldPosition::relPoint(WorldPosition* center)
{
    return WorldPosition(GetMapId(), GetPositionX() - center->GetPositionX(), GetPositionY() - center->GetPositionY(),
                         GetPositionZ() - center->GetPositionZ(), GetOrientation());
}

WorldPosition WorldPosition::offset(WorldPosition* center)
{
    return WorldPosition(GetMapId(), GetPositionX() + center->GetPositionX(), GetPositionY() + center->GetPositionY(),
                         GetPositionZ() + center->GetPositionZ(), GetOrientation());
}

float WorldPosition::size()
{
    return sqrt(pow(GetPositionX(), 2.0) + pow(GetPositionY(), 2.0) + pow(GetPositionZ(), 2.0));
}

float WorldPosition::distance(WorldPosition* center)
{
    if (GetMapId() == center->getMapId())
        return relPoint(center).size();

    // this -> mapTransfer | mapTransfer -> center
    return sTravelMgr->mapTransDistance(*this, *center);
};

float WorldPosition::fDist(WorldPosition* center)
{
    if (GetMapId() == center->getMapId())
        return sqrt(sqDistance2d(center));

    // this -> mapTransfer | mapTransfer -> center
    return sTravelMgr->fastMapTransDistance(*this, *center);
};

float mapTransfer::fDist(WorldPosition start, WorldPosition end)
{
    return start.fDist(pointFrom) + portalLength + pointTo.fDist(end);
}

// When moving from this along list return last point that falls within range.
// Distance is move distance along path.
WorldPosition WorldPosition::lastInRange(std::vector<WorldPosition> list, float minDist, float maxDist)
{
    WorldPosition rPoint;

    float startDist = 0.0f;

    // Enter the path at the closest point.
    for (auto& p : list)
    {
        float curDist = distance(p);
        if (startDist < curDist || p == list.front())
            startDist = curDist + 0.1f;
    }

    float totalDist = 0.0f;

    // Follow the path from the last nearest point
    // Return last point in range.
    for (auto& p : list)
    {
        float curDist = distance(p);

        if (totalDist > 0)  // We have started the path. Keep counting.
            totalDist += p.distance(std::prev(&p, 1));

        if (curDist == startDist)  // Start the path here.
            totalDist = startDist;

        if (minDist > 0 && totalDist < minDist)
            continue;

        if (maxDist > 0 && totalDist > maxDist)
            continue;  // We do not break here because the path may loop back and have a second startDist point.

        rPoint = p;
    }

    return rPoint;
};

// Todo: remove or adjust to above standard.
WorldPosition WorldPosition::firstOutRange(std::vector<WorldPosition> list, float minDist, float maxDist)
{
    WorldPosition rPoint;

    for (auto& p : list)
    {
        if (minDist > 0 && distance(p) < minDist)
            return p;

        if (maxDist > 0 && distance(p) > maxDist)
            return p;

        rPoint = p;
    }

    return rPoint;
}

// Returns true if (on the x-y plane) the position is inside the three points.
bool WorldPosition::isInside(WorldPosition* p1, WorldPosition* p2, WorldPosition* p3)
{
    if (getMapId() != p1->getMapId() != p2->getMapId() != p3->getMapId())
        return false;

    float d1, d2, d3;
    bool has_neg, has_pos;

    d1 = mSign(p1, p2);
    d2 = mSign(p2, p3);
    d3 = mSign(p3, p1);

    has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
    has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);

    return !(has_neg && has_pos);
}

MapEntry const* WorldPosition::getMapEntry() { return sMapStore.LookupEntry(GetMapId()); };

uint32 WorldPosition::getInstanceId()
{
    if (Map* map = sMapMgr->FindBaseMap(getMapId()))
        return map->GetInstanceId();

    return 0;
}

Map* WorldPosition::getMap()
{
    return sMapMgr->FindMap(GetMapId(), getMapEntry()->Instanceable() ? getInstanceId() : 0);
}

float WorldPosition::getHeight()  // remove const - whipowill
{
    return getMap()->GetHeight(getX(), getY(), getZ());
}

G3D::Vector3 WorldPosition::getVector3() { return G3D::Vector3(GetPositionX(), GetPositionY(), GetPositionZ()); }

std::string const WorldPosition::print()
{
    std::ostringstream out;
    out << GetMapId() << std::fixed << std::setprecision(2);
    out << ';' << GetPositionX();
    out << ';' << GetPositionY();
    out << ';' << GetPositionZ();
    out << ';' << GetOrientation();

    return out.str();
}

std::string const WorldPosition::to_string()
{
    std::stringstream out;
    out << m_mapId << '|';
    out << m_positionX << '|';
    out << m_positionY << '|';
    out << m_positionZ << '|';
    out << m_orientation;
    return out.str();
}

std::vector<std::string> WorldPosition::split(const std::string& s, char delimiter)
{
    std::vector<std::string> tokens;
    std::string token;
    std::istringstream tokenStream(s);
    while (std::getline(tokenStream, token, delimiter))
    {
        tokens.push_back(token);
    }
    return tokens;
}

void WorldPosition::printWKT(std::vector<WorldPosition> points, std::ostringstream& out, uint32 dim, bool loop)
{
    switch (dim)
    {
        case 0:
            if (points.size() == 1)
                out << "\"POINT(";
            else
                out << "\"MULTIPOINT(";
            break;
        case 1:
            out << "\"LINESTRING(";
            break;
        case 2:
            out << "\"POLYGON((";
    }

    for (auto& p : points)
        out << p.getDisplayX() << " " << p.getDisplayY() << (!loop && &p == &points.back() ? "" : ",");

    if (loop)
        out << points.front().getDisplayX() << " " << points.front().getDisplayY();

    out << (dim == 2 ? "))\"," : ")\",");
}

WorldPosition WorldPosition::getDisplayLocation()
{
    WorldPosition pos = sTravelNodeMap->getMapOffset(getMapId());
    return offset(const_cast<WorldPosition*>(&pos));
}

uint16 WorldPosition::getAreaId() { return sMapMgr->GetAreaId(PHASEMASK_NORMAL, getMapId(), getX(), getY(), getZ()); }

AreaTableEntry const* WorldPosition::getArea()
{
    uint16 areaId = getAreaId();
    if (!areaId)
        return nullptr;

    return sAreaTableStore.LookupEntry(areaId);
}

std::string const WorldPosition::getAreaName(bool fullName, bool zoneName)
{
    if (!isOverworld())
    {
        MapEntry const* map = sMapStore.LookupEntry(getMapId());
        if (map)
            return map->name[0];
    }

    AreaTableEntry const* area = getArea();
    if (!area)
        return "";

    std::string areaName = area->area_name[0];

    if (fullName)
    {
        uint16 zoneId = area->zone;

        while (zoneId > 0)
        {
            AreaTableEntry const* parentArea = sAreaTableStore.LookupEntry(zoneId);
            if (!parentArea)
                break;

            std::string const subAreaName = parentArea->area_name[0];

            if (zoneName)
                areaName = subAreaName;
            else
                areaName = subAreaName + " " + areaName;

            zoneId = parentArea->zone;
        }
    }

    return std::move(areaName);
}

std::set<Transport*> WorldPosition::getTransports(uint32 entry)
{
    /*
    if(!entry)
        return getMap()->m_transports;
    else
    {
    */
    std::set<Transport*> transports;
    /*
    for (auto transport : getMap()->m_transports)
        if(transport->GetEntry() == entry)
            transports.insert(transport);

    return transports;
}
*/
    return transports;
}

std::vector<GridCoord> WorldPosition::getGridCoord(WorldPosition secondPos)
{
    std::vector<GridCoord> retVec;

    int lx = std::min(getGridCoord().x_coord, secondPos.getGridCoord().x_coord);
    int ly = std::min(getGridCoord().y_coord, secondPos.getGridCoord().y_coord);
    int ux = std::max(getGridCoord().x_coord, secondPos.getGridCoord().x_coord);
    int uy = std::max(getGridCoord().y_coord, secondPos.getGridCoord().y_coord);

    int32 border = 1;

    lx = std::min(std::max(border, lx), MAX_NUMBER_OF_GRIDS - border);
    ly = std::min(std::max(border, ly), MAX_NUMBER_OF_GRIDS - border);
    ux = std::min(std::max(border, ux), MAX_NUMBER_OF_GRIDS - border);
    uy = std::min(std::max(border, uy), MAX_NUMBER_OF_GRIDS - border);

    for (int x = lx - border; x <= ux + border; x++)
    {
        for (int y = ly - border; y <= uy + border; y++)
        {
            retVec.push_back(GridCoord(x, y));
        }
    }

    return retVec;
}

std::vector<WorldPosition> WorldPosition::fromGridCoord(GridCoord gridCoord)
{
    std::vector<WorldPosition> retVec;
    GridCoord g;

    for (uint32 d = 0; d < 4; d++)
    {
        g = gridCoord;

        if (d == 1 || d == 2)
            g.inc_x(1);

        if (d == 2 || d == 3)
            g.inc_y(1);

        retVec.push_back(WorldPosition(getMapId(), g));
    }

    return retVec;
}

std::vector<WorldPosition> WorldPosition::fromCellCoord(CellCoord cellcoord)
{
    std::vector<WorldPosition> retVec;
    CellCoord p;

    for (uint32 d = 0; d < 4; d++)
    {
        p = cellcoord;

        if (d == 1 || d == 2)
            p.inc_x(1);

        if (d == 2 || d == 3)
            p.inc_y(1);

        retVec.push_back(WorldPosition(getMapId(), p));
    }
    return retVec;
}

std::vector<WorldPosition> WorldPosition::gridFromCellCoord(CellCoord cellCoord)
{
    Cell c(cellCoord);

    return fromGridCoord(GridCoord(c.GridX(), c.GridY()));
}

std::vector<std::pair<int32, int32>> WorldPosition::getmGridCoords(WorldPosition secondPos)
{
    std::vector<mGridCoord> retVec;

    int lx = std::min(getmGridCoord().first, secondPos.getmGridCoord().first);
    int ly = std::min(getmGridCoord().second, secondPos.getmGridCoord().second);
    int ux = std::max(getmGridCoord().first, secondPos.getmGridCoord().first);
    int uy = std::max(getmGridCoord().second, secondPos.getmGridCoord().second);
    int border = 1;

    // lx = std::min(std::max(border, lx), MAX_NUMBER_OF_GRIDS - border);
    // ly = std::min(std::max(border, ly), MAX_NUMBER_OF_GRIDS - border);
    // ux = std::min(std::max(border, ux), MAX_NUMBER_OF_GRIDS - border);
    // uy = std::min(std::max(border, uy), MAX_NUMBER_OF_GRIDS - border);

    for (int x = lx - border; x <= ux + border; x++)
    {
        for (int y = ly - border; y <= uy + border; y++)
        {
            retVec.push_back(std::make_pair(x, y));
        }
    }

    return retVec;
}

std::vector<WorldPosition> WorldPosition::frommGridCoord(mGridCoord GridCoord)
{
    std::vector<WorldPosition> retVec;
    mGridCoord g;

    for (uint32 d = 0; d < 4; d++)
    {
        g = GridCoord;

        if (d == 1 || d == 2)
            g.second++;
        if (d == 2 || d == 3)
            g.first++;

        retVec.push_back(WorldPosition(getMapId(), g));
    }

    return retVec;
}

void WorldPosition::loadMapAndVMap(uint32 mapId, uint8 x, uint8 y)
{
    std::string const fileName = "load_map_grid.csv";

    if (isOverworld() && false || false)
    {
        if (!MMAP::MMapFactory::createOrGetMMapMgr()->loadMap(mapId, x, y))
            if (sPlayerbotAIConfig->hasLog(fileName))
            {
                std::ostringstream out;
                out << sPlayerbotAIConfig->GetTimestampStr();
                out << "+00,\"mmap\", " << x << "," << y << "," << (sTravelMgr->isBadMmap(mapId, x, y) ? "0" : "1")
                    << ",";
                printWKT(fromGridCoord(GridCoord(x, y)), out, 1, true);
                sPlayerbotAIConfig->log(fileName, out.str().c_str());
            }
    }
    else
    {
        // This needs to be disabled or maps will not load.
        // Needs more testing to check for impact on movement.
        if (false)
            if (!sTravelMgr->isBadVmap(mapId, x, y))
            {
                // load VMAPs for current map/grid...
                const MapEntry* i_mapEntry = sMapStore.LookupEntry(mapId);
                const char* mapName = i_mapEntry ? i_mapEntry->name[sWorld->GetDefaultDbcLocale()] : "UNNAMEDMAP\x0";

                int vmapLoadResult = VMAP::VMapFactory::createOrGetVMapMgr()->loadMap(
                    (sWorld->GetDataPath() + "vmaps").c_str(), mapId, x, y);
                switch (vmapLoadResult)
                {
                    case VMAP::VMAP_LOAD_RESULT_OK:
                        // LOG_ERROR("playerbots", "VMAP loaded name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})",
                        // mapName, mapId, x, y, x, y);
                        break;
                    case VMAP::VMAP_LOAD_RESULT_ERROR:
                        // LOG_ERROR("playerbots", "Could not load VMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{},
                        // y:{})", mapName, mapId, x, y, x, y);
                        sTravelMgr->addBadVmap(mapId, x, y);
                        break;
                    case VMAP::VMAP_LOAD_RESULT_IGNORED:
                        sTravelMgr->addBadVmap(mapId, x, y);
                        // LOG_INFO("playerbots", "Ignored VMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})",
                        // mapName, mapId, x, y, x, y);
                        break;
                }

                if (sPlayerbotAIConfig->hasLog(fileName))
                {
                    std::ostringstream out;
                    out << sPlayerbotAIConfig->GetTimestampStr();
                    out << "+00,\"vmap\", " << x << "," << y << ", " << (sTravelMgr->isBadVmap(mapId, x, y) ? "0" : "1")
                        << ",";
                    printWKT(frommGridCoord(mGridCoord(x, y)), out, 1, true);
                    sPlayerbotAIConfig->log(fileName, out.str().c_str());
                }
            }

        if (!sTravelMgr->isBadMmap(mapId, x, y))
        {
            // load navmesh
            if (!MMAP::MMapFactory::createOrGetMMapMgr()->loadMap(mapId, x, y))
                sTravelMgr->addBadMmap(mapId, x, y);

            if (sPlayerbotAIConfig->hasLog(fileName))
            {
                std::ostringstream out;
                out << sPlayerbotAIConfig->GetTimestampStr();
                out << "+00,\"mmap\", " << x << "," << y << "," << (sTravelMgr->isBadMmap(mapId, x, y) ? "0" : "1")
                    << ",";
                printWKT(fromGridCoord(GridCoord(x, y)), out, 1, true);
                sPlayerbotAIConfig->log(fileName, out.str().c_str());
            }
        }
    }
}

void WorldPosition::loadMapAndVMaps(WorldPosition secondPos)
{
    for (auto& grid : getmGridCoords(secondPos))
    {
        loadMapAndVMap(getMapId(), grid.first, grid.second);
    }
}

std::vector<WorldPosition> WorldPosition::fromPointsArray(std::vector<G3D::Vector3> path)
{
    std::vector<WorldPosition> retVec;
    for (auto p : path)
        retVec.push_back(WorldPosition(getMapId(), p.x, p.y, p.z, getO()));

    return retVec;
}

// A single pathfinding attempt from one position to another. Returns pathfinding status and path.
std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos, Unit* bot)
{
    if (!bot)
        return {};

    // Load mmaps and vmaps between the two points.
    loadMapAndVMaps(startPos);

    PathGenerator path(bot);
    path.CalculatePath(startPos.getX(), startPos.getY(), startPos.getZ());

    Movement::PointsArray points = path.GetPath();
    PathType type = path.GetPathType();

    if (sPlayerbotAIConfig->hasLog("pathfind_attempt_point.csv"))
    {
        std::ostringstream out;
        out << std::fixed << std::setprecision(1);
        printWKT({startPos, *this}, out);
        sPlayerbotAIConfig->log("pathfind_attempt_point.csv", out.str().c_str());
    }

    if (sPlayerbotAIConfig->hasLog("pathfind_attempt.csv") && (type == PATHFIND_INCOMPLETE || type == PATHFIND_NORMAL))
    {
        std::ostringstream out;
        out << sPlayerbotAIConfig->GetTimestampStr() << "+00,";
        out << std::fixed << std::setprecision(1) << type << ",";
        printWKT(fromPointsArray(points), out, 1);
        sPlayerbotAIConfig->log("pathfind_attempt.csv", out.str().c_str());
    }

    if (type == PATHFIND_INCOMPLETE || type == PATHFIND_NORMAL)
        return fromPointsArray(points);

    return {};
}

bool WorldPosition::cropPathTo(std::vector<WorldPosition>& path, float maxDistance)
{
    if (path.empty())
        return false;

    auto bestPos = std::min_element(path.begin(), path.end(),
                                    [this](WorldPosition i, WorldPosition j)
                                    { return this->sqDistance(i) < this->sqDistance(j); });

    bool insRange = this->sqDistance(*bestPos) <= maxDistance * maxDistance;

    if (bestPos == path.end())
        return insRange;

    path.erase(std::next(bestPos), path.end());

    return insRange;
}

// A sequential series of pathfinding attempts. Returns the complete path and if the patfinder eventually found a way to
// the destination.
std::vector<WorldPosition> WorldPosition::getPathFromPath(std::vector<WorldPosition> startPath, Unit* bot,
                                                          uint8 maxAttempt)
{
    // We start at the end of the last path.
    WorldPosition currentPos = startPath.back();

    // No pathfinding across maps.
    if (getMapId() != currentPos.getMapId())
        return {};

    std::vector<WorldPosition> subPath, fullPath = startPath;

    // Limit the pathfinding attempts
    for (uint32 i = 0; i < maxAttempt; i++)
    {
        // Try to pathfind to this position.
        subPath = getPathStepFrom(currentPos, bot);

        // If we could not find a path return what we have now.
        if (subPath.empty() || currentPos.distance(&subPath.back()) < sPlayerbotAIConfig->targetPosRecalcDistance)
            break;

        // Append the path excluding the start (this should be the same as the end of the startPath)
        fullPath.insert(fullPath.end(), std::next(subPath.begin(), 1), subPath.end());

        // Are we there yet?
        if (isPathTo(subPath))
            break;

        // Continue pathfinding.
        currentPos = subPath.back();
    }

    return fullPath;
}

bool WorldPosition::GetReachableRandomPointOnGround(Player* bot, float radius, bool randomRange)
{
    radius *= randomRange ? rand_norm() : 1.f;
    float angle = rand_norm() * static_cast<float>(2 * M_PI);
    m_positionX += radius * cosf(angle);
    m_positionY += radius * sinf(angle);

    return getMap()->CanReachPositionAndGetValidCoords(bot, m_positionX, m_positionY, m_positionZ);
}

uint32 WorldPosition::getUnitsAggro(GuidVector& units, Player* bot)
{
    units.erase(std::remove_if(units.begin(), units.end(),
                               [this, bot](ObjectGuid guid)
                               {
                                   Creature* creature = ObjectAccessor::GetCreature(*bot, guid);
                                   if (!creature)
                                       return true;

                                   return sqDistance(WorldPosition(creature)) >
                                          creature->GetAttackDistance(bot) * creature->GetAttackDistance(bot);
                               }),
                units.end());

    return units.size();
}

void FindPointCreatureData::operator()(CreatureData const& creatureData)
{
    if (!entry || creatureData.id1 == entry)
        if ((!point || creatureData.mapid == point.getMapId()) &&
            (!radius || point.sqDistance(WorldPosition(creatureData.mapid, creatureData.posX, creatureData.posY,
                                                       creatureData.posZ)) < radius * radius))
        {
            data.push_back(&creatureData);
        }
}

void FindPointGameObjectData::operator()(GameObjectData const& gameobjectData)
{
    if (!entry || gameobjectData.id == entry)
        if ((!point || gameobjectData.mapid == point.getMapId()) &&
            (!radius || point.sqDistance(WorldPosition(gameobjectData.mapid, gameobjectData.posX, gameobjectData.posY,
                                                       gameobjectData.posZ)) < radius * radius))
        {
            data.push_back(&gameobjectData);
        }
}

std::vector<CreatureData const*> WorldPosition::getCreaturesNear(float radius, uint32 entry)
{
    FindPointCreatureData worker(*this, radius, entry);
    for (auto const& itr : sObjectMgr->GetAllCreatureData())
        worker(itr.second);

    return worker.GetResult();
}

std::vector<GameObjectData const*> WorldPosition::getGameObjectsNear(float radius, uint32 entry)
{
    FindPointGameObjectData worker(*this, radius, entry);
    for (auto const& itr : sObjectMgr->GetAllGOData())
        worker(itr.second);

    return worker.GetResult();
}

Creature* GuidPosition::GetCreature()
{
    if (!*this)
        return nullptr;

    if (loadedFromDB)
    {
        auto creatureBounds = getMap()->GetCreatureBySpawnIdStore().equal_range(GetCounter());
        if (creatureBounds.first != creatureBounds.second)
            return creatureBounds.second->second;

        return nullptr;
    }

    return getMap()->GetCreature(*this);
}

Unit* GuidPosition::GetUnit()
{
    if (!*this)
        return nullptr;

    if (loadedFromDB)
    {
        auto creatureBounds = getMap()->GetCreatureBySpawnIdStore().equal_range(GetCounter());
        if (creatureBounds.first != creatureBounds.second)
            return creatureBounds.second->second;

        return nullptr;
    }

    if (IsPlayer())
        return ObjectAccessor::FindPlayer(*this);

    if (IsPet())
        return getMap()->GetPet(*this);

    return GetCreature();
}

GameObject* GuidPosition::GetGameObject()
{
    if (!*this)
        return nullptr;

    if (loadedFromDB)
    {
        auto gameobjectBounds = getMap()->GetGameObjectBySpawnIdStore().equal_range(GetCounter());
        if (gameobjectBounds.first != gameobjectBounds.second)
            return gameobjectBounds.second->second;

        return nullptr;
    }

    return getMap()->GetGameObject(*this);
}

Player* GuidPosition::GetPlayer()
{
    if (!*this)
        return nullptr;

    if (IsPlayer())
        return ObjectAccessor::FindPlayer(*this);

    return nullptr;
}

bool GuidPosition::isDead()
{
    if (!getMap())
        return false;

    if (!getMap()->IsGridLoaded(getX(), getY()))
        return false;

    if (IsUnit() && GetUnit() && GetUnit()->IsInWorld() && GetUnit()->IsAlive())
        return false;

    if (IsGameObject() && GetGameObject() && GetGameObject()->IsInWorld())
        return false;

    return true;
}

GuidPosition::GuidPosition(WorldObject* wo) : ObjectGuid(wo->GetGUID()), WorldPosition(wo), loadedFromDB(false) {}

GuidPosition::GuidPosition(CreatureData const& creData)
    : ObjectGuid(HighGuid::Unit, creData.id1, creData.spawnId),
      WorldPosition(creData.mapid, creData.posX, creData.posY, creData.posZ, creData.orientation)
{
    loadedFromDB = true;
}

GuidPosition::GuidPosition(GameObjectData const& goData)
    : ObjectGuid(HighGuid::GameObject, goData.id),
      WorldPosition(goData.mapid, goData.posX, goData.posY, goData.posZ, goData.orientation)
{
    loadedFromDB = true;
}

CreatureTemplate const* GuidPosition::GetCreatureTemplate()
{
    return IsCreature() ? sObjectMgr->GetCreatureTemplate(GetEntry()) : nullptr;
}

GameObjectTemplate const* GuidPosition::GetGameObjectTemplate()
{
    return IsGameObject() ? sObjectMgr->GetGameObjectTemplate(GetEntry()) : nullptr;
}

WorldObject* GuidPosition::GetWorldObject()
{
    if (!*this)
        return nullptr;

    switch (GetHigh())
    {
        case HighGuid::Player:
            return ObjectAccessor::FindPlayer(*this);
        case HighGuid::Transport:
        case HighGuid::Mo_Transport:
        case HighGuid::GameObject:
            return GetGameObject();
        case HighGuid::Vehicle:
        case HighGuid::Unit:
            return GetCreature();
        case HighGuid::Pet:
            return getMap()->GetPet(*this);
        case HighGuid::DynamicObject:
            return getMap()->GetDynamicObject(*this);
        case HighGuid::Corpse:
            return getMap()->GetCorpse(*this);
        default:
            return nullptr;
    }

    return nullptr;
}

bool GuidPosition::HasNpcFlag(NPCFlags flag) { return IsCreature() && GetCreatureTemplate()->npcflag & flag; }

std::vector<WorldPosition*> TravelDestination::getPoints(bool ignoreFull)
{
    if (ignoreFull)
        return points;

    uint32 max = maxVisitorsPerPoint;
    if (!max)
        return points;

    std::vector<WorldPosition*> retVec;
    std::copy_if(points.begin(), points.end(), std::back_inserter(retVec),
                 [max](WorldPosition* p) { return p->getVisitors() < max; });
    return retVec;
}

WorldPosition* TravelDestination::nearestPoint(WorldPosition* pos)
{
    return *std::min_element(points.begin(), points.end(),
                             [pos](WorldPosition* i, WorldPosition* j) { return i->distance(pos) < j->distance(pos); });
}

std::vector<WorldPosition*> TravelDestination::touchingPoints(WorldPosition* pos)
{
    std::vector<WorldPosition*> ret_points;
    for (auto& point : points)
    {
        float dist = pos->distance(point);
        if (!dist)
            continue;

        if (dist > radiusMax * 2)
            continue;

        ret_points.push_back(point);
    }

    return ret_points;
};

std::vector<WorldPosition*> TravelDestination::sortedPoints(WorldPosition* pos)
{
    std::vector<WorldPosition*> ret_points = points;
    std::sort(ret_points.begin(), ret_points.end(),
              [pos](WorldPosition* i, WorldPosition* j) { return i->distance(pos) < j->distance(pos); });
    return ret_points;
};

std::vector<WorldPosition*> TravelDestination::nextPoint(WorldPosition* pos, bool ignoreFull)
{
    return sTravelMgr->getNextPoint(pos, ignoreFull ? points : getPoints());
}

bool TravelDestination::isFull(bool ignoreFull)
{
    if (!ignoreFull && maxVisitors > 0 && visitors >= maxVisitors)
        return true;

    if (maxVisitorsPerPoint > 0)
        if (getPoints(ignoreFull).empty())
            return true;

    return false;
}

std::string const QuestTravelDestination::getTitle() { return ChatHelper::FormatQuest(questTemplate); }

bool QuestRelationTravelDestination::isActive(Player* bot)
{
    PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
    AiObjectContext* context = botAI->GetAiObjectContext();

    if (botAI && !botAI->HasStrategy("rpg quest", BOT_STATE_NON_COMBAT))
        return false;

    if (relation == 0)
    {
        if ((int32)questTemplate->GetQuestLevel() >= (int32)bot->GetLevel() + (int32)5)
            return false;

        // skip for now this quest
        if (getPoints().front()->GetMapId() != bot->GetMapId())
            return false;

        if (!bot->GetMap()->GetEntry()->IsWorldMap() || !bot->CanTakeQuest(questTemplate, false))
            return false;

        uint32 dialogStatus = sTravelMgr->getDialogStatus(bot, entry, questTemplate);

        if (AI_VALUE(bool, "can fight equal"))
        {
            if (AI_VALUE(uint8, "free quest log slots") < 5)
                return false;

            //None has yellow exclamation mark.
            if (!AI_VALUE2(bool, "group or", "following party,near leader,can accept quest npc::" + std::to_string(entry)))
                if (!AI_VALUE2(bool, "group or", "following party,near leader,can accept quest low level npc::" + std::to_string(entry) + "need quest objective::" + std::to_string(questId))) //Noone can do this quest for a usefull reward.
                    return false;
        }
        else
        {
            if (!AI_VALUE2(bool, "group or", "following party,near leader,can accept quest low level npc::" + std::to_string(entry))) //Noone can pick up this quest for money.
                return false;

            if (AI_VALUE(uint8, "free quest log slots") < 10)
                return false;
        }

        // Do not try to pick up dungeon/elite quests in instances without a group.
        if ((questTemplate->GetType() == QUEST_TYPE_ELITE || questTemplate->GetType() == QUEST_TYPE_DUNGEON) &&
            !AI_VALUE(bool, "can fight boss"))
            return false;
    }
    else
    {
        if (!AI_VALUE2(bool, "group or", "following party,near leader,can turn in quest npc::" + std::to_string(entry)))
            return false;

        //Do not try to hand-in dungeon/elite quests in instances without a group.
        if ((questTemplate->GetType() == QUEST_TYPE_ELITE || questTemplate->GetType() == QUEST_TYPE_DUNGEON) && !AI_VALUE(bool, "can fight boss"))
        {
            WorldPosition pos(bot);
            if (!this->nearestPoint(&pos)->isOverworld())
                return false;
        }
    }

    return true;
}

std::string const QuestRelationTravelDestination::getTitle()
{
    std::ostringstream out;

    if (relation == 0)
        out << "questgiver";
    else
        out << "questtaker";

    out << " " << ChatHelper::FormatWorldEntry(entry);
    return out.str();
}

bool QuestObjectiveTravelDestination::isActive(Player* bot)
{
    if (questTemplate->GetQuestLevel() > bot->GetLevel() + 1)
        return false;

    PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
    AiObjectContext* context = botAI->GetAiObjectContext();
    if (questTemplate->GetQuestLevel() + 5 > bot->GetLevel() && !AI_VALUE(bool, "can fight equal"))
        return false;

    // Check mob level
    if (getEntry() > 0)
    {
        CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(getEntry());
        if (cInfo && (int)cInfo->maxlevel - (int)bot->GetLevel() > 4)
            return false;

        // Do not try to hand-in dungeon/elite quests in instances without a group.
        if (cInfo->rank > CREATURE_ELITE_NORMAL)
        {
            WorldPosition pos(bot);
            if (!this->nearestPoint(const_cast<WorldPosition*>(&pos))->isOverworld() &&
                !AI_VALUE(bool, "can fight boss"))
                return false;

            if (!AI_VALUE(bool, "can fight elite"))
                return false;
        }
    }

    if (questTemplate->GetType() == QUEST_TYPE_ELITE && !AI_VALUE(bool, "can fight elite"))
        return false;

    if (!sTravelMgr->getObjectiveStatus(bot, questTemplate, objective))
        return false;

    WorldPosition botPos(bot);

    if (getEntry() > 0 && !isOut(&botPos))
    {
        GuidVector targets = AI_VALUE(GuidVector, "possible targets");

        for (auto& target : targets)
            if (target.GetEntry() == getEntry() && target.IsCreature() && botAI->GetCreature(target) &&
                botAI->GetCreature(target)->IsAlive())
                return true;

        return false;
    }

    return true;
}

std::string const QuestObjectiveTravelDestination::getTitle()
{
    std::ostringstream out;

    out << "objective " << objective;

    if (itemId)
        out << " loot " << ChatHelper::FormatItem(sObjectMgr->GetItemTemplate(itemId), 0, 0) << " from";
    else
        out << " to kill";

    out << " " << ChatHelper::FormatWorldEntry(entry);
    return out.str();
}

bool RpgTravelDestination::isActive(Player* bot)
{
    PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
    AiObjectContext* context = botAI->GetAiObjectContext();

    CreatureTemplate const* cInfo = GetCreatureTemplate();
    if (!cInfo)
        return false;

    bool isUsefull = false;

    if (cInfo->npcflag & UNIT_NPC_FLAG_VENDOR)
        if (AI_VALUE2_LAZY(bool, "group or", "should sell,can sell,following party,near leader"))
            isUsefull = true;

    if (cInfo->npcflag & UNIT_NPC_FLAG_REPAIR)
        if (AI_VALUE2_LAZY(bool, "group or", "should repair,can repair,following party,near leader"))
            isUsefull = true;

    if (!isUsefull)
        return false;

    // Once the target rpged with it is added to the ignore list. We can now move on.
    GuidSet& ignoreList = GET_PLAYERBOT_AI(bot)->GetAiObjectContext()->GetValue<GuidSet&>("ignore rpg target")->Get();

    for (ObjectGuid const guid : ignoreList)
    {
        if (guid.GetEntry() == getEntry())
        {
            return false;
        }
    }

    FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(cInfo->faction);
    ReputationRank reaction = bot->GetReputationRank(factionEntry->faction);

    return reaction > REP_NEUTRAL;
}

CreatureTemplate const* RpgTravelDestination::GetCreatureTemplate() { return sObjectMgr->GetCreatureTemplate(entry); }

std::string const RpgTravelDestination::getTitle()
{
    std::ostringstream out;

    if(entry > 0)
        out << "rpg npc ";

    out << " " << ChatHelper::FormatWorldEntry(entry);

    return out.str();
}

bool ExploreTravelDestination::isActive(Player* bot)
{
    AreaTableEntry const* area = sAreaTableStore.LookupEntry(areaId);

    if (area->area_level && (uint32)area->area_level > bot->GetLevel() && bot->GetLevel() < DEFAULT_MAX_LEVEL)
        return false;

    if (area->exploreFlag == 0xffff)
        return false;

    int offset = area->exploreFlag / 32;

    uint32 val = (uint32)(1 << (area->exploreFlag % 32));
    uint32 currFields = bot->GetUInt32Value(PLAYER_EXPLORED_ZONES_1 + offset);

    return !(currFields & val);
}

// std::string const ExploreTravelDestination::getTitle()
//{
//     return points[0]->getAreaName();
// };

bool GrindTravelDestination::isActive(Player* bot)
{
    PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
    AiObjectContext* context = botAI->GetAiObjectContext();

    if (!AI_VALUE(bool, "should get money"))
        return false;

    if (AI_VALUE(bool, "should sell"))
        return false;

    CreatureTemplate const* cInfo = GetCreatureTemplate();

    int32 botLevel = bot->GetLevel();

    uint8 botPowerLevel = AI_VALUE(uint8, "durability");
    float levelMod = botPowerLevel / 500.0f;   //(0-0.2f)
    float levelBoost = botPowerLevel / 50.0f;  //(0-2.0f)

    int32 maxLevel = std::max(botLevel * (0.5f + levelMod), botLevel - 5.0f + levelBoost);

    if ((int32)cInfo->maxlevel > maxLevel)  //@lvl5 max = 3, @lvl60 max = 57
        return false;

    int32 minLevel = std::max(botLevel * (0.4f + levelMod), botLevel - 12.0f + levelBoost);

    if ((int32)cInfo->maxlevel < minLevel)  //@lvl5 min = 3, @lvl60 max = 50
        return false;

    if (!cInfo->mingold)
        return false;

    if (cInfo->rank > CREATURE_ELITE_NORMAL && !AI_VALUE(bool, "can fight elite"))
        return false;

    FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(cInfo->faction);
    ReputationRank reaction = bot->GetReputationRank(factionEntry->faction);

    return reaction < REP_NEUTRAL;
}

CreatureTemplate const* GrindTravelDestination::GetCreatureTemplate() { return sObjectMgr->GetCreatureTemplate(entry); }

std::string const GrindTravelDestination::getTitle()
{
    std::ostringstream out;

    out << "grind mob ";

    out << " " << ChatHelper::FormatWorldEntry(entry);

    return out.str();
}

bool BossTravelDestination::isActive(Player* bot)
{
    PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
    AiObjectContext* context = botAI->GetAiObjectContext();

    if (!AI_VALUE(bool, "can fight boss"))
        return false;

    CreatureTemplate const* cInfo = getCreatureTemplate();

    /*
    int32 botLevel = bot->GetLevel();

    uint8 botPowerLevel = AI_VALUE(uint8, "durability");
    float levelMod = botPowerLevel / 500.0f; //(0-0.2f)
    float levelBoost = botPowerLevel / 50.0f; //(0-2.0f)

    int32 maxLevel = botLevel + 3.0;

    if ((int32)cInfo->MaxLevel > maxLevel) //@lvl5 max = 3, @lvl60 max = 57
        return false;

    int32 minLevel = botLevel - 10;

    if ((int32)cInfo->MaxLevel < minLevel) //@lvl5 min = 3, @lvl60 max = 50
        return false;
    */

    if ((int32)cInfo->maxlevel > bot->GetLevel() + 3)
        return false;

    FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(cInfo->faction);
    ReputationRank reaction = Unit::GetFactionReactionTo(bot->GetFactionTemplateEntry(), factionEntry);

    if (reaction >= REP_NEUTRAL)
        return false;

    WorldPosition botPos(bot);
    if (!isOut(&botPos))
    {
        GuidVector targets = AI_VALUE(GuidVector, "possible targets");

        for (auto& target : targets)
            if (target.GetEntry() == getEntry() && target.IsCreature() && botAI->GetCreature(target) &&
                botAI->GetCreature(target)->IsAlive())
                return true;

        return false;
    }

    if (!AI_VALUE2(bool, "has upgrade", getEntry()))
        return false;

    return true;
}

CreatureTemplate const* BossTravelDestination::getCreatureTemplate() { return sObjectMgr->GetCreatureTemplate(entry); }

std::string const BossTravelDestination::getTitle()
{
    std::ostringstream out;
    out << "boss mob ";
    out << " " << ChatHelper::FormatWorldEntry(entry);

    return out.str();
}

TravelTarget::~TravelTarget()
{
    if (!tDestination)
        return;

    releaseVisitors();
    // sTravelMgr->botTargets.erase(std::remove(sTravelMgr->botTargets.begin(), sTravelMgr->botTargets.end(), this),
    // sTravelMgr->botTargets.end());
}

void TravelTarget::setTarget(TravelDestination* tDestination1, WorldPosition* wPosition1, bool groupCopy1)
{
    releaseVisitors();

    wPosition = wPosition1;
    tDestination = tDestination1;
    groupCopy = groupCopy1;
    forced = false;
    radius = 0;

    addVisitors();

    setStatus(TRAVEL_STATUS_TRAVEL);
}

void TravelTarget::copyTarget(TravelTarget* target)
{
    setTarget(target->tDestination, target->wPosition);
    groupCopy = target->isGroupCopy();
    forced = target->forced;
    extendRetryCount = target->extendRetryCount;
}

void TravelTarget::addVisitors()
{
    if (!visitor)
    {
        wPosition->addVisitor();
        tDestination->addVisitor();
    }

    visitor = true;
}

void TravelTarget::releaseVisitors()
{
    if (visitor)
    {
        if (tDestination)
            tDestination->remVisitor();
        if (wPosition)
            wPosition->remVisitor();
    }

    visitor = false;
}

float TravelTarget::distance(Player* bot)
{
    WorldPosition pos(bot);
    return wPosition->distance(&pos);
}

WorldPosition* TravelTarget::getPosition() { return wPosition; }

TravelDestination* TravelTarget::getDestination() { return tDestination; }

void TravelTarget::setStatus(TravelStatus status)
{
    m_status = status;
    startTime = getMSTime();

    switch (m_status)
    {
        case TRAVEL_STATUS_NONE:
        case TRAVEL_STATUS_PREPARE:
        case TRAVEL_STATUS_EXPIRED:
            statusTime = 1;
            break;
        case TRAVEL_STATUS_TRAVEL:
            statusTime = getMaxTravelTime() * 2 + sPlayerbotAIConfig->maxWaitForMove;
            break;
        case TRAVEL_STATUS_WORK:
            statusTime = tDestination->getExpireDelay();
            break;
        case TRAVEL_STATUS_COOLDOWN:
            statusTime = tDestination->getCooldownDelay();
        default:
            break;
    }
}

bool TravelTarget::isActive()
{
    if (m_status == TRAVEL_STATUS_NONE || m_status == TRAVEL_STATUS_EXPIRED || m_status == TRAVEL_STATUS_PREPARE)
        return false;

    if (forced && isTraveling())
        return true;

    if ((statusTime > 0 && startTime + statusTime < getMSTime()))
    {
        setStatus(TRAVEL_STATUS_EXPIRED);
        return false;
    }

    if (m_status == TRAVEL_STATUS_COOLDOWN)
        return true;

    if (isTraveling())
        return true;

    if (isWorking())
        return true;

    if (!tDestination->isActive(bot))  // Target has become invalid. Stop.
    {
        setStatus(TRAVEL_STATUS_COOLDOWN);
        return true;
    }

    return true;
};

uint32 TravelTarget::getMaxTravelTime() { return (1000.0 * distance(bot)) / bot->GetSpeed(MOVE_RUN); }

bool TravelTarget::isTraveling()
{
    if (m_status != TRAVEL_STATUS_TRAVEL)
        return false;

    if (!tDestination->isActive(bot) && !forced)  // Target has become invalid. Stop.
    {
        setStatus(TRAVEL_STATUS_COOLDOWN);
        return false;
    }

    WorldPosition pos(bot);

    bool HasArrived = tDestination->isIn(&pos, radius);

    if (HasArrived)
    {
        setStatus(TRAVEL_STATUS_WORK);
        return false;
    }

    if (!botAI->HasStrategy("travel", BOT_STATE_NON_COMBAT))
    {
        setTarget(sTravelMgr->nullTravelDestination, sTravelMgr->nullWorldPosition, true);
        return false;
    }

    return true;
}

bool TravelTarget::isWorking()
{
    if (m_status != TRAVEL_STATUS_WORK)
        return false;

    if (!tDestination->isActive(bot))  // Target has become invalid. Stop.
    {
        setStatus(TRAVEL_STATUS_COOLDOWN);
        return false;
    }

    WorldPosition pos(bot);

    /*
    bool HasLeft = tDestination->isOut(&pos);

    if (HasLeft)
    {
        setStatus(TRAVEL_STATUS_TRAVEL);
        return false;
    }
    */

    if (!botAI->HasStrategy("travel", BOT_STATE_NON_COMBAT))
    {
        setTarget(sTravelMgr->nullTravelDestination, sTravelMgr->nullWorldPosition, true);
        return false;
    }

    return true;
}

bool TravelTarget::isPreparing()
{
    if (m_status != TRAVEL_STATUS_PREPARE)
        return false;

    return true;
}

TravelState TravelTarget::getTravelState()
{
    if (!tDestination || tDestination->getName() == "NullTravelDestination")
        return TRAVEL_STATE_IDLE;

    if (tDestination->getName() == "QuestRelationTravelDestination")
    {
        if (((QuestRelationTravelDestination*)tDestination)->getRelation() == 0)
        {
            if (isTraveling() || isPreparing())
                return TRAVEL_STATE_TRAVEL_PICK_UP_QUEST;

            if (isWorking())
                return TRAVEL_STATE_WORK_PICK_UP_QUEST;
        }
        else
        {
            if (isTraveling() || isPreparing())
                return TRAVEL_STATE_TRAVEL_HAND_IN_QUEST;

            if (isWorking())
                return TRAVEL_STATE_WORK_HAND_IN_QUEST;
        }
    }
    else if (tDestination->getName() == "QuestObjectiveTravelDestination")
    {
        if (isTraveling() || isPreparing())
            return TRAVEL_STATE_TRAVEL_DO_QUEST;

        if (isWorking())
            return TRAVEL_STATE_WORK_DO_QUEST;
    }
    else if (tDestination->getName() == "RpgTravelDestination")
    {
        return TRAVEL_STATE_TRAVEL_RPG;
    }
    else if (tDestination->getName() == "ExploreTravelDestination")
    {
        return TRAVEL_STATE_TRAVEL_EXPLORE;
    }

    return TRAVEL_STATE_IDLE;
}

void TravelMgr::Clear()
{
    std::shared_lock<std::shared_mutex> lock(*HashMapHolder<Player>::GetLock());
    HashMapHolder<Player>::MapType const& m = ObjectAccessor::GetPlayers();
    for (HashMapHolder<Player>::MapType::const_iterator itr = m.begin(); itr != m.end(); ++itr)
        TravelMgr::setNullTravelTarget(itr->second);

    for (auto& quest : quests)
    {
        for (auto& dest : quest.second->questGivers)
        {
            delete dest;
        }

        for (auto& dest : quest.second->questTakers)
        {
            delete dest;
        }

        for (auto& dest : quest.second->questObjectives)
        {
            delete dest;
        }
    }

    questGivers.clear();
    quests.clear();
}

void TravelMgr::logQuestError(uint32 errorNr, Quest* quest, uint32 objective, uint32 unitId, uint32 itemId)
{
    bool logQuestErrors = false;  // For debugging.

    if (!logQuestErrors)
        return;

    std::string unitName = "<unknown>";
    CreatureTemplate const* cInfo = nullptr;
    GameObjectTemplate const* gInfo = nullptr;

    if (unitId > 0)
        cInfo = sObjectMgr->GetCreatureTemplate(unitId);
    else
        gInfo = sObjectMgr->GetGameObjectTemplate(unitId * -1);

    if (cInfo)
        unitName = cInfo->Name;
    else if (gInfo)
        unitName = gInfo->name;

    ItemTemplate const* proto = sObjectMgr->GetItemTemplate(itemId);

    if (errorNr == 1)
    {
        LOG_ERROR("playerbots", "Quest {} [{}] has {} {} [{}] but none is found in the world.",
                  quest->GetTitle().c_str(), quest->GetQuestId(), objective == 0 ? "quest giver" : "quest taker",
                  unitName.c_str(), unitId);
    }
    else if (errorNr == 2)
    {
        LOG_ERROR("playerbots", "Quest {} [{}] needs {} [{}] for objective {} but none is found in the world.",
                  quest->GetTitle().c_str(), quest->GetQuestId(), unitName.c_str(), unitId, objective);
    }
    else if (errorNr == 3)
    {
        LOG_ERROR("playerbots", "Quest {} [{}] needs itemId {} but no such item exists.", quest->GetTitle().c_str(),
                  quest->GetQuestId(), itemId);
    }
    else if (errorNr == 4)
    {
        LOG_ERROR(
            "playerbots",
            "Quest {} [{}] needs {} [{}] for loot of item {} [{}] for objective {} but none is found in the world.",
            quest->GetTitle().c_str(), quest->GetQuestId(), unitName.c_str(), unitId, proto->Name1.c_str(), itemId,
            objective);
    }
    else if (errorNr == 5)
    {
        LOG_ERROR("playerbots", "Quest {} [{}] needs item {} [{}] for objective {} but none is found in the world.",
                  quest->GetTitle().c_str(), quest->GetQuestId(), proto->Name1.c_str(), itemId, objective);
    }
    else if (errorNr == 6)
    {
        LOG_ERROR("playerbots", "Quest {} [{}] has no quest giver.", quest->GetTitle().c_str(), quest->GetQuestId());
    }
    else if (errorNr == 7)
    {
        LOG_ERROR("playerbots", "Quest {} [{}] has no quest taker.", quest->GetTitle().c_str(), quest->GetQuestId());
    }
    else if (errorNr == 8)
    {
        LOG_ERROR("playerbots", "Quest {} [{}] has no quest viable quest objective.", quest->GetTitle().c_str(),
                  quest->GetQuestId());
    }
}

void TravelMgr::LoadQuestTravelTable()
{
    if (!sTravelMgr->quests.empty())
        return;

    // Clearing store (for reloading case)
    Clear();

    /* remove this
    questGuidMap cQuestMap = GAI_VALUE(questGuidMap,"quest objects");

    for (auto cQuest : cQuestMap)
    {
        LOG_INFO("playerbots", "[Quest id: {}]", cQuest.first);

        for (auto cObj : cQuest.second)
        {
            LOG_INFO("playerbots", " [Objective type: {}]", cObj.first);

            for (auto cCre : cObj.second)
            {
                LOG_INFO("playerbots", " {} {}", cCre.GetTypeName().c_str(), cCre.GetEntry());
            }
        }
    }
    */

    struct unit
    {
        uint32 type;
        uint32 entry;
        uint32 map;
        float x;
        float y;
        float z;
        float o;
        uint32 c;
    } t_unit;
    std::vector<unit> units;

    /*struct relation
    {
        uint32 type;
        uint32 role;
        uint32 entry;
        uint32 questId;
    } t_rel;
    std::vector<relation> relations;

    struct loot
    {
        uint32 type;
        uint32 entry;
        uint32 item;
    } t_loot;
    std::vector<loot> loots;*/

    ObjectMgr::QuestMap const& questMap = sObjectMgr->GetQuestTemplates();
    std::vector<uint32> questIds;
    std::unordered_map<uint32, uint32> entryCount;

    for (auto& quest : questMap)
        questIds.push_back(quest.first);

    sort(questIds.begin(), questIds.end());

    LOG_INFO("playerbots", "Loading units locations.");
    for (auto& creatureData : WorldPosition().getCreaturesNear())
    {
        t_unit.type = 0;
        t_unit.entry = creatureData->id1;
        t_unit.map = creatureData->mapid;
        t_unit.x = creatureData->posX;
        t_unit.y = creatureData->posY;
        t_unit.z = creatureData->posZ;
        t_unit.o = creatureData->orientation;

        entryCount[creatureData->id1]++;

        units.push_back(t_unit);
    }

    for (auto& unit : units)
    {
        unit.c = entryCount[unit.entry];
    }

    LOG_INFO("playerbots", "Loading game object locations.");
    for (auto& gameobjectData : WorldPosition().getGameObjectsNear())
    {
        t_unit.type = 1;
        t_unit.entry = gameobjectData->id;
        t_unit.map = gameobjectData->mapid;
        t_unit.x = gameobjectData->posX;
        t_unit.y = gameobjectData->posY;
        t_unit.z = gameobjectData->posZ;
        t_unit.o = gameobjectData->orientation;
        t_unit.c = 1;

        units.push_back(t_unit);
    }

    /*
    //                          0    1  2   3          4          5          6           7     8
    std::string const query = "SELECT 0,guid,id,map,position_x,position_y,position_z,orientation, (SELECT COUNT(*) FROM
    creature k WHERE c.id1 = k.id1) FROM creature c UNION ALL SELECT
    1,guid,id,map,position_x,position_y,position_z,orientation, (SELECT COUNT(*) FROM gameobject h WHERE h.id = g.id)
    FROM gameobject g";

    QueryResult result = WorldDatabase.Query(query.c_str());
    if (result)
    {
        do
        {
            Field* fields = result->Fetch();

            t_unit.type = fields[0].Get<uint32>();
            t_unit.guid = fields[1].Get<uint32>();
            t_unit.entry = fields[2].Get<uint32>();
            t_unit.map = fields[3].Get<uint32>();
            t_unit.x = fields[4].Get<float>();
            t_unit.y = fields[5].Get<float>();
            t_unit.z = fields[6].Get<float>();
            t_unit.o = fields[7].Get<float>();
            t_unit.c = uint32(fields[8].Get<uint64>());

            units.push_back(t_unit);

        } while (result->NextRow());

        LOG_INFO("playerbots", ">> Loaded {} units locations.", units.size());
    }
    else
    {
        LOG_ERROR("playerbots", ">> Error loading units locations.");
    }

    query = "SELECT 0, 0, id, quest FROM creature_queststarter UNION ALL SELECT 0, 1, id, quest FROM creature_questender
    UNION ALL SELECT 1, 0, id, quest FROM gameobject_queststarter UNION ALL SELECT 1, 1, id, quest FROM
    gameobject_questender"; result = WorldDatabase.Query(query.c_str());

    if (result)
    {
        do
        {
            Field* fields = result->Fetch();

            t_rel.type = fields[0].Get<uint32>();
            t_rel.role = fields[1].Get<uint32>();
            t_rel.entry = fields[2].Get<uint32>();
            t_rel.questId = fields[3].Get<uint32>();

            relations.push_back(t_rel);

        } while (result->NextRow());

        LOG_INFO("playerbots", ">> Loaded {} relations.", relations.size());
    }
    else
    {
        LOG_ERROR("playerbots", ">> Error loading relations.");
    }

    query = "SELECT 0, ct.entry, item FROM creature_template ct JOIN creature_loot_template clt ON (ct.lootid =
    clt.entry) UNION ALL SELECT 0, entry, item FROM npc_vendor UNION ALL SELECT 1, gt.entry, item FROM
    gameobject_template gt JOIN gameobject_loot_template glt ON (gt.TYPE = 3 AND gt.DATA1 = glt.entry)"; result =
    WorldDatabase.Query(query.c_str());

    if (result)
    {
        do
        {
            Field* fields = result->Fetch();

            t_loot.type = fields[0].Get<uint32>();
            t_loot.entry = fields[1].Get<uint32>();
            t_loot.item = fields[2].Get<uint32>();

            loots.push_back(t_loot);

        } while (result->NextRow());

        LOG_INFO("playerbots", ">> Loaded {} loot lists.", loots.size());
    }
    else
    {
        LOG_ERROR("playerbots", ">> Error loading loot lists.");
    }
    */

    LOG_INFO("playerbots", "Loading quest data.");

    bool loadQuestData = true;
    if (loadQuestData)
    {
        questGuidpMap questMap = GAI_VALUE(questGuidpMap, "quest guidp map");

        for (auto& q : questMap)
        {
            uint32 questId = q.first;

            QuestContainer* container = new QuestContainer;

            for (auto& r : q.second)
            {
                uint32 flag = r.first;

                for (auto& e : r.second)
                {
                    int32 entry = e.first;

                    QuestTravelDestination* loc;
                    std::vector<QuestTravelDestination*> locs;

                    if (flag & (uint32)QuestRelationFlag::questGiver)
                    {
                        loc = new QuestRelationTravelDestination(
                            questId, entry, 0, sPlayerbotAIConfig->tooCloseDistance, sPlayerbotAIConfig->sightDistance);
                        loc->setExpireDelay(5 * 60 * 1000);
                        loc->setMaxVisitors(15, 0);
                        container->questGivers.push_back(loc);
                        locs.push_back(loc);
                    }
                    if (flag & (uint32)QuestRelationFlag::questTaker)
                    {
                        loc = new QuestRelationTravelDestination(
                            questId, entry, 1, sPlayerbotAIConfig->tooCloseDistance, sPlayerbotAIConfig->sightDistance);
                        loc->setExpireDelay(5 * 60 * 1000);
                        loc->setMaxVisitors(15, 0);
                        container->questTakers.push_back(loc);
                        locs.push_back(loc);
                    }
                    else
                    {
                        uint32 objective = 0;
                        if (flag & (uint32)QuestRelationFlag::objective1)
                            objective = 0;
                        else if (flag & (uint32)QuestRelationFlag::objective2)
                            objective = 1;
                        else if (flag & (uint32)QuestRelationFlag::objective3)
                            objective = 2;
                        else if (flag & (uint32)QuestRelationFlag::objective4)
                            objective = 3;

                        loc = new QuestObjectiveTravelDestination(questId, entry, objective,
                                                                  sPlayerbotAIConfig->tooCloseDistance,
                                                                  sPlayerbotAIConfig->sightDistance);
                        loc->setExpireDelay(1 * 60 * 1000);
                        loc->setMaxVisitors(100, 1);
                        container->questObjectives.push_back(loc);
                        locs.push_back(loc);
                    }

                    for (auto& guidP : e.second)
                    {
                        WorldPosition point = guidP;
                        for (auto tLoc : locs)
                        {
                            tLoc->addPoint(&point);
                        }
                    }
                }
            }

            if (!container->questTakers.empty())
            {
                quests.insert(std::make_pair(questId, container));

                for (auto loc : container->questGivers)
                    questGivers.push_back(loc);
            }
        }
    }

    /*
    if (loadQuestData && false)
    {
        for (auto& questId : questIds)
        {
            Quest* quest = questMap.find(questId)->second;

            QuestContainer* container = new QuestContainer;
            QuestTravelDestination* loc = nullptr;
            WorldPosition point;

            bool hasError = false;

            //Relations
            for (auto& r : relations)
            {
                if (questId != r.questId)
                    continue;

                int32 entry = r.type == 0 ? r.entry : r.entry * -1;

                loc = new QuestRelationTravelDestination(r.questId, entry, r.role, sPlayerbotAIConfig->tooCloseDistance,
    sPlayerbotAIConfig->sightDistance); loc->setExpireDelay(5 * 60 * 1000); loc->setMaxVisitors(15, 0);

                for (auto& u : units)
                {
                    if (r.type != u.type || r.entry != u.entry)
                        continue;

                    int32 guid = u.type == 0 ? u.guid : u.guid * -1;

                    point = WorldPosition(u.map, u.x, u.y, u.z, u.o);
                    loc->addPoint(&point);
                }

                if (loc->getPoints(0).empty())
                {
                    logQuestError(1, quest, r.role, entry);
                    delete loc;
                    continue;
                }


                if (r.role == 0)
                {
                    container->questGivers.push_back(loc);
                }
                else
                    container->questTakers.push_back(loc);

            }

            //Mobs
            for (uint32 i = 0; i < 4; i++)
            {
                if (quest->RequiredNpcOrGoCount[i] == 0)
                    continue;

                uint32 reqEntry = quest->RequiredNpcOrGo[i];

                loc = new QuestObjectiveTravelDestination(questId, reqEntry, i, sPlayerbotAIConfig->tooCloseDistance,
    sPlayerbotAIConfig->sightDistance); loc->setExpireDelay(1 * 60 * 1000); loc->setMaxVisitors(100, 1);

                for (auto& u : units)
                {
                    int32 entry = u.type == 0 ? u.entry : u.entry * -1;

                    if (entry != reqEntry)
                        continue;

                    int32 guid = u.type == 0 ? u.guid : u.guid * -1;

                    point = WorldPosition(u.map, u.x, u.y, u.z, u.o);
                    loc->addPoint(&point);
                }

                if (loc->getPoints(0).empty())
                {
                    logQuestError(2, quest, i, reqEntry);

                    delete loc;
                    hasError = true;
                    continue;
                }

                container->questObjectives.push_back(loc);
            }

            //Loot
            for (uint32 i = 0; i < 4; i++)
            {
                if (quest->RequiredItemCount[i] == 0)
                    continue;

                ItemTemplate const* proto = sObjectMgr->GetItemTemplate(quest->RequiredItemId[i]);
                if (!proto)
                {
                    logQuestError(3, quest, i, 0, quest->RequiredItemId[i]);
                    hasError = true;
                    continue;
                }

                uint32 foundLoot = 0;

                for (auto& l : loots)
                {
                    if (l.item != quest->RequiredItemId[i])
                        continue;

                    int32 entry = l.type == 0 ? l.entry : l.entry * -1;

                    loc = new QuestObjectiveTravelDestination(questId, entry, i, sPlayerbotAIConfig->tooCloseDistance,
    sPlayerbotAIConfig->sightDistance, l.item); loc->setExpireDelay(1 * 60 * 1000); loc->setMaxVisitors(100, 1);

                    for (auto& u : units)
                    {
                        if (l.type != u.type || l.entry != u.entry)
                            continue;

                        int32 guid = u.type == 0 ? u.guid : u.guid * -1;

                        point = WorldPosition(u.map, u.x, u.y, u.z, u.o);
                        loc->addPoint(&point);
                    }

                    if (loc->getPoints(0).empty())
                    {
                        logQuestError(4, quest, i, entry, quest->RequiredItemId[i]);
                        delete loc;
                        continue;
                    }

                    container->questObjectives.push_back(loc);

                    foundLoot++;
                }

                if (foundLoot == 0)
                {
                    hasError = true;
                    logQuestError(5, quest, i, 0, quest->RequiredItemId[i]);
                }
            }

            if (container->questTakers.empty())
                logQuestError(7, quest);

            if (!container->questGivers.empty() || !container->questTakers.empty() || hasError)
            {
                quests.insert(std::make_pair(questId, container));

                for (auto loc : container->questGivers)
                    questGivers.push_back(loc);
            }
        }

        LOG_INFO("playerbots", ">> Loaded {} quest details.", questIds.size());
    }
    */

    WorldPosition point;

    LOG_INFO("playerbots", "Loading Rpg, Grind and Boss locations.");

    // Rpg locations
    for (auto& u : units)
    {
        RpgTravelDestination* rLoc;
        GrindTravelDestination* gLoc;
        BossTravelDestination* bLoc;

        if (u.type != 0)
            continue;

        CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(u.entry);
        if (!cInfo)
            continue;

        std::vector<uint32> allowedNpcFlags;

        allowedNpcFlags.push_back(UNIT_NPC_FLAG_INNKEEPER);
        allowedNpcFlags.push_back(UNIT_NPC_FLAG_GOSSIP);
        allowedNpcFlags.push_back(UNIT_NPC_FLAG_QUESTGIVER);
        allowedNpcFlags.push_back(UNIT_NPC_FLAG_FLIGHTMASTER);
        allowedNpcFlags.push_back(UNIT_NPC_FLAG_BANKER);
        allowedNpcFlags.push_back(UNIT_NPC_FLAG_AUCTIONEER);
        allowedNpcFlags.push_back(UNIT_NPC_FLAG_STABLEMASTER);
        allowedNpcFlags.push_back(UNIT_NPC_FLAG_PETITIONER);
        allowedNpcFlags.push_back(UNIT_NPC_FLAG_TABARDDESIGNER);

        allowedNpcFlags.push_back(UNIT_NPC_FLAG_TRAINER);
        allowedNpcFlags.push_back(UNIT_NPC_FLAG_VENDOR);
        allowedNpcFlags.push_back(UNIT_NPC_FLAG_REPAIR);

        point = WorldPosition(u.map, u.x, u.y, u.z, u.o);

        for (std::vector<uint32>::iterator i = allowedNpcFlags.begin(); i != allowedNpcFlags.end(); ++i)
        {
            if ((cInfo->npcflag & *i) != 0)
            {
                rLoc = new RpgTravelDestination(u.entry, sPlayerbotAIConfig->tooCloseDistance,
                                                sPlayerbotAIConfig->sightDistance);
                rLoc->setExpireDelay(5 * 60 * 1000);
                rLoc->setMaxVisitors(15, 0);

                rLoc->addPoint(&point);
                rpgNpcs.push_back(rLoc);
                break;
            }
        }

        if (cInfo->mingold > 0)
        {
            gLoc = new GrindTravelDestination(u.entry, sPlayerbotAIConfig->tooCloseDistance,
                                              sPlayerbotAIConfig->sightDistance);
            gLoc->setExpireDelay(5 * 60 * 1000);
            gLoc->setMaxVisitors(100, 0);

            point = WorldPosition(u.map, u.x, u.y, u.z, u.o);
            gLoc->addPoint(&point);
            grindMobs.push_back(gLoc);
        }

        if (cInfo->rank == 3 || (cInfo->rank == 1 && !point.isOverworld() && u.c == 1))
        {
            std::string const nodeName = cInfo->Name;

            bLoc = new BossTravelDestination(u.entry, sPlayerbotAIConfig->tooCloseDistance,
                                             sPlayerbotAIConfig->sightDistance);
            bLoc->setExpireDelay(5 * 60 * 1000);
            bLoc->setMaxVisitors(0, 0);

            bLoc->addPoint(&point);
            bossMobs.push_back(bLoc);
        }
    }

    LOG_INFO("playerbots", "Loading Explore locations.");

    // Explore points
    for (auto& u : units)
    {
        ExploreTravelDestination* loc;

        WorldPosition point = WorldPosition(u.map, u.x, u.y, u.z, u.o);
        AreaTableEntry const* area = point.getArea();

        if (!area)
            continue;

        if (!area->exploreFlag)
            continue;

        if (u.type == 1)
            continue;

        auto iloc = exploreLocs.find(area->ID);

        if (iloc == exploreLocs.end())
        {
            loc = new ExploreTravelDestination(area->ID, sPlayerbotAIConfig->tooCloseDistance,
                                               sPlayerbotAIConfig->sightDistance);
            loc->setMaxVisitors(1000, 0);
            loc->setCooldownDelay(1000);
            loc->setExpireDelay(1000);
            loc->setTitle(area->area_name[0]);
            exploreLocs.insert_or_assign(area->ID, loc);
        }
        else
        {
            loc = iloc->second;
        }

        loc->addPoint(&point);
    }

    // Clear these logs files
    sPlayerbotAIConfig->openLog("zones.csv", "w");
    sPlayerbotAIConfig->openLog("creatures.csv", "w");
    sPlayerbotAIConfig->openLog("gos.csv", "w");
    sPlayerbotAIConfig->openLog("bot_movement.csv", "w");
    sPlayerbotAIConfig->openLog("bot_pathfinding.csv", "w");
    sPlayerbotAIConfig->openLog("pathfind_attempt.csv", "w");
    sPlayerbotAIConfig->openLog("pathfind_attempt_point.csv", "w");
    sPlayerbotAIConfig->openLog("pathfind_result.csv", "w");
    sPlayerbotAIConfig->openLog("load_map_grid.csv", "w");
    sPlayerbotAIConfig->openLog("strategy.csv", "w");

    sPlayerbotAIConfig->openLog("unload_grid.csv", "w");
    sPlayerbotAIConfig->openLog("unload_obj.csv", "w");

    sTravelNodeMap->loadNodeStore();

    sTravelNodeMap->generateAll();

    /*
    bool fullNavPointReload = false;
    bool storeNavPointReload = true;

   if (!fullNavPointReload && true)
        TravelNodeStore::loadNodes();

    //sTravelNodeMap->loadNodeStore();

    for (auto node : sTravelNodeMap->getNodes())
    {
        node->setLinked(true);
    }

    bool reloadNavigationPoints = false || fullNavPointReload || storeNavPointReload;

    if (reloadNavigationPoints)
    {
        LOG_INFO("playerbots", "Loading navigation points");

        //Npc nodes

        WorldPosition pos;

        for (auto& u : units)
        {
            if (u.type != 0)
                continue;

            CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(u.entry);
            if (!cInfo)
                continue;

            std::vector<uint32> allowedNpcFlags;

            allowedNpcFlags.push_back(UNIT_NPC_FLAG_INNKEEPER);
            allowedNpcFlags.push_back(UNIT_NPC_FLAG_FLIGHTMASTER);
            //allowedNpcFlags.push_back(UNIT_NPC_FLAG_QUESTGIVER);

            for (std::vector<uint32>::iterator i = allowedNpcFlags.begin(); i != allowedNpcFlags.end(); ++i)
            {
                if ((cInfo->npcflag & *i) != 0)
                {
                    pos = WorldPosition(u.map, u.x, u.y, u.z, u.o);

                    std::string const nodeName = pos.getAreaName(false);
                    if ((cInfo->npcflag & UNIT_NPC_FLAG_INNKEEPER) != 0)
                        nodeName += " innkeeper";
                    else
                        nodeName += " flightMaster";

                    sTravelNodeMap->addNode(&pos, nodeName, true, true);

                    break;
                }
            }
        }

        //Build flight paths
        for (uint32 i = 0; i < sTaxiPathStore.GetNumRows(); ++i)
        {
            TaxiPathEntry const* taxiPath = sTaxiPathStore.LookupEntry(i);

            if (!taxiPath)
                continue;

            TaxiNodesEntry const* startTaxiNode = sTaxiNodesStore.LookupEntry(taxiPath->from);
            if (!startTaxiNode)
                continue;

            TaxiNodesEntry const* endTaxiNode = sTaxiNodesStore.LookupEntry(taxiPath->to);
            if (!endTaxiNode)
                continue;

            TaxiPathNodeList const& nodes = sTaxiPathNodesByPath[taxiPath->ID];
            if (nodes.empty())
                continue;

            WorldPosition startPos(startTaxiNode->map_id, startTaxiNode->x, startTaxiNode->y, startTaxiNode->z);
            WorldPosition endPos(endTaxiNode->map_id, endTaxiNode->x, endTaxiNode->y, endTaxiNode->z);

            TravelNode* startNode = sTravelNodeMap->getNode(&startPos, nullptr, 15.0f);
            TravelNode* endNode = sTravelNodeMap->getNode(&endPos, nullptr, 15.0f);

            if (!startNode || !endNode)
                continue;

            std::vector<WorldPosition> ppath;

            for (auto& n : nodes)
                ppath.push_back(WorldPosition(n->mapid, n->x, n->y, n->z, 0.0));

            float totalTime = startPos.getPathLength(ppath) / (450 * 8.0f);

            TravelNodePath travelPath(0.1f, totalTime, (uint8) TravelNodePathType::flightPath, i, true);
            travelPath.setPath(ppath);

            startNode->setPathTo(endNode, travelPath);
        }

        //Unique bosses
        for (auto& u : units)
        {
            if (u.type != 0)
                continue;

            CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(u.entry);
            if (!cInfo)
                continue;

            pos = WorldPosition(u.map, u.x, u.y, u.z, u.o);

            if (cInfo->rank == 3 || (cInfo->rank == 1 && !pos.isOverworld() && u.c == 1))
            {
                std::string const nodeName = cInfo->Name;
                sTravelNodeMap->addNode(&pos, nodeName, true, true);
            }
        }

        std::map<uint8, std::string> startNames;
        startNames[RACE_HUMAN] = "Human";
        startNames[RACE_ORC] = "Orc and Troll";
        startNames[RACE_DWARF] = "Dwarf and Gnome";
        startNames[RACE_NIGHTELF] = "Night Elf";
        startNames[RACE_UNDEAD_PLAYER] = "Undead";
        startNames[RACE_TAUREN] = "Tauren";
        startNames[RACE_GNOME] = "Dwarf and Gnome";
        startNames[RACE_TROLL] = "Orc and Troll";
        startNames[RACE_DRAENEI] = "Draenei";
        startNames[RACE_BLOODELF] = "Blood Elf";

        for (uint32 i = 0; i < MAX_RACES; i++)
        {
            for (uint32 j = 0; j < MAX_CLASSES; j++)
            {
                PlayerInfo const* info = sObjectMgr->GetPlayerInfo(i, j);
                if (!info)
                    continue;

                pos = WorldPosition(info->mapId, info->positionX, info->positionY, info->positionZ, info->orientation);

                std::string const nodeName = startNames[i] + " start";
                sTravelNodeMap->addNode(&pos, nodeName, true, true);
            }
        }

        //Transports
        GameObjectTemplateContainer const* goTemplates = sObjectMgr->GetGameObjectTemplates();
        for (auto const& iter : *goTemplates)
        {
            GameObjectTemplate const* data = &iter.second;
            if (data && (data->type == GAMEOBJECT_TYPE_TRANSPORT || data->type == GAMEOBJECT_TYPE_MO_TRANSPORT))
            {
                TransportAnimation const* animation = sTransportMgr->GetTransportAnimInfo(iter.first);

                uint32 pathId = data->moTransport.taxiPathId;
                float moveSpeed = data->moTransport.moveSpeed;
                if (pathId >= sTaxiPathNodesByPath.size())
                    continue;

                TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];

                std::vector<WorldPosition> ppath;
                TravelNode* prevNode = nullptr;

                //Elevators/Trams
                if (path.empty())
                {
                    if (animation)
                    {
                        TransportPathContainer aPath = animation->Path;
                        float timeStart;

                        for (auto& u : units)
                        {
                            if (u.type != 1)
                                continue;

                            if (u.entry != iter.first)
                                continue;

                            prevNode = nullptr;
                            WorldPosition lPos = WorldPosition(u.map, 0, 0, 0, 0);

                            for (auto& p : aPath)
                            {
                                float dx = cos(u.o) * p.second->X - sin(u.o) * p.second->Y;
                                float dy = sin(u.o) * p.second->X + cos(u.o) * p.second->Y;
                                WorldPosition pos = WorldPosition(u.map, u.x + dx, u.y + dy, u.z + p.second->Z, u.o);

                                if (prevNode)
                                {
                                    ppath.push_back(pos);
                                }

                                if (pos.distance(&lPos) == 0)
                                {
                                    TravelNode* node = sTravelNodeMap->addNode(&pos, data->name, true, true, true,
   iter.first);

                                    if (!prevNode)
                                    {
                                        ppath.push_back(pos);
                                        timeStart = p.second->TimeSeg;
                                    }
                                    else
                                    {
                                        float totalTime = (p.second->TimeSeg - timeStart) / 1000.0f;
                                        TravelNodePath travelPath(0.1f, totalTime, (uint8)
   TravelNodePathType::transport, entry, true); node->setPathTo(prevNode, travelPath); ppath.clear();
                                        ppath.push_back(pos);
                                        timeStart = p.second->TimeSeg;
                                    }

                                    prevNode = node;
                                }

                                lPos = pos;
                            }

                            if (prevNode)
                            {
                                for (auto& p : aPath)
                                {
                                    float dx = cos(u.o) * p.second->X - sin(u.o) * p.second->Y;
                                    float dy = sin(u.o) * p.second->X + cos(u.o) * p.second->Y;
                                    WorldPosition pos = WorldPosition(u.map, u.x + dx, u.y + dy, u.z + p.second->Z,
   u.o);

                                    ppath.push_back(pos);

                                    if (pos.distance(&lPos) == 0)
                                    {
                                        TravelNode* node = sTravelNodeMap->addNode(&pos, data->name, true, true, true,
   iter.first); if (node != prevNode)
                                        {
                                            float totalTime = (p.second->TimeSeg - timeStart) / 1000.0f;
                                            TravelNodePath travelPath(0.1f, totalTime, (uint8)
   TravelNodePathType::transport, entry, true); travelPath.setPath(ppath); node->setPathTo(prevNode, travelPath);
                                            ppath.clear();
                                            ppath.push_back(pos);
                                            timeStart = p.second->TimeSeg;
                                        }
                                    }

                                    lPos = pos;
                                }
                            }

                            ppath.clear();
                        }
                    }
                }
                else //Boats/Zepelins
                {
                    //Loop over the path and connect stop locations.
                    for (auto& p : path)
                    {
                        WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);

                        //if (data->displayId == 3015)
                        //    pos.setZ(pos.getZ() + 6.0f);
                        //else if(data->displayId == 3031)
                       //     pos.setZ(pos.getZ() - 17.0f);

                        if (prevNode)
                        {
                            ppath.push_back(pos);
                        }

                        if (p->delay > 0)
                        {
                            TravelNode* node = sTravelNodeMap->addNode(&pos, data->name, true, true, true, iter.first);

                            if (!prevNode)
                            {
                                ppath.push_back(pos);
                            }
                            else
                            {
                                TravelNodePath travelPath(0.1f, 0.0, (uint8) TravelNodePathType::transport, entry,
   true); travelPath.setPathAndCost(ppath, moveSpeed); node->setPathTo(prevNode, travelPath); ppath.clear();
                                ppath.push_back(pos);
                            }

                            prevNode = node;
                        }
                    }

                    if (prevNode)
                    {
                        //Continue from start until first stop and connect to end.
                        for (auto& p : path)
                        {
                            WorldPosition pos = WorldPosition(p->mapid, p->x, p->y, p->z, 0);

                            //if (data->displayId == 3015)
                            //    pos.setZ(pos.getZ() + 6.0f);
                            //else if (data->displayId == 3031)
                            //    pos.setZ(pos.getZ() - 17.0f);

                            ppath.push_back(pos);

                            if (p->delay > 0)
                            {
                                TravelNode* node = sTravelNodeMap->getNode(&pos, nullptr, 5.0f);
                                if (node != prevNode)
                                {
                                    TravelNodePath travelPath(0.1f, 0.0, (uint8) TravelNodePathType::transport, entry,
   true); travelPath.setPathAndCost(ppath, moveSpeed); node->setPathTo(prevNode, travelPath);
                                }
                            }
                        }
                    }

                    ppath.clear();
                }
            }
        }

        //Zone means
        for (auto& loc : exploreLocs)
        {
            std::vector<WorldPosition*> points;

            for (auto p : loc.second->getPoints(true))
                if (!p->isUnderWater())
                    points.push_back(p);

            if (points.empty())
                points = loc.second->getPoints(true);

            WorldPosition  pos = WorldPosition(points, WP_MEAN_CENTROID);

            TravelNode* node = sTravelNodeMap->addNode(&pos, pos.getAreaName(), true, true, false);
        }

        LOG_INFO("playerbots", ">> Loaded {} navigation points.", sTravelNodeMap->getNodes().size());
    }

    sTravelNodeMap->calcMapOffset();
    loadMapTransfers();
    */

    /*
    bool preloadNodePaths = false || fullNavPointReload || storeNavPointReload;             //Calculate paths using
    PathGenerator. bool preloadReLinkFullyLinked = false || fullNavPointReload || storeNavPointReload;      //Retry
    nodes that are fully linked. bool preloadUnlinkedPaths = false || fullNavPointReload;        //Try to connect points
    currently unlinked. bool preloadWorldPaths = true;            //Try to load paths in overworld. bool
    preloadInstancePaths = true;         //Try to load paths in instances. bool preloadSubPrint = false; //Print output
    every 2%.

    if (preloadNodePaths)
    {
        std::unordered_map<uint32, Map*> instances;

        //PathGenerator
        std::vector<WorldPosition> ppath;

        uint32 cur = 0, max = sTravelNodeMap->getNodes().size();

        for (auto& startNode : sTravelNodeMap->getNodes())
        {
            if (!preloadReLinkFullyLinked && startNode->isLinked())
                continue;

            for (auto& endNode : sTravelNodeMap->getNodes())
            {
                if (startNode == endNode)
                    continue;

                if (startNode->getPosition()->isOverworld() && !preloadWorldPaths)
                    continue;

                if (!startNode->getPosition()->isOverworld() && !preloadInstancePaths)
                    continue;

                if (startNode->hasCompletePathTo(endNode))
                    continue;

                if (!preloadUnlinkedPaths && !startNode->hasLinkTo(endNode))
                    continue;

                if (startNode->getMapId() != endNode->getMapId())
                    continue;

                //if (preloadUnlinkedPaths && !startNode->hasLinkTo(endNode) && startNode->isUselessLink(endNode))
                //    continue;

                startNode->buildPath(endNode, nullptr, false);

                //if (startNode->hasLinkTo(endNode) && !startNode->getPathTo(endNode)->getComplete())
                    //startNode->removeLinkTo(endNode);
            }

            startNode->setLinked(true);

            cur++;

            if (preloadSubPrint && (cur * 50) / max > ((cur - 1) * 50) / max)
            {
                sTravelNodeMap->printMap();
                sTravelNodeMap->printNodeStore();
            }
        }

        if (!preloadSubPrint)
        {
            sTravelNodeMap->printNodeStore();
            sTravelNodeMap->printMap();
        }

        LOG_INFO("playerbots", ">> Loaded paths for {} nodes.", sTravelNodeMap->getNodes().size());
    }

    bool removeLowLinkNodes = false || fullNavPointReload || storeNavPointReload;

    if (removeLowLinkNodes)
    {
        std::vector<TravelNode*> goodNodes;
        std::vector<TravelNode*> remNodes;
        for (auto& node : sTravelNodeMap->getNodes())
        {
            if (!node->getPosition()->isOverworld())
                continue;

            if (std::find(goodNodes.begin(), goodNodes.end(), node) != goodNodes.end())
                continue;

            if (std::find(remNodes.begin(), remNodes.end(), node) != remNodes.end())
                continue;

            std::vector<TravelNode*> nodes = node->getNodeMap(true);

            if (nodes.size() < 5)
                remNodes.insert(remNodes.end(), nodes.begin(), nodes.end());
            else
                goodNodes.insert(goodNodes.end(), nodes.begin(), nodes.end());
        }

        for (auto& node : remNodes)
            sTravelNodeMap->removeNode(node);

        LOG_INFO("playerbots", ">> Checked {} nodes.", sTravelNodeMap->getNodes().size());
    }

    bool cleanUpNodeLinks = false || fullNavPointReload || storeNavPointReload;
    bool cleanUpSubPrint = false;             //Print output every 2%.

    if (cleanUpNodeLinks)
    {
        //Routes
        uint32 cur = 0;
        uint32 max = sTravelNodeMap->getNodes().size();

        //Clean up node links
        for (auto& startNode : sTravelNodeMap->getNodes())
        {
             startNode->cropUselessLinks();

             cur++;
             if (cleanUpSubPrint && (cur * 10) / max > ((cur - 1) * 10) / max)
             {
                 sTravelNodeMap->printMap();
                 sTravelNodeMap->printNodeStore();
             }
        }

        LOG_INFO("playerbots", ">> Cleaned paths for {} nodes.", sTravelNodeMap->getNodes().size());
    }

    bool reCalculateCost = false || fullNavPointReload || storeNavPointReload;
    bool forceReCalculate = false;

    if (reCalculateCost)
    {
        for (auto& startNode : sTravelNodeMap->getNodes())
        {
            for (auto& path : *startNode->getLinks())
            {
                TravelNodePath* nodePath = path.second;

                if (path.second->getPathType() != TravelNodePathType::walk)
                    continue;

                if (nodePath->getCalculated() && !forceReCalculate)
                    continue;

                nodePath->calculateCost();
            }
        }

        LOG_INFO("playerbots", ">> Calculated pathcost for {} nodes.", sTravelNodeMap->getNodes().size());
    }

    bool mirrorMissingPaths = true || fullNavPointReload || storeNavPointReload;

    if (mirrorMissingPaths)
    {
        for (auto& startNode : sTravelNodeMap->getNodes())
        {
            for (auto& path : *startNode->getLinks())
            {
                TravelNode* endNode = path.first;

                if (endNode->hasLinkTo(startNode))
                    continue;

                if (path.second->getPathType() != TravelNodePathType::walk)
                    continue;

                TravelNodePath nodePath = *path.second;

                std::vector<WorldPosition> pPath = nodePath.getPath();
                std::reverse(pPath.begin(), pPath.end());

                nodePath.setPath(pPath);

                endNode->setPathTo(startNode, nodePath, true);
            }
        }

        LOG_INFO("playerbots", ">> Reversed missing paths for {} nodes.", sTravelNodeMap->getNodes().size());
    }
    */

    sTravelNodeMap->printMap();
    sTravelNodeMap->printNodeStore();
    sTravelNodeMap->saveNodeStore();

    // Creature/gos/zone export.
    if (sPlayerbotAIConfig->hasLog("creatures.csv"))
    {
        for (CreatureData const* cData : WorldPosition().getCreaturesNear())
        {
            CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(cData->id1);
            if (!cInfo)
                continue;

            WorldPosition point =
                WorldPosition(cData->mapid, cData->posX, cData->posY, cData->posZ, cData->orientation);

            std::string name = cInfo->Name;
            name.erase(remove(name.begin(), name.end(), ','), name.end());
            name.erase(remove(name.begin(), name.end(), '\"'), name.end());

            std::ostringstream out;
            out << name << ",";
            point.printWKT(out);
            out << cInfo->maxlevel << ",";
            out << cInfo->rank << ",";
            out << cInfo->faction << ",";
            out << cInfo->npcflag << ",";
            out << point.getAreaName() << ",";
            out << std::fixed;

            sPlayerbotAIConfig->log("creatures.csv", out.str().c_str());
        }
    }

    if (sPlayerbotAIConfig->hasLog("vmangoslines.csv"))
    {
        uint32 mapId = 0;
        std::vector<WorldPosition> pos;

        static float const topNorthSouthLimit[] = {
            2032.048340f,  -6927.750000f, 1634.863403f,  -6157.505371f, 1109.519775f,  -5181.036133f, 1315.204712f,
            -4096.020508f, 1073.089233f,  -3372.571533f, 825.8331910f,  -3125.778809f, 657.3439940f,  -2314.813232f,
            424.7361450f,  -1888.283691f, 744.3958130f,  -1647.935425f, 1424.160645f,  -654.9481810f, 1447.065308f,
            -169.7513580f, 1208.715454f,  189.74870300f, 1596.240356f,  998.61669900f, 1577.923706f,  1293.4199220f,
            1458.520264f,  1727.3732910f, 1591.916138f,  3728.1394040f};

        pos.clear();

#define my_sizeof(type) ((char*)(&type + 1) - (char*)(&type))

        uint32 size = my_sizeof(topNorthSouthLimit) / my_sizeof(topNorthSouthLimit[0]);
        for (uint32 i = 0; i < size - 1; i = i + 2)
        {
            if (topNorthSouthLimit[i] == 0)
                break;

            pos.push_back(WorldPosition(mapId, topNorthSouthLimit[i], topNorthSouthLimit[i + 1], 0));
        }

        std::ostringstream out;
        out << "topNorthSouthLimit"
            << ",";
        WorldPosition().printWKT(pos, out, 1);
        out << std::fixed;

        sPlayerbotAIConfig->log("vmangoslines.csv", out.str().c_str());

        static float const ironforgeAreaSouthLimit[] = {
            -7491.33f, 3093.740f, -7472.04f, -391.880f, -6366.68f, -730.100f, -6063.96f, -1411.76f,
            -6087.62f, -2190.21f, -6349.54f, -2533.66f, -6308.63f, -3049.32f, -6107.82f, -3345.30f,
            -6008.49f, -3590.52f, -5989.37f, -4312.29f, -5806.26f, -5864.11f};

        pos.clear();

        size = my_sizeof(ironforgeAreaSouthLimit) / my_sizeof(ironforgeAreaSouthLimit[0]);

        for (uint32 i = 0; i < size - 1; i = i + 2)
        {
            if (ironforgeAreaSouthLimit[i] == 0)
                break;

            pos.push_back(WorldPosition(mapId, ironforgeAreaSouthLimit[i], ironforgeAreaSouthLimit[i + 1], 0));
        }

        out.str("");
        out.clear();

        out << "ironforgeAreaSouthLimit"
            << ",";
        WorldPosition().printWKT(pos, out, 1);
        out << std::fixed;

        sPlayerbotAIConfig->log("vmangoslines.csv", out.str().c_str());

        static float const stormwindAreaNorthLimit[] = {
            -8004.250f, 3714.110f, -8075.000f, -179.000f, -8638.000f, 169.0000f, -9044.000f, 35.00000f,
            -9068.000f, -125.000f, -9094.000f, -147.000f, -9206.000f, -290.000f, -9097.000f, -510.000f,
            -8739.000f, -501.000f, -8725.500f, -1618.45f, -9810.400f, -1698.41f, -10049.60f, -1740.40f,
            -10670.61f, -1692.51f, -10908.48f, -1563.87f, -13006.40f, -1622.80f, -12863.23f, -4798.42f};

        pos.clear();

        size = my_sizeof(stormwindAreaNorthLimit) / my_sizeof(stormwindAreaNorthLimit[0]);

        for (uint32 i = 0; i < size - 1; i = i + 2)
        {
            if (stormwindAreaNorthLimit[i] == 0)
                break;

            pos.push_back(WorldPosition(mapId, stormwindAreaNorthLimit[i], stormwindAreaNorthLimit[i + 1], 0));
        }

        out.str("");
        out.clear();

        out << "stormwindAreaNorthLimit"
            << ",";
        WorldPosition().printWKT(pos, out, 1);
        out << std::fixed;

        sPlayerbotAIConfig->log("vmangoslines.csv", out.str().c_str());

        static float const stormwindAreaSouthLimit[] = {
            -8725.3378910f, 3535.62402300f, -9525.6992190f, 910.13256800f, -9796.9531250f, 839.06958000f,
            -9946.3417970f, 743.10284400f,  -10287.361328f, 760.07647700f, -10083.828125f, 380.38989300f,
            -10148.072266f, 80.056450000f,  -10014.583984f, -161.6385190f, -9978.1464840f, -361.6380310f,
            -9877.4892580f, -563.3048710f,  -9980.9677730f, -1128.510498f, -9991.7177730f, -1428.793213f,
            -9887.5791020f, -1618.514038f,  -10169.600586f, -1801.582031f, -9966.2744140f, -2227.197754f,
            -9861.3095700f, -2989.841064f,  -9944.0263670f, -3205.886963f, -9610.2099610f, -3648.369385f,
            -7949.3295900f, -4081.389404f,  -7910.8593750f, -5855.578125f};

        pos.clear();

        size = my_sizeof(stormwindAreaSouthLimit) / my_sizeof(stormwindAreaSouthLimit[0]);

        for (uint32 i = 0; i < size - 1; i = i + 2)
        {
            if (stormwindAreaSouthLimit[i] == 0)
                break;

            pos.push_back(WorldPosition(mapId, stormwindAreaSouthLimit[i], stormwindAreaSouthLimit[i + 1], 0));
        }

        out.str("");
        out.clear();

        out << "stormwindAreaSouthLimit"
            << ",";
        WorldPosition().printWKT(pos, out, 1);
        out << std::fixed;

        sPlayerbotAIConfig->log("vmangoslines.csv", out.str().c_str());

        mapId = 1;

        static float const northMiddleLimit[] = {
            -2280.00f, 4054.000f, -2401.00f, 2365.000f, -2432.00f, 1338.000f, -2286.00f, 769.0000f, -2137.00f,
            662.0000f, -2044.54f, 489.8600f, -1808.52f, 436.3900f, -1754.85f, 504.5500f, -1094.55f, 651.7500f,
            -747.460f, 647.7300f, -685.550f, 408.4300f, -311.380f, 114.4300f, -358.400f, -587.420f, -377.920f,
            -748.700f, -512.570f, -919.490f, -280.650f, -1008.87f, -81.2900f, -930.890f, 284.3100f, -1105.39f,
            568.8600f, -892.280f, 1211.090f, -1135.55f, 879.6000f, -2110.18f, 788.9600f, -2276.02f, 899.6800f,
            -2625.56f, 1281.540f, -2689.42f, 1521.820f, -3047.85f, 1424.220f, -3365.69f, 1694.110f, -3615.20f,
            2373.780f, -4019.96f, 2388.130f, -5124.35f, 2193.790f, -5484.38f, 1703.570f, -5510.53f, 1497.590f,
            -6376.56f, 1368.000f, -8530.00f};

        pos.clear();

        size = my_sizeof(northMiddleLimit) / my_sizeof(northMiddleLimit[0]);

        for (uint32 i = 0; i < size - 1; i = i + 2)
        {
            if (northMiddleLimit[i] == 0)
                break;

            pos.push_back(WorldPosition(mapId, northMiddleLimit[i], northMiddleLimit[i + 1], 0));
        }

        out.str("");
        out.clear();

        out << "northMiddleLimit"
            << ",";
        WorldPosition().printWKT(pos, out, 1);
        out << std::fixed;

        sPlayerbotAIConfig->log("vmangoslines.csv", out.str().c_str());

        static float const durotarSouthLimit[] = {
            2755.0f, -3766.f, 2225.0f, -3596.f, 1762.0f, -3746.f, 1564.0f, -3943.f, 1184.0f, -3915.f, 737.00f,
            -3782.f, -75.00f, -3742.f, -263.0f, -3836.f, -173.0f, -4064.f, -81.00f, -4091.f, -49.00f, -4089.f,
            -16.00f, -4187.f, -5.000f, -4192.f, -14.00f, -4551.f, -397.0f, -4601.f, -522.0f, -4583.f, -668.0f,
            -4539.f, -790.0f, -4502.f, -1176.f, -4213.f, -1387.f, -4674.f, -2243.f, -6046.f};

        pos.clear();

        size = my_sizeof(durotarSouthLimit) / my_sizeof(durotarSouthLimit[0]);

        for (uint32 i = 0; i < size - 1; i = i + 2)
        {
            if (durotarSouthLimit[i] == 0)
                break;

            pos.push_back(WorldPosition(mapId, durotarSouthLimit[i], durotarSouthLimit[i + 1], 0));
        }

        out.str("");
        out.clear();

        out << "durotarSouthLimit"
            << ",";
        WorldPosition().printWKT(pos, out, 1);
        out << std::fixed;

        sPlayerbotAIConfig->log("vmangoslines.csv", out.str().c_str());

        static float const valleyoftrialsSouthLimit[] = {-324.f,  -3869.f, -774.f,  -3992.f, -965.f,  -4290.f, -932.f,
                                                         -4349.f, -828.f,  -4414.f, -661.f,  -4541.f, -521.f,  -4582.f};

        pos.clear();

        size = my_sizeof(valleyoftrialsSouthLimit) / my_sizeof(valleyoftrialsSouthLimit[0]);

        for (uint32 i = 0; i < size - 1; i = i + 2)
        {
            if (valleyoftrialsSouthLimit[i] == 0)
                break;

            pos.push_back(WorldPosition(mapId, valleyoftrialsSouthLimit[i], valleyoftrialsSouthLimit[i + 1], 0));
        }

        out.str("");
        out.clear();

        out << "valleyoftrialsSouthLimit"
            << ",";
        WorldPosition().printWKT(pos, out, 1);
        out << std::fixed;

        sPlayerbotAIConfig->log("vmangoslines.csv", out.str().c_str());

        static float const middleToSouthLimit[] = {
            -2402.010000f, 4255.7000000f, -2475.933105f, 3199.5683590f,  // Desolace
            -2344.124023f, 1756.1643070f, -2826.438965f, 403.82473800f,  // Mulgore
            -3472.819580f, 182.52247600f,                                // Feralas
            -4365.006836f, -1602.575439f,                                // the Barrens
            -4515.219727f, -1681.356079f, -4543.093750f, -1882.869385f,  // Thousand Needles
            -4824.160000f, -2310.110000f, -5102.913574f, -2647.062744f, -5248.286621f,
            -3034.536377f, -5246.920898f, -3339.139893f, -5459.449707f, -4920.155273f,  // Tanaris
            -5437.000000f, -5863.000000f};

        pos.clear();

        size = my_sizeof(middleToSouthLimit) / my_sizeof(middleToSouthLimit[0]);

        for (uint32 i = 0; i < size - 1; i = i + 2)
        {
            if (middleToSouthLimit[i] == 0)
                break;

            pos.push_back(WorldPosition(mapId, middleToSouthLimit[i], middleToSouthLimit[i + 1], 0));
        }

        out.str("");
        out.clear();

        out << "middleToSouthLimit"
            << ",";
        WorldPosition().printWKT(pos, out, 1);
        out << std::fixed;

        sPlayerbotAIConfig->log("vmangoslines.csv", out.str().c_str());

        static float const orgrimmarSouthLimit[] = {
            2132.5076f, -3912.2478f, 1944.4298f, -3855.2583f, 1735.6906f, -3834.2417f, 1654.3671f, -3380.9902f,
            1593.9861f, -3975.5413f, 1439.2548f, -4249.6923f, 1436.3106f, -4007.8950f, 1393.3199f, -4196.0625f,
            1445.2428f, -4373.9052f, 1407.2349f, -4429.4145f, 1464.7142f, -4545.2875f, 1584.1331f, -4596.8764f,
            1716.8065f, -4601.1323f, 1875.8312f, -4788.7187f, 1979.7647f, -4883.4585f, 2219.1562f, -4854.3330f};

        pos.clear();

        size = my_sizeof(orgrimmarSouthLimit) / my_sizeof(orgrimmarSouthLimit[0]);

        for (uint32 i = 0; i < size - 1; i = i + 2)
        {
            if (orgrimmarSouthLimit[i] == 0)
                break;

            pos.push_back(WorldPosition(mapId, orgrimmarSouthLimit[i], orgrimmarSouthLimit[i + 1], 0));
        }

        out.str("");
        out.clear();

        out << "orgrimmarSouthLimit"
            << ",";
        WorldPosition().printWKT(pos, out, 1);
        out << std::fixed;

        sPlayerbotAIConfig->log("vmangoslines.csv", out.str().c_str());

        static float const feralasThousandNeedlesSouthLimit[] = {
            -6495.4995f, -4711.9810f, -6674.9995f, -4515.0019f, -6769.5717f, -4122.4272f, -6838.2651f, -3874.2792f,
            -6851.1314f, -3659.1179f, -6624.6845f, -3063.3843f, -6416.9067f, -2570.1301f, -5959.8466f, -2287.2634f,
            -5947.9135f, -1866.5028f, -5947.9135f, -820.48810f, -5876.7114f, -3.5138000f, -5876.7114f, 917.640700f,
            -6099.3603f, 1153.28840f, -6021.8989f, 1638.18090f, -6091.6176f, 2335.88920f, -6744.9946f, 2393.48550f,
            -6973.8608f, 3077.02810f, -7068.7241f, 4376.23040f, -7142.1211f, 4808.43310f};

        pos.clear();

        size = my_sizeof(feralasThousandNeedlesSouthLimit) / my_sizeof(feralasThousandNeedlesSouthLimit[0]);

        for (uint32 i = 0; i < size - 1; i = i + 2)
        {
            if (feralasThousandNeedlesSouthLimit[i] == 0)
                break;

            pos.push_back(
                WorldPosition(mapId, feralasThousandNeedlesSouthLimit[i], feralasThousandNeedlesSouthLimit[i + 1], 0));
        }

        out.str("");
        out.clear();

        out << "feralasThousandNeedlesSouthLimit"
            << ",";
        WorldPosition().printWKT(pos, out, 1);
        out << std::fixed;

        sPlayerbotAIConfig->log("vmangoslines.csv", out.str().c_str());
    }

    if (sPlayerbotAIConfig->hasLog("gos.csv"))
    {
        for (GameObjectData const* gData : WorldPosition().getGameObjectsNear())
        {
            GameObjectTemplate const* data = sObjectMgr->GetGameObjectTemplate(gData->id);
            if (!data)
                continue;

            WorldPosition point =
                WorldPosition(gData->mapid, gData->posX, gData->posY, gData->posZ, gData->orientation);

            std::string name = data->name;
            name.erase(remove(name.begin(), name.end(), ','), name.end());
            name.erase(remove(name.begin(), name.end(), '\"'), name.end());

            std::ostringstream out;
            out << name << ",";
            point.printWKT(out);
            out << data->type << ",";
            out << point.getAreaName() << ",";
            out << std::fixed;

            sPlayerbotAIConfig->log("gos.csv", out.str().c_str());
        }
    }

    if (sPlayerbotAIConfig->hasLog("zones.csv"))
    {
        std::unordered_map<std::string, std::vector<WorldPosition>> zoneLocs;

        std::vector<WorldPosition> Locs;

        for (auto& u : units)
        {
            WorldPosition point = WorldPosition(u.map, u.x, u.y, u.z, u.o);
            std::string const name = std::to_string(u.map) + point.getAreaName();

            if (zoneLocs.find(name) == zoneLocs.end())
                zoneLocs.insert_or_assign(name, Locs);

            zoneLocs.find(name)->second.push_back(point);
        }

        for (auto& loc : zoneLocs)
        {
            if (loc.second.empty())
                continue;

            if (!sTravelNodeMap->getMapOffset(loc.second.front().getMapId()) && loc.second.front().getMapId() != 0)
                continue;

            std::vector<WorldPosition> points = loc.second;
            ;

            std::ostringstream out;

            WorldPosition pos = WorldPosition(points, WP_MEAN_CENTROID);

            out << "\"center\""
                << ",";
            out << points.begin()->getMapId() << ",";
            out << points.begin()->getAreaName() << ",";
            out << points.begin()->getAreaName(true, true) << ",";

            pos.printWKT(out);

            out << "\n";

            out << "\"area\""
                << ",";
            out << points.begin()->getMapId() << ",";
            out << points.begin()->getAreaName() << ",";
            out << points.begin()->getAreaName(true, true) << ",";

            point.printWKT(points, out, 0);

            sPlayerbotAIConfig->log("zones.csv", out.str().c_str());
        }
    }

    bool printStrategyMap = false;

    if (printStrategyMap && sPlayerbotAIConfig->hasLog("strategy.csv"))
    {
        static std::map<uint8, std::string> classes;
        static std::map<uint8, std::map<uint8, std::string>> specs;
        classes[CLASS_DRUID] = "druid";
        specs[CLASS_DRUID][0] = "balance";
        specs[CLASS_DRUID][1] = "feral combat";
        specs[CLASS_DRUID][2] = "restoration";

        classes[CLASS_HUNTER] = "hunter";
        specs[CLASS_HUNTER][0] = "beast mastery";
        specs[CLASS_HUNTER][1] = "marksmanship";
        specs[CLASS_HUNTER][2] = "survival";

        classes[CLASS_MAGE] = "mage";
        specs[CLASS_MAGE][0] = "arcane";
        specs[CLASS_MAGE][1] = "fire";
        specs[CLASS_MAGE][2] = "frost";

        classes[CLASS_PALADIN] = "paladin";
        specs[CLASS_PALADIN][0] = "holy";
        specs[CLASS_PALADIN][1] = "protection";
        specs[CLASS_PALADIN][2] = "retribution";

        classes[CLASS_PRIEST] = "priest";
        specs[CLASS_PRIEST][0] = "discipline";
        specs[CLASS_PRIEST][1] = "holy";
        specs[CLASS_PRIEST][2] = "shadow";

        classes[CLASS_ROGUE] = "rogue";
        specs[CLASS_ROGUE][0] = "assasination";
        specs[CLASS_ROGUE][1] = "combat";
        specs[CLASS_ROGUE][2] = "subtlety";

        classes[CLASS_SHAMAN] = "shaman";
        specs[CLASS_SHAMAN][0] = "elemental";
        specs[CLASS_SHAMAN][1] = "enhancement";
        specs[CLASS_SHAMAN][2] = "restoration";

        classes[CLASS_WARLOCK] = "warlock";
        specs[CLASS_WARLOCK][0] = "affliction";
        specs[CLASS_WARLOCK][1] = "demonology";
        specs[CLASS_WARLOCK][2] = "destruction";

        classes[CLASS_WARRIOR] = "warrior";
        specs[CLASS_WARRIOR][0] = "arms";
        specs[CLASS_WARRIOR][1] = "fury";
        specs[CLASS_WARRIOR][2] = "protection";

        classes[CLASS_DEATH_KNIGHT] = "dk";
        specs[CLASS_DEATH_KNIGHT][0] = "blood";
        specs[CLASS_DEATH_KNIGHT][1] = "frost";
        specs[CLASS_DEATH_KNIGHT][2] = "unholy";

        // Use randombot 0.
        std::ostringstream cout;
        cout << sPlayerbotAIConfig->randomBotAccountPrefix << 0;
        std::string const accountName = cout.str();

        LoginDatabasePreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_GET_ACCOUNT_ID_BY_USERNAME);
        stmt->SetData(0, accountName);
        PreparedQueryResult result = LoginDatabase.Query(stmt);
        if (result)
        {
            Field* fields = result->Fetch();
            uint32 accountId = fields[0].Get<uint32>();

            WorldSession* session =
                new WorldSession(accountId, "", nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
                                 LOCALE_enUS, 0, false, false, 0, true);

            std::vector<std::pair<std::pair<uint32, uint32>, uint32>> classSpecLevel;

            std::unordered_map<std::string, std::vector<std::pair<std::pair<uint32, uint32>, uint32>>> actions;

            std::ostringstream out;

            for (uint8 race = RACE_HUMAN; race < MAX_RACES; race++)
            {
                for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls)
                {
                    if (cls != 10)
                    {
                        std::unique_ptr<CharacterCreateInfo> characterInfo =
                            std::make_unique<CharacterCreateInfo>("dummy", race, cls, 1, 1, 1, 1, 1, 1);
                        Player* player = new Player(session);
                        if (player->Create(sObjectMgr->GetGenerator<HighGuid::Player>().Generate(),
                                           characterInfo.get()))
                        {
                            for (uint8 tab = 0; tab < 3; tab++)
                            {
                                TalentSpec newSpec;
                                if (tab == 0)
                                    newSpec = TalentSpec(player, "1-0-0");
                                else if (tab == 1)
                                    newSpec = TalentSpec(player, "0-1-0");
                                else
                                    newSpec = TalentSpec(player, "0-0-1");

                                for (uint32 lvl = 1; lvl < MAX_LEVEL; lvl++)
                                {
                                    player->SetLevel(lvl);

                                    std::ostringstream tout;
                                    newSpec.ApplyTalents(player, &tout);

                                    PlayerbotAI* botAI = new PlayerbotAI(player);

                                    botAI->ResetStrategies(false);

                                    AiObjectContext* con = botAI->GetAiObjectContext();

                                    std::vector<std::string> tstrats;
                                    std::set<std::string> strategies;
                                    std::set<std::string> sstrats;

                                    tstrats = botAI->GetStrategies(BOT_STATE_COMBAT);
                                    sstrats = con->GetSupportedStrategies();
                                    if (!sstrats.empty())
                                        strategies.insert(tstrats.begin(), tstrats.end());

                                    tstrats = botAI->GetStrategies(BOT_STATE_NON_COMBAT);
                                    if (!tstrats.empty())
                                        strategies.insert(tstrats.begin(), tstrats.end());

                                    tstrats = botAI->GetStrategies(BOT_STATE_DEAD);
                                    if (!tstrats.empty())
                                        strategies.insert(tstrats.begin(), tstrats.end());

                                    sstrats = con->GetSupportedStrategies();
                                    if (!sstrats.empty())
                                        strategies.insert(sstrats.begin(), sstrats.end());

                                    for (auto& stratName : strategies)
                                    {
                                        Strategy* strat = con->GetStrategy(stratName);

                                        if (strat->getDefaultActions())
                                            for (uint32 i = 0; i < NextAction::size(strat->getDefaultActions()); i++)
                                            {
                                                NextAction* nextAction = strat->getDefaultActions()[i];

                                                std::ostringstream aout;
                                                aout << nextAction->getRelevance() << "," << nextAction->getName()
                                                     << ",,S:" << stratName;

                                                if (actions.find(aout.str().c_str()) != actions.end())
                                                    classSpecLevel = actions.find(aout.str().c_str())->second;
                                                else
                                                    classSpecLevel.clear();

                                                classSpecLevel.push_back(std::make_pair(std::make_pair(cls, tab), lvl));

                                                actions.insert_or_assign(aout.str().c_str(), classSpecLevel);
                                            }

                                        std::vector<TriggerNode*> triggers;
                                        strat->InitTriggers(triggers);
                                        for (auto& triggerNode : triggers)
                                        {
                                            // out << " TN:" << triggerNode->getName();

                                            if (Trigger* trigger = con->GetTrigger(triggerNode->getName()))
                                            {
                                                triggerNode->setTrigger(trigger);

                                                NextAction** nextActions = triggerNode->getHandlers();

                                                for (uint32 i = 0; i < NextAction::size(nextActions); i++)
                                                {
                                                    NextAction* nextAction = nextActions[i];
                                                    // out << " A:" << nextAction->getName() << "(" <<
                                                    // nextAction->getRelevance() << ")";

                                                    std::ostringstream aout;
                                                    aout << nextAction->getRelevance() << "," << nextAction->getName()
                                                         << "," << triggerNode->getName() << "," << stratName;

                                                    if (actions.find(aout.str().c_str()) != actions.end())
                                                        classSpecLevel = actions.find(aout.str().c_str())->second;
                                                    else
                                                        classSpecLevel.clear();

                                                    classSpecLevel.push_back(
                                                        std::make_pair(std::make_pair(cls, tab), lvl));

                                                    actions.insert_or_assign(aout.str().c_str(), classSpecLevel);
                                                }
                                            }
                                        }
                                    }

                                    delete botAI;
                                }
                            }
                        }

                        delete player;
                    }
                }
            }

            std::vector<std::string> actionKeys;

            for (auto& action : actions)
                actionKeys.push_back(action.first);

            std::sort(actionKeys.begin(), actionKeys.end(),
                      [](std::string const i, std::string const j)
                      {
                          std::stringstream is(i);
                          std::stringstream js(j);
                          float iref, jref;
                          std::string iact, jact, itrig, jtrig, istrat, jstrat;
                          is >> iref >> iact >> itrig >> istrat;
                          js >> jref >> jact >> jtrig >> jstrat;

                          if (iref > jref)
                              return true;

                          if (iref == jref && istrat < jstrat)
                              return true;

                          if (iref == jref && !(istrat > jstrat) && iact < jact)
                              return true;

                          if (iref == jref && !(istrat > jstrat) && !(iact > jact) && itrig < jtrig)
                              return true;

                          return false;
                      });

            sPlayerbotAIConfig->log("strategy.csv", "relevance, action, trigger, strategy, classes");

            for (auto& actionkey : actionKeys)
            {
                if (actions.find(actionkey)->second.size() != (MAX_LEVEL - 1) * (MAX_CLASSES - 1))
                {
                    classSpecLevel = actions.find(actionkey)->second;

                    std::vector<std::pair<std::pair<uint32, uint32>, std::pair<uint32, uint32>>> classs;

                    for (auto cl : classSpecLevel)
                    {
                        uint32 minLevel = MAX_LEVEL;
                        uint32 maxLevel = 0;

                        uint32 cls = cl.first.first;
                        uint32 tb = cl.first.second;

                        if (std::find_if(classs.begin(), classs.end(),
                                         [cls, tb](std::pair<std::pair<uint32, uint32>, std::pair<uint32, uint32>> i)
                                         { return i.first.first == cls && i.first.second == tb; }) == classs.end())
                        {
                            for (auto cll : classSpecLevel)
                            {
                                if (cll.first.first == cl.first.first && cll.first.second == cl.first.second)
                                {
                                    minLevel = std::min(minLevel, cll.second);
                                    maxLevel = std::max(maxLevel, cll.second);
                                }
                            }

                            classs.push_back(std::make_pair(cl.first, std::make_pair(minLevel, maxLevel)));
                        }
                    }

                    out << actionkey;

                    if (classs.size() != 9 * 3)
                    {
                        out << ",";

                        for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls)
                        {
                            bool a[3] = {false, false, false};
                            uint32 min[3] = {0, 0, 0};
                            uint32 max[3] = {0, 0, 0};

                            if (std::find_if(classs.begin(), classs.end(),
                                             [cls](std::pair<std::pair<uint32, uint32>, std::pair<uint32, uint32>> i)
                                             { return i.first.first == cls; }) == classs.end())
                                continue;

                            for (uint32 tb = 0; tb < 3; tb++)
                            {
                                auto tcl = std::find_if(
                                    classs.begin(), classs.end(),
                                    [cls, tb](std::pair<std::pair<uint32, uint32>, std::pair<uint32, uint32>> i)
                                    { return i.first.first == cls && i.first.second == tb; });

                                if (tcl == classs.end())
                                    continue;

                                a[tb] = true;
                                min[tb] = tcl->second.first;
                                max[tb] = tcl->second.second;
                            }

                            if (a[0] && a[1] && a[2] && min[0] == min[1] == min[2] && max[0] == max[1] == max[2])
                            {
                                if (min[0] != 1 || max[0] != MAX_LEVEL - 1)
                                    out << classes[cls] << "(" << min[0] << "-" << max[0] << ")";
                                else
                                    out << classes[cls];

                                if (cls != classs.back().first.first)
                                    out << ";";
                            }
                            else
                            {
                                for (uint32 tb = 0; tb < 3; tb++)
                                {
                                    if (!a[tb])
                                        continue;

                                    if (min[tb] != 1 || max[tb] != MAX_LEVEL - 1)
                                        out << specs[cls][tb] << " " << classes[cls] << "(" << min[tb] << "-" << max[tb]
                                            << ")";
                                    else
                                        out << specs[cls][tb] << " " << classes[cls];

                                    if (cls != classs.back().first.first || tb != classs.back().first.second)
                                        out << ";";
                                }
                            }
                        }
                    }
                    else
                        out << "all";

                    out << "\n";
                }
                else
                    out << actionkey << "\n";
            }

            sPlayerbotAIConfig->log("strategy.csv", out.str().c_str());
        }
    }

    /*

    sPlayerbotAIConfig->openLog(7, "w");

    //Zone area map REMOVE!
    uint32 k = 0;
    for (auto& node : sTravelNodeMap->getNodes())
    {
        WorldPosition* pos = node->getPosition();
        //map area
        for (uint32 x = 0; x < 2000; x++)
        {
            for (uint32 y = 0; y < 2000; y++)
            {
                if (!pos->getMap())
                    continue;

                float nx = pos->getX() + (x*5)-5000.0f;
                float ny = pos->getY() + (y*5)-5000.0f;
                float nz = pos->getZ() + 100.0f;

                //pos->getMap()->GetHitPosition(nx, ny, nz + 200.0f, nx, ny, nz, -0.5f);

                if (!pos->getMap()->GetHeightInRange(nx, ny, nz, 5000.0f)) // GetHeight can fail
                    continue;

                WorldPosition  npos = WorldPosition(pos->getMapId(), nx, ny, nz, 0.0);
                uint32 area = path.getArea(npos.getMapId(), npos.getX(), npos.getY(), npos.getZ());

                std::ostringstream out;
                out << std::fixed << area << "," << npos.getDisplayX() << "," << npos.getDisplayY();
                sPlayerbotAIConfig->log(7, out.str().c_str());
            }
        }
        k++;

        if (k > 0)
            break;
    }

    //Explore map output (REMOVE!)

    sPlayerbotAIConfig->openLog(5, "w");
    for (auto i : exploreLocs)
    {
        for (auto j : i.second->getPoints())
        {
            std::ostringstream out;
            std::string const name = i.second->getTitle();
            name.erase(remove(name.begin(), name.end(), '\"'), name.end());
            out << std::fixed << std::setprecision(2) << name.c_str() << "," << i.first << "," << j->getDisplayX() <<
    "," << j->getDisplayY() << "," << j->getX() << "," << j->getY() << "," << j->getZ(); sPlayerbotAIConfig->log(5,
    out.str().c_str());
        }
    }

    */
}

uint32 TravelMgr::getDialogStatus(Player* pPlayer, int32 questgiver, Quest const* pQuest)
{
    uint32 dialogStatus = DIALOG_STATUS_NONE;

    QuestRelationBounds rbounds;   // QuestRelations (quest-giver)
    QuestRelationBounds irbounds;  // InvolvedRelations (quest-finisher)

    uint32 questId = pQuest->GetQuestId();

    if (questgiver > 0)
    {
        rbounds = sObjectMgr->GetCreatureQuestRelationBounds(questgiver);
        irbounds = sObjectMgr->GetCreatureQuestInvolvedRelationBounds(questgiver);
    }
    else
    {
        rbounds = sObjectMgr->GetGOQuestRelationBounds(questgiver * -1);
        irbounds = sObjectMgr->GetGOQuestInvolvedRelationBounds(questgiver * -1);
    }

    // Check markings for quest-finisher
    for (QuestRelations::const_iterator itr = irbounds.first; itr != irbounds.second; ++itr)
    {
        if (itr->second != questId)
            continue;

        uint32 dialogStatusNew = DIALOG_STATUS_NONE;

        if (!pQuest)
        {
            continue;
        }

        QuestStatus status = pPlayer->GetQuestStatus(questId);

        if ((status == QUEST_STATUS_COMPLETE && !pPlayer->GetQuestRewardStatus(questId)) ||
            (pQuest->IsAutoComplete() && pPlayer->CanTakeQuest(pQuest, false)))
        {
            if (pQuest->IsAutoComplete() && pQuest->IsRepeatable())
            {
                dialogStatusNew = DIALOG_STATUS_REWARD_REP;
            }
            else
            {
                dialogStatusNew = DIALOG_STATUS_REWARD2;
            }
        }
        else if (status == QUEST_STATUS_INCOMPLETE)
        {
            dialogStatusNew = DIALOG_STATUS_INCOMPLETE;
        }

        if (dialogStatusNew > dialogStatus)
        {
            dialogStatus = dialogStatusNew;
        }
    }

    // check markings for quest-giver
    for (QuestRelations::const_iterator itr = rbounds.first; itr != rbounds.second; ++itr)
    {
        if (itr->second != questId)
            continue;

        uint32 dialogStatusNew = DIALOG_STATUS_NONE;

        if (!pQuest)
        {
            continue;
        }

        QuestStatus status = pPlayer->GetQuestStatus(questId);

        if (status == QUEST_STATUS_NONE)  // For all other cases the mark is handled either at some place else, or with
                                          // involved-relations already
        {
            if (pPlayer->CanSeeStartQuest(pQuest))
            {
                if (pPlayer->SatisfyQuestLevel(pQuest, false))
                {
                    int32 lowLevelDiff = sWorld->getIntConfig(CONFIG_QUEST_LOW_LEVEL_HIDE_DIFF);
                    if (pQuest->IsAutoComplete() || (pQuest->IsRepeatable() && pPlayer->IsQuestRewarded(questId)))
                    {
                        dialogStatusNew = DIALOG_STATUS_REWARD_REP;
                    }
                    else if (lowLevelDiff < 0 ||
                             pPlayer->GetLevel() <= pPlayer->GetQuestLevel(pQuest) + uint32(lowLevelDiff))
                    {
                        dialogStatusNew = DIALOG_STATUS_AVAILABLE;
                    }
                    else
                    {
                        dialogStatusNew = DIALOG_STATUS_LOW_LEVEL_AVAILABLE;
                    }
                }
                else
                {
                    dialogStatusNew = DIALOG_STATUS_UNAVAILABLE;
                }
            }
        }

        if (dialogStatusNew > dialogStatus)
        {
            dialogStatus = dialogStatusNew;
        }
    }

    return dialogStatus;
}

// Selects a random WorldPosition from a list. Use a distance weighted distribution.
std::vector<WorldPosition*> TravelMgr::getNextPoint(WorldPosition* center, std::vector<WorldPosition*> points,
                                                    uint32 amount)
{
    std::vector<WorldPosition*> retVec;

    if (points.size() < 2)
    {
        if (points.size() == 1)
            retVec.push_back(points[0]);

        return retVec;
    }

    retVec = points;

    std::vector<uint32> weights;

    // List of weights based on distance (Gausian curve that starts at 100 and lower to 1 at 1000 distance)
    // std::transform(retVec.begin(), retVec.end(), std::back_inserter(weights), [center](WorldPosition point) { return
    // 1 + 1000 * exp(-1 * pow(point.distance(center) / 400.0, 2)); });

    // List of weights based on distance (Twice the distance = half the weight). Caps out at 200.0000 range.
    std::transform(retVec.begin(), retVec.end(), std::back_inserter(weights),
                   [center](WorldPosition* point)
                   { return static_cast<uint32>(200000.f / (1.f + point->distance(center))); });

    Acore::Containers::RandomShuffle(retVec);

    std::vector<float> dists;

    // Total sum of all those weights.
    /*
    uint32 sum = std::accumulate(weights.begin(), weights.end(), 0);

    //Pick a random point based on weights.
    for (uint32 nr = 0; nr < amount; nr++)
    {
        //Pick a random number in that range.
        uint32 rnd = urand(0, sum);

        for (unsigned i = 0; i < points.size(); ++i)
            if (rnd < weights[i] && (retVec.empty() || std::find(retVec.begin(), retVec.end(), points[i]) ==
    retVec.end()))
            {
                retVec.push_back(points[i]);
                break;
            }
            else
                rnd -= weights[i];
    }*/

    return retVec;
}

std::vector<WorldPosition> TravelMgr::getNextPoint(WorldPosition center, std::vector<WorldPosition> points,
                                                   uint32 amount)
{
    std::vector<WorldPosition> retVec;

    if (points.size() == 1)
    {
        retVec.push_back(points[0]);
        return retVec;
    }

    // List of weights based on distance (Gausian curve that starts at 100 and lower to 1 at 1000 distance)
    std::vector<uint32> weights;

    std::transform(points.begin(), points.end(), std::back_inserter(weights),
                   [center](WorldPosition point)
                   { return 1 + 1000 * static_cast<uint32>(exp(-1.f * pow(point.distance(center) / 400.f, 2.f))); });

    // Total sum of all those weights.
    uint32 sum = std::accumulate(weights.begin(), weights.end(), 0);

    // Pick a random number in that range.
    uint32 rnd = urand(0, sum);

    // Pick a random point based on weights.
    for (uint32 nr = 0; nr < amount; nr++)
    {
        for (unsigned i = 0; i < points.size(); ++i)
            if (rnd < weights[i] &&
                (retVec.empty() || std::find(retVec.begin(), retVec.end(), points[i]) == retVec.end()))
            {
                retVec.push_back(points[i]);
                break;
            }
            else
                rnd -= weights[i];
    }

    // Peiru: Crash failsafe - if the retVec is still empty but points exist, return first point
    if (retVec.empty() && points.size() > 0)
        retVec.push_back(points[0]);

    if (!retVec.empty())
        return retVec;

    assert(!"No valid point found.");

    return retVec;
}

QuestStatusData* TravelMgr::getQuestStatus(Player* bot, uint32 questId) { return &bot->getQuestStatusMap()[questId]; }

bool TravelMgr::getObjectiveStatus(Player* bot, Quest const* pQuest, uint32 objective)
{
    uint32 questId = pQuest->GetQuestId();
    if (!bot->IsActiveQuest(questId))
        return false;

    if (bot->GetQuestStatus(questId) != QUEST_STATUS_INCOMPLETE)
        return false;

    QuestStatusData* questStatus = sTravelMgr->getQuestStatus(bot, questId);

    uint32 reqCount = pQuest->RequiredItemCount[objective];
    uint32 hasCount = questStatus->ItemCount[objective];

    if (reqCount && hasCount < reqCount)
        return true;

    reqCount = pQuest->RequiredNpcOrGoCount[objective];
    hasCount = questStatus->CreatureOrGOCount[objective];

    if (reqCount && hasCount < reqCount)
        return true;

    return false;
}

std::vector<TravelDestination*> TravelMgr::getQuestTravelDestinations(Player* bot, int32 questId, bool ignoreFull,
                                                                      bool ignoreInactive, float maxDistance,
                                                                      bool ignoreObjectives)
{
    WorldPosition botLocation(bot);

    std::vector<TravelDestination*> retTravelLocations;

    if (!questId)
    {
        for (auto& dest : questGivers)
        {
            if (!ignoreInactive && !dest->isActive(bot))
                continue;

            if (maxDistance > 0 && dest->distanceTo(&botLocation) > maxDistance)
                continue;

            retTravelLocations.push_back(dest);
        }
        for (auto& quest : quests)
        {
            for (auto& dest : quest.second->questTakers)
            {
                if (!ignoreInactive && !dest->isActive(bot))
                    continue;

                if (maxDistance > 0 && dest->distanceTo(&botLocation) > maxDistance)
                    continue;

                retTravelLocations.push_back(dest);
            }

            if (!ignoreObjectives)
                for (auto& dest : quest.second->questObjectives)
                {
                    if (!ignoreInactive && !dest->isActive(bot))
                        continue;

                    if (maxDistance > 0 && dest->distanceTo(&botLocation) > maxDistance)
                        continue;

                    retTravelLocations.push_back(dest);
                }
        }
    }
    else if (questId == -1)
    {
        for (auto& dest : questGivers)
        {
            if (!ignoreInactive && !dest->isActive(bot))
                continue;

            if (dest->isFull(ignoreFull))
                continue;

            if (maxDistance > 0 && dest->distanceTo(&botLocation) > maxDistance)
                continue;

            retTravelLocations.push_back(dest);
        }
    }
    else
    {
        auto i = quests.find(questId);

        if (i != quests.end())
        {
            for (auto& dest : i->second->questTakers)
            {
                if (!ignoreInactive && !dest->isActive(bot))
                    continue;

                if (dest->isFull(ignoreFull))
                    continue;

                if (maxDistance > 0 && dest->distanceTo(&botLocation) > maxDistance)
                    continue;

                retTravelLocations.push_back(dest);
            }

            if (!ignoreObjectives)
                for (auto& dest : i->second->questObjectives)
                {
                    if (!ignoreInactive && !dest->isActive(bot))
                        continue;

                    if (dest->isFull(ignoreFull))
                        continue;

                    if (maxDistance > 0 && dest->distanceTo(&botLocation) > maxDistance)
                        continue;

                    retTravelLocations.push_back(dest);
                }
        }
    }

    return retTravelLocations;
}

std::vector<TravelDestination*> TravelMgr::getRpgTravelDestinations(Player* bot, bool ignoreFull, bool ignoreInactive,
                                                                    float maxDistance)
{
    WorldPosition botLocation(bot);

    std::vector<TravelDestination*> retTravelLocations;

    for (auto& dest : rpgNpcs)
    {
        if (!ignoreInactive && !dest->isActive(bot))
            continue;

        if (dest->isFull(ignoreFull))
            continue;

        if (maxDistance > 0 && dest->distanceTo(&botLocation) > maxDistance)
            continue;

        retTravelLocations.push_back(dest);
    }

    return std::move(retTravelLocations);
}

std::vector<TravelDestination*> TravelMgr::getExploreTravelDestinations(Player* bot, bool ignoreFull,
                                                                        bool ignoreInactive)
{
    WorldPosition botLocation(bot);

    std::vector<TravelDestination*> retTravelLocations;

    for (auto& dest : exploreLocs)
    {
        if (!ignoreInactive && !dest.second->isActive(bot))
            continue;

        if (dest.second->isFull(ignoreFull))
            continue;

        retTravelLocations.push_back(dest.second);
    }

    return retTravelLocations;
}

std::vector<TravelDestination*> TravelMgr::getGrindTravelDestinations(Player* bot, bool ignoreFull, bool ignoreInactive,
                                                                      float maxDistance)
{
    WorldPosition botLocation(bot);

    std::vector<TravelDestination*> retTravelLocations;

    for (auto& dest : grindMobs)
    {
        if (!ignoreInactive && !dest->isActive(bot))
            continue;

        if (dest->isFull(ignoreFull))
            continue;

        if (maxDistance > 0 && dest->distanceTo(&botLocation) > maxDistance)
            continue;

        retTravelLocations.push_back(dest);
    }

    return retTravelLocations;
}

void TravelMgr::setNullTravelTarget(Player* player)
{
    if (!player)
        return;

    PlayerbotAI* playerBotAI = GET_PLAYERBOT_AI(player);
    if (!playerBotAI)
        return;

    TravelTarget* target = playerBotAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get();

    if (target)
        target->setTarget(sTravelMgr->nullTravelDestination, sTravelMgr->nullWorldPosition, true);
}

void TravelMgr::addMapTransfer(WorldPosition start, WorldPosition end, float portalDistance, bool makeShortcuts)
{
    uint32 sMap = start.getMapId();
    uint32 eMap = end.getMapId();

    if (sMap == eMap)
        return;

    // Calculate shortcuts.
    if (makeShortcuts)
    {
        for (auto& mapTransfers : mapTransfersMap)
        {
            uint32 sMapt = mapTransfers.first.first;
            uint32 eMapt = mapTransfers.first.second;

            for (auto& mapTransfer : mapTransfers.second)
            {
                if (eMapt == sMap && sMapt != eMap)  // [S1 >MT> E1 -> S2] >THIS> E2
                {
                    float newDistToEnd = mapTransDistance(*mapTransfer.getPointFrom(), start) + portalDistance;
                    if (mapTransDistance(*mapTransfer.getPointFrom(), end) > newDistToEnd)
                        addMapTransfer(*mapTransfer.getPointFrom(), end, newDistToEnd, false);
                }

                if (sMapt == eMap && eMapt != sMap)  // S1 >THIS> [E1 -> S2 >MT> E2]
                {
                    float newDistToEnd = portalDistance + mapTransDistance(end, *mapTransfer.getPointTo());
                    if (mapTransDistance(start, *mapTransfer.getPointTo()) > newDistToEnd)
                        addMapTransfer(start, *mapTransfer.getPointTo(), newDistToEnd, false);
                }
            }
        }
    }

    // Add actual transfer.
    auto mapTransfers = mapTransfersMap.find(std::make_pair(start.getMapId(), end.getMapId()));

    if (mapTransfers == mapTransfersMap.end())
        mapTransfersMap.insert({{sMap, eMap}, {mapTransfer(start, end, portalDistance)}});
    else
        mapTransfers->second.push_back(mapTransfer(start, end, portalDistance));
};

void TravelMgr::loadMapTransfers()
{
    for (auto& node : sTravelNodeMap->getNodes())
    {
        for (auto& link : *node->getLinks())
        {
            addMapTransfer(*node->getPosition(), *link.first->getPosition(), link.second->getDistance());
        }
    }
}

float TravelMgr::mapTransDistance(WorldPosition start, WorldPosition end)
{
    uint32 sMap = start.getMapId();
    uint32 eMap = end.getMapId();

    if (sMap == eMap)
        return start.distance(end);

    float minDist = 200000;

    auto mapTransfers = mapTransfersMap.find({sMap, eMap});
    if (mapTransfers == mapTransfersMap.end())
        return minDist;

    for (auto& mapTrans : mapTransfers->second)
    {
        float dist = mapTrans.distance(start, end);

        if (dist < minDist)
            minDist = dist;
    }

    return minDist;
}

float TravelMgr::fastMapTransDistance(WorldPosition start, WorldPosition end)
{
    uint32 sMap = start.getMapId();
    uint32 eMap = end.getMapId();

    if (sMap == eMap)
        return start.fDist(end);

    float minDist = 200000;

    auto mapTransfers = mapTransfersMap.find({sMap, eMap});

    if (mapTransfers == mapTransfersMap.end())
        return minDist;

    for (auto& mapTrans : mapTransfers->second)
    {
        float dist = mapTrans.fDist(start, end);

        if (dist < minDist)
            minDist = dist;
    }

    return minDist;
}

QuestTravelDestination::QuestTravelDestination(uint32 questId1, float radiusMin1, float radiusMax1)
    : TravelDestination(radiusMin1, radiusMax1)
{
    questId = questId1;
    questTemplate = sObjectMgr->GetQuestTemplate(questId);
}

bool QuestTravelDestination::isActive(Player* bot) { return bot->IsActiveQuest(questId); }

bool QuestObjectiveTravelDestination::isCreature() { return GetQuestTemplate()->RequiredNpcOrGo[objective] > 0; }

uint32 QuestObjectiveTravelDestination::ReqCreature()
{
    return isCreature() ? GetQuestTemplate()->RequiredNpcOrGo[objective] : 0;
}

uint32 QuestObjectiveTravelDestination::ReqGOId()
{
    return !isCreature() ? abs(GetQuestTemplate()->RequiredNpcOrGo[objective]) : 0;
}

uint32 QuestObjectiveTravelDestination::ReqCount() { return GetQuestTemplate()->RequiredNpcOrGoCount[objective]; }

void TravelMgr::printGrid(uint32 mapId, int x, int y, std::string const type)
{
    std::string const fileName = "unload_grid.csv";

    if (sPlayerbotAIConfig->hasLog(fileName))
    {
        WorldPosition p = WorldPosition(mapId, 0, 0, 0, 0);

        std::ostringstream out;
        out << sPlayerbotAIConfig->GetTimestampStr();
        out << "+00, " << 0 << 0 << x << "," << y << ", " << type << ",";
        p.printWKT(p.fromGridCoord(GridCoord(x, y)), out, 1, true);
        sPlayerbotAIConfig->log(fileName, out.str().c_str());
    }
}

void TravelMgr::printObj(WorldObject* obj, std::string const type)
{
    std::string fileName = "unload_grid.csv";

    if (sPlayerbotAIConfig->hasLog(fileName))
    {
        WorldPosition p = WorldPosition(obj);

        Cell cell(obj->GetPositionX(), obj->GetPositionY());

        std::vector<WorldPosition> vcell, vgrid;
        vcell = p.fromCellCoord(p.getCellCoord());
        vgrid = p.gridFromCellCoord(p.getCellCoord());

        {
            std::ostringstream out;
            out << sPlayerbotAIConfig->GetTimestampStr();
            out << "+00, " << obj->GetGUID().GetEntry() << "," << obj->GetGUID().GetCounter() << "," << cell.GridX()
                << "," << cell.GridY() << ", " << type << ",";

            p.printWKT(vcell, out, 1, true);
            sPlayerbotAIConfig->log(fileName, out.str().c_str());
        }

        {
            std::ostringstream out;
            out << sPlayerbotAIConfig->GetTimestampStr();
            out << "+00, " << obj->GetGUID().GetEntry() << "," << obj->GetGUID().GetCounter() << "," << cell.GridX()
                << "," << cell.GridY() << ", " << type << ",";

            p.printWKT(vgrid, out, 1, true);
            sPlayerbotAIConfig->log(fileName, out.str().c_str());
        }
    }

    fileName = "unload_obj.csv";

    if (sPlayerbotAIConfig->hasLog(fileName))
    {
        WorldPosition p = WorldPosition(obj);

        Cell cell(obj->GetPositionX(), obj->GetPositionY());
        {
            std::ostringstream out;
            out << sPlayerbotAIConfig->GetTimestampStr();
            out << "+00, " << obj->GetGUID().GetEntry() << "," << obj->GetGUID().GetCounter() << "," << cell.GridX()
                << "," << cell.GridY() << ", " << type << ",";

            p.printWKT({p}, out, 0);
            sPlayerbotAIConfig->log(fileName, out.str().c_str());
        }
    }
}
