diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b75c35..5789011 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,13 @@ project(bt) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED true) +if (NOT DEFINED ALPACABOT_DIR) + set(ALPACABOT_DIR $ENV{USERPROFILE}\\AlpacaBot) +endif() + add_library(bt INTERFACE) -target_include_directories(bt INTERFACE include) \ No newline at end of file +target_include_directories(bt INTERFACE + ${ALPACABOT_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/include +) \ No newline at end of file diff --git a/include/bt/bt.hpp b/include/bt/bt.hpp index 7e0cbf2..3b72384 100644 --- a/include/bt/bt.hpp +++ b/include/bt/bt.hpp @@ -8,6 +8,8 @@ #include #include +#include + namespace bt { enum class task_result { failure, success }; @@ -133,10 +135,6 @@ public: } }; -template auto sequence() { - return std::make_unique>(); -} - template class selector_node : public composite_node { public: @@ -156,10 +154,6 @@ public: } }; -template auto selector() { - return std::make_unique>(); -} - template class action_node : public behavior_node { public: @@ -171,10 +165,6 @@ private: TickFn _tick; }; -template auto action(TickFn tick) { - return std::make_unique>(tick); -} - template class conditional_node : public behavior_node { public: @@ -191,10 +181,176 @@ private: ConditionFn _condition; }; -template -auto conditional(ConditionFn condition) { - return std::make_unique>(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: @@ -217,7 +373,8 @@ public: _children.pop_back(); for (auto &&child : children) { - static_cast *>(node.get())->add_child(std::move(child)); + static_cast *>(node.get()) + ->add_child(std::move(child)); } if (_node_stack.empty()) { @@ -246,7 +403,8 @@ public: _children.pop_back(); for (auto &&child : children) { - static_cast *>(node.get())->add_child(std::move(child)); + static_cast *>(node.get()) + ->add_child(std::move(child)); } if (_node_stack.empty()) { @@ -263,17 +421,131 @@ public: throw std::runtime_error("Action must be within a sequence or selector"); } - _children.back().push_back(std::make_unique>(tick)); + _children.back().push_back( + std::make_unique>(tick)); return *this; } template tree_builder &conditional(ConditionFn condition) { if (_node_stack.empty()) { - throw std::runtime_error("Condition must be within a sequence or selector"); + throw std::runtime_error( + "Condition must be within a sequence or selector"); } - _children.back().push_back(std::make_unique>(condition)); + _children.back().push_back( + std::make_unique>(condition)); + return *this; + } + + tree_builder &invert() { + if (_node_stack.empty()) { + throw std::runtime_error("Invert must be within a sequence or selector"); + } + + if (_children.back().empty()) { + throw std::runtime_error("Invert requires a preceding node"); + } + + auto child = std::move(_children.back().back()); + _children.back().pop_back(); + _children.back().push_back( + std::make_unique>(std::move(child))); + + 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; } @@ -281,6 +553,7 @@ public: if (!_node_stack.empty()) { throw std::runtime_error("Mismatched begin/end"); } + return std::move(_root); } @@ -290,5 +563,4 @@ private: std::vector>>> _children; }; - -} // namespace bt +} // namespace bt \ No newline at end of file