diff --git a/include/bt/behavior_node.hpp b/include/bt/behavior_node.hpp new file mode 100644 index 0000000..c2b47e4 --- /dev/null +++ b/include/bt/behavior_node.hpp @@ -0,0 +1,130 @@ +#pragma once + +#include + +namespace bt { +using yield = std::suspend_always; + +template class behavior_node { +public: + virtual node_task tick(Context &ctx) noexcept = 0; + + virtual ~behavior_node() = default; +}; + +template +class composite_node : public behavior_node { +public: + void add_child(std::unique_ptr> &&child) { + _children.push_back(std::move(child)); + } + +protected: + std::vector>> _children; +}; + +template +class decorator_node : public behavior_node { +public: + decorator_node(std::unique_ptr> child) + : _child(std::move(child)) {} + + void set_child(std::unique_ptr> &&child) { + _child = std::move(child); + } + +protected: + std::unique_ptr> _child; +}; + +template +class sequence_node : public composite_node { +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 +class selector_node : public composite_node { +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 +class action_node : public behavior_node { +public: + action_node(TickFn tick) noexcept : _tick(tick) {} + + node_task tick(Context &ctx) noexcept override { return _tick(ctx); } + +private: + TickFn _tick; +}; + +template +class conditional_node : public behavior_node { +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 +class inverter_node : public decorator_node { +public: + inverter_node(std::unique_ptr> child) + : decorator_node(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; + } +}; +} \ No newline at end of file diff --git a/include/bt/bt.hpp b/include/bt/bt.hpp index 3b72384..fc637bd 100644 --- a/include/bt/bt.hpp +++ b/include/bt/bt.hpp @@ -10,348 +10,18 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace bt { -enum class task_result { failure, success }; - -class node_task { -public: - class promise_type; - - using handle_type = std::coroutine_handle; - - class promise_type { - public: - std::optional 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 try_result() noexcept { - return _handle.promise().result; - } - -private: - handle_type _handle; -}; - -using yield = std::suspend_always; - -template class behavior_node { -public: - virtual node_task tick(Context &ctx) noexcept = 0; - - virtual ~behavior_node() = default; -}; - -template -class composite_node : public behavior_node { -public: - void add_child(std::unique_ptr> &&child) { - _children.push_back(std::move(child)); - } - -protected: - std::vector>> _children; -}; - -template -class sequence_node : public composite_node { -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 -class selector_node : public composite_node { -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 -class action_node : public behavior_node { -public: - action_node(TickFn tick) noexcept : _tick(tick) {} - - node_task tick(Context &ctx) noexcept override { return _tick(ctx); } - -private: - TickFn _tick; -}; - -template -class conditional_node : public behavior_node { -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 -class inverter_node : public behavior_node { -public: - inverter_node(std::unique_ptr> 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> _child; -}; - -// Script -template -class terminate_script_node : public behavior_node { -public: - node_task tick(Context &ctx) noexcept override { - Terminate = true; - co_return task_result::success; - } -}; - -// Login -template -class login_player_node : public behavior_node { -public: - node_task tick(Context &ctx) noexcept override { - if (!Login::LoginPlayer()) - co_return task_result::failure; - - co_return task_result::success; - } -}; - -// Mainscreen -template -class is_logged_in_node : public behavior_node { -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 -class is_animating_node : public behavior_node { -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 - requires std::same_as || - std::same_as || - std::same_as || - std::same_as, ItemID> || - std::same_as, ItemID> -class inventory_contains_node : public behavior_node { -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 -class select_spell_node : public behavior_node { -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 -class spell_selected_node : public behavior_node { -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 -class cast_spell_node : public behavior_node { -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 class interact_node; - -template -class interact_node - : public behavior_node { -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 class tree_builder { public: tree_builder() noexcept : _root(nullptr) {} @@ -416,6 +86,16 @@ public: return *this; } + template 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>(std::forward(args)...)); + return *this; + } + template tree_builder &action(TickFn tick) { if (_node_stack.empty()) { throw std::runtime_error("Action must be within a sequence or selector"); @@ -455,100 +135,6 @@ public: 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>()); - 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>()); - 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>(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>()); - return *this; - } - - template - requires std::same_as || - std::same_as || - std::same_as || - std::same_as, ItemID> || - std::same_as, 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>(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>(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)); - 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>(spell)); - return *this; - } - - template - 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>(name, action)); - return *this; - } - std::unique_ptr> build() { if (!_node_stack.empty()) { throw std::runtime_error("Mismatched begin/end"); @@ -556,11 +142,10 @@ public: return std::move(_root); } - + private: std::unique_ptr> _root; std::vector>> _node_stack; std::vector>>> _children; }; - } // namespace bt \ No newline at end of file diff --git a/include/bt/interact.hpp b/include/bt/interact.hpp new file mode 100644 index 0000000..d7b1c95 --- /dev/null +++ b/include/bt/interact.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +namespace bt { +namespace meta { + +template +concept Interactable = + std::same_as || std::same_as || + std::same_as || + std::same_as || + std::same_as || + std::same_as; +} + +template + requires std::is_same_v || + std::is_same_v> || + std::is_same_v> && + meta::Interactable> || + meta::Interactable> +class interact_node : public behavior_node { +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) + return interactable_source(); + else if constexpr (std::is_invocable_v) + return interactable_source(ctx); + }(); + + if (!interactable) + co_return task_result::failure; + + const auto action = [&]() { + if constexpr (std::is_invocable_v) + return action_source(); + else if constexpr (std::is_invocable_v) + return action_source(ctx); + else if constexpr (std::is_same_v) + return action_source; + }(); + + if (!interactable.Interact(action)) + co_return task_result::failure; + + co_return task_result::success; + } +}; + +} // namespace bt \ No newline at end of file diff --git a/include/bt/inventory.hpp b/include/bt/inventory.hpp new file mode 100644 index 0000000..8d7738d --- /dev/null +++ b/include/bt/inventory.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +namespace bt { + +template + requires std::same_as || + std::same_as || + std::same_as || + std::same_as, ItemID> || + std::same_as, ItemID> || + std::same_as, ItemID> || + std::same_as, ItemID> +class inventory_contains_node : public behavior_node { +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 \ No newline at end of file diff --git a/include/bt/login.hpp b/include/bt/login.hpp new file mode 100644 index 0000000..d9b2742 --- /dev/null +++ b/include/bt/login.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace bt { +template +class login_player : public behavior_node { +public: + node_task tick(Context &ctx) noexcept override { + if (!Login::LoginPlayer()) + co_return task_result::failure; + + co_return task_result::success; + } +}; + +} // namespace bt \ No newline at end of file diff --git a/include/bt/magic.hpp b/include/bt/magic.hpp new file mode 100644 index 0000000..049bf6f --- /dev/null +++ b/include/bt/magic.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include + +#include + +namespace bt { +template class select_spell_node; + +template +class select_spell_node : public behavior_node { +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 + requires std::is_same_v> +class select_spell_node : public behavior_node { +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 class spell_selected_node; + +template +class spell_selected_node + : public behavior_node { +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 + requires std::is_same_v> +class spell_selected_node + : public behavior_node { +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 +// class cast_spell_node : public behavior_node { +// 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 +class cast_spell_node; + +template +class cast_spell_node : public behavior_node { +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 + requires std::is_same_v> +class cast_spell_node : public behavior_node { +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 \ No newline at end of file diff --git a/include/bt/mainscreen.hpp b/include/bt/mainscreen.hpp new file mode 100644 index 0000000..2e4a2d4 --- /dev/null +++ b/include/bt/mainscreen.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace bt { +template +class is_logged_in_node : public behavior_node { +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 \ No newline at end of file diff --git a/include/bt/navigation.hpp b/include/bt/navigation.hpp new file mode 100644 index 0000000..d0463e5 --- /dev/null +++ b/include/bt/navigation.hpp @@ -0,0 +1,193 @@ +#pragma once + +#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 { +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 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) {} + + 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 \ No newline at end of file diff --git a/include/bt/node_task.hpp b/include/bt/node_task.hpp new file mode 100644 index 0000000..dd161ad --- /dev/null +++ b/include/bt/node_task.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include +#include + +namespace bt { +enum class task_result { failure, success }; + +class node_task { +public: + class promise_type; + + using handle_type = std::coroutine_handle; + + class promise_type { + public: + std::optional 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 try_result() noexcept { + return _handle.promise().result; + } + +private: + handle_type _handle; +}; +} // namespace bt \ No newline at end of file diff --git a/include/bt/player.hpp b/include/bt/player.hpp new file mode 100644 index 0000000..f12b683 --- /dev/null +++ b/include/bt/player.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace bt { +template +class is_animating_node : public behavior_node { +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 \ No newline at end of file diff --git a/include/bt/script.hpp b/include/bt/script.hpp new file mode 100644 index 0000000..59cf9c0 --- /dev/null +++ b/include/bt/script.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace bt { +template +class terminate_script_node : public behavior_node { +public: + node_task tick(Context &ctx) noexcept override { + Terminate = true; + co_return task_result::success; + } +}; +} \ No newline at end of file