diff --git a/include/bt/antiban.hpp b/include/bt/antiban.hpp new file mode 100644 index 0000000..6a155f0 --- /dev/null +++ b/include/bt/antiban.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +namespace bt::antiban { + template + class lose_focus_node : public behavior_node { + 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; + } + }; +} \ No newline at end of file diff --git a/include/bt/behavior_node.hpp b/include/bt/behavior_node.hpp index c2b47e4..ea24c61 100644 --- a/include/bt/behavior_node.hpp +++ b/include/bt/behavior_node.hpp @@ -81,6 +81,32 @@ public: } }; +template +class parallel_node : public composite_node { +public: + node_task tick(Context &ctx) noexcept override { + std::vector 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 class action_node : public behavior_node { public: diff --git a/include/bt/bt.hpp b/include/bt/bt.hpp index fc637bd..e7fa25e 100644 --- a/include/bt/bt.hpp +++ b/include/bt/bt.hpp @@ -10,6 +10,7 @@ #include +#include #include #include #include @@ -18,6 +19,8 @@ #include #include #include +#include +#include #include #include @@ -26,64 +29,59 @@ template class tree_builder { public: tree_builder() noexcept : _root(nullptr) {} - tree_builder &begin_sequence() { - _node_stack.push_back(std::make_unique>()); + template class Compositor> + tree_builder &begin_compositor() { + _node_stack.push_back(std::make_unique>()); _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 *>(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(); + } + 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 *>(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>()); - _children.emplace_back(); - return *this; + return begin_compositor(); } 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(); + } - for (auto &&child : children) { - static_cast *>(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 class Node, typename... Args> @@ -118,7 +116,8 @@ public: return *this; } - tree_builder &invert() { + template 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>(std::move(child))); + std::make_unique>(std::move(child))); return *this; } + tree_builder &invert() { + return decorate(); + } + std::unique_ptr> build() { if (!_node_stack.empty()) { throw std::runtime_error("Mismatched begin/end"); diff --git a/include/bt/navigation.hpp b/include/bt/navigation.hpp index d0463e5..335b9f5 100644 --- a/include/bt/navigation.hpp +++ b/include/bt/navigation.hpp @@ -3,191 +3,43 @@ #include #include +#include -#include -#include -#include +#include #include - -namespace std { -template <> struct hash { - std::size_t operator()(const Tile &tile) const { - std::size_t h1 = std::hash()(tile.X); - std::size_t h2 = std::hash()(tile.Y); - std::size_t h3 = std::hash()(tile.Plane); - return h1 ^ (h2 << 1) ^ (h3 << 2); - } -}; -} // namespace std +#include +#include +#include +#include namespace bt { -namespace pathfinding { -class obstacle { +template + requires std::is_same_v || + std::is_same_v> || + std::is_same_v || + std::is_same_v> +class navigation_node : public behavior_node { 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 mapped_regions; -std::unordered_map collision_data; -std::unordered_map> 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 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(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, 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 find_path(const Tile &start, const Tile &end) { - class path_node { - public: - Tile tile; - std::shared_ptr parent; - double cost; - - path_node(const Tile &tile, std::shared_ptr parent, double cost) - : tile(tile), parent(parent), cost(cost) {} - }; - - const auto compare = [](const std::shared_ptr &a, - const std::shared_ptr &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::vector>, - decltype(compare)> - queue(compare); - - std::unordered_map> costs; - std::unordered_set> visited; - - queue.emplace(std::make_shared(start, nullptr, 0.0)); - costs[start] = 0.0; - - while (!queue.empty()) { - auto current = queue.top(); - queue.pop(); - - if (current->tile == end) { - std::vector 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(neighbor, current, priority)); - visited.insert(neighbor); - } - }); - } - - return std::vector(); // no path found -} -} // namespace pathfinding - -template class navigation_node; - -template -class navigation_node : public behavior_node { -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 \ No newline at end of file diff --git a/include/bt/pathfinding.hpp b/include/bt/pathfinding.hpp new file mode 100644 index 0000000..bd81d50 --- /dev/null +++ b/include/bt/pathfinding.hpp @@ -0,0 +1,235 @@ +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace std { +template <> struct hash { + std::size_t operator()(const Tile &tile) const { + std::size_t h1 = std::hash()(tile.X); + std::size_t h2 = std::hash()(tile.Y); + std::size_t h3 = std::hash()(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>; + +using path = std::vector; + +class path_node { +public: + path_step step; + std::shared_ptr parent; + double cost; + + path_node(const path_step &step, std::shared_ptr parent, + double cost) + : step(step), parent(parent), cost(cost) {} +}; + +class settings { +public: + std::int32_t agility_level; + + settings() noexcept = default; +}; + +std::unordered_set mapped_regions; +std::unordered_map collision_data; +std::unordered_map> links; + +void add_link(std::shared_ptr 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 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(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(step)) { + tile = std::get(step); + } else { + const auto &lnk = std::get>(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, 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 &a, + const std::shared_ptr &b) { + return a->cost > b->cost; + }; + + const auto heuristic = [](const path_step &a, const path_step &b) { + const Tile &tileA = + std::holds_alternative(a) + ? std::get(a) + : std::get>(a)->destination; + const Tile &tileB = + std::holds_alternative(b) + ? std::get(b) + : std::get>(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::vector>, + decltype(compare)> + queue(compare); + + std::unordered_map costs; + std::unordered_set visited; + + queue.emplace(std::make_shared(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; + if constexpr (std::is_same_v) { + 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(neighbor, current, priority)); + visited.insert(neighbor); + } + } else if constexpr (std::is_same_v, 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(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(&step)) { + Paint::DrawTile(*tile, 255, 0, 0, 255); + } + } +} +} // namespace bt::pathfinding \ No newline at end of file diff --git a/include/bt/pathfinding_obstacles.hpp b/include/bt/pathfinding_obstacles.hpp new file mode 100644 index 0000000..c224a75 --- /dev/null +++ b/include/bt/pathfinding_obstacles.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include + +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(Tile(3205, 3208, 0), "Climb-up", + Tile(3205, 3208, 0), + Tile(3205, 3209, 1))); +} +} // namespace bt::pathfinding \ No newline at end of file