mod-playerbots/src/Mgr/Travel/TravelMgr.cpp
Keleborn 2b273f6a2c
Fix merge error in test staging (#2226)
<!--
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.
-->

Fix merge error we missed due to core sync issues. 

## Pull Request Description
<!-- Describe what this change does and why it is needed -->



## 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.



## 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.
-->



## 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**)



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



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



## Messages to Translate
<!--
Bot messages have to be translatable, but you don't need to do the
translations here. You only need to make sure
the message is in a translatable format, and list in the table the
message_key and the default English message.
Search for GetBotTextOrDefault in the codebase for examples.
-->
Does this change add bot messages to translate?
- [x] No
- [ ] Yes (**list messages in the table**)

| Message key  | Default message |
| --------------- | ------------------ |
|			 |			      |
|			 |			      |

## 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?
- [x] No
- [ ] 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.
-->



## Final Checklist

- [x] Stability is not compromised.
- [x] Performance impact is understood, tested, and acceptable.
- [x] Added logic complexity is justified and explained.
- [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-03-21 08:23:07 +01:00

4790 lines
162 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 "Creature.h"
#include "Log.h"
#include "ObjectAccessor.h"
#include "TravelNode.h"
#include "Talentspec.h"
#include "ChatHelper.h"
#include "MMapFactory.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
enum class CityId : uint8
{
STORMWIND,
IRONFORGE,
DARNASSUS,
EXODAR,
ORGRIMMAR,
UNDERCITY,
THUNDER_BLUFF,
SILVERMOON_CITY,
SHATTRATH_CITY,
DALARAN
};
static const std::unordered_map<uint16, std::pair<CityId, TeamId>> bankerToCity = {
{2455, {CityId::STORMWIND, TEAM_ALLIANCE}}, {2456, {CityId::STORMWIND, TEAM_ALLIANCE}}, {2457, {CityId::STORMWIND, TEAM_ALLIANCE}},
{2460, {CityId::IRONFORGE, TEAM_ALLIANCE}}, {2461, {CityId::IRONFORGE, TEAM_ALLIANCE}}, {5099, {CityId::IRONFORGE, TEAM_ALLIANCE}},
{4155, {CityId::DARNASSUS, TEAM_ALLIANCE}}, {4208, {CityId::DARNASSUS, TEAM_ALLIANCE}}, {4209, {CityId::DARNASSUS, TEAM_ALLIANCE}},
{17773, {CityId::EXODAR, TEAM_ALLIANCE}}, {18350, {CityId::EXODAR, TEAM_ALLIANCE}}, {16710, {CityId::EXODAR, TEAM_ALLIANCE}},
{3320, {CityId::ORGRIMMAR, TEAM_HORDE}}, {3309, {CityId::ORGRIMMAR, TEAM_HORDE}}, {3318, {CityId::ORGRIMMAR, TEAM_HORDE}},
{4549, {CityId::UNDERCITY, TEAM_HORDE}}, {2459, {CityId::UNDERCITY, TEAM_HORDE}}, {2458, {CityId::UNDERCITY, TEAM_HORDE}}, {4550, {CityId::UNDERCITY, TEAM_HORDE}},
{2996, {CityId::THUNDER_BLUFF, TEAM_HORDE}}, {8356, {CityId::THUNDER_BLUFF, TEAM_HORDE}}, {8357, {CityId::THUNDER_BLUFF, TEAM_HORDE}},
{17631, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {17632, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {17633, {CityId::SILVERMOON_CITY, TEAM_HORDE}},
{16615, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {16616, {CityId::SILVERMOON_CITY, TEAM_HORDE}}, {16617, {CityId::SILVERMOON_CITY, TEAM_HORDE}},
{19246, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, {19338, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}},
{19034, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}}, {19318, {CityId::SHATTRATH_CITY, TEAM_NEUTRAL}},
{30604, {CityId::DALARAN, TEAM_NEUTRAL}}, {30605, {CityId::DALARAN, TEAM_NEUTRAL}}, {30607, {CityId::DALARAN, TEAM_NEUTRAL}},
{28675, {CityId::DALARAN, TEAM_NEUTRAL}}, {28676, {CityId::DALARAN, TEAM_NEUTRAL}}, {28677, {CityId::DALARAN, TEAM_NEUTRAL}}
};
static const std::unordered_map<CityId, std::vector<uint16>> cityToBankers = {
{CityId::STORMWIND, {2455, 2456, 2457}},
{CityId::IRONFORGE, {2460, 2461, 5099}},
{CityId::DARNASSUS, {4155, 4208, 4209}},
{CityId::EXODAR, {17773, 18350, 16710}},
{CityId::ORGRIMMAR, {3320, 3309, 3318}},
{CityId::UNDERCITY, {4549, 2459, 2458, 4550}},
{CityId::THUNDER_BLUFF, {2996, 8356, 8357}},
{CityId::SILVERMOON_CITY, {17631, 17632, 17633, 16615, 16616, 16617}},
{CityId::SHATTRATH_CITY, {19246, 19338, 19034, 19318}},
{CityId::DALARAN, {30604, 30605, 30607, 28675, 28676, 28677, 29530}}
};
static int GetCityWeight(CityId city)
{
int weight = 0;
switch (city)
{
case CityId::STORMWIND: weight = sPlayerbotAIConfig.weightTeleToStormwind; break;
case CityId::IRONFORGE: weight = sPlayerbotAIConfig.weightTeleToIronforge; break;
case CityId::DARNASSUS: weight = sPlayerbotAIConfig.weightTeleToDarnassus; break;
case CityId::EXODAR: weight = sPlayerbotAIConfig.weightTeleToExodar; break;
case CityId::ORGRIMMAR: weight = sPlayerbotAIConfig.weightTeleToOrgrimmar; break;
case CityId::UNDERCITY: weight = sPlayerbotAIConfig.weightTeleToUndercity; break;
case CityId::THUNDER_BLUFF: weight = sPlayerbotAIConfig.weightTeleToThunderBluff; break;
case CityId::SILVERMOON_CITY: weight = sPlayerbotAIConfig.weightTeleToSilvermoonCity; break;
case CityId::SHATTRATH_CITY: weight = sPlayerbotAIConfig.weightTeleToShattrathCity; break;
case CityId::DALARAN: weight = sPlayerbotAIConfig.weightTeleToDalaran; break;
default: weight = 0; break;
}
return weight;
}
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;
}
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
if (!MMAP::MMapFactory::createOrGetMMapMgr()->loadMap(mapId, x, y))
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.");
}
Creature* TravelMgr::GetNearestFlightMaster(Player* bot)
{
std::map<uint32, WorldPosition>& flightMasterCache =
(bot->GetTeamId() == TEAM_ALLIANCE) ? allianceFlightMasterCache : hordeFlightMasterCache;
Creature* nearestFlightMaster = nullptr;
float nearestDistance = std::numeric_limits<float>::max();
for (auto const& [entry, pos] : flightMasterCache)
{
if (pos.GetMapId() != bot->GetMapId())
continue;
float distance = bot->GetExactDist2dSq(pos);
if (distance > nearestDistance)
continue;
Creature* flightMaster = ObjectAccessor::GetSpawnedCreatureByDBGUID(bot->GetMapId(), entry);
if (flightMaster)
{
nearestDistance = distance;
nearestFlightMaster = flightMaster;
}
}
return nearestFlightMaster;
}
ObjectGuid TravelMgr::GetNearestFlightMasterGuid(Player* bot)
{
Creature* nearestFlightMaster = GetNearestFlightMaster(bot);
if (!nearestFlightMaster)
return ObjectGuid::Empty;
return nearestFlightMaster->GetGUID();
}
std::vector<std::vector<uint32>> TravelMgr::GetOptimalFlightDestinations(Player* bot)
{
std::vector<std::vector<uint32>> validDestinations;
Creature* nearestFlightMaster = GetNearestFlightMaster(bot);
if (!nearestFlightMaster || bot->GetDistance(nearestFlightMaster) > 500.0f)
return validDestinations;
uint32 fromNode = sObjectMgr->GetNearestTaxiNode(nearestFlightMaster->GetPositionX(), nearestFlightMaster->GetPositionY(),
nearestFlightMaster->GetPositionZ(), nearestFlightMaster->GetMapId(),
bot->GetTeamId());
if (!fromNode)
return validDestinations;
std::vector<WorldLocation> candidateLocations;
if (bot->GetLevel() >= 10 && urand(0, 100) < sPlayerbotAIConfig.probTeleToBankers * 100)
candidateLocations = GetCityLocations(bot);
std::vector<WorldLocation> hubLocations = GetTravelHubs(bot);
candidateLocations.insert(candidateLocations.end(), hubLocations.begin(), hubLocations.end());
for (auto const& loc : candidateLocations)
{
uint32 candidateNode = sObjectMgr->GetNearestTaxiNode(loc.GetPositionX(), loc.GetPositionY(),
loc.GetPositionZ(), loc.GetMapId(),
bot->GetTeamId());
if (!candidateNode)
continue;
std::vector<uint32> path = sTravelNodeMap.FindTaxiPath(fromNode, candidateNode);
if (!path.empty())
validDestinations.push_back(path);
}
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<CityId> validBankerCities;
for (auto& loc : bankerLocsPerLevelCache[level])
{
auto cityIt = bankerToCity.find(loc.entry);
if (cityIt == bankerToCity.end())
continue;
TeamId cityTeamId = cityIt->second.second;
if (cityTeamId == botTeamId ||
(cityTeamId == TEAM_NEUTRAL)
)
validBankerCities.insert(cityIt->second.first);
}
// Fallback if no valid cities
if (validBankerCities.empty())
return fallbackLocations;
// Apply weights to valid cities
std::vector<CityId> weightedCities;
for (CityId city : validBankerCities)
{
int weight = GetCityWeight(city);
if (weight <= 0)
continue;
for (int i = 0; i < weight; ++i)
weightedCities.push_back(city);
}
// Fallback if no valid cities
if (weightedCities.empty())
return fallbackLocations;
// Pick a weighted city randomly, then a random banker in that city
CityId selectedCity = weightedCities[urand(0, weightedCities.size() - 1)];
auto const& bankers = cityToBankers.at(selectedCity);
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 - Low - level zones
zone2LevelBracket[1] = {5, 12}; // Dun Morogh
zone2LevelBracket[12] = {5, 12}; // Elwynn Forest
zone2LevelBracket[14] = {5, 12}; // Durotar
zone2LevelBracket[85] = {5, 12}; // Tirisfal Glades
zone2LevelBracket[141] = {5, 12}; // Teldrassil
zone2LevelBracket[215] = {5, 12}; // Mulgore
zone2LevelBracket[3430] = {5, 12}; // Eversong Woods
zone2LevelBracket[3524] = {5, 12}; // Azuremyst Isle
// Classic WoW - Mid - level zones
zone2LevelBracket[17] = {10, 25}; // Barrens
zone2LevelBracket[38] = {10, 20}; // Loch Modan
zone2LevelBracket[40] = {10, 21}; // Westfall
zone2LevelBracket[130] = {10, 23}; // Silverpine Forest
zone2LevelBracket[148] = {10, 21}; // Darkshore
zone2LevelBracket[3433] = {10, 22}; // Ghostlands
zone2LevelBracket[3525] = {10, 21}; // Bloodmyst Isle
// Classic WoW - High - level zones
zone2LevelBracket[10] = {19, 33}; // Deadwind Pass
zone2LevelBracket[11] = {21, 30}; // Wetlands
zone2LevelBracket[44] = {16, 28}; // Redridge Mountains
zone2LevelBracket[267] = {20, 34}; // Hillsbrad Foothills
zone2LevelBracket[331] = {18, 33}; // Ashenvale
zone2LevelBracket[400] = {24, 36}; // Thousand Needles
zone2LevelBracket[406] = {16, 29}; // Stonetalon Mountains
// Classic WoW - Higher - level zones
zone2LevelBracket[3] = {36, 46}; // Badlands
zone2LevelBracket[8] = {36, 46}; // Swamp of Sorrows
zone2LevelBracket[15] = {35, 46}; // Dustwallow Marsh
zone2LevelBracket[16] = {45, 52}; // Azshara
zone2LevelBracket[33] = {32, 47}; // Stranglethorn Vale
zone2LevelBracket[45] = {30, 42}; // Arathi Highlands
zone2LevelBracket[47] = {42, 51}; // Hinterlands
zone2LevelBracket[51] = {45, 51}; // Searing Gorge
zone2LevelBracket[357] = {40, 52}; // Feralas
zone2LevelBracket[405] = {30, 41}; // Desolace
zone2LevelBracket[440] = {41, 52}; // Tanaris
// Classic WoW - Top - level zones
zone2LevelBracket[4] = {52, 57}; // Blasted Lands
zone2LevelBracket[28] = {50, 60}; // Western Plaguelands
zone2LevelBracket[46] = {51, 60}; // Burning Steppes
zone2LevelBracket[139] = {54, 62}; // Eastern Plaguelands
zone2LevelBracket[361] = {47, 57}; // Felwood
zone2LevelBracket[490] = {49, 56}; // Un'Goro Crater
zone2LevelBracket[618] = {54, 61}; // Winterspring
zone2LevelBracket[1377] = {54, 63}; // Silithus
// The Burning Crusade - Zones
zone2LevelBracket[3483] = {58, 66}; // Hellfire Peninsula
zone2LevelBracket[3518] = {64, 70}; // Nagrand
zone2LevelBracket[3519] = {62, 73}; // Terokkar Forest
zone2LevelBracket[3520] = {66, 73}; // Shadowmoon Valley
zone2LevelBracket[3521] = {60, 67}; // Zangarmarsh
zone2LevelBracket[3522] = {64, 73}; // Blade's Edge Mountains
zone2LevelBracket[3523] = {67, 73}; // Netherstorm
zone2LevelBracket[4080] = {68, 73}; // Isle of Quel'Danas
// Wrath of the Lich King - Zones
zone2LevelBracket[65] = {71, 77}; // Dragonblight
zone2LevelBracket[66] = {74, 80}; // Zul'Drak
zone2LevelBracket[67] = {77, 80}; // Storm Peaks
zone2LevelBracket[210] = {77, 80}; // Icecrown Glacier
zone2LevelBracket[394] = {72, 78}; // Grizzly Hills
zone2LevelBracket[495] = {68, 74}; // Howling Fjord
zone2LevelBracket[2817] = {77, 80}; // Crystalsong Forest
zone2LevelBracket[3537] = {68, 75}; // Borean Tundra
zone2LevelBracket[3711] = {75, 80}; // Sholazar Basin
zone2LevelBracket[4197] = {79, 80}; // Wintergrasp
// 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 = (x / 50.0f) * 10.0f;
uint32 roundY = (y / 50.0f) * 10.0f;
uint32 roundZ = (z / 50.0f) * 10.0f;
tempLocsCache[std::make_tuple(mapId, roundX, roundY, roundZ)].push_back(creatureData);
tempCreatureCache[templateEntry][areaId].push_back(WorldLocation(mapId, x, y, z));
}
// FLIGHT MASTERS
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)
hordeFlightMasterCache[guid] = pos;
if (forAlliance)
allianceFlightMasterCache[guid] = pos;
flightMastersCount++;
}
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);
}