seperated node implementations from single file.

master
Warren Ulrich 2023-07-05 00:44:02 -07:00
parent 2c1a4c597e
commit c84c81b71d
11 changed files with 727 additions and 437 deletions

View File

@ -0,0 +1,130 @@
#pragma once
#include <bt/node_task.hpp>
namespace bt {
using yield = std::suspend_always;
template <typename Context> class behavior_node {
public:
virtual node_task tick(Context &ctx) noexcept = 0;
virtual ~behavior_node() = default;
};
template <typename Context>
class composite_node : public behavior_node<Context> {
public:
void add_child(std::unique_ptr<behavior_node<Context>> &&child) {
_children.push_back(std::move(child));
}
protected:
std::vector<std::unique_ptr<behavior_node<Context>>> _children;
};
template <typename Context>
class decorator_node : public behavior_node<Context> {
public:
decorator_node(std::unique_ptr<behavior_node<Context>> child)
: _child(std::move(child)) {}
void set_child(std::unique_ptr<behavior_node<Context>> &&child) {
_child = std::move(child);
}
protected:
std::unique_ptr<behavior_node<Context>> _child;
};
template <typename Context>
class sequence_node : public composite_node<Context> {
public:
node_task tick(Context &ctx) noexcept override {
std::size_t idx = 0;
while (idx < this->_children.size()) {
auto task = this->_children[idx]->tick(ctx);
while (!task.done()) {
task.resume();
if (idx < this->_children.size() - 1)
co_await yield{};
}
if (task.result() == task_result::failure) {
co_return task_result::failure;
}
++idx;
}
co_return task_result::success;
}
};
template <typename Context>
class selector_node : public composite_node<Context> {
public:
node_task tick(Context &ctx) noexcept override {
for (std::size_t idx = 0; idx < this->_children.size(); ++idx) {
auto task = this->_children[idx]->tick(ctx);
while (!task.done()) {
task.resume();
co_await yield{};
}
if (task.result() == task_result::success) {
co_return task_result::success;
}
}
co_return task_result::failure;
}
};
template <typename Context, typename TickFn>
class action_node : public behavior_node<Context> {
public:
action_node(TickFn tick) noexcept : _tick(tick) {}
node_task tick(Context &ctx) noexcept override { return _tick(ctx); }
private:
TickFn _tick;
};
template <typename Context, typename ConditionFn>
class conditional_node : public behavior_node<Context> {
public:
conditional_node(ConditionFn condition) noexcept : _condition(condition) {}
node_task tick(Context &ctx) noexcept override {
if (_condition(ctx)) {
co_return task_result::success;
}
co_return task_result::failure;
}
private:
ConditionFn _condition;
};
template <typename Context>
class inverter_node : public decorator_node<Context> {
public:
inverter_node(std::unique_ptr<behavior_node<Context>> child)
: decorator_node<Context>(std::move(child)) {}
node_task tick(Context &ctx) noexcept override {
auto task = this->_child->tick(ctx);
while (!task.done()) {
task.resume();
co_await yield{};
}
if (task.result() == task_result::success) {
co_return task_result::failure;
}
co_return task_result::success;
}
};
}

View File

@ -10,348 +10,18 @@
#include <Game/Core.hpp> #include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
#include <bt/interact.hpp>
#include <bt/inventory.hpp>
#include <bt/login.hpp>
#include <bt/magic.hpp>
#include <bt/mainscreen.hpp>
#include <bt/navigation.hpp>
#include <bt/node_task.hpp>
#include <bt/player.hpp>
#include <bt/script.hpp>
namespace bt { namespace bt {
enum class task_result { failure, success };
class node_task {
public:
class promise_type;
using handle_type = std::coroutine_handle<promise_type>;
class promise_type {
public:
std::optional<task_result> result;
handle_type get_return_object() noexcept {
return handle_type::from_promise(*this);
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(task_result r) noexcept { result = r; }
void unhandled_exception() noexcept {
std::terminate(); // TODO: handle exception
}
};
class awaiter {
public:
awaiter(handle_type handle) noexcept : _handle(handle) {}
bool await_ready() const noexcept { return false; }
auto await_suspend(std::coroutine_handle<>) noexcept { return _handle; }
task_result await_resume() noexcept { return *_handle.promise().result; }
private:
handle_type _handle;
};
node_task(handle_type handle) noexcept : _handle(handle) {}
node_task(node_task &&other) noexcept : _handle(other._handle) {
other._handle = nullptr;
}
~node_task() {
if (_handle) {
_handle.destroy();
}
}
node_task &operator=(node_task &&other) noexcept {
if (&other != this) {
if (_handle) {
_handle.destroy();
}
_handle = other._handle;
other._handle = nullptr;
}
return *this;
}
awaiter operator co_await() noexcept { return awaiter{_handle}; }
void resume() noexcept { _handle.resume(); }
bool done() const noexcept { return _handle.done(); }
task_result result() noexcept { return *_handle.promise().result; }
std::optional<task_result> try_result() noexcept {
return _handle.promise().result;
}
private:
handle_type _handle;
};
using yield = std::suspend_always;
template <typename Context> class behavior_node {
public:
virtual node_task tick(Context &ctx) noexcept = 0;
virtual ~behavior_node() = default;
};
template <typename Context>
class composite_node : public behavior_node<Context> {
public:
void add_child(std::unique_ptr<behavior_node<Context>> &&child) {
_children.push_back(std::move(child));
}
protected:
std::vector<std::unique_ptr<behavior_node<Context>>> _children;
};
template <typename Context>
class sequence_node : public composite_node<Context> {
public:
node_task tick(Context &ctx) noexcept override {
std::size_t idx = 0;
while (idx < this->_children.size()) {
auto task = this->_children[idx]->tick(ctx);
while (!task.done()) {
task.resume();
if (idx < this->_children.size() - 1)
co_await yield{};
}
if (task.result() == task_result::failure) {
co_return task_result::failure;
}
++idx;
}
co_return task_result::success;
}
};
template <typename Context>
class selector_node : public composite_node<Context> {
public:
node_task tick(Context &ctx) noexcept override {
for (std::size_t idx = 0; idx < this->_children.size(); ++idx) {
auto task = this->_children[idx]->tick(ctx);
while (!task.done()) {
task.resume();
co_await yield{};
}
if (task.result() == task_result::success) {
co_return task_result::success;
}
}
co_return task_result::failure;
}
};
template <typename Context, typename TickFn>
class action_node : public behavior_node<Context> {
public:
action_node(TickFn tick) noexcept : _tick(tick) {}
node_task tick(Context &ctx) noexcept override { return _tick(ctx); }
private:
TickFn _tick;
};
template <typename Context, typename ConditionFn>
class conditional_node : public behavior_node<Context> {
public:
conditional_node(ConditionFn condition) noexcept : _condition(condition) {}
node_task tick(Context &ctx) noexcept override {
if (_condition(ctx)) {
co_return task_result::success;
}
co_return task_result::failure;
}
private:
ConditionFn _condition;
};
template <typename Context>
class inverter_node : public behavior_node<Context> {
public:
inverter_node(std::unique_ptr<behavior_node<Context>> child)
: _child(std::move(child)) {}
node_task tick(Context &ctx) noexcept override {
auto task = _child->tick(ctx);
while (!task.done()) {
task.resume();
co_await yield{};
}
if (task.result() == task_result::success) {
co_return task_result::failure;
}
co_return task_result::success;
}
private:
std::unique_ptr<behavior_node<Context>> _child;
};
// Script
template <typename Context>
class terminate_script_node : public behavior_node<Context> {
public:
node_task tick(Context &ctx) noexcept override {
Terminate = true;
co_return task_result::success;
}
};
// Login
template<typename Context>
class login_player_node : public behavior_node<Context> {
public:
node_task tick(Context &ctx) noexcept override {
if (!Login::LoginPlayer())
co_return task_result::failure;
co_return task_result::success;
}
};
// Mainscreen
template <typename Context>
class is_logged_in_node : public behavior_node<Context> {
public:
constexpr is_logged_in_node() noexcept = default;
node_task tick(Context &ctx) noexcept override {
if (!Mainscreen::IsLoggedIn())
co_return task_result::failure;
co_return task_result::success;
}
};
// Player
template <typename Context>
class is_animating_node : public behavior_node<Context> {
public:
std::int32_t anim_id;
is_animating_node(std::int32_t anim_id) : anim_id(anim_id) {}
node_task tick(Context &ctx) noexcept override {
const auto player = Players::GetLocal();
if (!player)
co_return task_result::failure;
if (player.GetAnimationID() != anim_id)
co_return task_result::failure;
co_return task_result::success;
}
};
// Inventory
template <typename Context, typename ItemID>
requires std::same_as<std::int32_t, ItemID> ||
std::same_as<std::string, ItemID> ||
std::same_as<std::regex, ItemID> ||
std::same_as<std::vector<std::int32_t>, ItemID> ||
std::same_as<std::vector<std::string>, ItemID>
class inventory_contains_node : public behavior_node<Context> {
public:
ItemID item_id;
inventory_contains_node(ItemID item_id) : item_id(item_id) {}
node_task tick(Context &ctx) noexcept override {
if (!Inventory::Contains(item_id))
co_return task_result::failure;
co_return task_result::success;
}
};
// Magic
template <typename Context>
class select_spell_node : public behavior_node<Context> {
public:
Magic::SPELL spell;
select_spell_node(Magic::SPELL spell) : spell(spell) {}
node_task tick(Context &ctx) noexcept override {
if (!Magic::SelectSpell(spell))
co_return task_result::failure;
co_return task_result::success;
}
};
template <typename Context>
class spell_selected_node : public behavior_node<Context> {
public:
Magic::SPELL spell;
spell_selected_node(Magic::SPELL spell) : spell(spell) {}
node_task tick(Context &ctx) noexcept override {
if (!Magic::IsSpellSelected(spell))
co_return task_result::failure;
co_return task_result::success;
}
};
template <typename Context>
class cast_spell_node : public behavior_node<Context> {
public:
Magic::SPELL spell;
cast_spell_node(Magic::SPELL spell) : spell(spell) {}
node_task tick(Context &ctx) noexcept override {
if (!Magic::CastSpell(spell))
co_return task_result::failure;
co_return task_result::success;
}
};
// Interact
template <typename Context, typename Interactable> class interact_node;
template <typename Context>
class interact_node<Context, Interactable::Item>
: public behavior_node<Context> {
public:
std::string name;
std::string action;
interact_node(const std::string &name, const std::string &action)
: name(name), action(action) {}
node_task tick(Context &ctx) noexcept override {
const auto item = Inventory::GetItem(name);
if (!item)
co_return task_result::failure;
if (!item.Interact(action))
co_return task_result::failure;
co_return task_result::success;
}
};
template <typename Context> class tree_builder { template <typename Context> class tree_builder {
public: public:
tree_builder() noexcept : _root(nullptr) {} tree_builder() noexcept : _root(nullptr) {}
@ -416,6 +86,16 @@ public:
return *this; return *this;
} }
template <template<typename...> class Node, typename... Args>
tree_builder &leaf(Args&&... args) {
if (_node_stack.empty()) {
throw std::runtime_error("Leaf must be within a sequence or selector");
}
_children.back().push_back(std::make_unique<Node<Context, Args...>>(std::forward<Args>(args)...));
return *this;
}
template <typename TickFn> tree_builder &action(TickFn tick) { template <typename TickFn> tree_builder &action(TickFn tick) {
if (_node_stack.empty()) { if (_node_stack.empty()) {
throw std::runtime_error("Action must be within a sequence or selector"); throw std::runtime_error("Action must be within a sequence or selector");
@ -455,100 +135,6 @@ public:
return *this; return *this;
} }
tree_builder& terminate_script() {
if (_node_stack.empty()) {
throw std::runtime_error("Terminate must be within a sequence or selector");
}
_children.back().push_back(std::make_unique<terminate_script_node<Context>>());
return *this;
}
tree_builder &login_player() {
if (_node_stack.empty()) {
throw std::runtime_error("Leaf must be within a sequence or selector");
}
_children.back().push_back(std::make_unique<login_player_node<Context>>());
return *this;
}
tree_builder &is_animating(std::int32_t anim_id) {
if (_node_stack.empty()) {
throw std::runtime_error("Leaf must be within a sequence or selector");
}
_children.back().push_back(
std::make_unique<is_animating_node<Context>>(anim_id));
return *this;
}
tree_builder &is_logged_in() {
if (_node_stack.empty()) {
throw std::runtime_error("Leaf must be within a sequence or selector");
}
_children.back().push_back(std::make_unique<is_logged_in_node<Context>>());
return *this;
}
template <typename ItemID>
requires std::same_as<std::int32_t, ItemID> ||
std::same_as<std::string, ItemID> ||
std::same_as<std::regex, ItemID> ||
std::same_as<std::vector<std::int32_t>, ItemID> ||
std::same_as<std::vector<std::string>, ItemID>
tree_builder &inventory_contains(ItemID item_id) {
if (_node_stack.empty()) {
throw std::runtime_error("Leaf must be within a sequence or selector");
}
_children.back().push_back(
std::make_unique<inventory_contains_node<Context, ItemID>>(item_id));
return *this;
}
tree_builder &select_spell(Magic::SPELL spell) {
if (_node_stack.empty()) {
throw std::runtime_error("Leaf must be within a sequence or selector");
}
_children.back().push_back(
std::make_unique<select_spell_node<Context>>(spell));
return *this;
}
tree_builder &spell_selected(Magic::SPELL spell) {
if (_node_stack.empty()) {
throw std::runtime_error("Leaf must be within a sequence or selector");
}
_children.back().push_back(
std::make_unique<spell_selected_node<Context>>(spell));
return *this;
}
tree_builder &cast_spell(Magic::SPELL spell) {
if (_node_stack.empty()) {
throw std::runtime_error("Leaf must be within a sequence or selector");
}
_children.back().push_back(
std::make_unique<cast_spell_node<Context>>(spell));
return *this;
}
template <typename Interactable>
tree_builder &interact(const std::string &name, const std::string &action) {
if (_node_stack.empty()) {
throw std::runtime_error("Leaf must be within a sequence or selector");
}
_children.back().push_back(
std::make_unique<interact_node<Context, Interactable>>(name, action));
return *this;
}
std::unique_ptr<behavior_node<Context>> build() { std::unique_ptr<behavior_node<Context>> build() {
if (!_node_stack.empty()) { if (!_node_stack.empty()) {
throw std::runtime_error("Mismatched begin/end"); throw std::runtime_error("Mismatched begin/end");
@ -562,5 +148,4 @@ private:
std::vector<std::unique_ptr<behavior_node<Context>>> _node_stack; std::vector<std::unique_ptr<behavior_node<Context>>> _node_stack;
std::vector<std::vector<std::unique_ptr<behavior_node<Context>>>> _children; std::vector<std::vector<std::unique_ptr<behavior_node<Context>>>> _children;
}; };
} // namespace bt } // namespace bt

60
include/bt/interact.hpp Normal file
View File

@ -0,0 +1,60 @@
#pragma once
#include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
namespace bt {
namespace meta {
template <typename T>
concept Interactable =
std::same_as<T, Interactable::Item> || std::same_as<T, Interactable::NPC> ||
std::same_as<T, Interactable::Player> ||
std::same_as<T, Interactable::Projectile> ||
std::same_as<T, Interactable::Widget> ||
std::same_as<T, Interactable::Item>;
}
template <typename Context, typename ActionSource, typename InteractableSource>
requires std::is_same_v<std::string, ActionSource> ||
std::is_same_v<std::string, std::invoke_result_t<ActionSource>> ||
std::is_same_v<std::string, std::invoke_result_t<ActionSource, Context &>> &&
meta::Interactable<std::invoke_result_t<InteractableSource>> ||
meta::Interactable<std::invoke_result_t<InteractableSource, Context &>>
class interact_node : public behavior_node<Context> {
public:
ActionSource action_source;
InteractableSource interactable_source;
interact_node(ActionSource action_source,
InteractableSource interactable_source)
: action_source(action_source), interactable_source(interactable_source) {}
node_task tick(Context &ctx) noexcept override {
const auto interactable = [&]() {
if constexpr (std::is_invocable_v<InteractableSource>)
return interactable_source();
else if constexpr (std::is_invocable_v<InteractableSource, Context &>)
return interactable_source(ctx);
}();
if (!interactable)
co_return task_result::failure;
const auto action = [&]() {
if constexpr (std::is_invocable_v<ActionSource>)
return action_source();
else if constexpr (std::is_invocable_v<ActionSource, Context &>)
return action_source(ctx);
else if constexpr (std::is_same_v<std::string, ActionSource>)
return action_source;
}();
if (!interactable.Interact(action))
co_return task_result::failure;
co_return task_result::success;
}
};
} // namespace bt

30
include/bt/inventory.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
namespace bt {
template <typename Context, typename ItemID>
requires std::same_as<std::int32_t, ItemID> ||
std::same_as<std::string, ItemID> ||
std::same_as<std::regex, ItemID> ||
std::same_as<std::vector<std::int32_t>, ItemID> ||
std::same_as<std::initializer_list<std::int32_t>, ItemID> ||
std::same_as<std::vector<std::string>, ItemID> ||
std::same_as<std::initializer_list<std::string>, ItemID>
class inventory_contains_node : public behavior_node<Context> {
public:
ItemID item_id;
inventory_contains_node(ItemID item_id) : item_id(item_id) {}
node_task tick(Context &ctx) noexcept override {
if (!Inventory::Contains(item_id))
co_return task_result::failure;
co_return task_result::success;
}
};
} // namespace bt

18
include/bt/login.hpp Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
namespace bt {
template <typename Context>
class login_player : public behavior_node<Context> {
public:
node_task tick(Context &ctx) noexcept override {
if (!Login::LoginPlayer())
co_return task_result::failure;
co_return task_result::success;
}
};
} // namespace bt

128
include/bt/magic.hpp Normal file
View File

@ -0,0 +1,128 @@
#pragma once
#include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
namespace bt {
template <typename Context, typename SpellSource> class select_spell_node;
template <typename Context>
class select_spell_node<Context, Magic::SPELL> : public behavior_node<Context> {
public:
Magic::SPELL spell;
select_spell_node(Magic::SPELL spell) : spell(spell) {}
node_task tick(Context &) noexcept override {
if (!Magic::SelectSpell(spell))
co_return task_result::failure;
co_return task_result::success;
}
};
template <typename Context, typename SpellGetter>
requires std::is_same_v<Magic::SPELL,
std::result_of_t<SpellGetter(Context &)>>
class select_spell_node<Context, SpellGetter> : public behavior_node<Context> {
public:
SpellGetter spell_getter;
select_spell_node(SpellGetter spell_getter) : spell_getter(spell_getter) {}
node_task tick(Context &ctx) noexcept override {
if (!Magic::SelectSpell(spell_getter(ctx)))
co_return task_result::failure;
co_return task_result::success;
}
};
template <typename Context, typename SpellSource> class spell_selected_node;
template<typename Context>
class spell_selected_node<Context, Magic::SPELL>
: public behavior_node<Context> {
public:
Magic::SPELL spell;
spell_selected_node(Magic::SPELL spell) : spell(spell) {}
node_task tick(Context &) noexcept override {
if (!Magic::IsSpellSelected(spell))
co_return task_result::failure;
co_return task_result::success;
}
};
template <typename Context, typename SpellGetter>
requires std::is_same_v<Magic::SPELL,
std::result_of_t<SpellGetter(Context &)>>
class spell_selected_node<Context, SpellGetter>
: public behavior_node<Context> {
public:
SpellGetter spell_getter;
spell_selected_node(SpellGetter spell_getter) : spell_getter(spell_getter) {}
node_task tick(Context &ctx) noexcept override {
if (!Magic::IsSpellSelected(spell_getter(ctx)))
co_return task_result::failure;
co_return task_result::success;
}
};
// template <typename Context>
// class cast_spell_node : public behavior_node<Context> {
// public:
// Magic::SPELL spell;
// cast_spell_node(Magic::SPELL spell) : spell(spell) {}
// node_task tick(Context &ctx) noexcept override {
// if (!Magic::CastSpell(spell))
// co_return task_result::failure;
// co_return task_result::success;
// }
// };
template <typename Context, typename SpellSource>
class cast_spell_node;
template <typename Context>
class cast_spell_node<Context, Magic::SPELL> : public behavior_node<Context> {
public:
Magic::SPELL spell;
cast_spell_node(Magic::SPELL spell) : spell(spell) {}
node_task tick(Context &) noexcept override {
if (!Magic::CastSpell(spell))
co_return task_result::failure;
co_return task_result::success;
}
};
template <typename Context, typename SpellGetter>
requires std::is_same_v<Magic::SPELL,
std::result_of_t<SpellGetter(Context &)>>
class cast_spell_node<Context, SpellGetter> : public behavior_node<Context> {
public:
SpellGetter spell_getter;
cast_spell_node(SpellGetter spell_getter) : spell_getter(spell_getter) {}
node_task tick(Context &ctx) noexcept override {
if (!Magic::CastSpell(spell_getter(ctx)))
co_return task_result::failure;
co_return task_result::success;
}
};
} // namespace bt

20
include/bt/mainscreen.hpp Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
namespace bt {
template <typename Context>
class is_logged_in_node : public behavior_node<Context> {
public:
constexpr is_logged_in_node() noexcept = default;
node_task tick(Context &ctx) noexcept override {
if (!Mainscreen::IsLoggedIn())
co_return task_result::failure;
co_return task_result::success;
}
};
} // namespace bt

193
include/bt/navigation.hpp Normal file
View File

@ -0,0 +1,193 @@
#pragma once
#include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
#include <queue>
#include <unordered_set>
#include <unordered_map>
#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
namespace bt {
namespace pathfinding {
class obstacle {
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) {}
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);
co_return task_result::success;
}
};
} // namespace bt

85
include/bt/node_task.hpp Normal file
View File

@ -0,0 +1,85 @@
#pragma once
#include <coroutine>
#include <optional>
namespace bt {
enum class task_result { failure, success };
class node_task {
public:
class promise_type;
using handle_type = std::coroutine_handle<promise_type>;
class promise_type {
public:
std::optional<task_result> result;
handle_type get_return_object() noexcept {
return handle_type::from_promise(*this);
}
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(task_result r) noexcept { result = r; }
void unhandled_exception() noexcept {
// std::terminate(); // TODO: handle exception
}
};
class awaiter {
public:
awaiter(handle_type handle) noexcept : _handle(handle) {}
bool await_ready() const noexcept { return false; }
auto await_suspend(std::coroutine_handle<>) noexcept { return _handle; }
task_result await_resume() noexcept { return *_handle.promise().result; }
private:
handle_type _handle;
};
node_task(handle_type handle) noexcept : _handle(handle) {}
node_task(node_task &&other) noexcept : _handle(other._handle) {
other._handle = nullptr;
}
~node_task() {
if (_handle) {
_handle.destroy();
}
}
node_task &operator=(node_task &&other) noexcept {
if (&other != this) {
if (_handle) {
_handle.destroy();
}
_handle = other._handle;
other._handle = nullptr;
}
return *this;
}
awaiter operator co_await() noexcept { return awaiter{_handle}; }
void resume() noexcept { _handle.resume(); }
bool done() const noexcept { return _handle.done(); }
task_result result() noexcept { return *_handle.promise().result; }
std::optional<task_result> try_result() noexcept {
return _handle.promise().result;
}
private:
handle_type _handle;
};
} // namespace bt

26
include/bt/player.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
namespace bt {
template <typename Context, typename Animation>
class is_animating_node : public behavior_node<Context> {
public:
Animation anim_id;
is_animating_node(std::int32_t anim_id) : anim_id(anim_id) {}
node_task tick(Context &ctx) noexcept override {
const auto player = Players::GetLocal();
if (!player)
co_return task_result::failure;
if (player.GetAnimationID() != anim_id)
co_return task_result::failure;
co_return task_result::success;
}
};
} // namespace bt

15
include/bt/script.hpp Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include <Game/Core.hpp>
#include <bt/behavior_node.hpp>
namespace bt {
template <typename Context>
class terminate_script_node : public behavior_node<Context> {
public:
node_task tick(Context &ctx) noexcept override {
Terminate = true;
co_return task_result::success;
}
};
}