mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-02-26 13:25:54 +00:00
# Pull Request This change replaces a few manual distance calculations in `WorldPosition` with AzerothCore distance helpers. The goal is to reduce duplicated math, keep behavior consistent with core utilities, and avoid reimplementing logic that already exists in the core. --- ## Design Philosophy We prioritize **stability, performance, and predictability** over behavioral realism. Complex player-mimicking logic is intentionally limited due to its negative impact on scalability, maintainability, and long-term robustness. Excessive processing overhead can lead to server hiccups, increased CPU usage, and degraded performance for all participants. Because every action and decision tree is executed **per bot and per trigger**, even small increases in logic complexity can scale poorly and negatively affect both players and world (random) bots. Bots are not expected to behave perfectly, and perfect simulation of human decision-making is not a project goal. Increased behavioral realism often introduces disproportionate cost, reduced predictability, and significantly higher maintenance overhead. Every additional branch of logic increases long-term responsibility. All decision paths must be tested, validated, and maintained continuously as the system evolves. If advanced or AI-intensive behavior is introduced, the **default configuration must remain the lightweight decision model**. More complex behavior should only be available as an **explicit opt-in option**, clearly documented as having a measurable performance cost. Principles: - **Stability before intelligence** A stable system is always preferred over a smarter one. - **Performance is a shared resource** Any increase in bot cost affects all players and all bots. - **Simple logic scales better than smart logic** Predictable behavior under load is more valuable than perfect decisions. - **Complexity must justify itself** If a feature cannot clearly explain its cost, it should not exist. - **Defaults must be cheap** Expensive behavior must always be optional and clearly communicated. - **Bots should look reasonable, not perfect** The goal is believable behavior, not human simulation. Before submitting, confirm that this change aligns with those principles. --- ## Feature Evaluation Please answer the following: - Describe the **minimum logic** required to achieve the intended behavior? Use existing core distance helpers instead of manual math, keeping the logic localized to `WorldPosition`. - Describe the **cheapest implementation** that produces an acceptable result? Directly call `GetExactDist`, `GetExactDist2d`, and `GetExactDist2dSq` where appropriate. - Describe the **runtime cost** when this logic executes across many bots? No additional cost; the helper calls replace equivalent math and avoid extra intermediate objects. --- ## How to Test the Changes - Step-by-step instructions to test the change - Build the module and run existing bot scenarios that rely on `WorldPosition` distance checks. - Verify no behavioral regressions in travel-related logic. - Any required setup (e.g. multiple players, bots, specific configuration) - Standard server + mod-playerbots setup. - Expected behavior and how to verify it - Distances computed in travel logic remain identical; no gameplay change expected. ## Complexity & Impact Does this change add new decision branches? - - [x] No - - [ ] Yes (**explain below**) Does this change increase per-bot or per-tick processing? - - [x] No - - [ ] Yes (**describe and justify impact**) Could this logic scale poorly under load? - - [x] No - - [ ] Yes (**explain why**) --- ## Defaults & Configuration Does this change modify default bot behavior? - - [x] No - - [ ] Yes (**explain why**) If this introduces more advanced or AI-heavy logic: - - [x] Lightweight mode remains the default - - [x] More complex behavior is optional and thereby configurable --- ## AI Assistance Was AI assistance (e.g. ChatGPT or similar tools) used while working on this change? - - [x] No - - [ ] Yes (**explain below**) --- ## Final Checklist - - [x] Stability is not compromised - - [x] Performance impact is understood, tested, and acceptable - - [x] Added logic complexity is justified and explained - - [ ] Documentation updated if needed --- ## Notes for Reviewers This is a localized refactor that replaces manual distance math with core helpers for consistency and maintainability. No behavioral change is expected. --------- Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
4289 lines
141 KiB
C++
4289 lines
141 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 "Talentspec.h"
|
|
#include "ChatHelper.h"
|
|
#include "MMapFactory.h"
|
|
#include "MapMgr.h"
|
|
#include "PathGenerator.h"
|
|
#include "Playerbots.h"
|
|
#include "TransportMgr.h"
|
|
#include "VMapFactory.h"
|
|
#include "VMapMgr2.h"
|
|
#include "Map.h"
|
|
#include "Corpse.h"
|
|
#include "CellImpl.h"
|
|
|
|
WorldPosition::WorldPosition(std::string const str)
|
|
{
|
|
std::vector<std::string> tokens = split(str, '|');
|
|
if (tokens.size() == 5)
|
|
{
|
|
try
|
|
{
|
|
m_mapId = std::stoi(tokens[0]);
|
|
m_positionX = std::stof(tokens[1]);
|
|
m_positionY = std::stof(tokens[2]);
|
|
m_positionZ = std::stof(tokens[3]);
|
|
m_orientation = std::stof(tokens[4]);
|
|
}
|
|
catch (const std::exception&)
|
|
{
|
|
m_mapId = 0;
|
|
m_positionX = 0.0f;
|
|
m_positionY = 0.0f;
|
|
m_positionZ = 0.0f;
|
|
m_orientation = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
WorldPosition::WorldPosition(uint32 mapId, const Position& pos)
|
|
: WorldLocation(mapId, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation())
|
|
{
|
|
}
|
|
|
|
WorldPosition::WorldPosition(WorldObject const* wo)
|
|
{
|
|
if (wo)
|
|
{
|
|
set(WorldLocation(wo->GetMapId(), wo->GetPositionX(), wo->GetPositionY(), wo->GetPositionZ(),
|
|
wo->GetOrientation()));
|
|
}
|
|
}
|
|
|
|
WorldPosition::WorldPosition(std::vector<WorldPosition*> list, WorldPositionConst conType)
|
|
{
|
|
uint32 size = list.size();
|
|
if (!size)
|
|
return;
|
|
|
|
if (size == 1)
|
|
set(*list.front());
|
|
else if (conType == WP_RANDOM)
|
|
set(*list[urand(0, size - 1)]);
|
|
else if (conType == WP_CENTROID)
|
|
{
|
|
set(std::accumulate(list.begin(), list.end(), WorldLocation(list[0]->GetMapId(), 0, 0, 0, 0),
|
|
[size](WorldLocation i, WorldPosition* j)
|
|
{
|
|
i.m_positionX += j->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 < MAX_RACES; race++)
|
|
{
|
|
for (uint8 cls = CLASS_WARRIOR; cls < MAX_CLASSES; ++cls)
|
|
{
|
|
if (cls != 10)
|
|
{
|
|
std::unique_ptr<CharacterCreateInfo> characterInfo =
|
|
std::make_unique<CharacterCreateInfo>("dummy", race, cls, 1, 1, 1, 1, 1, 1);
|
|
Player* player = new Player(session);
|
|
if (player->Create(sObjectMgr->GetGenerator<HighGuid::Player>().Generate(),
|
|
characterInfo.get()))
|
|
{
|
|
for (uint8 tab = 0; tab < 3; tab++)
|
|
{
|
|
TalentSpec newSpec;
|
|
if (tab == 0)
|
|
newSpec = TalentSpec(player, "1-0-0");
|
|
else if (tab == 1)
|
|
newSpec = TalentSpec(player, "0-1-0");
|
|
else
|
|
newSpec = TalentSpec(player, "0-0-1");
|
|
|
|
for (uint32 lvl = 1; lvl < MAX_LEVEL; lvl++)
|
|
{
|
|
player->SetLevel(lvl);
|
|
|
|
std::ostringstream tout;
|
|
newSpec.ApplyTalents(player, &tout);
|
|
|
|
PlayerbotAI* botAI = new PlayerbotAI(player);
|
|
|
|
botAI->ResetStrategies(false);
|
|
|
|
AiObjectContext* con = botAI->GetAiObjectContext();
|
|
|
|
std::vector<std::string> tstrats;
|
|
std::set<std::string> strategies;
|
|
std::set<std::string> sstrats;
|
|
|
|
tstrats = botAI->GetStrategies(BOT_STATE_COMBAT);
|
|
sstrats = con->GetSupportedStrategies();
|
|
if (!sstrats.empty())
|
|
strategies.insert(tstrats.begin(), tstrats.end());
|
|
|
|
tstrats = botAI->GetStrategies(BOT_STATE_NON_COMBAT);
|
|
if (!tstrats.empty())
|
|
strategies.insert(tstrats.begin(), tstrats.end());
|
|
|
|
tstrats = botAI->GetStrategies(BOT_STATE_DEAD);
|
|
if (!tstrats.empty())
|
|
strategies.insert(tstrats.begin(), tstrats.end());
|
|
|
|
sstrats = con->GetSupportedStrategies();
|
|
if (!sstrats.empty())
|
|
strategies.insert(sstrats.begin(), sstrats.end());
|
|
|
|
for (auto& stratName : strategies)
|
|
{
|
|
Strategy* strat = con->GetStrategy(stratName);
|
|
|
|
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());
|
|
}
|
|
}
|
|
}
|