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 "micropather.h"
#include "pwd.h"
#if ENABLE_VISUALS
#include <glez/draw.hpp>
#endif
namespace nav
{
extern bool init;
extern bool ReadyForCommands;
extern int priority;
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,
int instructionPriority = 5);
void clearInstructions();
int findClosestNavSquare(Vector vec);
bool Prepare();
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

View File

@ -4,11 +4,13 @@
#pragma once
#include <config.h>
#include <string>
#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
#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();
}
{
PROF_SECTION(CM_navbot);
PROF_SECTION(CM_navparse);
nav::CreateMove();
}
{
PROF_SECTION(CM_NavParse);
PROF_SECTION(CM_navbot);
hacks::tf2::NavBot::CreateMove();
}
// Walkbot can leave game.

View File

@ -3,19 +3,19 @@
namespace nav
{
static CNavFile navfile(nullptr);
// Vector containing areas on map
std::vector<CNavArea> areas;
// std::vector<CNavArea> SniperAreas;
bool init = false;
bool pathfinding = true;
bool ReadyForCommands = false;
std::vector<std::pair<int, int>> ignoredConnections;
bool init = false;
static bool pathfinding = true;
bool ReadyForCommands = false;
inactivityTracker inactiveTracker;
static settings::Bool enabled{ "misc.pathing", "true" };
static settings::Bool draw{ "misc.pathing.draw", "false" };
// 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)
return i;
@ -25,16 +25,11 @@ int FindInVector(int id)
struct MAP : public micropather::Graph
{
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 (ignoredConnections.at(i).first == currState &&
ignoredConnections.at(i).second == connectionID)
{
return true;
}
}
if (inactiveTracker.getTime({ currState, connectionID }) >= 5000)
return true;
return false;
}
Vector GetClosestCornerToArea(CNavArea *CornerOf, CNavArea *Target)
@ -50,7 +45,7 @@ struct MAP : public micropather::Graph
Vector bestVec;
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);
if (dist < bestDist)
@ -63,7 +58,7 @@ struct MAP : public micropather::Graph
Vector bestVec2;
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)
continue;
@ -84,6 +79,7 @@ struct MAP : public micropather::Graph
return z2 - z1;
}
// Function required by MicroPather for getting an estimated cost
float LeastCostEstimate(void *stateStart, void *stateEnd)
{
CNavArea *start = static_cast<CNavArea *>(stateStart);
@ -91,6 +87,8 @@ struct MAP : public micropather::Graph
float dist = start->m_center.DistTo(end->m_center);
return dist;
}
// Function required by MicroPather to retrieve neighbours and their
// associated costs.
void AdjacentCost(void *state, MP_VECTOR<micropather::StateCost> *adjacent)
{
CNavArea *area = static_cast<CNavArea *>(state);
@ -110,6 +108,10 @@ struct MAP : public micropather::Graph
}
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)
{
@ -122,14 +124,14 @@ struct MAP : public micropather::Graph
}
};
std::unique_ptr<MAP> TF2MAP;
static std::unique_ptr<MAP> TF2MAP;
void Init()
{
// TODO: Improve performance
// Get NavFile location
std::string lvlname(g_IEngine->GetLevelName());
int dotpos = lvlname.find('.');
lvlname = lvlname.substr(0, dotpos);
size_t dotpos = lvlname.find('.');
lvlname = lvlname.substr(0, dotpos);
std::string lvldir("/home/");
passwd *pwd = getpwuid(getuid());
@ -140,6 +142,7 @@ void Init()
logging::Info(format("Pathing: Nav File location: ", lvldir).c_str());
areas.clear();
// Load the NavFile
navfile = CNavFile(lvldir.c_str());
if (!navfile.m_isOK)
logging::Info("Pathing: Invalid Nav File");
@ -149,15 +152,20 @@ void Init()
logging::Info(
format("Pathing: Number of areas to index:", size).c_str());
areas.reserve(size);
// Add areas from CNavFile to our Vector
for (auto i : navfile.m_areas)
areas.push_back(i);
// Don't reserve more than 7000 states
if (size > 7000)
size = 7000;
// Initiate "Map", contains micropather object
TF2MAP = std::make_unique<MAP>(size);
}
pathfinding = true;
}
std::string lastmap;
static std::string lastmap;
// Function that decides if pathing is possible, and inits pathing if necessary
bool Prepare()
{
if (!enabled)
@ -181,24 +189,31 @@ bool Prepare()
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)
{
if (localAreas.size() > 5)
localAreas.erase(localAreas.begin());
if (findClosestNavSquare_localAreas.size() > 5)
findClosestNavSquare_localAreas.erase(
findClosestNavSquare_localAreas.begin());
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 (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) });
}
}
// If multiple candidates for LocalNav have been found, pick the closest
float bestDist = FLT_MAX;
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);
if (dist < bestDist)
@ -210,15 +225,17 @@ int findClosestNavSquare(Vector vec)
if (bestSquare != -1)
{
if (vec == g_pLocalPlayer->v_Origin)
localAreas.push_back(bestSquare);
findClosestNavSquare_localAreas.push_back(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);
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;
bestSquare = i;
@ -226,41 +243,56 @@ int findClosestNavSquare(Vector vec)
}
}
if (vec == g_pLocalPlayer->v_Origin)
localAreas.push_back(bestSquare);
findClosestNavSquare_localAreas.push_back(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())
return std::vector<Vector>(0);
int id_loc = findClosestNavSquare(loc);
int id_dest = findClosestNavSquare(dest);
// Get areas of Vector loc and dest
id_loc = findClosestNavSquare(loc);
id_dest = findClosestNavSquare(dest);
if (id_loc == -1 || id_dest == -1)
return std::vector<Vector>(0);
float cost;
micropather::MPVector<void *> pathNodes;
// Find a solution to get to location
int result = TF2MAP->pather->Solve(static_cast<void *>(&areas.at(id_loc)),
static_cast<void *>(&areas.at(id_dest)),
&pathNodes, &cost);
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);
// Convert (void *) CNavArea * to Vector
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);
}
// Add our destination to the std::vector
path.push_back(dest);
return path;
}
// Timer for measuring inactivity, aka time between not reaching a crumb
static Timer inactivity{};
// Time since our last Jump
static Timer lastJump{};
// std::vector containing our path
static std::vector<Vector> crumbs;
// Bot will keep trying to get to the target even if it fails a few times
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,
int instructionPriority)
{
@ -268,73 +300,49 @@ bool NavTo(Vector dest, bool navToLocalCenter, bool persistent,
return false;
if (!Prepare())
return false;
if (instructionPriority <= priority)
// Only allow instructions to overwrite others if their priority is higher
if (instructionPriority < priority)
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())
return false;
if (!crumbs.empty())
{
bool reset = false;
inactiveTracker.addTime({ lastArea, crumbs.at(0) }, inactivity, reset);
if (reset)
TF2MAP->pather->Reset();
}
crumbs.clear();
crumbs = std::move(path);
if (!navToLocalCenter)
crumbs.erase(crumbs.begin());
ensureArrival = persistent;
localAreas.clear();
findClosestNavSquare_localAreas.clear();
priority = instructionPriority;
inactivity.update();
return true;
}
static Vector lastArea = { 0.0f, 0.0f, 0.0f };
void ignoreConnection()
void clearInstructions()
{
if (crumbs.size() < 1)
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;
}
}
crumbs.clear();
}
static Timer ignoreReset{};
// Function for removing ignores
void clearIgnores()
{
if (ignoreReset.test_and_set(180000))
{
ignoredConnections.clear();
inactiveTracker.reset();
if (TF2MAP && TF2MAP->pather)
TF2MAP->pather->Reset();
}
}
// Main movement function, gets path from NavTo
void CreateMove()
{
if (!enabled)
@ -343,10 +351,12 @@ void CreateMove()
return;
if (!LOCAL_E->m_bAlivePlayer())
{
// Clear path if player dead
crumbs.clear();
return;
}
clearIgnores();
// Crumbs empty, prepare for next instruction
if (crumbs.empty())
{
priority = 0;
@ -355,6 +365,7 @@ void CreateMove()
return;
}
ReadyForCommands = false;
// Remove old crumbs
if (g_pLocalPlayer->v_Origin.DistTo(Vector{ crumbs.at(0).x, crumbs.at(0).y,
g_pLocalPlayer->v_Origin.z }) <
30.0f)
@ -365,32 +376,72 @@ void CreateMove()
}
if (crumbs.empty())
return;
// Detect when jumping is necessary
if (crumbs.at(0).z - g_pLocalPlayer->v_Origin.z > 18 &&
lastJump.test_and_set(200))
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)
{
logging::Info("Pathing: NavBot inactive for too long. Ignoring "
"connection and finding another path...");
// NavTo(crumbs.back(), true, true);
crumbs = findPath(g_pLocalPlayer->v_Origin, crumbs.back());
inactivity.update();
// Find a new path
int i1, i2;
crumbs = findPath(g_pLocalPlayer->v_Origin, crumbs.back(), i1, i2);
}
else
{
logging::Info(
"Pathing: NavBot inactive for too long. Canceling tasks and "
"ignoring connection...");
// Wait for new instructions
crumbs.clear();
}
inactivity.update();
return;
}
// Walk to next crumb
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) {
lastmap = "";
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) {
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())
{
logging::Info("Pathing: No path found");

View File

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