Pathfinding supports links WIP, navigation_node, antiban nodes, bt builder changes.

master
Warren Ulrich 2023-07-07 22:00:26 -07:00
parent c84c81b71d
commit 96a09ddf9d
6 changed files with 413 additions and 221 deletions

22
include/bt/antiban.hpp Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include <bt/behavior_node.hpp>
#include <Game/Core.hpp>
namespace bt::antiban {
template<typename Context>
class lose_focus_node : public behavior_node<Context> {
public:
bool move_mouse;
lose_focus_node() noexcept : move_mouse(true) {}
lose_focus_node(bool move_mouse) noexcept : move_mouse(move_mouse) {}
node_task tick(Context &ctx) noexcept override {
move_mouse ? Antiban::MouseOffClient(true) : Antiban::LoseClientFocus();
co_return task_result::success;
}
};
}

View File

@ -81,6 +81,32 @@ public:
}
};
template <typename Context>
class parallel_node : public composite_node<Context> {
public:
node_task tick(Context &ctx) noexcept override {
std::vector<node_task> tasks;
for (auto& child : this->_children) {
tasks.push_back(child->tick(ctx));
}
while (!tasks.empty()) {
for (auto& task : tasks) {
if (task.done())
continue;
task.resume();
co_await yield{};
}
std::erase_if(tasks, [](auto& task) { return task.done(); });
}
co_return task_result::success;
}
};
template <typename Context, typename TickFn>
class action_node : public behavior_node<Context> {
public:

View File

@ -10,6 +10,7 @@
#include <Game/Core.hpp>
#include <bt/antiban.hpp>
#include <bt/behavior_node.hpp>
#include <bt/interact.hpp>
#include <bt/inventory.hpp>
@ -18,6 +19,8 @@
#include <bt/mainscreen.hpp>
#include <bt/navigation.hpp>
#include <bt/node_task.hpp>
#include <bt/pathfinding.hpp>
#include <bt/pathfinding_obstacles.hpp>
#include <bt/player.hpp>
#include <bt/script.hpp>
@ -26,64 +29,59 @@ template <typename Context> class tree_builder {
public:
tree_builder() noexcept : _root(nullptr) {}
tree_builder &begin_sequence() {
_node_stack.push_back(std::make_unique<sequence_node<Context>>());
template<template<typename...> class Compositor>
tree_builder &begin_compositor() {
_node_stack.push_back(std::make_unique<Compositor<Context>>());
_children.emplace_back();
return *this;
}
tree_builder &end_compositor() {
if (_node_stack.empty()) {
throw std::runtime_error("Mismatched begin_compositor/end_compositor");
}
auto node = std::move(_node_stack.back());
_node_stack.pop_back();
auto children = std::move(_children.back());
_children.pop_back();
for (auto &&child : children) {
static_cast<composite_node<Context> *>(node.get())
->add_child(std::move(child));
}
if (_node_stack.empty()) {
_root = std::move(node);
} else {
_children.back().push_back(std::move(node));
}
return *this;
}
tree_builder &begin_sequence() {
return begin_compositor<sequence_node>();
}
tree_builder &end_sequence() {
if (_node_stack.empty()) {
throw std::runtime_error("Mismatched begin_sequence/end_sequence");
}
auto node = std::move(_node_stack.back());
_node_stack.pop_back();
auto children = std::move(_children.back());
_children.pop_back();
for (auto &&child : children) {
static_cast<composite_node<Context> *>(node.get())
->add_child(std::move(child));
}
if (_node_stack.empty()) {
_root = std::move(node);
} else {
_children.back().push_back(std::move(node));
}
return *this;
return end_compositor();
}
tree_builder &begin_selector() {
_node_stack.push_back(std::make_unique<selector_node<Context>>());
_children.emplace_back();
return *this;
return begin_compositor<selector_node>();
}
tree_builder &end_selector() {
if (_node_stack.empty()) {
throw std::runtime_error("Mismatched begin_selector/end_selector");
}
return end_compositor();
}
auto node = std::move(_node_stack.back());
_node_stack.pop_back();
auto children = std::move(_children.back());
_children.pop_back();
tree_builder &begin_parallel() {
return begin_compositor<parallel_node>();
}
for (auto &&child : children) {
static_cast<composite_node<Context> *>(node.get())
->add_child(std::move(child));
}
if (_node_stack.empty()) {
_root = std::move(node);
} else {
_children.back().push_back(std::move(node));
}
return *this;
tree_builder &end_parallel() {
return end_compositor();
}
template <template<typename...> class Node, typename... Args>
@ -118,7 +116,8 @@ public:
return *this;
}
tree_builder &invert() {
template<template<typename...> class Decorator>
tree_builder &decorate() {
if (_node_stack.empty()) {
throw std::runtime_error("Invert must be within a sequence or selector");
}
@ -130,11 +129,15 @@ public:
auto child = std::move(_children.back().back());
_children.back().pop_back();
_children.back().push_back(
std::make_unique<inverter_node<Context>>(std::move(child)));
std::make_unique<Decorator<Context>>(std::move(child)));
return *this;
}
tree_builder &invert() {
return decorate<inverter_node>();
}
std::unique_ptr<behavior_node<Context>> build() {
if (!_node_stack.empty()) {
throw std::runtime_error("Mismatched begin/end");

View File

@ -3,191 +3,43 @@
#include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
#include <bt/pathfinding.hpp>
#include <queue>
#include <unordered_set>
#include <unordered_map>
#include <chrono>
#include <fstream>
namespace std {
template <> struct hash<Tile> {
std::size_t operator()(const Tile &tile) const {
std::size_t h1 = std::hash<std::int32_t>()(tile.X);
std::size_t h2 = std::hash<std::int32_t>()(tile.Y);
std::size_t h3 = std::hash<std::int32_t>()(tile.Plane);
return h1 ^ (h2 << 1) ^ (h3 << 2);
}
};
} // namespace std
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <variant>
namespace bt {
namespace pathfinding {
class obstacle {
template <typename Context, typename DestinationSource, typename PFSSource>
requires std::is_same_v<Tile, DestinationSource> ||
std::is_same_v<Tile,
std::result_of_t<DestinationSource(Context &)>> ||
std::is_same_v<pathfinding::settings, PFSSource> ||
std::is_same_v<const pathfinding::settings &,
std::result_of_t<PFSSource(Context &)>>
class navigation_node : public behavior_node<Context> {
public:
Tile tile;
Tile destination;
obstacle(Tile tile, Tile destination) noexcept
: tile(tile), destination(destination) {}
obstacle(Tile tile) noexcept : tile(tile), destination(tile) {}
bool operator==(const obstacle &other) const noexcept {
return tile == other.tile;
}
bool operator!=(const obstacle &other) const noexcept {
return tile != other.tile;
}
virtual bool can_handle() const noexcept = 0;
virtual bool handle() noexcept = 0;
};
std::unordered_set<std::int32_t> mapped_regions;
std::unordered_map<Tile, Pathfinding::COLLISION_FLAG> collision_data;
std::unordered_map<Tile, std::shared_ptr<obstacle>> obstacles;
void load_collision_data(std::ifstream &file) {
std::string line;
std::getline(file, line); // skip csv header
while (std::getline(file, line)) {
std::stringstream ss(line);
std::string token;
std::vector<std::string> tokens;
while (std::getline(ss, token, ',')) {
tokens.emplace_back(token);
}
const auto region = std::stoi(tokens[0]);
const auto plane = std::stoi(tokens[1]);
const auto x = std::stoi(tokens[2]);
const auto y = std::stoi(tokens[3]);
const auto flag =
static_cast<Pathfinding::COLLISION_FLAG>(std::stoi(tokens[4]));
mapped_regions.insert(region);
collision_data.emplace(Tile(x, y, plane), flag);
}
}
void visit_neighbors(const Tile &tile, const auto &func) {
Pathfinding::COLLISION_FLAG flags = Pathfinding::COLLISION_FLAG::OPEN;
auto it = collision_data.find(tile);
if (it != collision_data.end()) {
flags = it->second;
}
const auto blocked = [](Pathfinding::COLLISION_FLAG flags) {
return (flags & Pathfinding::BLOCKED) || (flags & Pathfinding::OCCUPIED);
};
constexpr std::array<std::tuple<int, int, Pathfinding::COLLISION_FLAG>, 4>
directions = {{{0, 1, Pathfinding::COLLISION_FLAG::NORTH},
{1, 0, Pathfinding::COLLISION_FLAG::EAST},
{0, -1, Pathfinding::COLLISION_FLAG::SOUTH},
{-1, 0, Pathfinding::COLLISION_FLAG::WEST}}};
for (const auto &[dx, dy, dirFlag] : directions) {
if ((flags & dirFlag) == Pathfinding::COLLISION_FLAG::OPEN) {
int newX = tile.X + dx;
int newY = tile.Y + dy;
auto neighbor = Tile(newX, newY, tile.Plane);
auto neighborIt = collision_data.find(neighbor);
if (neighborIt != collision_data.end()) {
if (!blocked(neighborIt->second)) {
func(neighbor);
}
} else {
func(neighbor);
}
}
}
}
std::vector<Tile> find_path(const Tile &start, const Tile &end) {
class path_node {
public:
Tile tile;
std::shared_ptr<path_node> parent;
double cost;
path_node(const Tile &tile, std::shared_ptr<path_node> parent, double cost)
: tile(tile), parent(parent), cost(cost) {}
};
const auto compare = [](const std::shared_ptr<path_node> &a,
const std::shared_ptr<path_node> &b) {
return a->cost > b->cost;
};
const auto heuristic = [](const Tile &a, const Tile &b) {
return std::abs(a.X - b.X) + std::abs(a.Y - b.Y);
};
std::priority_queue<std::shared_ptr<path_node>,
std::vector<std::shared_ptr<path_node>>,
decltype(compare)>
queue(compare);
std::unordered_map<Tile, double, std::hash<Tile>> costs;
std::unordered_set<Tile, std::hash<Tile>> visited;
queue.emplace(std::make_shared<path_node>(start, nullptr, 0.0));
costs[start] = 0.0;
while (!queue.empty()) {
auto current = queue.top();
queue.pop();
if (current->tile == end) {
std::vector<Tile> path;
while (current != nullptr) {
path.push_back(current->tile);
current = current->parent;
}
std::reverse(path.begin(), path.end());
return path;
}
visit_neighbors(current->tile, [&](const Tile &neighbor) {
double new_cost =
costs[current->tile] + 1; // Assuming constant cost of 1 for each step
if (visited.find(neighbor) == visited.end() ||
new_cost < costs[neighbor]) {
costs[neighbor] = new_cost;
double priority = new_cost + heuristic(neighbor, end);
queue.emplace(std::make_shared<path_node>(neighbor, current, priority));
visited.insert(neighbor);
}
});
}
return std::vector<Tile>(); // no path found
}
} // namespace pathfinding
template <typename Context, typename DestinationSource> class navigation_node;
template <typename Context>
class navigation_node<Context, Tile> : public behavior_node<Context> {
public:
Tile destination;
navigation_node(const Tile &destination) : destination(destination) {}
navigation_node(const DestinationSource &destination_source,
const PFSSource &pfs_source)
: _destination_source(destination_source), _pfs_source(pfs_source) {}
node_task tick(Context &ctx) noexcept override {
const auto player = Players::GetLocal();
if (!player)
co_return task_result::failure;
const auto position = player.GetTile();
const auto path = pathfinding::find_path(position, destination);
auto position = player.GetTile();
if (!position)
co_return task_result::failure;
co_return task_result::success;
}
private:
DestinationSource _destination_source;
PFSSource _pfs_source;
};
} // namespace bt

235
include/bt/pathfinding.hpp Normal file
View File

@ -0,0 +1,235 @@
#pragma once
#include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
#include <chrono>
#include <fstream>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <variant>
namespace std {
template <> struct hash<Tile> {
std::size_t operator()(const Tile &tile) const {
std::size_t h1 = std::hash<std::int32_t>()(tile.X);
std::size_t h2 = std::hash<std::int32_t>()(tile.Y);
std::size_t h3 = std::hash<std::int32_t>()(tile.Plane);
return h1 ^ (h2 << 1) ^ (h3 << 2);
}
};
} // namespace std
namespace bt::pathfinding {
class link {
public:
Tile start;
Tile destination;
link(Tile start, Tile destination) noexcept
: start(start), destination(destination) {}
bool operator==(const link &other) const noexcept {
return start == other.start;
}
bool operator!=(const link &other) const noexcept {
return start != other.start;
}
virtual bool can_handle() const noexcept = 0;
virtual bool handle() noexcept = 0;
};
using path_step = std::variant<Tile, std::shared_ptr<link>>;
using path = std::vector<path_step>;
class path_node {
public:
path_step step;
std::shared_ptr<path_node> parent;
double cost;
path_node(const path_step &step, std::shared_ptr<path_node> parent,
double cost)
: step(step), parent(parent), cost(cost) {}
};
class settings {
public:
std::int32_t agility_level;
settings() noexcept = default;
};
std::unordered_set<std::int32_t> mapped_regions;
std::unordered_map<Tile, Pathfinding::COLLISION_FLAG> collision_data;
std::unordered_map<Tile, std::shared_ptr<link>> links;
void add_link(std::shared_ptr<link> link) noexcept {
links.emplace(link->start, link);
}
void load_collision_data(std::ifstream &file) {
std::string line;
std::getline(file, line); // skip csv header
while (std::getline(file, line)) {
std::stringstream ss(line);
std::string token;
std::vector<std::string> tokens;
while (std::getline(ss, token, ',')) {
tokens.emplace_back(token);
}
const auto region = std::stoi(tokens[0]);
const auto plane = std::stoi(tokens[1]);
const auto x = std::stoi(tokens[2]);
const auto y = std::stoi(tokens[3]);
const auto flag =
static_cast<Pathfinding::COLLISION_FLAG>(std::stoi(tokens[4]));
mapped_regions.insert(region);
collision_data.emplace(Tile(x, y, plane), flag);
}
}
void visit_neighbors(const path_step &step, const settings& settings_, const auto &func) {
Tile tile;
if (std::holds_alternative<Tile>(step)) {
tile = std::get<Tile>(step);
} else {
const auto &lnk = std::get<std::shared_ptr<link>>(step);
tile = lnk->destination;
}
Pathfinding::COLLISION_FLAG flags = Pathfinding::COLLISION_FLAG::OPEN;
auto it = collision_data.find(tile);
if (it != collision_data.end()) {
flags = it->second;
}
const auto blocked = [](Pathfinding::COLLISION_FLAG flags) {
return (flags & Pathfinding::BLOCKED) || (flags & Pathfinding::OCCUPIED);
};
constexpr std::array<std::tuple<int, int, Pathfinding::COLLISION_FLAG>, 4>
directions = {{{0, 1, Pathfinding::COLLISION_FLAG::NORTH},
{1, 0, Pathfinding::COLLISION_FLAG::EAST},
{0, -1, Pathfinding::COLLISION_FLAG::SOUTH},
{-1, 0, Pathfinding::COLLISION_FLAG::WEST}}};
for (const auto &[dx, dy, dirFlag] : directions) {
if ((flags & dirFlag) == Pathfinding::COLLISION_FLAG::OPEN) {
int newX = tile.X + dx;
int newY = tile.Y + dy;
const auto neighbor = Tile(newX, newY, tile.Plane);
const auto link_it = links.find(neighbor);
if (link_it != links.end()) {
func(link_it->second);
continue;
}
auto neighbor_it = collision_data.find(neighbor);
if (neighbor_it != collision_data.end()) {
if (!blocked(neighbor_it->second)) {
func(neighbor);
}
} else {
func(neighbor);
}
}
}
}
path find_path(const Tile &start, const Tile &end, const settings &settings_) {
const auto compare = [](const std::shared_ptr<path_node> &a,
const std::shared_ptr<path_node> &b) {
return a->cost > b->cost;
};
const auto heuristic = [](const path_step &a, const path_step &b) {
const Tile &tileA =
std::holds_alternative<Tile>(a)
? std::get<Tile>(a)
: std::get<std::shared_ptr<link>>(a)->destination;
const Tile &tileB =
std::holds_alternative<Tile>(b)
? std::get<Tile>(b)
: std::get<std::shared_ptr<link>>(b)->destination;
return std::abs(tileA.X - tileB.X) + std::abs(tileA.Y - tileB.Y) +
std::abs(tileA.Plane - tileB.Plane);
};
std::priority_queue<std::shared_ptr<path_node>,
std::vector<std::shared_ptr<path_node>>,
decltype(compare)>
queue(compare);
std::unordered_map<path_step, double> costs;
std::unordered_set<path_step> visited;
queue.emplace(std::make_shared<path_node>(start, nullptr, 0.0));
costs[start] = 0.0;
while (!queue.empty()) {
auto current = queue.top();
queue.pop();
if (heuristic(current->step, end) == 0) { // Reached destination
path p;
while (current != nullptr) {
p.emplace_back(current->step);
current = current->parent;
}
std::reverse(p.begin(), p.end());
return p;
}
visit_neighbors(current->step, settings_, [&](const auto &neighbor) {
using neighbor_t = std::decay_t<decltype(neighbor)>;
if constexpr (std::is_same_v<Tile, neighbor_t>) {
double new_cost = costs[current->step] +
1; // Assuming constant cost of 1 for each step
if (visited.find(neighbor) == visited.end() ||
new_cost < costs[neighbor]) {
costs[neighbor] = new_cost;
double priority = new_cost + heuristic(neighbor, end);
queue.emplace(
std::make_shared<path_node>(neighbor, current, priority));
visited.insert(neighbor);
}
} else if constexpr (std::is_same_v<std::shared_ptr<link>, neighbor_t>) {
auto new_cost = costs[current->step] + 1;
if (visited.find(neighbor->destination) == visited.end() ||
new_cost < costs[neighbor->destination]) {
costs[neighbor->destination] = new_cost;
double priority = new_cost + heuristic(neighbor->destination, end);
queue.emplace(
std::make_shared<path_node>(neighbor, current, priority));
visited.insert(neighbor);
}
}
});
}
return path();
}
void paint_path(Tile start, Tile end, const settings &settings_) {
auto path_ = find_path(start, end, settings_);
for (const auto &step : path_) {
if (const auto tile = std::get_if<Tile>(&step)) {
Paint::DrawTile(*tile, 255, 0, 0, 255);
}
}
}
} // namespace bt::pathfinding

View File

@ -0,0 +1,54 @@
#pragma once
#include <bt/pathfinding.hpp>
namespace bt::pathfinding {
class game_object : public link {
public:
Tile tile;
std::string action;
game_object(Tile tile, std::string action, Tile start,
Tile destination) noexcept
: link(start, destination), tile(tile), action(action) {}
bool can_handle() const noexcept override { return true; }
bool handle() noexcept override {
auto player = Players::GetLocal();
if (!player)
return false;
auto position = player.GetTile();
if (!position)
return false;
if (position.DistanceFrom(tile) >= 5)
return false;
const auto obj = GameObjects::Get(tile);
if (!obj)
return false;
if (!obj.Interact(action))
return false;
if (!WaitFunc(2000, 50, [&]() {
position = player.GetTile();
if (!position)
return false;
return position == destination;
}))
return false;
return true;
}
};
void load_obstacles() {
add_link(std::make_shared<game_object>(Tile(3205, 3208, 0), "Climb-up",
Tile(3205, 3208, 0),
Tile(3205, 3209, 1)));
}
} // namespace bt::pathfinding