various AB behavior nodes added
parent
c19dfa7c06
commit
2c1a4c597e
|
@ -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
|
||||||
|
)
|
|
@ -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,10 +181,176 @@ 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:
|
||||||
|
@ -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
|
Loading…
Reference in New Issue