mod-playerbots/src/Mgr/Travel/TravelMgr.cpp

4986 lines
168 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "TravelMgr.h"
#include <iomanip>
#include <numeric>
#include "AreaDefines.h"
#include "Creature.h"
#include "Log.h"
#include "ObjectAccessor.h"
#include "TravelNode.h"
#include "Talentspec.h"
#include "ChatHelper.h"
#include "MapCollisionData.h"
#include "MapMgr.h"
#include "PathGenerator.h"
#include "Playerbots.h"
#include "RaceMgr.h"
#include "TransportMgr.h"
#include "VMapFactory.h"
#include "VMapMgr2.h"
#include "Map.h"
#include "Corpse.h"
#include "CellImpl.h"
// Navigation data
struct Capital
{
uint32 zoneId;
TeamId team;
char const* name;
std::vector<uint16> bankers;
};
static const std::vector<Capital> capitals = {
{ AREA_STORMWIND_CITY, TEAM_ALLIANCE, "Stormwind", {2455, 2456, 2457} },
{ AREA_IRONFORGE, TEAM_ALLIANCE, "Ironforge", {2460, 2461, 5099} },
{ AREA_DARNASSUS, TEAM_ALLIANCE, "Darnassus", {4155, 4208, 4209} },
{ AREA_THE_EXODAR, TEAM_ALLIANCE, "Exodar", {17773, 18350, 16710} },
{ AREA_ORGRIMMAR, TEAM_HORDE, "Orgrimmar", {3320, 3309, 3318} },
{ AREA_UNDERCITY, TEAM_HORDE, "Undercity", {4549, 2459, 2458, 4550} },
{ AREA_THUNDER_BLUFF, TEAM_HORDE, "Thunder Bluff", {2996, 8356, 8357} },
{ AREA_SILVERMOON_CITY, TEAM_HORDE, "Silvermoon", {17631, 17632, 17633, 16615, 16616, 16617} },
{ AREA_SHATTRATH_CITY, TEAM_NEUTRAL, "Shattrath", {19246, 19338, 19034, 19318} },
{ AREA_DALARAN, TEAM_NEUTRAL, "Dalaran", {30604, 30605, 30607, 28675, 28676, 28677, 29530} }
};
static Capital const* FindCapitalByZone(uint32 zoneId)
{
for (Capital const& capital : capitals)
if (capital.zoneId == zoneId)
return &capital;
return nullptr;
}
static Capital const* FindCapitalByBanker(uint16 bankerEntry)
{
for (Capital const& capital : capitals)
for (uint16 bankerId : capital.bankers)
if (bankerId == bankerEntry)
return &capital;
return nullptr;
}
static int GetCityWeight(uint32 zoneId)
{
switch (zoneId)
{
case AREA_STORMWIND_CITY: return sPlayerbotAIConfig.weightTeleToStormwind;
case AREA_IRONFORGE: return sPlayerbotAIConfig.weightTeleToIronforge;
case AREA_DARNASSUS: return sPlayerbotAIConfig.weightTeleToDarnassus;
case AREA_THE_EXODAR: return sPlayerbotAIConfig.weightTeleToExodar;
case AREA_ORGRIMMAR: return sPlayerbotAIConfig.weightTeleToOrgrimmar;
case AREA_UNDERCITY: return sPlayerbotAIConfig.weightTeleToUndercity;
case AREA_THUNDER_BLUFF: return sPlayerbotAIConfig.weightTeleToThunderBluff;
case AREA_SILVERMOON_CITY: return sPlayerbotAIConfig.weightTeleToSilvermoonCity;
case AREA_SHATTRATH_CITY: return sPlayerbotAIConfig.weightTeleToShattrathCity;
case AREA_DALARAN: return sPlayerbotAIConfig.weightTeleToDalaran;
}
return 0;
}
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->GetPositionX() / size;
i.m_positionY += j->GetPositionY() / size;
i.m_positionZ += j->GetPositionZ() / size;
i.NormalizeOrientation(i.m_orientation += j->GetOrientation() / 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.GetPositionX() / size;
i.m_positionY += j.GetPositionY() / size;
i.m_positionZ += j.GetPositionZ() / size;
i.NormalizeOrientation(i.m_orientation += j.GetOrientation() / 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;
}
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;
};
bool WorldPosition::IsValid()
{
return !(GetMapId() == MAPID_INVALID && GetPositionX() == 0 && GetPositionY() == 0 && GetPositionZ() == 0);
}
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 GetExactDist(0.0f, 0.0f, 0.0f);
}
float WorldPosition::distance(WorldPosition* center)
{
if (GetMapId() == center->GetMapId())
return GetExactDist(center->GetPositionX(), center->GetPositionY(), center->GetPositionZ());
// this -> mapTransfer | mapTransfer -> center
return TravelMgr::instance().mapTransDistance(*this, *center);
};
float WorldPosition::fDist(WorldPosition* center)
{
if (GetMapId() == center->GetMapId())
return GetExactDist2d(center->GetPositionX(), center->GetPositionY());
// this -> mapTransfer | mapTransfer -> center
return TravelMgr::instance().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(GetPositionX(), GetPositionY(), GetPositionZ());
}
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 << GetMapId() << '|';
out << GetPositionX() << '|';
out << GetPositionY() << '|';
out << GetPositionZ() << '|';
out << GetOrientation();
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 = TravelNodeMap::instance().getMapOffset(GetMapId());
return offset(const_cast<WorldPosition*>(&pos));
}
uint16 WorldPosition::getAreaId()
{
return sMapMgr->GetAreaId(PHASEMASK_NORMAL, GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ());
}
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 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;
}
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, GetOrientation()));
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)
{
Unit* pathUnit = bot;
Creature* tempCreature = nullptr;
if (!pathUnit)
{
// Create a temporary creature for PathGenerator (same entry as DebugAction "show node")
Map* map = sMapMgr->FindBaseMap(startPos.GetMapId());
if (!map)
return {};
tempCreature = new Creature();
if (!tempCreature->Create(map->GenerateLowGuid<HighGuid::Unit>(), map,
PHASEMASK_NORMAL, 1 /*entry*/, 0,
startPos.GetPositionX(), startPos.GetPositionY(),
startPos.GetPositionZ(), 0))
{
delete tempCreature;
return {};
}
pathUnit = tempCreature;
// Ensure grids are created at both endpoints so mmap tiles are available.
// EnsureGridCreated loads terrain + vmaps + mmaps but NOT objects,
// which is all PathGenerator needs.
map->EnsureGridCreated(Acore::ComputeGridCoord(startPos.GetPositionX(), startPos.GetPositionY()));
map->EnsureGridCreated(Acore::ComputeGridCoord(GetPositionX(), GetPositionY()));
}
PathGenerator path(pathUnit);
path.AddExcludeFlag(NAV_GROUND_STEEP);
auto result = getPathStepFrom(startPos, path);
if (tempCreature)
delete tempCreature;
return result;
}
// Pathfinder-reuse overload — caller owns the PathGenerator and any
// per-call configuration (filters, area costs). Mirrors cmangos
// WorldPosition.cpp:958 which threads one PathFinder through the whole
// 40-step chain instead of constructing a new one per step.
std::vector<WorldPosition> WorldPosition::getPathStepFrom(WorldPosition startPos, PathGenerator& path)
{
// Explicit-start overload (PathGenerator.h:67). Without this,
// CalculatePath(destX,destY,destZ) defaults to the unit's
// current position as start — which means every iteration of
// getPathFromPath's "chain" begins from the bot's same real
// location and produces the same ~296y partial path. The chain
// never advances. With explicit start, each step extends from
// the previous step's endpoint, giving the 40-attempt walker
// its intended multi-tile reach.
path.CalculatePath(startPos.GetPositionX(), startPos.GetPositionY(), startPos.GetPositionZ(),
GetPositionX(), GetPositionY(), GetPositionZ(), false);
Movement::PointsArray points = path.GetPath();
PathType type = path.GetPathType();
// PathType is a bitmask. Two things to handle:
//
// 1. AC's PathGenerator can return INCOMPLETE | FARFROMPOLY_END
// (0x84) etc. — strict `== PATHFIND_INCOMPLETE` would reject
// these perfectly usable partial paths. Use bitwise to accept
// NORMAL/INCOMPLETE plus auxiliary flags.
//
// 2. AC's PathGenerator at PathGenerator.cpp:177-188 returns
// NORMAL | NOT_USING_PATH for player units when start or end
// polygon is INVALID_POLYREF (BuildShortcut → 2-point straight
// line through whatever's in the way). cmangos by contrast
// returns NOPATH for the same case (PathFinder.cpp:437-441).
// To match cmangos's intent (never silently dispatch a
// geometry-ignoring shortcut), reject any path with the
// NOT_USING_PATH bit set.
if (!(type & (PATHFIND_NORMAL | PATHFIND_INCOMPLETE)) ||
(type & PATHFIND_NOT_USING_PATH))
return {};
std::vector<WorldPosition> retvec = fromPointsArray(points);
// Underwater path-extension. Mirrors cmangos WorldPosition.cpp:997-1014.
// When PATHFIND_INCOMPLETE ends within 50y of dest and both endpoints
// are underwater with LOS between them, extend by one 5y step (or
// straight to dest if <5y). Lets bots traverse navmesh-poor water
// volumes the same way real swimmers do.
if (type & PATHFIND_INCOMPLETE)
{
WorldPosition end = *this;
WorldPosition lastPoint = retvec.back();
float dist = lastPoint.distance(&end);
if (dist < 50.0f && lastPoint.isUnderWater() && end.isUnderWater())
{
Map* m = end.getMap();
bool inLos = m && m->isInLineOfSight(
lastPoint.GetPositionX(), lastPoint.GetPositionY(), lastPoint.GetPositionZ() + 1.0f,
end.GetPositionX(), end.GetPositionY(), end.GetPositionZ() + 1.0f,
PHASEMASK_NORMAL, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing);
if (inLos)
{
if (dist < 5.0f)
retvec.push_back(end);
else
{
float dx = end.GetPositionX() - lastPoint.GetPositionX();
float dy = end.GetPositionY() - lastPoint.GetPositionY();
float dz = end.GetPositionZ() - lastPoint.GetPositionZ();
float scale = 5.0f / dist;
retvec.emplace_back(end.GetMapId(),
lastPoint.GetPositionX() + dx * scale,
lastPoint.GetPositionY() + dy * scale,
lastPoint.GetPositionZ() + dz * scale);
}
}
}
}
return retvec;
}
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;
// Construct ONE PathGenerator and thread it through every step.
// Mirrors cmangos WorldPosition.cpp:1091-1096. Avoids the per-step
// allocation and lets Detour reuse internal scratch.
Unit* pathUnit = bot;
Creature* tempCreature = nullptr;
if (!pathUnit)
{
Map* map = sMapMgr->FindBaseMap(GetMapId());
if (!map)
return fullPath;
tempCreature = new Creature();
if (!tempCreature->Create(map->GenerateLowGuid<HighGuid::Unit>(), map,
PHASEMASK_NORMAL, 1 /*entry*/, 0,
currentPos.GetPositionX(), currentPos.GetPositionY(),
currentPos.GetPositionZ(), 0))
{
delete tempCreature;
return fullPath;
}
pathUnit = tempCreature;
map->EnsureGridCreated(Acore::ComputeGridCoord(currentPos.GetPositionX(), currentPos.GetPositionY()));
map->EnsureGridCreated(Acore::ComputeGridCoord(GetPositionX(), GetPositionY()));
}
PathGenerator path(pathUnit);
path.AddExcludeFlag(NAV_GROUND_STEEP);
// Limit the pathfinding attempts
for (uint32 i = 0; i < maxAttempt; i++)
{
// Drop cached poly state from the previous step. AC's
// BuildPolyPath has a subpath-prefix optimization
// (PathGenerator.cpp:285-389) that keeps ~80% of the prior
// call's poly chain when the new startPoly is anywhere in
// it, then computes a suffix from there. For a chained
// walker this implicitly snaps the start to the previous
// corridor instead of using the explicit startPos we pass
// in, bending the route. Reset wipes the cache so each step
// is a fresh A*.
path.Clear();
// Try to pathfind to this position.
subPath = getPathStepFrom(currentPos, path);
// 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();
}
if (tempCreature)
delete tempCreature;
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);
setX(GetPositionX() + radius * cosf(angle));
setY(GetPositionY() + radius * sinf(angle));
float x = GetPositionX();
float y = GetPositionY();
float z = GetPositionZ();
bool canReach = getMap()->CanReachPositionAndGetValidCoords(bot, x, y, z);
setX(x);
setY(y);
setZ(z);
return canReach;
}
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();
}
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 GetPlayer();
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;
}
GameObject* GuidPosition::GetGameObject()
{
if (!*this)
return nullptr;
if (loadedFromDB)
return ObjectAccessor::GetSpawnedGameObjectByDBGUID(GetMapId(), GetCounter());
return getMap()->GetGameObject(*this); // fallback
}
Unit* GuidPosition::GetUnit()
{
if (!*this)
return nullptr;
if (IsPlayer())
return GetPlayer();
if (IsPet())
return getMap()->GetPet(*this);
return GetCreature();
}
Creature* GuidPosition::GetCreature()
{
if (!*this)
return nullptr;
if (loadedFromDB)
return ObjectAccessor::GetSpawnedCreatureByDBGUID(GetMapId(), GetCounter());
return getMap()->GetCreature(*this); // fallback
}
Player* GuidPosition::GetPlayer()
{
if (!*this)
return nullptr;
if (IsPlayer())
return ObjectAccessor::FindPlayer(*this);
return nullptr;
}
bool GuidPosition::HasNpcFlag(NPCFlags flag) { return IsCreature() && GetCreatureTemplate()->npcflag & flag; }
bool GuidPosition::IsCreatureOrGOAccessible()
{
Map* map = getMap();
if (!map || !map->IsGridLoaded(GetPositionX(), GetPositionY()))
return false;
if (IsCreature())
{
Creature* creature = GetCreature();
if (creature && creature->IsInWorld() && creature->IsAlive())
return true;
}
else if (IsGameObject())
{
GameObject* go = GetGameObject();
if (go && go->IsInWorld())
return true;
}
return false;
}
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;
}
TravelDestination::~TravelDestination()
{
for (WorldPosition* point : points)
delete point;
points.clear();
}
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 TravelMgr::instance().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 = TravelMgr::instance().getDialogStatus(bot, entry, questTemplate); //not used, shadowed by the next declaration, line marked for removal.
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 (!TravelMgr::instance().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();
// TravelMgr::instance().botTargets.erase(std::remove(TravelMgr::instance().botTargets.begin(), TravelMgr::instance().botTargets.end(), this),
// TravelMgr::instance().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(TravelMgr::instance().nullTravelDestination, TravelMgr::instance().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(TravelMgr::instance().nullTravelDestination, TravelMgr::instance().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 (!TravelMgr::instance().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 = SharedValueContext::instance().getGlobalValue<questGuidpMap>("quest guidp map")->Get();
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");
// Node loading/generation is handled by TravelNodeMap::Init() called from TravelMgr::Init().
/*
bool fullNavPointReload = false;
bool storeNavPointReload = true;
if (!fullNavPointReload && true)
TravelNodeStore::loadNodes();
//TravelNodeMap::instance().loadNodeStore();
for (auto node : TravelNodeMap::instance().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";
TravelNodeMap::instance().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 = TravelNodeMap::instance().getNode(&startPos, nullptr, 15.0f);
TravelNode* endNode = TravelNodeMap::instance().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;
TravelNodeMap::instance().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";
TravelNodeMap::instance().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 = TravelNodeMap::instance().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 = TravelNodeMap::instance().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 = TravelNodeMap::instance().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 = TravelNodeMap::instance().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 = TravelNodeMap::instance().addNode(&pos, pos.getAreaName(), true, true, false);
}
LOG_INFO("playerbots", ">> Loaded {} navigation points.", TravelNodeMap::instance().getNodes().size());
}
TravelNodeMap::instance().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 = TravelNodeMap::instance().getNodes().size();
for (auto& startNode : TravelNodeMap::instance().getNodes())
{
if (!preloadReLinkFullyLinked && startNode->isLinked())
continue;
for (auto& endNode : TravelNodeMap::instance().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)
{
TravelNodeMap::instance().printMap();
TravelNodeMap::instance().printNodeStore();
}
}
if (!preloadSubPrint)
{
TravelNodeMap::instance().printNodeStore();
TravelNodeMap::instance().printMap();
}
LOG_INFO("playerbots", ">> Loaded paths for {} nodes.", TravelNodeMap::instance().getNodes().size());
}
bool removeLowLinkNodes = false || fullNavPointReload || storeNavPointReload;
if (removeLowLinkNodes)
{
std::vector<TravelNode*> goodNodes;
std::vector<TravelNode*> remNodes;
for (auto& node : TravelNodeMap::instance().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)
TravelNodeMap::instance().removeNode(node);
LOG_INFO("playerbots", ">> Checked {} nodes.", TravelNodeMap::instance().getNodes().size());
}
bool cleanUpNodeLinks = false || fullNavPointReload || storeNavPointReload;
bool cleanUpSubPrint = false; //Print output every 2%.
if (cleanUpNodeLinks)
{
//Routes
uint32 cur = 0;
uint32 max = TravelNodeMap::instance().getNodes().size();
//Clean up node links
for (auto& startNode : TravelNodeMap::instance().getNodes())
{
startNode->cropUselessLinks();
cur++;
if (cleanUpSubPrint && (cur * 10) / max > ((cur - 1) * 10) / max)
{
TravelNodeMap::instance().printMap();
TravelNodeMap::instance().printNodeStore();
}
}
LOG_INFO("playerbots", ">> Cleaned paths for {} nodes.", TravelNodeMap::instance().getNodes().size());
}
bool reCalculateCost = false || fullNavPointReload || storeNavPointReload;
bool forceReCalculate = false;
if (reCalculateCost)
{
for (auto& startNode : TravelNodeMap::instance().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.", TravelNodeMap::instance().getNodes().size());
}
bool mirrorMissingPaths = true || fullNavPointReload || storeNavPointReload;
if (mirrorMissingPaths)
{
for (auto& startNode : TravelNodeMap::instance().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.", TravelNodeMap::instance().getNodes().size());
}
*/
TravelNodeMap::instance().printMap();
TravelNodeMap::instance().printNodeStore();
TravelNodeMap::instance().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 (!TravelNodeMap::instance().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, "", 0x0, 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 < sRaceMgr->GetMaxRaces(); 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);
const std::vector<NextAction> defaultActions = strat->getDefaultActions();
if (defaultActions.size() > 0)
{
for (NextAction nextAction : defaultActions)
{
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 (TriggerNode*& triggerNode : triggers)
{
if (Trigger* trigger = con->GetTrigger(triggerNode->getName()))
{
triggerNode->setTrigger(trigger);
std::vector<NextAction> nextActions = triggerNode->getHandlers();
// for (uint32_t i = 0; i < nextActions.size(); ++i)
for (NextAction nextAction : nextActions)
{
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 : TravelNodeMap::instance().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->GetPositionX() + (x * 5) - 5000.0f;
float ny = pos->GetPositionY() + (y * 5) - 5000.0f;
float nz = pos->GetPositionZ() + 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.GetPositionX(), npos.GetPositionY(),
npos.GetPositionZ());
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->GetPositionX() << "," << j->GetPositionY() << "," << j->GetPositionZ();
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 = TravelMgr::instance().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 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(TravelMgr::instance().nullTravelDestination, TravelMgr::instance().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 : TravelNodeMap::instance().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());
}
}
}
void TravelMgr::Init()
{
if (sPlayerbotAIConfig.enabled)
{
PrepareZone2LevelBracket();
PrepareDestinationCache();
}
sTravelNodeMap.Init();
}
TravelMgr::FlightMasterInfo const* TravelMgr::GetNearestFlightMasterInfo(Player* bot) const
{
auto const& flightMasterCache =
(bot->GetTeamId() == TEAM_ALLIANCE) ? allianceFlightMasterCache : hordeFlightMasterCache;
FlightMasterInfo const* nearest = nullptr;
float nearestDistance = std::numeric_limits<float>::max();
for (auto const& [dbGuid, info] : flightMasterCache)
{
if (info.pos.GetMapId() != bot->GetMapId())
continue;
float distance = bot->GetExactDist2dSq(info.pos);
if (distance < nearestDistance)
{
nearestDistance = distance;
nearest = &info;
}
}
return nearest;
}
std::vector<uint32> TravelMgr::GetFlightNodesInZone(uint32 zoneId, TeamId team, uint32 excludeNode) const
{
auto const& cache = (team == TEAM_ALLIANCE) ? allianceFlightMasterCache : hordeFlightMasterCache;
std::unordered_set<uint32> seen;
std::vector<uint32> result;
for (auto const& [entry, info] : cache)
{
if (info.zoneId != zoneId || info.taxiNodeId == 0 || info.taxiNodeId == excludeNode)
continue;
if (seen.insert(info.taxiNodeId).second)
result.push_back(info.taxiNodeId);
}
return result;
}
std::vector<std::vector<uint32>> TravelMgr::GetOptimalFlightDestinations(Player* bot)
{
std::vector<std::vector<uint32>> validDestinations;
FlightMasterInfo const* nearestFlightMaster = GetNearestFlightMasterInfo(bot);
if (!nearestFlightMaster)
return validDestinations;
uint32 fromNode = nearestFlightMaster->taxiNodeId;
if (!fromNode)
return validDestinations;
TaxiNodesEntry const* startNode = sTaxiNodesStore.LookupEntry(fromNode);
if (!startNode)
return validDestinations;
uint32 botLevel = bot->GetLevel();
// Bots already in a capital shouldn't have another capital picked as a
// flight destination — that just shuffles them between cities.
bool botInCapital = false;
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(bot->GetZoneId()))
botInCapital = (area->flags & AREA_FLAG_CAPITAL) != 0;
std::vector<uint32> candidateZones;
if (botLevel >= 10 && !botInCapital &&
urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100)
{
TeamId botTeam = bot->GetTeamId();
for (Capital const& capital : capitals)
{
if (capital.team != TEAM_NEUTRAL && capital.team != botTeam)
continue;
candidateZones.push_back(capital.zoneId);
}
}
if (candidateZones.empty())
{
for (auto const& [zoneId, bracket] : zone2LevelBracket)
{
if (botLevel < bracket.low || botLevel > bracket.high)
continue;
if (GetFlightNodesInZone(zoneId, bot->GetTeamId(), fromNode).empty())
continue;
candidateZones.push_back(zoneId);
}
}
if (candidateZones.empty())
return validDestinations;
while (!candidateZones.empty())
{
uint32 zoneIndex = urand(0, candidateZones.size() - 1);
uint32 pickedZone = candidateZones[zoneIndex];
std::vector<uint32> usableNodes = GetFlightNodesInZone(pickedZone, bot->GetTeamId(), fromNode);
if (!usableNodes.empty())
{
uint32 pickedNode = usableNodes[urand(0, usableNodes.size() - 1)];
std::vector<uint32> path = sTravelNodeMap.FindTaxiPath(fromNode, pickedNode);
if (!path.empty())
{
validDestinations.push_back(std::move(path));
return validDestinations;
}
}
candidateZones.erase(candidateZones.begin() + zoneIndex);
}
return validDestinations;
}
const std::vector<WorldLocation> TravelMgr::GetTeleportLocations(Player* bot)
{
uint32 level = bot->GetLevel();
uint8 isAlliance = bot->GetTeamId() == TEAM_ALLIANCE;
if (sPlayerbotAIConfig.enableNewRpgStrategy)
return isAlliance ? allianceHubsPerLevelCache[level] : hordeHubsPerLevelCache[level];
return locsPerLevelCache[level];
}
const std::vector<WorldLocation> TravelMgr::GetTravelHubs(Player* bot)
{
std::vector<WorldLocation> locs = bot->GetTeamId() == TEAM_ALLIANCE
? allianceHubsPerLevelCache[bot->GetLevel()]
: hordeHubsPerLevelCache[bot->GetLevel()];
return locs;
}
std::vector<WorldLocation> TravelMgr::GetCityLocations(Player* bot)
{
uint32 level = bot->GetLevel();
std::vector<WorldLocation> fallbackLocations;
for (auto& bLoc : bankerLocsPerLevelCache[level])
fallbackLocations.push_back(bLoc.loc);
if (!sPlayerbotAIConfig.enableWeightTeleToCityBankers)
return fallbackLocations;
TeamId botTeamId = bot->GetTeamId();
std::unordered_set<uint32> validBankerCities;
for (auto& loc : bankerLocsPerLevelCache[level])
{
Capital const* capital = FindCapitalByBanker(loc.entry);
if (!capital)
continue;
TeamId cityTeamId = capital->team;
if (cityTeamId == botTeamId ||
(cityTeamId == TEAM_NEUTRAL)
)
validBankerCities.insert(capital->zoneId);
}
// Fallback if no valid cities
if (validBankerCities.empty())
return fallbackLocations;
// Apply weights to valid cities
std::vector<uint32> weightedCities;
for (uint32 zoneId : validBankerCities)
{
int weight = GetCityWeight(zoneId);
if (weight <= 0)
continue;
for (int i = 0; i < weight; ++i)
weightedCities.push_back(zoneId);
}
// Fallback if no valid cities
if (weightedCities.empty())
return fallbackLocations;
// Pick a weighted city randomly, then a random banker in that city
uint32 selectedCity = weightedCities[urand(0, weightedCities.size() - 1)];
Capital const* selectedCapital = FindCapitalByZone(selectedCity);
if (!selectedCapital)
return fallbackLocations;
auto const& bankers = selectedCapital->bankers;
uint32 selectedBankerEntry = bankers[urand(0, bankers.size() - 1)];
auto locIt = bankerEntryToLocation.find(selectedBankerEntry);
if (locIt != bankerEntryToLocation.end())
return { locIt->second };
// Fallback if something went wrong
return fallbackLocations;
}
bool TravelMgr::SelectAuctioneerByMap(Player* bot, NpcLocation& outAuctioneer)
{
uint16 botMapId = bot->GetMapId();
auto const& cache = (bot->GetTeamId() == TEAM_HORDE) ? hordeAuctioneerCache : allianceAuctioneerCache;
auto mapIt = cache.find(botMapId);
if (mapIt == cache.end() || mapIt->second.empty())
return false;
// Collect all areas on this map that have auctioneers
std::vector<uint32> areaIds;
areaIds.reserve(mapIt->second.size());
for (auto const& [areaId, npcs] : mapIt->second)
{
if (!npcs.empty())
areaIds.push_back(areaId);
}
if (areaIds.empty())
return false;
// Pick a random area, then a random auctioneer in that area
uint32 selectedArea = areaIds[urand(0, areaIds.size() - 1)];
auto const& auctioneers = mapIt->second.at(selectedArea);
outAuctioneer = auctioneers[urand(0, auctioneers.size() - 1)];
return true;
}
void TravelMgr::PrepareZone2LevelBracket()
{
// Classic WoW - starter zones
zone2LevelBracket[AREA_DUN_MOROGH] = {5, 12};
zone2LevelBracket[AREA_ELWYNN_FOREST] = {5, 12};
zone2LevelBracket[AREA_DUROTAR] = {5, 12};
zone2LevelBracket[AREA_TIRISFAL_GLADES] = {5, 12};
zone2LevelBracket[AREA_TELDRASSIL] = {5, 12};
zone2LevelBracket[AREA_MULGORE] = {5, 12};
zone2LevelBracket[AREA_EVERSONG_WOODS] = {5, 12};
zone2LevelBracket[AREA_AZUREMYST_ISLE] = {5, 12};
// Classic WoW - low level zones
zone2LevelBracket[AREA_THE_BARRENS] = {10, 25};
zone2LevelBracket[AREA_LOCH_MODAN] = {10, 20};
zone2LevelBracket[AREA_WESTFALL] = {10, 21};
zone2LevelBracket[AREA_SILVERPINE_FOREST] = {10, 23};
zone2LevelBracket[AREA_DARKSHORE] = {10, 21};
zone2LevelBracket[AREA_GHOSTLANDS] = {10, 22};
zone2LevelBracket[AREA_BLOODMYST_ISLE] = {10, 21};
// Classic WoW - mid-level zones
zone2LevelBracket[AREA_DUSKWOOD] = {19, 33};
zone2LevelBracket[AREA_WETLANDS] = {21, 30};
zone2LevelBracket[AREA_REDRIDGE_MOUNTAINS] = {16, 28};
zone2LevelBracket[AREA_HILLSBRAD_FOOTHILLS] = {20, 34};
zone2LevelBracket[AREA_ASHENVALE] = {18, 33};
zone2LevelBracket[AREA_THOUSAND_NEEDLES] = {24, 36};
zone2LevelBracket[AREA_STONETALON_MOUNTAINS] = {16, 29};
// Classic WoW - 30-52 zones
zone2LevelBracket[AREA_BADLANDS] = {36, 46};
zone2LevelBracket[AREA_SWAMP_OF_SORROWS] = {36, 46};
zone2LevelBracket[AREA_DUSTWALLOW_MARSH] = {35, 46};
zone2LevelBracket[AREA_AZSHARA] = {45, 52};
zone2LevelBracket[AREA_STRANGLETHORN_VALE] = {32, 47};
zone2LevelBracket[AREA_ARATHI_HIGHLANDS] = {30, 42};
zone2LevelBracket[AREA_THE_HINTERLANDS] = {42, 51};
zone2LevelBracket[AREA_SEARING_GORGE] = {45, 51};
zone2LevelBracket[AREA_FERALAS] = {40, 52};
zone2LevelBracket[AREA_DESOLACE] = {30, 41};
zone2LevelBracket[AREA_TANARIS] = {41, 52};
// Classic WoW - top level zones
zone2LevelBracket[AREA_BLASTED_LANDS] = {52, 57};
zone2LevelBracket[AREA_WESTERN_PLAGUELANDS] = {50, 60};
zone2LevelBracket[AREA_BURNING_STEPPES] = {51, 60};
zone2LevelBracket[AREA_EASTERN_PLAGUELANDS] = {54, 62};
zone2LevelBracket[361] = {47, 57}; // Felwood (no AREA_ define)
zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater (no AREA_ define)
zone2LevelBracket[AREA_WINTERSPRING] = {54, 61};
zone2LevelBracket[AREA_SILITHUS] = {54, 63};
// The Burning Crusade zones
zone2LevelBracket[AREA_HELLFIRE_PENINSULA] = {58, 66};
zone2LevelBracket[AREA_NAGRAND] = {64, 70};
zone2LevelBracket[AREA_TEROKKAR_FOREST] = {62, 73};
zone2LevelBracket[AREA_SHADOWMOON_VALLEY] = {66, 73};
zone2LevelBracket[AREA_ZANGARMARSH] = {60, 67};
zone2LevelBracket[AREA_BLADES_EDGE_MOUNTAINS] = {64, 73};
zone2LevelBracket[AREA_NETHERSTORM] = {67, 73};
zone2LevelBracket[AREA_ISLE_OF_QUEL_DANAS] = {68, 73};
// Wrath of the Lich King zones
zone2LevelBracket[AREA_DRAGONBLIGHT] = {71, 77};
zone2LevelBracket[AREA_ZUL_DRAK] = {74, 80};
zone2LevelBracket[AREA_THE_STORM_PEAKS] = {77, 80};
zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier (no AREA_ define)
zone2LevelBracket[AREA_GRIZZLY_HILLS] = {72, 78};
zone2LevelBracket[AREA_HOWLING_FJORD] = {68, 74};
zone2LevelBracket[AREA_CRYSTALSONG_FOREST] = {77, 80};
zone2LevelBracket[AREA_BOREAN_TUNDRA] = {68, 75};
zone2LevelBracket[AREA_SHOLAZAR_BASIN] = {75, 80};
zone2LevelBracket[AREA_WINTERGRASP] = {79, 80};
// Override with values from config
for (auto const& [zoneId, bracketPair] : sPlayerbotAIConfig.zoneBrackets)
zone2LevelBracket[zoneId] = {bracketPair.first, bracketPair.second};
}
void TravelMgr::PrepareDestinationCache()
{
uint32 maxLevel = sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL);
uint32 flightMastersCount = 0;
uint32 innkeepersCount = 0;
uint32 bankerCount = 0;
uint32 auctioneerCount = 0;
LOG_INFO("playerbots", "Preparing destination caches for {} levels...", maxLevel);
// Temporary map to group creatures by entry and area
std::map<std::tuple<uint16, int32, int32, int32>, std::vector<CreatureData>> tempLocsCache;
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> tempCreatureCache;
for (auto const& [guid, creatureData] : sObjectMgr->GetAllCreatureData())
{
CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureData.id1);
if (!creatureTemplate)
continue;
uint16 mapId = creatureData.mapid;
if (std::find(sPlayerbotAIConfig.randomBotMaps.begin(), sPlayerbotAIConfig.randomBotMaps.end(), mapId)
== sPlayerbotAIConfig.randomBotMaps.end())
continue;
float x = creatureData.posX;
float y = creatureData.posY;
float z = creatureData.posZ;
float orient = creatureData.orientation;
uint32 templateEntry = creatureData.id1;
Map* map = sMapMgr->FindMap(mapId, 0);
if (!map)
continue;
AreaTableEntry const* area = sAreaTableStore.LookupEntry(map->GetAreaId(PHASEMASK_NORMAL, x, y, z));
if (!area)
continue;
uint32 areaId = area->zone ? area->zone : area->ID;
// CREATURES
if (creatureTemplate->npcflag == 0 &&
creatureTemplate->lootid != 0 &&
creatureTemplate->maxlevel - creatureTemplate->minlevel < 3 &&
creatureTemplate->Entry != 32820 && creatureTemplate->Entry != 24196 &&
creatureTemplate->Entry != 30627 && creatureTemplate->Entry != 30617 &&
creatureData.spawntimesecs < 1000 &&
creatureTemplate->faction != 11 && creatureTemplate->faction != 71 &&
creatureTemplate->faction != 79 && creatureTemplate->faction != 85 &&
creatureTemplate->faction != 188 && creatureTemplate->faction != 1575 &&
(creatureTemplate->unit_flags & 256) == 0 &&
(creatureTemplate->unit_flags & 4096) == 0 &&
creatureTemplate->rank == 0)
{
uint32 roundX = static_cast<uint32>(std::round(x / 50.0f));
uint32 roundY = static_cast<uint32>(std::round(y / 50.0f));
uint32 roundZ = static_cast<uint32>(std::round(z / 50.0f));
tempLocsCache[std::make_tuple(mapId, roundX, roundY, roundZ)].push_back(creatureData);
tempCreatureCache[templateEntry][areaId].push_back(WorldLocation(mapId, x, y, z));
}
// FLIGHT MASTERS
// Entry 29480 is Grimwing (Storm Peaks) — has FLIGHTMASTER flag but
// isn't a real usable flight master; skip it.
else if ((creatureTemplate->npcflag & UNIT_NPC_FLAG_FLIGHTMASTER ||
creatureTemplate->npcflag & UNIT_NPC_FLAG_INNKEEPER) &&
creatureTemplate->Entry != 29480)
{
FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(creatureTemplate->faction);
bool forHorde = !(factionEntry->hostileMask & 4);
bool forAlliance = !(factionEntry->hostileMask & 2);
if (creatureTemplate->npcflag & UNIT_NPC_FLAG_FLIGHTMASTER)
{
WorldPosition pos(mapId, x, y, z, orient);
if (forHorde)
{
FlightMasterInfo info;
info.pos = pos;
info.zoneId = areaId;
info.taxiNodeId = sObjectMgr->GetNearestTaxiNode(x, y, z, mapId, TEAM_HORDE);
info.templateEntry = templateEntry;
info.dbGuid = guid;
hordeFlightMasterCache[guid] = info;
}
if (forAlliance)
{
FlightMasterInfo info;
info.pos = pos;
info.zoneId = areaId;
info.taxiNodeId = sObjectMgr->GetNearestTaxiNode(x, y, z, mapId, TEAM_ALLIANCE);
info.templateEntry = templateEntry;
info.dbGuid = guid;
allianceFlightMasterCache[guid] = info;
}
flightMastersCount++;
// Zones that have flight masters but no innkeepers — use flight master as hub
static const std::set<uint32> zonesWithoutInnkeeper = {
AREA_BLASTED_LANDS,
AREA_AZSHARA,
AREA_WESTERN_PLAGUELANDS,
AREA_BURNING_STEPPES,
AREA_SEARING_GORGE,
361, // Felwood (47-57)
490, // Un'Goro Crater (49-56)
AREA_CRYSTALSONG_FOREST,
AREA_WINTERGRASP
};
if (zonesWithoutInnkeeper.count(areaId))
{
LevelBracket bracket = zone2LevelBracket[areaId];
WorldPosition loc(mapId, x + cos(orient) * 5.0f, y + sin(orient) * 5.0f, z + 0.5f, orient + M_PI);
for (int i = bracket.low; i <= bracket.high; i++)
{
if (forHorde)
hordeHubsPerLevelCache[i].push_back(loc);
if (forAlliance)
allianceHubsPerLevelCache[i].push_back(loc);
}
}
}
else if (creatureTemplate->npcflag & UNIT_NPC_FLAG_INNKEEPER)
{
if (zone2LevelBracket.find(areaId) == zone2LevelBracket.end())
continue;
LevelBracket bracket = zone2LevelBracket[areaId];
WorldPosition loc(mapId, x + cos(orient) * 5.0f, y + sin(orient) * 5.0f, z + 0.5f, orient + M_PI);
for (int i = bracket.low; i <= bracket.high; i++)
{
if (forHorde)
hordeHubsPerLevelCache[i].push_back(loc);
if (forAlliance)
allianceHubsPerLevelCache[i].push_back(loc);
innkeepersCount++;
}
}
}
// === BANKERS ===
else if (creatureTemplate->npcflag & UNIT_NPC_FLAG_BANKER &&
creatureTemplate->npcflag != 135298 &&
creatureTemplate->minlevel != 55 &&
creatureTemplate->minlevel != 65 &&
creatureTemplate->faction != 35 && creatureTemplate->faction != 474 &&
creatureTemplate->faction != 69 && creatureTemplate->faction != 57 &&
creatureTemplate->Entry != 30606 && creatureTemplate->Entry != 30608 &&
creatureTemplate->Entry != 29282)
{
NpcLocation bLoc;
bLoc.loc = WorldLocation(mapId, x + cos(orient) * 6.0f, y + sin(orient) * 6.0f, z + 2.0f, orient + M_PI);
bLoc.entry = templateEntry;
uint32 level = (creatureTemplate->minlevel + creatureTemplate->maxlevel + 1) / 2;
for (int32 l = 1; l <= maxLevel; l++)
{
// Bots 1-60 go to base game bankers (all have minlevel 30 or 45)
if (l <=60 && level > 45)
continue;
// Bots 61-70 go to Shattrath bankers (all have minlevel 60 or 70)
if ((l >=61 && l <=70) && (level < 60 || level > 70))
continue;
// Bots 71+ go to Dalaran bankers (all have minlevel 75)
if ((l >=71) && level != 75)
continue;
bankerLocsPerLevelCache[(uint8)l].push_back(bLoc);
bankerEntryToLocation[bLoc.entry] = bLoc.loc;
}
bankerCount++;
}
// === AUCTIONEERS ===
else if (creatureTemplate->npcflag & UNIT_NPC_FLAG_AUCTIONEER)
{
FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(creatureTemplate->faction);
if (!factionEntry)
continue;
bool forHorde = !(factionEntry->hostileMask & 4);
bool forAlliance = !(factionEntry->hostileMask & 2);
if (!forHorde && !forAlliance)
continue;
NpcLocation aLoc;
aLoc.loc = WorldLocation(mapId, x + cos(orient) * 3.0f, y + sin(orient) * 3.0f, z + 0.5f, orient + M_PI);
aLoc.entry = templateEntry;
if (forHorde)
hordeAuctioneerCache[mapId][areaId].push_back(aLoc);
if (forAlliance)
allianceAuctioneerCache[mapId][areaId].push_back(aLoc);
auctioneerCount++;
}
}
// Process temporary caches
for (auto const& [gridTuple, creatureDataList] : tempLocsCache)
{
if (creatureDataList.size() >= 2)
{
CreatureTemplate const* creatureTemplate = sObjectMgr->GetCreatureTemplate(creatureDataList[0].id1);
uint32 level = (creatureTemplate->minlevel + creatureTemplate->maxlevel + 1) / 2;
float totalX = 0.0f;
float totalY = 0.0f;
float totalZ = 0.0f;
for (CreatureData const& creatureData : creatureDataList)
{
totalX += creatureData.posX;
totalY += creatureData.posY;
totalZ += creatureData.posZ;
}
float avgX = totalX / creatureDataList.size();
float avgY = totalY / creatureDataList.size();
float avgZ = totalZ / creatureDataList.size();
uint32 mapId = std::get<0>(gridTuple);
for (int32 l = (int32)level - (int32)sPlayerbotAIConfig.randomBotTeleLowerLevel;
l <= (int32)level + (int32)sPlayerbotAIConfig.randomBotTeleHigherLevel; l++)
{
if (l < 1 || l > maxLevel)
continue;
locsPerLevelCache[(uint8)l].push_back(WorldLocation(mapId, avgX, avgY, avgZ, 0.0f));
}
}
}
for (auto const& [entry, areaMap] : tempCreatureCache)
{
for (auto const& [area, locList] : areaMap)
{
if (locList.size() > 3)
continue;
float totalX = 0, totalY = 0, totalZ = 0;
for (auto const& loc : locList)
{
totalX += loc.GetPositionX();
totalY += loc.GetPositionY();
totalZ += loc.GetPositionZ();
}
float avgX = totalX / locList.size();
float avgY = totalY / locList.size();
float avgZ = totalZ / locList.size();
creatureSpawnsByTemplate[entry].push_back(WorldLocation(locList[0].GetMapId(), avgX, avgY, avgZ, 0));
}
}
// Add travel hubs based on player start locations
for (uint32 i = 1; i < sRaceMgr->GetMaxRaces(); i++)
{
for (uint32 j = 1; j < MAX_CLASSES; j++)
{
PlayerInfo const* info = sObjectMgr->GetPlayerInfo(i, j);
if (!info)
continue;
WorldPosition pos(info->mapId, info->positionX, info->positionY, info->positionZ, info->orientation);
for (int32 l = 1; l <= 5; l++)
{
if ((1 << (i - 1)) & sRaceMgr->GetAllianceRaceMask())
allianceHubsPerLevelCache[(uint8)l].push_back(pos);
else
hordeHubsPerLevelCache[(uint8)l].push_back(pos);
}
break;
}
}
LOG_INFO("playerbots", ">> {} flight masters, {} innkeepers, {} bankers, {} auctioneers collected.", flightMastersCount, innkeepersCount, bankerCount, auctioneerCount);
}