mod-playerbots/src/Mgr/Travel/TravelMgr.cpp
Keleborn c819516325
Fix rpg travel flying (#2324)
<!--
Thank you for contributing to mod-playerbots, please make sure that
you...
1. Submit your PR to the test-staging branch, not master.
2. Read the guidelines below before submitting.
3. Don't delete parts of this template.

DESIGN PHILOSOPHY: We prioritize STABILITY, PERFORMANCE, AND
PREDICTABILITY over behavioral realism.

Every action and decision executes PER BOT AND PER TRIGGER. Small
increases in logic complexity scale
poorly across thousands of bots and negatively affect all. We prioritize
a stable system over a smarter
one. Bots don't need to behave perfectly; believable behavior is the
goal, not human simulation.
Default behavior must be cheap in processing; expensive behavior must be
opt-in.

Before submitting, make sure your changes aligns with these principles.
-->

## Pull Request Description
<!-- Describe what this change does and why it is needed -->
Clean up values that were incorrectly translated from the sql search
into the dbc search.
Refactors structure for cities in TravelMgr to try to resolve some
duplication issues.
Change to position based search, so that bots dont get stuck if they
fail to resolve the flightmaster game object when it hasnt spawned.
TravelFlight state now stores flight master entry + world position
instead of ObjectGuid, so the bot can move back into range and
re-resolve the NPC locally via FindNearestCreature
Bundles reliability cleanup in NewRpgTravelFlightAction: uses
info.ChangeToIdle() consistently and adds the missing return true after
a failed taxi path

## Feature Evaluation
<!--
If your PR is very minimal (comment typo, wrong ID reference, etc), and
it is very obvious it will not have
any impact on performance, you may skip these question. If necessary, a
maintainer may ask you for them later.
-->

<!-- Please answer the following: -->
- Describe the **minimum logic** required to achieve the intended
behavior.
- Describe the **processing cost** when this logic executes across many
bots.

No expected shanges. 

## How to Test the Changes
<!--
- Step-by-step instructions to test the change.
- Any required setup (e.g. multiple players, number of bots, specific
configuration).
- Expected behavior and how to verify it.
-->

Run the server and check if zones are getting populated well. 

## Impact Assessment
<!-- As a generic test, before and after measure of pmon (playerbot pmon
tick) can help you here. -->
- Does this change increase per-bot/per-tick processing or risk scaling
poorly with thousands of bots?
    - - [x] No, not at all
    - - [ ] Minimal impact (**explain below**)
    - - [ ] Moderate impact (**explain below**)

Run with 4k bots, no issues. 

- Does this change modify default bot behavior?
    - - [ ] No
    - - [x] Yes (**explain why**)

It should correctly send bots to the areas appropriate for their level
in an equally weighted manner.


- Does this change add new decision branches or increase maintenance
complexity?
    - - [x] No
    - - [ ] Yes (**explain below**)



## AI Assistance
<!--
AI assistance is allowed, but all submitted code must be fully
understood, reviewed, and owned by the contributor.
We expect contributors to be honest about what they do and do not
understand.
-->
Was AI assistance used while working on this change?
- - [ ] No
- - x ] Yes (**explain below**)
<!--
If yes, please specify:
- Purpose of usage (e.g. brainstorming, refactoring, documentation, code
generation).
- Which parts of the change were influenced or generated, and whether it
was thoroughly reviewed.
-->
Refactoring the data structure based on my instruction. 
All parts reviewed. 


<!--
TRANSLATIONS:
Anything new that the bots say in chat must be in a translatable format.
This is done using GetBotTextOrDefault,
which you can search for in the codebase to find examples. Your code
needs to have English as the default fallback,
while the full translations need to be in an SQL update file. The
languages in the file are the nine language
options supported by AzerothCore: English, Korean, French, German,
Chinese, Taiwanese, Spanish, Spanish Mexico, and
Russian. See
data/sql/playerbots/updates/2025_12_27_ai_playerbot_fishing_text.sql as
an example of a translation SQL
update, whose content are called within the codebase at
src/strategy/actions/FishingAction.cpp
-->

## Final Checklist

- - [x] Stability is not compromised.
- - [x] Performance impact is understood, tested, and acceptable.
- - [x] Added logic complexity is justified and explained.
- - [x] Any new bot dialogue lines are translated.
- - [x] Documentation updated if needed (Conf comments, WiKi commands).

## Notes for Reviewers
<!-- Anything else that's helpful to review or test your pull request.
-->
2026-05-02 12:19:23 -07:00

4872 lines
164 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;
}
// TODO: Cleanup — make this actually work.
void WorldPosition::loadMapAndVMap(uint32 mapId, uint8 x, uint8 y)
{
std::string const fileName = "load_map_grid.csv";
/*
if (isOverworld() && false || false)
{
if (!MMAP::MMapFactory::createOrGetMMapMgr()->loadMap(mapId, x, y))
if (sPlayerbotAIConfig.hasLog(fileName))
{
std::ostringstream out;
out << sPlayerbotAIConfig.GetTimestampStr();
out << "+00,\"mmap\", " << x << "," << y << "," << (TravelMgr::instance().isBadMmap(mapId, x, y) ? "0" : "1")
<< ",";
printWKT(fromGridCoord(GridCoord(x, y)), out, 1, true);
sPlayerbotAIConfig.log(fileName, out.str().c_str());
}
}
else
{
// This needs to be disabled or maps will not load.
// Needs more testing to check for impact on movement.
if (false)
if (!TravelMgr::instance().isBadVmap(mapId, x, y))
{
// load VMAPs for current map/grid...
const MapEntry* i_mapEntry = sMapStore.LookupEntry(mapId);
//const char* mapName = i_mapEntry ? i_mapEntry->name[sWorld->GetDefaultDbcLocale()] : "UNNAMEDMAP\x0"; //not used, (usage are commented out below), line marked for removal.
int vmapLoadResult = VMAP::VMapFactory::createOrGetVMapMgr()->loadMap(
(sWorld->GetDataPath() + "vmaps").c_str(), mapId, x, y);
switch (vmapLoadResult)
{
case VMAP::VMAP_LOAD_RESULT_OK:
// LOG_ERROR("playerbots", "VMAP loaded name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})",
// mapName, mapId, x, y, x, y);
break;
case VMAP::VMAP_LOAD_RESULT_ERROR:
// LOG_ERROR("playerbots", "Could not load VMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{},
// y:{})", mapName, mapId, x, y, x, y);
TravelMgr::instance().addBadVmap(mapId, x, y);
break;
case VMAP::VMAP_LOAD_RESULT_IGNORED:
TravelMgr::instance().addBadVmap(mapId, x, y);
// LOG_INFO("playerbots", "Ignored VMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})",
// mapName, mapId, x, y, x, y);
break;
}
if (sPlayerbotAIConfig.hasLog(fileName))
{
std::ostringstream out;
out << sPlayerbotAIConfig.GetTimestampStr();
out << "+00,\"vmap\", " << x << "," << y << ", " << (TravelMgr::instance().isBadVmap(mapId, x, y) ? "0" : "1")
<< ",";
printWKT(frommGridCoord(mGridCoord(x, y)), out, 1, true);
sPlayerbotAIConfig.log(fileName, out.str().c_str());
}
}
*/
if (!TravelMgr::instance().isBadMmap(mapId, x, y))
{
// load navmesh
Map* map = getMap();
if (map && map->GetMapCollisionData().LoadMMapTile(x, y) == MMAP::MMAP_LOAD_RESULT_ERROR)
TravelMgr::instance().addBadMmap(mapId, x, y);
if (sPlayerbotAIConfig.hasLog(fileName))
{
std::ostringstream out;
out << sPlayerbotAIConfig.GetTimestampStr();
out << "+00,\"mmap\", " << x << "," << y << "," << (TravelMgr::instance().isBadMmap(mapId, x, y) ? "0" : "1")
<< ",";
printWKT(fromGridCoord(GridCoord(x, y)), out, 1, true);
sPlayerbotAIConfig.log(fileName, out.str().c_str());
}
}
}
void WorldPosition::loadMapAndVMaps(WorldPosition secondPos)
{
for (auto& grid : getmGridCoords(secondPos))
{
loadMapAndVMap(GetMapId(), grid.first, grid.second);
}
}
std::vector<WorldPosition> WorldPosition::fromPointsArray(std::vector<G3D::Vector3> path)
{
std::vector<WorldPosition> retVec;
for (auto p : path)
retVec.push_back(WorldPosition(GetMapId(), p.x, p.y, p.z, 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)
{
if (!bot)
return {};
// Load mmaps and vmaps between the two points.
loadMapAndVMaps(startPos);
PathGenerator path(bot);
path.CalculatePath(startPos.GetPositionX(), startPos.GetPositionY(), startPos.GetPositionZ());
Movement::PointsArray points = path.GetPath();
PathType type = path.GetPathType();
if (sPlayerbotAIConfig.hasLog("pathfind_attempt_point.csv"))
{
std::ostringstream out;
out << std::fixed << std::setprecision(1);
printWKT({startPos, *this}, out);
sPlayerbotAIConfig.log("pathfind_attempt_point.csv", out.str().c_str());
}
if (sPlayerbotAIConfig.hasLog("pathfind_attempt.csv") && (type == PATHFIND_INCOMPLETE || type == PATHFIND_NORMAL))
{
std::ostringstream out;
out << sPlayerbotAIConfig.GetTimestampStr() << "+00,";
out << std::fixed << std::setprecision(1) << type << ",";
printWKT(fromPointsArray(points), out, 1);
sPlayerbotAIConfig.log("pathfind_attempt.csv", out.str().c_str());
}
if (type == PATHFIND_INCOMPLETE || type == PATHFIND_NORMAL)
return fromPointsArray(points);
return {};
}
bool WorldPosition::cropPathTo(std::vector<WorldPosition>& path, float maxDistance)
{
if (path.empty())
return false;
auto bestPos = std::min_element(path.begin(), path.end(),
[this](WorldPosition i, WorldPosition j)
{ return this->sqDistance(i) < this->sqDistance(j); });
bool insRange = this->sqDistance(*bestPos) <= maxDistance * maxDistance;
if (bestPos == path.end())
return insRange;
path.erase(std::next(bestPos), path.end());
return insRange;
}
// A sequential series of pathfinding attempts. Returns the complete path and if the patfinder eventually found a way to
// the destination.
std::vector<WorldPosition> WorldPosition::getPathFromPath(std::vector<WorldPosition> startPath, Unit* bot,
uint8 maxAttempt)
{
// We start at the end of the last path.
WorldPosition currentPos = startPath.back();
// No pathfinding across maps.
if (GetMapId() != currentPos.GetMapId())
return {};
std::vector<WorldPosition> subPath, fullPath = startPath;
// Limit the pathfinding attempts
for (uint32 i = 0; i < maxAttempt; i++)
{
// Try to pathfind to this position.
subPath = getPathStepFrom(currentPos, bot);
// If we could not find a path return what we have now.
if (subPath.empty() || currentPos.distance(&subPath.back()) < sPlayerbotAIConfig.targetPosRecalcDistance)
break;
// Append the path excluding the start (this should be the same as the end of the startPath)
fullPath.insert(fullPath.end(), std::next(subPath.begin(), 1), subPath.end());
// Are we there yet?
if (isPathTo(subPath))
break;
// Continue pathfinding.
currentPos = subPath.back();
}
return fullPath;
}
bool WorldPosition::GetReachableRandomPointOnGround(Player* bot, float radius, bool randomRange)
{
radius *= randomRange ? rand_norm() : 1.f;
float angle = rand_norm() * static_cast<float>(2 * M_PI);
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;
}
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");
TravelNodeMap::instance().loadNodeStore();
TravelNodeMap::instance().generateAll();
/*
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.InitTaxiGraph();
LOG_INFO("playerbots", "Playerbots Taxi graph and destination cache built.");
}
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 || bot->GetDistance(nearestFlightMaster->pos) > 500.0f)
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;
//Simplify destination delection. Its either target cities (Based on config value) or target world.
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;
}
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;
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)
// Entry 3838 is Vesprystus in Rut'Theran. Need Travel Node system to resolve this one.
else if ((creatureTemplate->npcflag & UNIT_NPC_FLAG_FLIGHTMASTER ||
creatureTemplate->npcflag & UNIT_NPC_FLAG_INNKEEPER) &&
creatureTemplate->Entry != 3838 && 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)
{
BankerLocation 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++;
}
}
// 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;
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(std::get<0>(gridTuple)));
}
}
}
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 and {} innkeepers and {} banker locations for level collected.", flightMastersCount, innkeepersCount, bankerCount);
}