Merge pull request #553 from TotallyNotElite/master

Navparser and pathing improvements
This commit is contained in:
LightCat 2018-08-19 20:32:52 +02:00 committed by GitHub
commit a9616460b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 227 additions and 94 deletions

View File

@ -3,16 +3,91 @@
#include "common.hpp" #include "common.hpp"
#include "micropather.h" #include "micropather.h"
#include "pwd.h" #include "pwd.h"
#if ENABLE_VISUALS
#include <glez/draw.hpp>
#endif
namespace nav namespace nav
{ {
extern bool init; extern bool init;
extern bool ReadyForCommands; extern bool ReadyForCommands;
extern int priority;
extern std::vector<CNavArea> areas; extern std::vector<CNavArea> areas;
std::vector<Vector> findPath(Vector loc, Vector dest); std::vector<Vector> findPath(Vector loc, Vector dest, int &id_loc,
int &id_dest);
bool NavTo(Vector dest, bool navToLocalCenter = true, bool persistent = true, bool NavTo(Vector dest, bool navToLocalCenter = true, bool persistent = true,
int instructionPriority = 5); int instructionPriority = 5);
void clearInstructions();
int findClosestNavSquare(Vector vec); int findClosestNavSquare(Vector vec);
bool Prepare(); bool Prepare();
void CreateMove(); void CreateMove();
void Draw();
struct inactivityTracker
{
// Map for storing inactivity per connection
std::map<std::pair<int, int>, unsigned int> inactives;
void reset()
{
// inactives.clear();
}
void addTime(std::pair<int, int> connection, Timer &timer,
bool &resetPather)
{
if (inactives.find(connection) == inactives.end())
{
inactives[connection] = 0;
}
inactives[connection] =
inactives[connection] +
(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - timer.last)
.count());
if (inactives[connection] >= 5000)
resetPather = true;
}
void addTime(std::pair<Vector, Vector> connection, Timer &timer,
bool &resetPather)
{
CNavArea *currnode = nullptr;
for (size_t i = 0; i < areas.size(); i++)
{
if (areas.at(i).m_center == connection.first)
{
currnode = &areas.at(i);
break;
}
}
if (!currnode)
return;
CNavArea *nextnode = nullptr;
for (size_t i = 0; i < areas.size(); i++)
{
if (areas.at(i).m_center == connection.second)
{
nextnode = &areas.at(i);
break;
}
}
if (!nextnode)
return;
for (auto i : currnode->m_connections)
{
if (i.area->m_id == nextnode->m_id)
{
addTime(std::pair{ currnode->m_id, nextnode->m_id }, timer,
resetPather);
return;
}
}
}
unsigned int getTime(std::pair<int, int> connection)
{
return inactives[connection];
}
};
} // namespace nav } // namespace nav

View File

@ -4,11 +4,13 @@
#pragma once #pragma once
#include <config.h>
#include <string> #include <string>
#include <functional> #include <functional>
#include <glez/color.hpp>
#include <config.h> #if ENABLE_VISUALS
#include <glez/color.hpp>
#endif
/* /*
@ -142,4 +144,4 @@ protected:
#if ENABLE_VISUALS #if ENABLE_VISUALS
#include "Rgba.hpp" #include "Rgba.hpp"
#endif #endif

View File

@ -287,11 +287,11 @@ DEFINE_HOOKED_METHOD(CreateMove, bool, void *this_, float input_sample_time,
hacks::shared::walkbot::Move(); hacks::shared::walkbot::Move();
} }
{ {
PROF_SECTION(CM_navbot); PROF_SECTION(CM_navparse);
nav::CreateMove(); nav::CreateMove();
} }
{ {
PROF_SECTION(CM_NavParse); PROF_SECTION(CM_navbot);
hacks::tf2::NavBot::CreateMove(); hacks::tf2::NavBot::CreateMove();
} }
// Walkbot can leave game. // Walkbot can leave game.

View File

@ -3,19 +3,19 @@
namespace nav namespace nav
{ {
static CNavFile navfile(nullptr); static CNavFile navfile(nullptr);
// Vector containing areas on map
std::vector<CNavArea> areas; std::vector<CNavArea> areas;
// std::vector<CNavArea> SniperAreas; bool init = false;
bool init = false; static bool pathfinding = true;
bool pathfinding = true; bool ReadyForCommands = false;
bool ReadyForCommands = false; inactivityTracker inactiveTracker;
std::vector<std::pair<int, int>> ignoredConnections;
static settings::Bool enabled{ "misc.pathing", "true" }; static settings::Bool enabled{ "misc.pathing", "true" };
static settings::Bool draw{ "misc.pathing.draw", "false" };
// Todo fix // Todo fix
int FindInVector(int id) size_t FindInVector(size_t id)
{ {
for (int i = 0; i < areas.size(); i++) for (size_t i = 0; i < areas.size(); i++)
{ {
if (areas.at(i).m_id == id) if (areas.at(i).m_id == id)
return i; return i;
@ -25,16 +25,11 @@ int FindInVector(int id)
struct MAP : public micropather::Graph struct MAP : public micropather::Graph
{ {
std::unique_ptr<micropather::MicroPather> pather; std::unique_ptr<micropather::MicroPather> pather;
bool IsIgnored(int currState, int connectionID) // Function to check if a connection is ignored
bool IsIgnored(size_t currState, size_t connectionID)
{ {
for (int i = 0; i < ignoredConnections.size(); i++) if (inactiveTracker.getTime({ currState, connectionID }) >= 5000)
{ return true;
if (ignoredConnections.at(i).first == currState &&
ignoredConnections.at(i).second == connectionID)
{
return true;
}
}
return false; return false;
} }
Vector GetClosestCornerToArea(CNavArea *CornerOf, CNavArea *Target) Vector GetClosestCornerToArea(CNavArea *CornerOf, CNavArea *Target)
@ -50,7 +45,7 @@ struct MAP : public micropather::Graph
Vector bestVec; Vector bestVec;
float bestDist = FLT_MAX; float bestDist = FLT_MAX;
for (int i = 0; i < corners.size(); i++) for (size_t i = 0; i < corners.size(); i++)
{ {
float dist = corners.at(i).DistTo(Target->m_center); float dist = corners.at(i).DistTo(Target->m_center);
if (dist < bestDist) if (dist < bestDist)
@ -63,7 +58,7 @@ struct MAP : public micropather::Graph
Vector bestVec2; Vector bestVec2;
float bestDist2 = FLT_MAX; float bestDist2 = FLT_MAX;
for (int i = 0; i < corners.size(); i++) for (size_t i = 0; i < corners.size(); i++)
{ {
if (corners.at(i) == bestVec2) if (corners.at(i) == bestVec2)
continue; continue;
@ -84,6 +79,7 @@ struct MAP : public micropather::Graph
return z2 - z1; return z2 - z1;
} }
// Function required by MicroPather for getting an estimated cost
float LeastCostEstimate(void *stateStart, void *stateEnd) float LeastCostEstimate(void *stateStart, void *stateEnd)
{ {
CNavArea *start = static_cast<CNavArea *>(stateStart); CNavArea *start = static_cast<CNavArea *>(stateStart);
@ -91,6 +87,8 @@ struct MAP : public micropather::Graph
float dist = start->m_center.DistTo(end->m_center); float dist = start->m_center.DistTo(end->m_center);
return dist; return dist;
} }
// Function required by MicroPather to retrieve neighbours and their
// associated costs.
void AdjacentCost(void *state, MP_VECTOR<micropather::StateCost> *adjacent) void AdjacentCost(void *state, MP_VECTOR<micropather::StateCost> *adjacent)
{ {
CNavArea *area = static_cast<CNavArea *>(state); CNavArea *area = static_cast<CNavArea *>(state);
@ -110,6 +108,10 @@ struct MAP : public micropather::Graph
} }
void PrintStateInfo(void *state) void PrintStateInfo(void *state)
{ {
CNavArea *area = static_cast<CNavArea *>(state);
logging::Info(format(area->m_center.x, " ", area->m_center.y, " ",
area->m_center.z)
.c_str());
} }
MAP(size_t size) MAP(size_t size)
{ {
@ -122,14 +124,14 @@ struct MAP : public micropather::Graph
} }
}; };
std::unique_ptr<MAP> TF2MAP; static std::unique_ptr<MAP> TF2MAP;
void Init() void Init()
{ {
// TODO: Improve performance // Get NavFile location
std::string lvlname(g_IEngine->GetLevelName()); std::string lvlname(g_IEngine->GetLevelName());
int dotpos = lvlname.find('.'); size_t dotpos = lvlname.find('.');
lvlname = lvlname.substr(0, dotpos); lvlname = lvlname.substr(0, dotpos);
std::string lvldir("/home/"); std::string lvldir("/home/");
passwd *pwd = getpwuid(getuid()); passwd *pwd = getpwuid(getuid());
@ -140,6 +142,7 @@ void Init()
logging::Info(format("Pathing: Nav File location: ", lvldir).c_str()); logging::Info(format("Pathing: Nav File location: ", lvldir).c_str());
areas.clear(); areas.clear();
// Load the NavFile
navfile = CNavFile(lvldir.c_str()); navfile = CNavFile(lvldir.c_str());
if (!navfile.m_isOK) if (!navfile.m_isOK)
logging::Info("Pathing: Invalid Nav File"); logging::Info("Pathing: Invalid Nav File");
@ -149,15 +152,20 @@ void Init()
logging::Info( logging::Info(
format("Pathing: Number of areas to index:", size).c_str()); format("Pathing: Number of areas to index:", size).c_str());
areas.reserve(size); areas.reserve(size);
// Add areas from CNavFile to our Vector
for (auto i : navfile.m_areas) for (auto i : navfile.m_areas)
areas.push_back(i); areas.push_back(i);
// Don't reserve more than 7000 states
if (size > 7000) if (size > 7000)
size = 7000; size = 7000;
// Initiate "Map", contains micropather object
TF2MAP = std::make_unique<MAP>(size); TF2MAP = std::make_unique<MAP>(size);
} }
pathfinding = true; pathfinding = true;
} }
std::string lastmap;
static std::string lastmap;
// Function that decides if pathing is possible, and inits pathing if necessary
bool Prepare() bool Prepare()
{ {
if (!enabled) if (!enabled)
@ -181,24 +189,31 @@ bool Prepare()
return true; return true;
} }
std::vector<int> localAreas(6); // This prevents the bot from gettings completely stuck in some cases
static std::vector<int> findClosestNavSquare_localAreas(6);
// Function for getting closest Area to player, aka "LocalNav"
int findClosestNavSquare(Vector vec) int findClosestNavSquare(Vector vec)
{ {
if (localAreas.size() > 5) if (findClosestNavSquare_localAreas.size() > 5)
localAreas.erase(localAreas.begin()); findClosestNavSquare_localAreas.erase(
findClosestNavSquare_localAreas.begin());
std::vector<std::pair<int, CNavArea *>> overlapping; std::vector<std::pair<int, CNavArea *>> overlapping;
for (int i = 0; i < areas.size(); i++) for (size_t i = 0; i < areas.size(); i++)
{ {
// Check if we are within x and y bounds of an area
if (areas.at(i).IsOverlapping(vec)) if (areas.at(i).IsOverlapping(vec))
{ {
if (std::count(localAreas.begin(), localAreas.end(), i) < 2) // Make sure we're not stuck on the same area for too long
if (std::count(findClosestNavSquare_localAreas.begin(),
findClosestNavSquare_localAreas.end(), i) < 3)
overlapping.push_back({ i, &areas.at(i) }); overlapping.push_back({ i, &areas.at(i) });
} }
} }
// If multiple candidates for LocalNav have been found, pick the closest
float bestDist = FLT_MAX; float bestDist = FLT_MAX;
int bestSquare = -1; int bestSquare = -1;
for (int i = 0; i < overlapping.size(); i++) for (size_t i = 0; i < overlapping.size(); i++)
{ {
float dist = overlapping.at(i).second->m_center.DistTo(vec); float dist = overlapping.at(i).second->m_center.DistTo(vec);
if (dist < bestDist) if (dist < bestDist)
@ -210,15 +225,17 @@ int findClosestNavSquare(Vector vec)
if (bestSquare != -1) if (bestSquare != -1)
{ {
if (vec == g_pLocalPlayer->v_Origin) if (vec == g_pLocalPlayer->v_Origin)
localAreas.push_back(bestSquare); findClosestNavSquare_localAreas.push_back(bestSquare);
return bestSquare; return bestSquare;
} }
for (int i = 0; i < areas.size(); i++) // If no LocalNav was found, pick the closest available Area
for (size_t i = 0; i < areas.size(); i++)
{ {
float dist = areas.at(i).m_center.DistTo(vec); float dist = areas.at(i).m_center.DistTo(vec);
if (dist < bestDist) if (dist < bestDist)
{ {
if (std::count(localAreas.begin(), localAreas.end(), i) < 2) if (std::count(findClosestNavSquare_localAreas.begin(),
findClosestNavSquare_localAreas.end(), i) < 3)
{ {
bestDist = dist; bestDist = dist;
bestSquare = i; bestSquare = i;
@ -226,41 +243,56 @@ int findClosestNavSquare(Vector vec)
} }
} }
if (vec == g_pLocalPlayer->v_Origin) if (vec == g_pLocalPlayer->v_Origin)
localAreas.push_back(bestSquare); findClosestNavSquare_localAreas.push_back(bestSquare);
return bestSquare; return bestSquare;
} }
std::vector<Vector> findPath(Vector loc, Vector dest) std::vector<Vector> findPath(Vector loc, Vector dest, int &id_loc, int &id_dest)
{ {
if (areas.empty()) if (areas.empty())
return std::vector<Vector>(0); return std::vector<Vector>(0);
int id_loc = findClosestNavSquare(loc); // Get areas of Vector loc and dest
int id_dest = findClosestNavSquare(dest); id_loc = findClosestNavSquare(loc);
id_dest = findClosestNavSquare(dest);
if (id_loc == -1 || id_dest == -1) if (id_loc == -1 || id_dest == -1)
return std::vector<Vector>(0); return std::vector<Vector>(0);
float cost; float cost;
micropather::MPVector<void *> pathNodes; micropather::MPVector<void *> pathNodes;
// Find a solution to get to location
int result = TF2MAP->pather->Solve(static_cast<void *>(&areas.at(id_loc)), int result = TF2MAP->pather->Solve(static_cast<void *>(&areas.at(id_loc)),
static_cast<void *>(&areas.at(id_dest)), static_cast<void *>(&areas.at(id_dest)),
&pathNodes, &cost); &pathNodes, &cost);
logging::Info(format(result).c_str()); logging::Info(format(result).c_str());
if (result == 1) // If no result found, return empty Vector
if (result == micropather::MicroPather::NO_SOLUTION)
return std::vector<Vector>(0); return std::vector<Vector>(0);
// Convert (void *) CNavArea * to Vector
std::vector<Vector> path; std::vector<Vector> path;
for (int i = 0; i < pathNodes.size(); i++) for (size_t i = 0; i < pathNodes.size(); i++)
{ {
path.push_back(static_cast<CNavArea *>(pathNodes[i])->m_center); path.push_back(static_cast<CNavArea *>(pathNodes[i])->m_center);
} }
// Add our destination to the std::vector
path.push_back(dest); path.push_back(dest);
return path; return path;
} }
// Timer for measuring inactivity, aka time between not reaching a crumb
static Timer inactivity{}; static Timer inactivity{};
// Time since our last Jump
static Timer lastJump{}; static Timer lastJump{};
// std::vector containing our path
static std::vector<Vector> crumbs; static std::vector<Vector> crumbs;
// Bot will keep trying to get to the target even if it fails a few times
static bool ensureArrival; static bool ensureArrival;
int priority = 0; // Priority value for current instructions, only higher priority can overwrite
// itlocalAreas
int priority = 0;
static Vector lastArea = { 0.0f, 0.0f, 0.0f };
// dest = Destination, navToLocalCenter = Should bot travel to local center
// first before resuming pathing activity? (Increases accuracy) persistent =
// ensureArrival above, instructionPriority = priority above
bool NavTo(Vector dest, bool navToLocalCenter, bool persistent, bool NavTo(Vector dest, bool navToLocalCenter, bool persistent,
int instructionPriority) int instructionPriority)
{ {
@ -268,73 +300,49 @@ bool NavTo(Vector dest, bool navToLocalCenter, bool persistent,
return false; return false;
if (!Prepare()) if (!Prepare())
return false; return false;
if (instructionPriority <= priority) // Only allow instructions to overwrite others if their priority is higher
if (instructionPriority < priority)
return false; return false;
auto path = findPath(g_pLocalPlayer->v_Origin, dest); int locNav, tarNav;
auto path = findPath(g_pLocalPlayer->v_Origin, dest, locNav, tarNav);
if (path.empty()) if (path.empty())
return false; return false;
if (!crumbs.empty())
{
bool reset = false;
inactiveTracker.addTime({ lastArea, crumbs.at(0) }, inactivity, reset);
if (reset)
TF2MAP->pather->Reset();
}
crumbs.clear(); crumbs.clear();
crumbs = std::move(path); crumbs = std::move(path);
if (!navToLocalCenter) if (!navToLocalCenter)
crumbs.erase(crumbs.begin()); crumbs.erase(crumbs.begin());
ensureArrival = persistent; ensureArrival = persistent;
localAreas.clear(); findClosestNavSquare_localAreas.clear();
priority = instructionPriority; priority = instructionPriority;
inactivity.update();
return true; return true;
} }
static Vector lastArea = { 0.0f, 0.0f, 0.0f }; void clearInstructions()
void ignoreConnection()
{ {
if (crumbs.size() < 1) crumbs.clear();
return;
CNavArea *currnode = nullptr;
for (int i = 0; i < areas.size(); i++)
{
if (areas.at(i).m_center == lastArea)
{
currnode = &areas.at(i);
break;
}
}
if (!currnode)
return;
CNavArea *nextnode = nullptr;
for (int i = 0; i < areas.size(); i++)
{
if (areas.at(i).m_center == crumbs.at(0))
{
nextnode = &areas.at(i);
break;
}
}
if (!nextnode)
return;
for (auto i : currnode->m_connections)
{
if (i.area->m_id == nextnode->m_id)
{
ignoredConnections.push_back({ currnode->m_id, nextnode->m_id });
TF2MAP->pather->Reset();
return;
}
}
} }
static Timer ignoreReset{}; static Timer ignoreReset{};
// Function for removing ignores
void clearIgnores() void clearIgnores()
{ {
if (ignoreReset.test_and_set(180000)) if (ignoreReset.test_and_set(180000))
{ {
ignoredConnections.clear(); inactiveTracker.reset();
if (TF2MAP && TF2MAP->pather) if (TF2MAP && TF2MAP->pather)
TF2MAP->pather->Reset(); TF2MAP->pather->Reset();
} }
} }
// Main movement function, gets path from NavTo
void CreateMove() void CreateMove()
{ {
if (!enabled) if (!enabled)
@ -343,10 +351,12 @@ void CreateMove()
return; return;
if (!LOCAL_E->m_bAlivePlayer()) if (!LOCAL_E->m_bAlivePlayer())
{ {
// Clear path if player dead
crumbs.clear(); crumbs.clear();
return; return;
} }
clearIgnores(); clearIgnores();
// Crumbs empty, prepare for next instruction
if (crumbs.empty()) if (crumbs.empty())
{ {
priority = 0; priority = 0;
@ -355,6 +365,7 @@ void CreateMove()
return; return;
} }
ReadyForCommands = false; ReadyForCommands = false;
// Remove old crumbs
if (g_pLocalPlayer->v_Origin.DistTo(Vector{ crumbs.at(0).x, crumbs.at(0).y, if (g_pLocalPlayer->v_Origin.DistTo(Vector{ crumbs.at(0).x, crumbs.at(0).y,
g_pLocalPlayer->v_Origin.z }) < g_pLocalPlayer->v_Origin.z }) <
30.0f) 30.0f)
@ -365,32 +376,72 @@ void CreateMove()
} }
if (crumbs.empty()) if (crumbs.empty())
return; return;
// Detect when jumping is necessary
if (crumbs.at(0).z - g_pLocalPlayer->v_Origin.z > 18 && if (crumbs.at(0).z - g_pLocalPlayer->v_Origin.z > 18 &&
lastJump.test_and_set(200)) lastJump.test_and_set(200))
current_user_cmd->buttons |= IN_JUMP; current_user_cmd->buttons |= IN_JUMP;
if (inactivity.test_and_set(5000)) // If inactive for too long
if (inactivity.check(5000))
{ {
ignoreConnection(); // Ignore connection
bool i3 = false;
inactiveTracker.addTime({ lastArea, crumbs.at(0) }, inactivity, i3);
if (i3)
TF2MAP->pather->Reset();
if (ensureArrival) if (ensureArrival)
{ {
logging::Info("Pathing: NavBot inactive for too long. Ignoring " logging::Info("Pathing: NavBot inactive for too long. Ignoring "
"connection and finding another path..."); "connection and finding another path...");
// NavTo(crumbs.back(), true, true); // Find a new path
crumbs = findPath(g_pLocalPlayer->v_Origin, crumbs.back()); int i1, i2;
inactivity.update(); crumbs = findPath(g_pLocalPlayer->v_Origin, crumbs.back(), i1, i2);
} }
else else
{ {
logging::Info( logging::Info(
"Pathing: NavBot inactive for too long. Canceling tasks and " "Pathing: NavBot inactive for too long. Canceling tasks and "
"ignoring connection..."); "ignoring connection...");
// Wait for new instructions
crumbs.clear(); crumbs.clear();
} }
inactivity.update();
return; return;
} }
// Walk to next crumb
WalkTo(crumbs.at(0)); WalkTo(crumbs.at(0));
} }
void Draw()
{
#if ENABLE_VISUALS
if (!enabled || !draw)
return;
if (!enabled)
return;
if (CE_BAD(LOCAL_E))
return;
if (!LOCAL_E->m_bAlivePlayer())
return;
if (crumbs.size() < 2)
return;
for (size_t i = 0; i < crumbs.size() - 1; i++)
{
Vector wts1, wts2;
if (draw::WorldToScreen(crumbs[i], wts1) &&
draw::WorldToScreen(crumbs[i + 1], wts2))
{
glez::draw::line(wts1.x, wts1.y, wts2.x - wts1.x, wts2.y - wts1.y,
colors::white, 0.1f);
}
}
Vector wts;
if (!draw::WorldToScreen(crumbs[0], wts))
return;
glez::draw::rect(wts.x - 4, wts.y - 4, 8, 8, colors::white);
glez::draw::rect_outline(wts.x - 4, wts.y - 4, 7, 7, colors::white, 1.0f);
#endif
}
CatCommand navinit("nav_init", "Debug nav init", [](const CCommand &args) { CatCommand navinit("nav_init", "Debug nav init", [](const CCommand &args) {
lastmap = ""; lastmap = "";
init = false; init = false;
@ -407,7 +458,8 @@ CatCommand navprint("nav_print", "Debug nav print", [](const CCommand &args) {
}); });
CatCommand navfind("nav_find", "Debug nav find", [](const CCommand &args) { CatCommand navfind("nav_find", "Debug nav find", [](const CCommand &args) {
std::vector<Vector> path = findPath(g_pLocalPlayer->v_Origin, loc); int i1, i2;
std::vector<Vector> path = findPath(g_pLocalPlayer->v_Origin, loc, i1, i2);
if (path.empty()) if (path.empty())
{ {
logging::Info("Pathing: No path found"); logging::Info("Pathing: No path found");

View File

@ -143,6 +143,10 @@ void DrawCheatVisuals()
PROF_SECTION(DRAW_walkbot); PROF_SECTION(DRAW_walkbot);
hacks::shared::walkbot::Draw(); hacks::shared::walkbot::Draw();
} }
{
PROF_SECTION(DRAW_navparse);
nav::Draw();
}
IF_GAME(IsTF()) IF_GAME(IsTF())
{ {
PROF_SECTION(PT_antidisguise); PROF_SECTION(PT_antidisguise);