various AB behavior nodes added

master
Warren Ulrich 2023-06-12 04:28:44 -07:00
parent c19dfa7c06
commit 2c1a4c597e
2 changed files with 303 additions and 24 deletions

View File

@ -5,6 +5,13 @@ project(bt)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED true) set(CMAKE_CXX_STANDARD_REQUIRED true)
if (NOT DEFINED ALPACABOT_DIR)
set(ALPACABOT_DIR $ENV{USERPROFILE}\\AlpacaBot)
endif()
add_library(bt INTERFACE) add_library(bt INTERFACE)
target_include_directories(bt INTERFACE include) target_include_directories(bt INTERFACE
${ALPACABOT_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/include
)

View File

@ -8,6 +8,8 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <Game/Core.hpp>
namespace bt { namespace bt {
enum class task_result { failure, success }; enum class task_result { failure, success };
@ -133,10 +135,6 @@ public:
} }
}; };
template <typename Context> auto sequence() {
return std::make_unique<sequence_node<Context>>();
}
template <typename Context> template <typename Context>
class selector_node : public composite_node<Context> { class selector_node : public composite_node<Context> {
public: public:
@ -156,10 +154,6 @@ public:
} }
}; };
template <typename Context> auto selector() {
return std::make_unique<selector_node<Context>>();
}
template <typename Context, typename TickFn> template <typename Context, typename TickFn>
class action_node : public behavior_node<Context> { class action_node : public behavior_node<Context> {
public: public:
@ -171,10 +165,6 @@ private:
TickFn _tick; TickFn _tick;
}; };
template <typename Context, typename TickFn> auto action(TickFn tick) {
return std::make_unique<action_node<Context, TickFn>>(tick);
}
template <typename Context, typename ConditionFn> template <typename Context, typename ConditionFn>
class conditional_node : public behavior_node<Context> { class conditional_node : public behavior_node<Context> {
public: public:
@ -191,11 +181,177 @@ private:
ConditionFn _condition; ConditionFn _condition;
}; };
template <typename Context, typename ConditionFn> template <typename Context>
auto conditional(ConditionFn condition) { class inverter_node : public behavior_node<Context> {
return std::make_unique<conditional_node<Context, ConditionFn>>(condition); 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) {}
@ -217,7 +373,8 @@ public:
_children.pop_back(); _children.pop_back();
for (auto &&child : children) { for (auto &&child : children) {
static_cast<composite_node<Context> *>(node.get())->add_child(std::move(child)); static_cast<composite_node<Context> *>(node.get())
->add_child(std::move(child));
} }
if (_node_stack.empty()) { if (_node_stack.empty()) {
@ -246,7 +403,8 @@ public:
_children.pop_back(); _children.pop_back();
for (auto &&child : children) { for (auto &&child : children) {
static_cast<composite_node<Context> *>(node.get())->add_child(std::move(child)); static_cast<composite_node<Context> *>(node.get())
->add_child(std::move(child));
} }
if (_node_stack.empty()) { if (_node_stack.empty()) {
@ -263,17 +421,131 @@ public:
throw std::runtime_error("Action must be within a sequence or selector"); throw std::runtime_error("Action must be within a sequence or selector");
} }
_children.back().push_back(std::make_unique<action_node<Context, TickFn>>(tick)); _children.back().push_back(
std::make_unique<action_node<Context, TickFn>>(tick));
return *this; return *this;
} }
template <typename ConditionFn> template <typename ConditionFn>
tree_builder &conditional(ConditionFn condition) { tree_builder &conditional(ConditionFn condition) {
if (_node_stack.empty()) { 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<conditional_node<Context, ConditionFn>>(condition)); _children.back().push_back(
std::make_unique<conditional_node<Context, ConditionFn>>(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<inverter_node<Context>>(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<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; return *this;
} }
@ -281,6 +553,7 @@ public:
if (!_node_stack.empty()) { if (!_node_stack.empty()) {
throw std::runtime_error("Mismatched begin/end"); throw std::runtime_error("Mismatched begin/end");
} }
return std::move(_root); return std::move(_root);
} }
@ -290,5 +563,4 @@ private:
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