diff --git a/blackboard/bb_variable.cpp b/blackboard/bb_variable.cpp new file mode 100644 index 0000000..12ad83b --- /dev/null +++ b/blackboard/bb_variable.cpp @@ -0,0 +1,153 @@ +/** + * bb_variable.cpp + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "bb_variable.h" + +#include "../util/limbo_compat.h" + +void BBVariable::unref() { + if (data && data->refcount.unref()) { + memdelete(data); + } + data = nullptr; +} + +// void BBVariable::init_ref() { +// if (data) { +// unref(); +// } +// data = memnew(Data); +// data->refcount.init(); +// } + +void BBVariable::set_value(const Variant &p_value) { + data->value = p_value; +} + +Variant BBVariable::get_value() const { + return data->value; +} + +void BBVariable::set_type(Variant::Type p_type) { + data->type = p_type; + data->value = VARIANT_DEFAULT(p_type); +} + +Variant::Type BBVariable::get_type() const { + return data->type; +} + +void BBVariable::set_hint(PropertyHint p_hint) { + data->hint = p_hint; +} + +PropertyHint BBVariable::get_hint() const { + return data->hint; +} + +void BBVariable::set_hint_string(const String &p_hint_string) { + data->hint_string = p_hint_string; +} + +String BBVariable::get_hint_string() const { + return data->hint_string; +} + +BBVariable BBVariable::duplicate() const { + BBVariable var; + var.data->hint = data->hint; + var.data->hint_string = data->hint_string; + var.data->type = data->type; + var.data->value = data->value; + return var; +} + +bool BBVariable::is_same_prop_info(const BBVariable &p_other) const { + if (data->type != p_other.data->type) { + return false; + } + if (data->hint != p_other.data->hint) { + return false; + } + if (data->hint_string != p_other.data->hint_string) { + return false; + } + return true; +} + +void BBVariable::copy_prop_info(const BBVariable &p_other) { + data->type = p_other.data->type; + data->hint = p_other.data->hint; + data->hint_string = p_other.data->hint_string; +} + +bool BBVariable::operator==(const BBVariable &p_var) const { + if (data == p_var.data) { + return true; + } + + if (!data || !p_var.data) { + return false; + } + + if (data->type != p_var.data->type) { + return false; + } + + if (data->hint != p_var.data->hint) { + return false; + } + + if (data->value != p_var.data->value) { + return false; + } + + if (data->hint_string != p_var.data->hint_string) { + return false; + } + + return true; +} + +bool BBVariable::operator!=(const BBVariable &p_var) const { + return !(*this == p_var); +} + +void BBVariable::operator=(const BBVariable &p_var) { + if (this == &p_var) { + return; + } + + unref(); + + if (p_var.data && p_var.data->refcount.ref()) { + data = p_var.data; + } +} + +BBVariable::BBVariable(const BBVariable &p_var) { + if (p_var.data && p_var.data->refcount.ref()) { + data = p_var.data; + } +} + +BBVariable::BBVariable(Variant::Type p_type, PropertyHint p_hint, const String &p_hint_string) { + data = memnew(Data); + data->refcount.init(); + + set_type(p_type); + data->hint = p_hint; + data->hint_string = p_hint_string; +} + +BBVariable::~BBVariable() { + unref(); +} diff --git a/blackboard/bb_variable.h b/blackboard/bb_variable.h new file mode 100644 index 0000000..cccf2b8 --- /dev/null +++ b/blackboard/bb_variable.h @@ -0,0 +1,77 @@ +/** + * bb_variable.h + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#ifndef BB_VARIABLE_H +#define BB_VARIABLE_H + +#ifdef LIMBOAI_MODULE +#include "core/object/object.h" +#include "core/templates/safe_refcount.h" +#include "core/variant/variant.h" +#endif // LIMBOAI_MODULE + +#ifdef LIMBOAI_GDEXTENSION +#include "godot_cpp/core/defs.hpp" +#include "godot_cpp/templates/safe_refcount.hpp" +#include "godot_cpp/variant/variant.hpp" +using namespace godot; +#endif // LIMBOAI_GDEXTENSION + +class BBVariable { +private: + struct Data { + SafeRefCount refcount; + Variant value; + Variant::Type type = Variant::NIL; + PropertyHint hint = PropertyHint::PROPERTY_HINT_NONE; + String hint_string; + // bool bound = false; + // uint64_t bound_object = 0; + // StringName bound_property; + }; + + Data *data = nullptr; + void unref(); + // void init_ref(); + +public: + void set_value(const Variant &p_value); + Variant get_value() const; + + void set_type(Variant::Type p_type); + Variant::Type get_type() const; + + void set_hint(PropertyHint p_hint); + PropertyHint get_hint() const; + + void set_hint_string(const String &p_hint_string); + String get_hint_string() const; + + BBVariable duplicate() const; + + bool is_same_prop_info(const BBVariable &p_other) const; + void copy_prop_info(const BBVariable &p_other); + + // bool is_bound() { return bound; } + + // void bind(Node *p_root, NodePath p_path); + // void unbind(); + + bool operator==(const BBVariable &p_var) const; + bool operator!=(const BBVariable &p_var) const; + void operator=(const BBVariable &p_var); + + BBVariable(const BBVariable &p_var); + BBVariable(Variant::Type p_type = Variant::Type::NIL, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = ""); + ~BBVariable(); +}; + +#endif // BB_VARIABLE_H diff --git a/blackboard/blackboard.cpp b/blackboard/blackboard.cpp index cfd6973..62584e0 100644 --- a/blackboard/blackboard.cpp +++ b/blackboard/blackboard.cpp @@ -22,63 +22,71 @@ #include #include #include -#include using namespace godot; #endif Ref Blackboard::top() const { Ref bb(this); - while (bb->get_parent_scope().is_valid()) { - bb = bb->get_parent_scope(); + while (bb->get_parent().is_valid()) { + bb = bb->get_parent(); } return bb; } -Variant Blackboard::get_var(const Variant &p_key, const Variant &p_default) const { - if (data.has(p_key)) { - return data.get(p_key, Variant()); +Variant Blackboard::get_var(const String &p_name, const Variant &p_default) const { + if (data.has(p_name)) { + return data.get(p_name).get_value(); } else if (parent.is_valid()) { - return parent->get_var(p_key, p_default); + return parent->get_var(p_name, p_default); } else { return p_default; } } -void Blackboard::set_var(const Variant &p_key, const Variant &p_value) { - data[p_key] = p_value; +void Blackboard::set_var(const String &p_name, const Variant &p_value) { + if (data.has(p_name)) { + // Not checking type - allowing duck-typing. + data[p_name].set_value(p_value); + } else { + BBVariable var(p_value.get_type()); + var.set_value(p_value); + data.insert(p_name, var); + } } -bool Blackboard::has_var(const Variant &p_key) const { - return data.has(p_key) || (parent.is_valid() && parent->has_var(p_key)); +bool Blackboard::has_var(const String &p_name) const { + return data.has(p_name) || (parent.is_valid() && parent->has_var(p_name)); } -void Blackboard::erase_var(const Variant &p_key) { - data.erase(p_key); +void Blackboard::erase_var(const String &p_name) { + data.erase(p_name); +} + +void Blackboard::add_var(const String &p_name, const BBVariable &p_var) { + ERR_FAIL_COND(data.has(p_name)); + data.insert(p_name, p_var); } void Blackboard::prefetch_nodepath_vars(Node *p_node) { ERR_FAIL_COND(p_node == nullptr); - Array keys = data.keys(); - Array values = data.values(); - for (int i = 0; i < keys.size(); i++) { - if (values[i].get_type() == Variant::NODE_PATH) { - Node *fetched_node = p_node->get_node_or_null(values[i]); + for (const KeyValue &kv : data) { + BBVariable var = kv.value; + if (var.get_value().get_type() == Variant::NODE_PATH) { + Node *fetched_node = p_node->get_node_or_null(var.get_value()); if (fetched_node != nullptr) { - data[keys[i]] = fetched_node; + var.set_value(fetched_node); } } } } void Blackboard::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_data"), &Blackboard::get_data); - ClassDB::bind_method(D_METHOD("set_data", "p_data"), &Blackboard::set_data); - ClassDB::bind_method(D_METHOD("get_var", "p_key", "p_default"), &Blackboard::get_var, Variant()); - ClassDB::bind_method(D_METHOD("set_var", "p_key", "p_value"), &Blackboard::set_var); - ClassDB::bind_method(D_METHOD("has_var", "p_key"), &Blackboard::has_var); - ClassDB::bind_method(D_METHOD("set_parent_scope", "p_blackboard"), &Blackboard::set_parent_scope); - ClassDB::bind_method(D_METHOD("get_parent_scope"), &Blackboard::get_parent_scope); - ClassDB::bind_method(D_METHOD("erase_var", "p_key"), &Blackboard::erase_var); + ClassDB::bind_method(D_METHOD("get_var", "p_name", "p_default"), &Blackboard::get_var, Variant()); + ClassDB::bind_method(D_METHOD("set_var", "p_name", "p_value"), &Blackboard::set_var); + ClassDB::bind_method(D_METHOD("has_var", "p_name"), &Blackboard::has_var); + ClassDB::bind_method(D_METHOD("set_parent_scope", "p_blackboard"), &Blackboard::set_parent); + ClassDB::bind_method(D_METHOD("get_parent_scope"), &Blackboard::get_parent); + ClassDB::bind_method(D_METHOD("erase_var", "p_name"), &Blackboard::erase_var); ClassDB::bind_method(D_METHOD("prefetch_nodepath_vars", "p_node"), &Blackboard::prefetch_nodepath_vars); ClassDB::bind_method(D_METHOD("top"), &Blackboard::top); } diff --git a/blackboard/blackboard.h b/blackboard/blackboard.h index 893bd89..42c04c7 100644 --- a/blackboard/blackboard.h +++ b/blackboard/blackboard.h @@ -12,10 +12,11 @@ #ifndef BLACKBOARD_H #define BLACKBOARD_H +#include "bb_variable.h" + #ifdef LIMBOAI_MODULE #include "core/object/object.h" #include "core/object/ref_counted.h" -#include "core/variant/dictionary.h" #include "core/variant/variant.h" #include "scene/main/node.h" #endif // LIMBOAI_MODULE @@ -25,7 +26,7 @@ #include #include #include -#include +#include using namespace godot; #endif // LIMBOAI_GDEXTENSION @@ -33,27 +34,28 @@ class Blackboard : public RefCounted { GDCLASS(Blackboard, RefCounted); private: - Dictionary data; + HashMap data; Ref parent; protected: static void _bind_methods(); public: - void set_data(const Dictionary &p_value) { data = p_value; } - Dictionary get_data() const { return data; } - - void set_parent_scope(const Ref &p_blackboard) { parent = p_blackboard; } - Ref get_parent_scope() const { return parent; } + void set_parent(const Ref &p_blackboard) { parent = p_blackboard; } + Ref get_parent() const { return parent; } Ref top() const; - Variant get_var(const Variant &p_key, const Variant &p_default) const; - void set_var(const Variant &p_key, const Variant &p_value); - bool has_var(const Variant &p_key) const; - void erase_var(const Variant &p_key); + Variant get_var(const String &p_name, const Variant &p_default) const; + void set_var(const String &p_name, const Variant &p_value); + bool has_var(const String &p_name) const; + void erase_var(const String &p_name); + + void add_var(const String &p_name, const BBVariable &p_var); void prefetch_nodepath_vars(Node *p_node); + + // TODO: Add serialization API. }; -#endif // BLACKBOARD_H \ No newline at end of file +#endif // BLACKBOARD_H diff --git a/blackboard/blackboard_plan.cpp b/blackboard/blackboard_plan.cpp new file mode 100644 index 0000000..a52cb6c --- /dev/null +++ b/blackboard/blackboard_plan.cpp @@ -0,0 +1,269 @@ +/** + * blackboard_plan.cpp + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "blackboard_plan.h" + +bool BlackboardPlan::_set(const StringName &p_name, const Variant &p_value) { + String prop_name = p_name; + + // * Editor + if (var_map.has(prop_name)) { + var_map[prop_name].set_value(p_value); + return true; + } + + // * Storage + if (prop_name.begins_with("var/")) { + String var_name = prop_name.get_slicec('/', 1); + String what = prop_name.get_slicec('/', 2); + if (!var_map.has(var_name) && what == "name") { + add_var(var_name, BBVariable()); + } + if (what == "name") { + // We don't store variable name with the variable. + } else if (what == "type") { + var_map[var_name].set_type((Variant::Type)(int)p_value); + } else if (what == "value") { + var_map[var_name].set_value(p_value); + } else if (what == "hint") { + var_map[var_name].set_hint((PropertyHint)(int)p_value); + } else if (what == "hint_string") { + var_map[var_name].set_hint_string(p_value); + } else { + return false; + } + return true; + } + + return false; +} + +bool BlackboardPlan::_get(const StringName &p_name, Variant &r_ret) const { + String prop_name = p_name; + + // * Editor + if (var_map.has(prop_name)) { + r_ret = var_map[prop_name].get_value(); + return true; + } + + // * Storage + if (!prop_name.begins_with("var/")) { + return false; + } + + String var_name = prop_name.get_slicec('/', 1); + String what = prop_name.get_slicec('/', 2); + ERR_FAIL_COND_V(!var_map.has(var_name), false); + + if (what == "name") { + r_ret = var_name; + } else if (what == "type") { + r_ret = var_map[var_name].get_type(); + } else if (what == "value") { + r_ret = var_map[var_name].get_value(); + } else if (what == "hint") { + r_ret = var_map[var_name].get_hint(); + } else if (what == "hint_string") { + r_ret = var_map[var_name].get_hint_string(); + } + return true; +} + +void BlackboardPlan::_get_property_list(List *p_list) const { + for (const Pair &p : var_list) { + String var_name = p.first; + BBVariable var = p.second; + + // * Editor + if (!is_derived() || !var_name.begins_with("_")) { + p_list->push_back(PropertyInfo(var.get_type(), var_name, var.get_hint(), var.get_hint_string(), PROPERTY_USAGE_EDITOR)); + } + + // * Storage + p_list->push_back(PropertyInfo(Variant::STRING, "var/" + var_name + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::INT, "var/" + var_name + "/type", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(var.get_type(), "var/" + var_name + "/value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::INT, "var/" + var_name + "/hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + p_list->push_back(PropertyInfo(Variant::STRING, "var/" + var_name + "/hint_string", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); + } +} + +bool BlackboardPlan::_property_can_revert(const StringName &p_name) const { + return base.is_valid() && base->var_map.has(p_name); +} + +bool BlackboardPlan::_property_get_revert(const StringName &p_name, Variant &r_property) const { + if (base->var_map.has(p_name)) { + r_property = base->var_map[p_name].get_value(); + return true; + } + return false; +} + +void BlackboardPlan::set_base_plan(const Ref &p_base) { + base = p_base; + sync_with_base_plan(); +} + +void BlackboardPlan::add_var(const String &p_name, const BBVariable &p_var) { + ERR_FAIL_COND(var_map.has(p_name)); + var_map.insert(p_name, p_var); + var_list.push_back(Pair(p_name, p_var)); + notify_property_list_changed(); + emit_changed(); +} + +void BlackboardPlan::remove_var(const String &p_name) { + ERR_FAIL_COND(!var_map.has(p_name)); + var_list.erase(Pair(p_name, var_map[p_name])); + var_map.erase(p_name); + notify_property_list_changed(); + emit_changed(); +} + +BBVariable BlackboardPlan::get_var(const String &p_name) { + ERR_FAIL_COND_V(!var_map.has(p_name), BBVariable()); + return var_map.get(p_name); +} + +Pair BlackboardPlan::get_var_by_index(int p_index) { + Pair ret; + ERR_FAIL_INDEX_V(p_index, (int)var_map.size(), ret); + return var_list[p_index]; +} + +PackedStringArray BlackboardPlan::list_vars() const { + PackedStringArray ret; + for (const Pair &p : var_list) { + ret.append(p.first); + } + return ret; +} + +String BlackboardPlan::get_var_name(const BBVariable &p_var) const { + for (const Pair &p : var_list) { + if (p.second == p_var) { + return p.first; + } + } + return String(); +} + +void BlackboardPlan::rename_var(const String &p_name, const String &p_new_name) { + ERR_FAIL_COND(p_new_name.is_empty()); + ERR_FAIL_COND(var_map.has(p_new_name)); + ERR_FAIL_COND(!var_map.has(p_name)); + + BBVariable var = var_map[p_name]; + Pair new_entry(p_new_name, var); + Pair old_entry(p_name, var); + var_list.find(old_entry)->set(new_entry); + + var_map.erase(p_name); + var_map.insert(p_new_name, var); + + notify_property_list_changed(); + emit_changed(); +} + +void BlackboardPlan::move_var(int p_index, int p_new_index) { + ERR_FAIL_INDEX(p_index, (int)var_map.size()); + ERR_FAIL_INDEX(p_new_index, (int)var_map.size()); + + if (p_index == p_new_index) { + return; + } + + List>::Element *E = var_list.front(); + for (int i = 0; i < p_index; i++) { + E = E->next(); + } + List>::Element *E2 = var_list.front(); + for (int i = 0; i < p_new_index; i++) { + E2 = E2->next(); + } + + var_list.move_before(E, E2); + if (p_new_index > p_index) { + var_list.move_before(E2, E); + } + + notify_property_list_changed(); + emit_changed(); +} + +void BlackboardPlan::sync_with_base_plan() { + if (base.is_null()) { + return; + } + + bool changed = false; + + // Sync variables with the base plan. + for (const Pair &p : base->var_list) { + const String &base_name = p.first; + const BBVariable &base_var = p.second; + + if (!var_map.has(base_name)) { + add_var(base_name, base_var.duplicate()); + changed = true; + continue; + } + + BBVariable var = var_map[base_name]; + if (!var.is_same_prop_info(base_var)) { + var.copy_prop_info(base_var); + changed = true; + } + if (var.get_value().get_type() != base_var.get_type()) { + var.set_value(base_var.get_value()); + changed = true; + } + } + + // Erase variables that do not exist in the base plan. + for (const Pair &p : var_list) { + if (!base->has_var(p.first)) { + remove_var(p.first); + changed = true; + } + } + + if (changed) { + notify_property_list_changed(); + emit_changed(); + } +} + +Ref BlackboardPlan::create_blackboard() { + Ref bb = memnew(Blackboard); + for (const Pair &p : var_list) { + bb->add_var(p.first, p.second.duplicate()); + } + return bb; +} + +void BlackboardPlan::populate_blackboard(const Ref &p_blackboard, bool overwrite) { + for (const Pair &p : var_list) { + if (p_blackboard->has_var(p.first)) { + if (overwrite) { + p_blackboard->erase_var(p.first); + } else { + continue; + } + } + p_blackboard->add_var(p.first, p.second.duplicate()); + } +} + +BlackboardPlan::BlackboardPlan() { +} diff --git a/blackboard/blackboard_plan.h b/blackboard/blackboard_plan.h new file mode 100644 index 0000000..e5a315a --- /dev/null +++ b/blackboard/blackboard_plan.h @@ -0,0 +1,74 @@ +/** + * blackboard_plan.h + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#ifndef BLACKBOARD_PLAN_H +#define BLACKBOARD_PLAN_H + +#include "bb_variable.h" +#include "blackboard.h" + +#ifdef LIMBOAI_MODULE +#include "core/io/resource.h" +#endif // LIMBOAI_MODULE + +#ifdef LIMBOAI_GDEXTENSION +#include +using namespace godot; +#endif // LIMBOAI_GDEXTENSION + +class BlackboardPlan : public Resource { + GDCLASS(BlackboardPlan, Resource); + +private: + List> var_list; + HashMap var_map; + + // When base is not null, the plan is considered to be derived from the base plan. + // A derived plan can only have variables that exist in the base plan, + // and only the values can be different in those variables. + Ref base; + +protected: + static void _bind_methods() {} + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + +public: + void set_base_plan(const Ref &p_base); + Ref get_base_plan() const { return base; } + + void add_var(const String &p_name, const BBVariable &p_var); + void remove_var(const String &p_name); + BBVariable get_var(const String &p_name); + Pair get_var_by_index(int p_index); + bool has_var(const String &p_name) { return var_map.has(p_name); } + bool is_empty() const { return var_map.is_empty(); } + int get_var_count() const { return var_map.size(); } + + PackedStringArray list_vars() const; + String get_var_name(const BBVariable &p_var) const; + void rename_var(const String &p_name, const String &p_new_name); + void move_var(int p_index, int p_new_index); + + void sync_with_base_plan(); + bool is_derived() const { return base.is_valid(); } + + Ref create_blackboard(); + void populate_blackboard(const Ref &p_blackboard, bool overwrite); + + BlackboardPlan(); +}; + +#endif // BLACKBOARD_PLAN_H diff --git a/bt/behavior_tree.cpp b/bt/behavior_tree.cpp index b6fc0d9..53d2442 100644 --- a/bt/behavior_tree.cpp +++ b/bt/behavior_tree.cpp @@ -22,6 +22,24 @@ #include "godot_cpp/core/error_macros.hpp" #endif // ! LIMBOAI_GDEXTENSION +void BehaviorTree::set_description(const String &p_value) { + description = p_value; + emit_changed(); +} + +void BehaviorTree::set_blackboard_plan(const Ref &p_plan) { + blackboard_plan = p_plan; + if (blackboard_plan.is_null()) { + blackboard_plan = Ref(memnew(BlackboardPlan)); + } + emit_changed(); +} + +void BehaviorTree::set_root_task(const Ref &p_value) { + root_task = p_value; + emit_changed(); +} + Ref BehaviorTree::clone() const { Ref copy = duplicate(false); copy->set_path(""); @@ -47,6 +65,8 @@ Ref BehaviorTree::instantiate(Node *p_agent, const Ref &p_bl void BehaviorTree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_description", "p_value"), &BehaviorTree::set_description); ClassDB::bind_method(D_METHOD("get_description"), &BehaviorTree::get_description); + ClassDB::bind_method(D_METHOD("set_blackboard_plan", "p_plan"), &BehaviorTree::set_blackboard_plan); + ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &BehaviorTree::get_blackboard_plan); ClassDB::bind_method(D_METHOD("set_root_task", "p_value"), &BehaviorTree::set_root_task); ClassDB::bind_method(D_METHOD("get_root_task"), &BehaviorTree::get_root_task); ClassDB::bind_method(D_METHOD("clone"), &BehaviorTree::clone); @@ -54,5 +74,9 @@ void BehaviorTree::_bind_methods() { ClassDB::bind_method(D_METHOD("instantiate", "p_agent", "p_blackboard"), &BehaviorTree::instantiate); ADD_PROPERTY(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT), "set_description", "get_description"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard_plan", PROPERTY_HINT_RESOURCE_TYPE, "BlackboardPlan", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_blackboard_plan", "get_blackboard_plan"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_task", PROPERTY_HINT_RESOURCE_TYPE, "BTTask", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_root_task", "get_root_task"); } + +BehaviorTree::BehaviorTree() { +} diff --git a/bt/behavior_tree.h b/bt/behavior_tree.h index ffd6f53..c04cd11 100644 --- a/bt/behavior_tree.h +++ b/bt/behavior_tree.h @@ -12,11 +12,11 @@ #ifndef BEHAVIOR_TREE_H #define BEHAVIOR_TREE_H +#include "../blackboard/blackboard_plan.h" #include "tasks/bt_task.h" #ifdef LIMBOAI_MODULE #include "core/io/resource.h" -#include "modules/limboai/blackboard/blackboard.h" #endif // LIMBOAI_MODULE #ifdef LIMBOAI_GDEXTENSION @@ -29,6 +29,7 @@ class BehaviorTree : public Resource { private: String description; + Ref blackboard_plan; Ref root_task; protected: @@ -39,21 +40,20 @@ public: virtual bool editor_can_reload_from_file() override { return false; } #endif - void set_description(String p_value) { - description = p_value; - emit_changed(); - } + void set_description(const String &p_value); String get_description() const { return description; } - void set_root_task(const Ref &p_value) { - root_task = p_value; - emit_changed(); - } + void set_blackboard_plan(const Ref &p_plan); + Ref get_blackboard_plan() const { return blackboard_plan; } + + void set_root_task(const Ref &p_value); Ref get_root_task() const { return root_task; } Ref clone() const; void copy_other(const Ref &p_other); Ref instantiate(Node *p_agent, const Ref &p_blackboard) const; + + BehaviorTree(); }; #endif // BEHAVIOR_TREE_H diff --git a/bt/bt_player.cpp b/bt/bt_player.cpp index 25d8004..32548dd 100644 --- a/bt/bt_player.cpp +++ b/bt/bt_player.cpp @@ -63,11 +63,34 @@ void BTPlayer::_load_tree() { #endif } -void BTPlayer::set_behavior_tree(const Ref &p_tree) { - behavior_tree = p_tree; - if (Engine::get_singleton()->is_editor_hint() == false && get_owner()) { - _load_tree(); +void BTPlayer::_update_blackboard_plan() { + if (blackboard_plan.is_null()) { + blackboard_plan = Ref(memnew(BlackboardPlan)); } + blackboard_plan->set_base_plan(behavior_tree.is_valid() ? behavior_tree->get_blackboard_plan() : nullptr); +} + +void BTPlayer::set_behavior_tree(const Ref &p_tree) { + if (Engine::get_singleton()->is_editor_hint()) { + if (behavior_tree.is_valid() && behavior_tree->is_connected(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan))) { + behavior_tree->disconnect(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan)); + } + if (p_tree.is_valid()) { + p_tree->connect(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan)); + } + behavior_tree = p_tree; + _update_blackboard_plan(); + } else { + behavior_tree = p_tree; + if (get_owner()) { + _load_tree(); + } + } +} + +void BTPlayer::set_blackboard_plan(const Ref &p_plan) { + blackboard_plan = p_plan; + _update_blackboard_plan(); } void BTPlayer::set_update_mode(UpdateMode p_mode) { @@ -160,6 +183,12 @@ void BTPlayer::_notification(int p_notification) { } break; case NOTIFICATION_READY: { if (!Engine::get_singleton()->is_editor_hint()) { + if (blackboard.is_null()) { + blackboard = Ref(memnew(Blackboard)); + } + if (blackboard_plan.is_valid()) { + blackboard_plan->populate_blackboard(blackboard, false); + } if (behavior_tree.is_valid()) { _load_tree(); } @@ -169,18 +198,27 @@ void BTPlayer::_notification(int p_notification) { #endif } } break; -#ifdef DEBUG_ENABLED case NOTIFICATION_ENTER_TREE: { +#ifdef DEBUG_ENABLED if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path()); } +#endif // DEBUG_ENABLED } break; case NOTIFICATION_EXIT_TREE: { +#ifdef DEBUG_ENABLED if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path()); } - } break; #endif // DEBUG_ENABLED + + if (Engine::get_singleton()->is_editor_hint()) { + if (behavior_tree.is_valid() && behavior_tree->is_connected(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan))) { + behavior_tree->disconnect(LW_NAME(changed), callable_mp(this, &BTPlayer::_update_blackboard_plan)); + } + } + + } break; } } @@ -196,8 +234,8 @@ void BTPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_prefetch_nodepath_vars", "p_value"), &BTPlayer::set_prefetch_nodepath_vars); ClassDB::bind_method(D_METHOD("get_prefetch_nodepath_vars"), &BTPlayer::get_prefetch_nodepath_vars); - ClassDB::bind_method(D_METHOD("_set_blackboard_data", "p_blackboard"), &BTPlayer::_set_blackboard_data); - ClassDB::bind_method(D_METHOD("_get_blackboard_data"), &BTPlayer::_get_blackboard_data); + ClassDB::bind_method(D_METHOD("set_blackboard_plan", "p_plan"), &BTPlayer::set_blackboard_plan); + ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &BTPlayer::get_blackboard_plan); ClassDB::bind_method(D_METHOD("update", "p_delta"), &BTPlayer::update); ClassDB::bind_method(D_METHOD("restart"), &BTPlayer::restart); @@ -207,7 +245,7 @@ void BTPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Idle,Physics,Manual"), "set_update_mode", "get_update_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "get_active"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard", PROPERTY_HINT_NONE, "Blackboard", 0), "set_blackboard", "get_blackboard"); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_blackboard_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_blackboard_data", "_get_blackboard_data"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard_plan", PROPERTY_HINT_RESOURCE_TYPE, "BlackboardPlan", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_blackboard_plan", "get_blackboard_plan"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefetch_nodepath_vars"), "set_prefetch_nodepath_vars", "get_prefetch_nodepath_vars"); BIND_ENUM_CONSTANT(IDLE); diff --git a/bt/bt_player.h b/bt/bt_player.h index 175fafc..703596e 100644 --- a/bt/bt_player.h +++ b/bt/bt_player.h @@ -13,6 +13,7 @@ #define BT_PLAYER_H #include "../blackboard/blackboard.h" +#include "../blackboard/blackboard_plan.h" #include "behavior_tree.h" #include "tasks/bt_task.h" @@ -36,6 +37,7 @@ public: private: Ref behavior_tree; + Ref blackboard_plan; UpdateMode update_mode = UpdateMode::PHYSICS; bool active = true; Ref blackboard; @@ -45,19 +47,20 @@ private: Ref tree_instance; void _load_tree(); + void _update_blackboard_plan(); protected: static void _bind_methods(); - void _set_blackboard_data(Dictionary p_value) { blackboard->set_data(p_value.duplicate()); } - Dictionary _get_blackboard_data() const { return blackboard->get_data(); } - void _notification(int p_notification); public: void set_behavior_tree(const Ref &p_tree); Ref get_behavior_tree() const { return behavior_tree; }; + void set_blackboard_plan(const Ref &p_plan); + Ref get_blackboard_plan() const { return blackboard_plan; } + void set_update_mode(UpdateMode p_mode); UpdateMode get_update_mode() const { return update_mode; } diff --git a/bt/bt_state.cpp b/bt/bt_state.cpp index 2a18104..75b2787 100644 --- a/bt/bt_state.cpp +++ b/bt/bt_state.cpp @@ -26,6 +26,25 @@ #include #endif // LIMBOAI_GDEXTENSION +void BTState::set_behavior_tree(const Ref &p_tree) { + if (Engine::get_singleton()->is_editor_hint()) { + if (behavior_tree.is_valid() && behavior_tree->is_connected(LW_NAME(changed), callable_mp(this, &BTState::_update_blackboard_plan))) { + behavior_tree->disconnect(LW_NAME(changed), callable_mp(this, &BTState::_update_blackboard_plan)); + } + if (p_tree.is_valid()) { + p_tree->connect(LW_NAME(changed), callable_mp(this, &BTState::_update_blackboard_plan)); + } + } + behavior_tree = p_tree; +} + +void BTState::_update_blackboard_plan() { + if (get_blackboard_plan().is_null()) { + set_blackboard_plan(Ref(memnew(BlackboardPlan))); + } + get_blackboard_plan()->set_base_plan(behavior_tree.is_valid() ? behavior_tree->get_blackboard_plan() : nullptr); +} + void BTState::_setup() { ERR_FAIL_COND_MSG(behavior_tree.is_null(), "BTState: BehaviorTree is not assigned."); tree_instance = behavior_tree->instantiate(get_agent(), get_blackboard()); @@ -53,22 +72,30 @@ void BTState::_update(double p_delta) { } } -#ifdef DEBUG_ENABLED void BTState::_notification(int p_notification) { switch (p_notification) { +#ifdef DEBUG_ENABLED case NOTIFICATION_ENTER_TREE: { if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { LimboDebugger::get_singleton()->register_bt_instance(tree_instance, get_path()); } } break; +#endif // DEBUG_ENABLED case NOTIFICATION_EXIT_TREE: { +#ifdef DEBUG_ENABLED if (tree_instance.is_valid() && IS_DEBUGGER_ACTIVE()) { LimboDebugger::get_singleton()->unregister_bt_instance(tree_instance, get_path()); } +#endif // DEBUG_ENABLED + + if (Engine::get_singleton()->is_editor_hint()) { + if (behavior_tree.is_valid() && behavior_tree->is_connected(LW_NAME(changed), callable_mp(this, &BTState::_update_blackboard_plan))) { + behavior_tree->disconnect(LW_NAME(changed), callable_mp(this, &BTState::_update_blackboard_plan)); + } + } } break; } } -#endif // DEBUG_ENABLED void BTState::_bind_methods() { ClassDB::bind_method(D_METHOD("set_behavior_tree", "p_value"), &BTState::set_behavior_tree); diff --git a/bt/bt_state.h b/bt/bt_state.h index b3baa74..3ebd9ba 100644 --- a/bt/bt_state.h +++ b/bt/bt_state.h @@ -26,6 +26,8 @@ private: String success_event; String failure_event; + void _update_blackboard_plan(); + protected: static void _bind_methods(); @@ -34,7 +36,7 @@ protected: virtual void _update(double p_delta) override; public: - void set_behavior_tree(const Ref &p_value) { behavior_tree = p_value; } + void set_behavior_tree(const Ref &p_value); Ref get_behavior_tree() const { return behavior_tree; } void set_success_event(String p_success_event) { success_event = p_success_event; } @@ -45,11 +47,8 @@ public: BTState(); -#ifdef DEBUG_ENABLED protected: void _notification(int p_notification); - -#endif }; #endif // BT_STATE_H diff --git a/bt/tasks/decorators/bt_new_scope.cpp b/bt/tasks/decorators/bt_new_scope.cpp index 7bca1b3..dd462f1 100644 --- a/bt/tasks/decorators/bt_new_scope.cpp +++ b/bt/tasks/decorators/bt_new_scope.cpp @@ -15,10 +15,14 @@ void BTNewScope::initialize(Node *p_agent, const Ref &p_blackboard) ERR_FAIL_COND(p_agent == nullptr); ERR_FAIL_COND(p_blackboard == nullptr); - Ref bb = memnew(Blackboard); + Ref bb; + if (blackboard_plan.is_valid()) { + bb = blackboard_plan->create_blackboard(); + } else { + bb = Ref(memnew(Blackboard)); + } - bb->set_data(blackboard_data.duplicate()); - bb->set_parent_scope(p_blackboard); + bb->set_parent(p_blackboard); BTDecorator::initialize(p_agent, bb); } @@ -29,8 +33,8 @@ BT::Status BTNewScope::_tick(double p_delta) { } void BTNewScope::_bind_methods() { - ClassDB::bind_method(D_METHOD("_set_blackboard_data", "p_data"), &BTNewScope::_set_blackboard_data); - ClassDB::bind_method(D_METHOD("_get_blackboard_data"), &BTNewScope::_get_blackboard_data); + ClassDB::bind_method(D_METHOD("set_blackboard_plan", "p_plan"), &BTNewScope::set_blackboard_plan); + ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &BTNewScope::get_blackboard_plan); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_blackboard_data"), "_set_blackboard_data", "_get_blackboard_data"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard_plan", PROPERTY_HINT_RESOURCE_TYPE, "BlackboardPlan", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_blackboard_plan", "get_blackboard_plan"); } diff --git a/bt/tasks/decorators/bt_new_scope.h b/bt/tasks/decorators/bt_new_scope.h index 919ac89..c5644a1 100644 --- a/bt/tasks/decorators/bt_new_scope.h +++ b/bt/tasks/decorators/bt_new_scope.h @@ -14,18 +14,20 @@ #include "../bt_decorator.h" +#include "../../../blackboard/blackboard_plan.h" + class BTNewScope : public BTDecorator { GDCLASS(BTNewScope, BTDecorator); TASK_CATEGORY(Decorators); private: - Dictionary blackboard_data; + Ref blackboard_plan; protected: static void _bind_methods(); - void _set_blackboard_data(const Dictionary &p_value) { blackboard_data = p_value; } - Dictionary _get_blackboard_data() const { return blackboard_data; } + void set_blackboard_plan(const Ref &p_plan) { blackboard_plan = p_plan; } + Ref get_blackboard_plan() const { return blackboard_plan; } virtual Status _tick(double p_delta) override; diff --git a/config.py b/config.py index e5a8134..458072f 100644 --- a/config.py +++ b/config.py @@ -60,6 +60,7 @@ def get_doc_classes(): "BehaviorTree", "BehaviorTreeView", "Blackboard", + "BlackboardPlan", "BT", "BTAction", "BTAlwaysFail", diff --git a/demo/demo/ai/trees/waypoints.tres b/demo/demo/ai/trees/waypoints.tres index f94049a..e48e485 100644 --- a/demo/demo/ai/trees/waypoints.tres +++ b/demo/demo/ai/trees/waypoints.tres @@ -1,7 +1,14 @@ -[gd_resource type="BehaviorTree" load_steps=10 format=3 uid="uid://cjkqi41oagagd"] +[gd_resource type="BehaviorTree" load_steps=11 format=3 uid="uid://cjkqi41oagagd"] [ext_resource type="Script" path="res://demo/ai/tasks/arrive_pos.gd" id="1_rhs33"] +[sub_resource type="BlackboardPlan" id="BlackboardPlan_2hcqi"] +var/speed/name = "speed" +var/speed/type = 3 +var/speed/value = 200.0 +var/speed/hint = 1 +var/speed/hint_string = "10,1000,10" + [sub_resource type="BTAction" id="BTAction_3xal7"] script = ExtResource("1_rhs33") target_position_var = "wp" @@ -36,4 +43,5 @@ duration = 3.0 children = [SubResource("BTCooldown_gen0l")] [resource] +blackboard_plan = SubResource("BlackboardPlan_2hcqi") root_task = SubResource("BTSelector_5dclr") diff --git a/demo/demo/examples/waypoints/example_waypoints.gd b/demo/demo/examples/waypoints/example_waypoints.gd index 19d153d..c9ddbe8 100644 --- a/demo/demo/examples/waypoints/example_waypoints.gd +++ b/demo/demo/examples/waypoints/example_waypoints.gd @@ -24,5 +24,3 @@ func _ready() -> void: waypoints.reverse() for wp in waypoints: agent2.add_waypoint(wp.global_position) - - diff --git a/demo/demo/examples/waypoints/patrolling_agent.tscn b/demo/demo/examples/waypoints/patrolling_agent.tscn index e058444..a91dc6b 100644 --- a/demo/demo/examples/waypoints/patrolling_agent.tscn +++ b/demo/demo/examples/waypoints/patrolling_agent.tscn @@ -1,9 +1,16 @@ -[gd_scene load_steps=7 format=3 uid="uid://c26b8c8dndtop"] +[gd_scene load_steps=8 format=3 uid="uid://c26b8c8dndtop"] [ext_resource type="Script" path="res://demo/examples/waypoints/patrolling_agent.gd" id="1_5wwhb"] [ext_resource type="BehaviorTree" uid="uid://cjkqi41oagagd" path="res://demo/ai/trees/waypoints.tres" id="2_66y4v"] [ext_resource type="Texture2D" uid="uid://d0mht3ntak7e5" path="res://demo/godot.png" id="3_64ge2"] +[sub_resource type="BlackboardPlan" id="BlackboardPlan_b86q8"] +var/speed/name = "speed" +var/speed/type = 3 +var/speed/value = 300.0 +var/speed/hint = 1 +var/speed/hint_string = "10,1000,10" + [sub_resource type="Animation" id="Animation_5id00"] length = 0.001 tracks/0/type = "value" @@ -46,9 +53,7 @@ script = ExtResource("1_5wwhb") [node name="BTPlayer" type="BTPlayer" parent="."] behavior_tree = ExtResource("2_66y4v") -_blackboard_data = { -"speed": 200.0 -} +blackboard_plan = SubResource("BlackboardPlan_b86q8") [node name="Sprite2D" type="Sprite2D" parent="."] texture = ExtResource("3_64ge2") diff --git a/doc/source/classes/class_bbfloat32array.rst b/doc/source/classes/class_bbfloat32array.rst new file mode 100644 index 0000000..8add4a9 --- /dev/null +++ b/doc/source/classes/class_bbfloat32array.rst @@ -0,0 +1,23 @@ +:github_url: hide + +.. DO NOT EDIT THIS FILE!!! +.. Generated automatically from Godot engine sources. +.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py. +.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BBFloat32Array.xml. + +.. _class_BBFloat32Array: + +BBFloat32Array +============== + +**Inherits:** :ref:`BBParam` + +PackedFloat32Array-type parameter for :ref:`BehaviorTree` tasks. See :ref:`BBParam`. + +.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` +.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` +.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` +.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` +.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` +.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` +.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` diff --git a/doc/source/classes/class_bbfloat64array.rst b/doc/source/classes/class_bbfloat64array.rst new file mode 100644 index 0000000..3db474f --- /dev/null +++ b/doc/source/classes/class_bbfloat64array.rst @@ -0,0 +1,23 @@ +:github_url: hide + +.. DO NOT EDIT THIS FILE!!! +.. Generated automatically from Godot engine sources. +.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py. +.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BBFloat64Array.xml. + +.. _class_BBFloat64Array: + +BBFloat64Array +============== + +**Inherits:** :ref:`BBParam` + +PackedFloat64Array-type parameter for :ref:`BehaviorTree` tasks. See :ref:`BBParam`. + +.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` +.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` +.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` +.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` +.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` +.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` +.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` diff --git a/doc/source/classes/class_bbint32array.rst b/doc/source/classes/class_bbint32array.rst new file mode 100644 index 0000000..d4fcaf7 --- /dev/null +++ b/doc/source/classes/class_bbint32array.rst @@ -0,0 +1,23 @@ +:github_url: hide + +.. DO NOT EDIT THIS FILE!!! +.. Generated automatically from Godot engine sources. +.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py. +.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BBInt32Array.xml. + +.. _class_BBInt32Array: + +BBInt32Array +============ + +**Inherits:** :ref:`BBParam` + +PackedInt32Array-type parameter for :ref:`BehaviorTree` tasks. See :ref:`BBParam`. + +.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` +.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` +.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` +.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` +.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` +.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` +.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` diff --git a/doc/source/classes/class_bbint64array.rst b/doc/source/classes/class_bbint64array.rst new file mode 100644 index 0000000..51ebf2a --- /dev/null +++ b/doc/source/classes/class_bbint64array.rst @@ -0,0 +1,23 @@ +:github_url: hide + +.. DO NOT EDIT THIS FILE!!! +.. Generated automatically from Godot engine sources. +.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py. +.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BBInt64Array.xml. + +.. _class_BBInt64Array: + +BBInt64Array +============ + +**Inherits:** :ref:`BBParam` + +PackedInt64Array-type parameter for :ref:`BehaviorTree` tasks. See :ref:`BBParam`. + +.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` +.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` +.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` +.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` +.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` +.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` +.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` diff --git a/doc/source/classes/class_bbparam.rst b/doc/source/classes/class_bbparam.rst index 32c3934..04ba624 100644 --- a/doc/source/classes/class_bbparam.rst +++ b/doc/source/classes/class_bbparam.rst @@ -12,7 +12,7 @@ BBParam **Inherits:** -**Inherited By:** :ref:`BBAabb`, :ref:`BBArray`, :ref:`BBBasis`, :ref:`BBBool`, :ref:`BBByteArray`, :ref:`BBColor`, :ref:`BBColorArray`, :ref:`BBDictionary`, :ref:`BBFloat`, :ref:`BBFloatArray`, :ref:`BBInt`, :ref:`BBIntArray`, :ref:`BBNode`, :ref:`BBPlane`, :ref:`BBQuaternion`, :ref:`BBRect2`, :ref:`BBRect2i`, :ref:`BBString`, :ref:`BBStringArray`, :ref:`BBStringName`, :ref:`BBTransform2D`, :ref:`BBTransform3D`, :ref:`BBVariant`, :ref:`BBVector2`, :ref:`BBVector2Array`, :ref:`BBVector2i`, :ref:`BBVector3`, :ref:`BBVector3Array`, :ref:`BBVector3i`, :ref:`BBVector4`, :ref:`BBVector4i` +**Inherited By:** :ref:`BBAabb`, :ref:`BBArray`, :ref:`BBBasis`, :ref:`BBBool`, :ref:`BBByteArray`, :ref:`BBColor`, :ref:`BBColorArray`, :ref:`BBDictionary`, :ref:`BBFloat`, :ref:`BBFloat32Array`, :ref:`BBFloat64Array`, :ref:`BBInt`, :ref:`BBInt32Array`, :ref:`BBInt64Array`, :ref:`BBNode`, :ref:`BBPlane`, :ref:`BBProjection`, :ref:`BBQuaternion`, :ref:`BBRect2`, :ref:`BBRect2i`, :ref:`BBString`, :ref:`BBStringArray`, :ref:`BBStringName`, :ref:`BBTransform2D`, :ref:`BBTransform3D`, :ref:`BBVariant`, :ref:`BBVector2`, :ref:`BBVector2Array`, :ref:`BBVector2i`, :ref:`BBVector3`, :ref:`BBVector3Array`, :ref:`BBVector3i`, :ref:`BBVector4`, :ref:`BBVector4i` A base class for LimboAI typed parameters. diff --git a/doc/source/classes/class_bbprojection.rst b/doc/source/classes/class_bbprojection.rst new file mode 100644 index 0000000..7a09149 --- /dev/null +++ b/doc/source/classes/class_bbprojection.rst @@ -0,0 +1,23 @@ +:github_url: hide + +.. DO NOT EDIT THIS FILE!!! +.. Generated automatically from Godot engine sources. +.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py. +.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BBProjection.xml. + +.. _class_BBProjection: + +BBProjection +============ + +**Inherits:** :ref:`BBParam` + +Projection-type parameter for :ref:`BehaviorTree` tasks. See :ref:`BBParam`. + +.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` +.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` +.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` +.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` +.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` +.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` +.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` diff --git a/doc/source/classes/class_behaviortree.rst b/doc/source/classes/class_behaviortree.rst index e6fc70f..a1ba8e9 100644 --- a/doc/source/classes/class_behaviortree.rst +++ b/doc/source/classes/class_behaviortree.rst @@ -41,9 +41,11 @@ Properties .. table:: :widths: auto - +--------+-------------------------------------------------------------+--------+ - | String | :ref:`description` | ``""`` | - +--------+-------------------------------------------------------------+--------+ + +---------------------------------------------+---------------------------------------------------------------------+--------+ + | :ref:`BlackboardPlan` | :ref:`blackboard_plan` | | + +---------------------------------------------+---------------------------------------------------------------------+--------+ + | String | :ref:`description` | ``""`` | + +---------------------------------------------+---------------------------------------------------------------------+--------+ .. rst-class:: classref-reftable-group @@ -74,6 +76,23 @@ Methods Property Descriptions --------------------- +.. _class_BehaviorTree_property_blackboard_plan: + +.. rst-class:: classref-property + +:ref:`BlackboardPlan` **blackboard_plan** + +.. rst-class:: classref-property-setget + +- void **set_blackboard_plan** **(** :ref:`BlackboardPlan` value **)** +- :ref:`BlackboardPlan` **get_blackboard_plan** **(** **)** + +Stores and manages variables that will be used in constructing new :ref:`Blackboard` instances. + +.. rst-class:: classref-item-separator + +---- + .. _class_BehaviorTree_property_description: .. rst-class:: classref-property diff --git a/doc/source/classes/class_blackboard.rst b/doc/source/classes/class_blackboard.rst index 7e4672d..c21130b 100644 --- a/doc/source/classes/class_blackboard.rst +++ b/doc/source/classes/class_blackboard.rst @@ -34,23 +34,19 @@ Methods :widths: auto +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ - | void | :ref:`erase_var` **(** Variant p_key **)** | - +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ - | Dictionary | :ref:`get_data` **(** **)** |const| | + | void | :ref:`erase_var` **(** String p_name **)** | +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Blackboard` | :ref:`get_parent_scope` **(** **)** |const| | +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ - | Variant | :ref:`get_var` **(** Variant p_key, Variant p_default=null **)** |const| | + | Variant | :ref:`get_var` **(** String p_name, Variant p_default=null **)** |const| | +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ - | bool | :ref:`has_var` **(** Variant p_key **)** |const| | + | bool | :ref:`has_var` **(** String p_name **)** |const| | +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ | void | :ref:`prefetch_nodepath_vars` **(** Node p_node **)** | +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ - | void | :ref:`set_data` **(** Dictionary p_data **)** | - +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ | void | :ref:`set_parent_scope` **(** :ref:`Blackboard` p_blackboard **)** | +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ - | void | :ref:`set_var` **(** Variant p_key, Variant p_value **)** | + | void | :ref:`set_var` **(** String p_name, Variant p_value **)** | +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ | :ref:`Blackboard` | :ref:`top` **(** **)** |const| | +-------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+ @@ -68,7 +64,7 @@ Method Descriptions .. rst-class:: classref-method -void **erase_var** **(** Variant p_key **)** +void **erase_var** **(** String p_name **)** Removes a variable by its name. @@ -76,18 +72,6 @@ Removes a variable by its name. ---- -.. _class_Blackboard_method_get_data: - -.. rst-class:: classref-method - -Dictionary **get_data** **(** **)** |const| - -Returns Blackboard data as a ``Dictionary``. - -.. rst-class:: classref-item-separator - ----- - .. _class_Blackboard_method_get_parent_scope: .. rst-class:: classref-method @@ -104,7 +88,7 @@ Returns a Blackboard that serves as the parent scope for this instance. .. rst-class:: classref-method -Variant **get_var** **(** Variant p_key, Variant p_default=null **)** |const| +Variant **get_var** **(** String p_name, Variant p_default=null **)** |const| Returns variable value. @@ -116,7 +100,7 @@ Returns variable value. .. rst-class:: classref-method -bool **has_var** **(** Variant p_key **)** |const| +bool **has_var** **(** String p_name **)** |const| Returns ``true`` if the Blackboard contains the ``p_key`` variable, including the parent scopes. @@ -136,18 +120,6 @@ If ``true``, any ``NodePath`` variables in the **Blackboard** are replaced with ---- -.. _class_Blackboard_method_set_data: - -.. rst-class:: classref-method - -void **set_data** **(** Dictionary p_data **)** - -Overwrites Blackboard data, replacing any previously stored variables within current scope. Use with caution. - -.. rst-class:: classref-item-separator - ----- - .. _class_Blackboard_method_set_parent_scope: .. rst-class:: classref-method @@ -164,7 +136,7 @@ Assigns the parent scope. If a value isn't in the current Blackboard scope, it w .. rst-class:: classref-method -void **set_var** **(** Variant p_key, Variant p_value **)** +void **set_var** **(** String p_name, Variant p_value **)** Assigns a value to a Blackboard variable. diff --git a/doc/source/classes/class_blackboardplan.rst b/doc/source/classes/class_blackboardplan.rst new file mode 100644 index 0000000..251fab3 --- /dev/null +++ b/doc/source/classes/class_blackboardplan.rst @@ -0,0 +1,23 @@ +:github_url: hide + +.. DO NOT EDIT THIS FILE!!! +.. Generated automatically from Godot engine sources. +.. Generator: https://github.com/godotengine/godot/tree/4.2/doc/tools/make_rst.py. +.. XML source: https://github.com/godotengine/godot/tree/4.2/modules/limboai/doc_classes/BlackboardPlan.xml. + +.. _class_BlackboardPlan: + +BlackboardPlan +============== + +**Inherits:** + +Stores and manages variables that will be used in constructing new :ref:`Blackboard` instances. + +.. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` +.. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` +.. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)` +.. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)` +.. |static| replace:: :abbr:`static (This method doesn't need an instance to be called, so it can be called directly using the class name.)` +.. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)` +.. |bitfield| replace:: :abbr:`BitField (This value is an integer composed as a bitmask of the following flags.)` diff --git a/doc/source/classes/class_btnewscope.rst b/doc/source/classes/class_btnewscope.rst index 8fe6a9a..a1f84f6 100644 --- a/doc/source/classes/class_btnewscope.rst +++ b/doc/source/classes/class_btnewscope.rst @@ -33,9 +33,9 @@ Properties .. table:: :widths: auto - +------------+---------------------------------------------------------------------+--------+ - | Dictionary | :ref:`_blackboard_data` | ``{}`` | - +------------+---------------------------------------------------------------------+--------+ + +---------------------------------------------+-------------------------------------------------------------------+ + | :ref:`BlackboardPlan` | :ref:`blackboard_plan` | + +---------------------------------------------+-------------------------------------------------------------------+ .. rst-class:: classref-section-separator @@ -46,13 +46,18 @@ Properties Property Descriptions --------------------- -.. _class_BTNewScope_property__blackboard_data: +.. _class_BTNewScope_property_blackboard_plan: .. rst-class:: classref-property -Dictionary **_blackboard_data** = ``{}`` +:ref:`BlackboardPlan` **blackboard_plan** -Data that is used to populate a new scope of the :ref:`Blackboard`. +.. rst-class:: classref-property-setget + +- void **set_blackboard_plan** **(** :ref:`BlackboardPlan` value **)** +- :ref:`BlackboardPlan` **get_blackboard_plan** **(** **)** + +Stores and manages variables that will be used in constructing new :ref:`Blackboard` instances. .. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)` .. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)` diff --git a/doc/source/classes/class_btplayer.rst b/doc/source/classes/class_btplayer.rst index a8eea4c..4bb23ea 100644 --- a/doc/source/classes/class_btplayer.rst +++ b/doc/source/classes/class_btplayer.rst @@ -38,6 +38,8 @@ Properties +---------------------------------------------+-------------------------------------------------------------------------------+-----------+ | :ref:`Blackboard` | :ref:`blackboard` | | +---------------------------------------------+-------------------------------------------------------------------------------+-----------+ + | :ref:`BlackboardPlan` | :ref:`blackboard_plan` | | + +---------------------------------------------+-------------------------------------------------------------------------------+-----------+ | bool | :ref:`monitor_performance` | ``false`` | +---------------------------------------------+-------------------------------------------------------------------------------+-----------+ | bool | :ref:`prefetch_nodepath_vars` | ``true`` | @@ -193,6 +195,23 @@ Holds data shared by the behavior tree tasks. See :ref:`Blackboard` **blackboard_plan** + +.. rst-class:: classref-property-setget + +- void **set_blackboard_plan** **(** :ref:`BlackboardPlan` value **)** +- :ref:`BlackboardPlan` **get_blackboard_plan** **(** **)** + +Stores and manages variables that will be used in constructing new :ref:`Blackboard` instances. + +.. rst-class:: classref-item-separator + +---- + .. _class_BTPlayer_property_monitor_performance: .. rst-class:: classref-property diff --git a/doc/source/classes/class_limbostate.rst b/doc/source/classes/class_limbostate.rst index d289989..4f10e24 100644 --- a/doc/source/classes/class_limbostate.rst +++ b/doc/source/classes/class_limbostate.rst @@ -35,13 +35,15 @@ Properties .. table:: :widths: auto - +-------------------------------------+-----------------------------------------------------------------+ - | String | :ref:`EVENT_FINISHED` | - +-------------------------------------+-----------------------------------------------------------------+ - | Node | :ref:`agent` | - +-------------------------------------+-----------------------------------------------------------------+ - | :ref:`Blackboard` | :ref:`blackboard` | - +-------------------------------------+-----------------------------------------------------------------+ + +---------------------------------------------+-------------------------------------------------------------------+ + | String | :ref:`EVENT_FINISHED` | + +---------------------------------------------+-------------------------------------------------------------------+ + | Node | :ref:`agent` | + +---------------------------------------------+-------------------------------------------------------------------+ + | :ref:`Blackboard` | :ref:`blackboard` | + +---------------------------------------------+-------------------------------------------------------------------+ + | :ref:`BlackboardPlan` | :ref:`blackboard_plan` | + +---------------------------------------------+-------------------------------------------------------------------+ .. rst-class:: classref-reftable-group @@ -188,6 +190,23 @@ An agent associated with the state, assigned during initialization. A key/value data store shared by states within the state machine to which this state belongs. +.. rst-class:: classref-item-separator + +---- + +.. _class_LimboState_property_blackboard_plan: + +.. rst-class:: classref-property + +:ref:`BlackboardPlan` **blackboard_plan** + +.. rst-class:: classref-property-setget + +- void **set_blackboard_plan** **(** :ref:`BlackboardPlan` value **)** +- :ref:`BlackboardPlan` **get_blackboard_plan** **(** **)** + +Stores and manages variables that will be used in constructing new :ref:`Blackboard` instances. + .. rst-class:: classref-section-separator ---- diff --git a/doc_classes/BTNewScope.xml b/doc_classes/BTNewScope.xml index 252fa48..ddb1965 100644 --- a/doc_classes/BTNewScope.xml +++ b/doc_classes/BTNewScope.xml @@ -10,8 +10,8 @@ - - Data that is used to populate a new scope of the [Blackboard]. + + Stores and manages variables that will be used in constructing new [Blackboard] instances. diff --git a/doc_classes/BTPlayer.xml b/doc_classes/BTPlayer.xml index 29cbdbf..a284468 100644 --- a/doc_classes/BTPlayer.xml +++ b/doc_classes/BTPlayer.xml @@ -40,6 +40,9 @@ Holds data shared by the behavior tree tasks. See [Blackboard]. + + Stores and manages variables that will be used in constructing new [Blackboard] instances. + If [code]true[/code], adds a performance monitor to "Debugger->Monitors" for each instance of this [BTPlayer] node. diff --git a/doc_classes/BehaviorTree.xml b/doc_classes/BehaviorTree.xml index 958c4c5..6f1efb3 100644 --- a/doc_classes/BehaviorTree.xml +++ b/doc_classes/BehaviorTree.xml @@ -51,6 +51,9 @@ + + Stores and manages variables that will be used in constructing new [Blackboard] instances. + User-provided description of the BehaviorTree. diff --git a/doc_classes/Blackboard.xml b/doc_classes/Blackboard.xml index 6fbfbbf..91f4492 100644 --- a/doc_classes/Blackboard.xml +++ b/doc_classes/Blackboard.xml @@ -13,17 +13,11 @@ - + Removes a variable by its name. - - - - Returns Blackboard data as a [Dictionary]. - - @@ -32,7 +26,7 @@ - + Returns variable value. @@ -40,7 +34,7 @@ - + Returns [code]true[/code] if the Blackboard contains the [param p_key] variable, including the parent scopes. @@ -52,13 +46,6 @@ If [code]true[/code], any [NodePath] variables in the [Blackboard] are replaced with [Node] references when the tree is instantiated. References are retrieved by calling [method Node.get_node] on the agent instance. - - - - - Overwrites Blackboard data, replacing any previously stored variables within current scope. Use with caution. - - @@ -68,7 +55,7 @@ - + Assigns a value to a Blackboard variable. diff --git a/doc_classes/BlackboardPlan.xml b/doc_classes/BlackboardPlan.xml new file mode 100644 index 0000000..6a7a48c --- /dev/null +++ b/doc_classes/BlackboardPlan.xml @@ -0,0 +1,10 @@ + + + + Stores and manages variables that will be used in constructing new [Blackboard] instances. + + + + + + diff --git a/doc_classes/LimboState.xml b/doc_classes/LimboState.xml index f3a3c79..508bf41 100644 --- a/doc_classes/LimboState.xml +++ b/doc_classes/LimboState.xml @@ -117,6 +117,9 @@ A key/value data store shared by states within the state machine to which this state belongs. + + Stores and manages variables that will be used in constructing new [Blackboard] instances. + diff --git a/editor/blackboard_plan_editor.cpp b/editor/blackboard_plan_editor.cpp new file mode 100644 index 0000000..bde3d01 --- /dev/null +++ b/editor/blackboard_plan_editor.cpp @@ -0,0 +1,449 @@ +/** + * blackboard_plan_editor.cpp + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#include "blackboard_plan_editor.h" + +#include "../util/limbo_compat.h" +#include "../util/limbo_string_names.h" +#include "../util/limbo_utility.h" + +#ifdef LIMBOAI_MODULE +#include "editor/editor_interface.h" +#include "editor/editor_scale.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/panel_container.h" +#include "scene/resources/style_box_flat.h" +#endif // LIMBOAI_MODULE + +#ifdef LIMBOAI_GDEXTENSION +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace godot; +#endif // LIMBOAI_GDEXTENSION + +void BlackboardPlanEditor::_add_var() { + ERR_FAIL_NULL(plan); + + int suffix = 1; + String name = "var" + itos(suffix); + while (plan->has_var(name)) { + suffix += 1; + name = "var" + itos(suffix); + } + + BBVariable var(Variant::Type::FLOAT); + plan->add_var(name, var); + _refresh(); +} + +void BlackboardPlanEditor::_trash_var(int p_index) { + ERR_FAIL_NULL(plan); + String var_name = plan->get_var_by_index(p_index).first; + plan->remove_var(var_name); + _refresh(); +} + +void BlackboardPlanEditor::_rename_var(const String &p_new_name, int p_index) { + ERR_FAIL_NULL(plan); + plan->rename_var(plan->get_var_by_index(p_index).first, p_new_name); +} + +void BlackboardPlanEditor::_change_var_type(Variant::Type p_new_type, int p_index) { + ERR_FAIL_NULL(plan); + plan->get_var_by_index(p_index).second.set_type(p_new_type); + plan->notify_property_list_changed(); + _refresh(); +} + +void BlackboardPlanEditor::_change_var_hint(PropertyHint p_new_hint, int p_index) { + ERR_FAIL_NULL(plan); + plan->get_var_by_index(p_index).second.set_hint(p_new_hint); + plan->notify_property_list_changed(); + _refresh(); +} + +void BlackboardPlanEditor::_change_var_hint_string(const String &p_new_hint_string, int p_index) { + ERR_FAIL_NULL(plan); + plan->get_var_by_index(p_index).second.set_hint_string(p_new_hint_string); + plan->notify_property_list_changed(); +} + +void BlackboardPlanEditor::edit_plan(const Ref &p_plan) { + plan = p_plan; + _refresh(); +} + +void BlackboardPlanEditor::_show_button_popup(Button *p_button, PopupMenu *p_popup, int p_index) { + ERR_FAIL_NULL(p_button); + ERR_FAIL_NULL(p_popup); + + Transform2D xform = p_button->get_screen_transform(); + Rect2 rect(xform.get_origin(), xform.get_scale() * p_button->get_size()); + rect.position.y += rect.size.height; + rect.size.height = 0; + p_popup->set_size(rect.size); + p_popup->set_position(rect.position); + + last_index = p_index; + p_popup->popup(); +} + +void BlackboardPlanEditor::_type_chosen(int id) { + _change_var_type(Variant::Type(id), last_index); +} + +void BlackboardPlanEditor::_hint_chosen(int id) { + _change_var_hint(PropertyHint(id), last_index); +} + +void BlackboardPlanEditor::_drag_button_down(Control *p_row) { + drag_index = p_row->get_index(); + drag_start = drag_index; + drag_mouse_y_delta = 0.0; + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED); +} + +void BlackboardPlanEditor::_drag_button_up() { + Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); + plan->move_var(drag_start, drag_index); + drag_index = -1; + drag_start = -1; + _refresh(); +} + +void BlackboardPlanEditor::_drag_button_gui_input(const Ref &p_event) { + if (drag_index < 0) { + return; + } + + Ref mm = p_event; + if (mm.is_null()) { + return; + } + + drag_mouse_y_delta += mm->get_relative().y; + + if ((drag_index == 0 && drag_mouse_y_delta < 0.0) || (drag_index == (plan->get_var_count() - 1) && drag_mouse_y_delta > 0.0)) { + drag_mouse_y_delta = 0.0; + return; + } + + float required_distance = 30.0f * EDSCALE; + if (ABS(drag_mouse_y_delta) > required_distance) { + int drag_dir = drag_mouse_y_delta > 0.0f ? 1 : -1; + drag_mouse_y_delta -= required_distance * drag_dir; + + Control *row = Object::cast_to(rows_vbox->get_child(drag_index)); + Control *other_row = Object::cast_to(rows_vbox->get_child(drag_index + drag_dir)); + ERR_FAIL_NULL(row); + ERR_FAIL_NULL(other_row); + rows_vbox->move_child(row, drag_index + drag_dir); + ADD_STYLEBOX_OVERRIDE(row, LW_NAME(panel), row->get_index() % 2 ? theme_cache.odd_style : theme_cache.even_style); + ADD_STYLEBOX_OVERRIDE(other_row, LW_NAME(panel), other_row->get_index() % 2 ? theme_cache.odd_style : theme_cache.even_style); + + drag_index += drag_dir; + } +} + +void BlackboardPlanEditor::_visibility_changed() { + if (!is_visible() && plan.is_valid()) { + plan->notify_property_list_changed(); + } +} + +void BlackboardPlanEditor::_refresh() { + for (int i = 0; i < rows_vbox->get_child_count(); i++) { + Control *child = Object::cast_to(rows_vbox->get_child(i)); + ERR_FAIL_NULL(child); + child->hide(); + child->queue_free(); + } + + // TODO: Name validation + + PackedStringArray names = plan->list_vars(); + int idx = 0; + for (const String &var_name : names) { + BBVariable var = plan->get_var(var_name); + + PanelContainer *row_panel = memnew(PanelContainer); + rows_vbox->add_child(row_panel); + ADD_STYLEBOX_OVERRIDE(row_panel, LW_NAME(panel), idx % 2 ? theme_cache.odd_style : theme_cache.even_style); + row_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + HBoxContainer *props_hbox = memnew(HBoxContainer); + row_panel->add_child(props_hbox); + props_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + Button *drag_button = memnew(Button); + props_hbox->add_child(drag_button); + drag_button->set_custom_minimum_size(Size2(28.0, 28.0) * EDSCALE); + BUTTON_SET_ICON(drag_button, theme_cache.grab_icon); + drag_button->connect(LW_NAME(gui_input), callable_mp(this, &BlackboardPlanEditor::_drag_button_gui_input)); + drag_button->connect(LW_NAME(button_down), callable_mp(this, &BlackboardPlanEditor::_drag_button_down).bind(row_panel)); + drag_button->connect(LW_NAME(button_up), callable_mp(this, &BlackboardPlanEditor::_drag_button_up)); + + LineEdit *name_edit = memnew(LineEdit); + props_hbox->add_child(name_edit); + name_edit->set_text(var_name); + name_edit->set_placeholder(TTR("Variable name")); + name_edit->set_flat(true); + name_edit->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE); + name_edit->connect(LW_NAME(text_changed), callable_mp(this, &BlackboardPlanEditor::_rename_var).bind(idx)); + name_edit->connect(LW_NAME(text_submitted), callable_mp(this, &BlackboardPlanEditor::_refresh).unbind(1)); + + Button *type_choice = memnew(Button); + props_hbox->add_child(type_choice); + type_choice->set_custom_minimum_size(Size2(170, 0.0) * EDSCALE); + type_choice->set_text(Variant::get_type_name(var.get_type())); + type_choice->set_tooltip_text(Variant::get_type_name(var.get_type())); + BUTTON_SET_ICON(type_choice, get_theme_icon(Variant::get_type_name(var.get_type()), LW_NAME(EditorIcons))); + type_choice->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + type_choice->set_flat(true); + type_choice->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + type_choice->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_show_button_popup).bind(type_choice, type_menu, idx)); + + Button *hint_choice = memnew(Button); + props_hbox->add_child(hint_choice); + hint_choice->set_custom_minimum_size(Size2(150.0, 0.0) * EDSCALE); + hint_choice->set_text(LimboUtility::get_singleton()->get_property_hint_text(var.get_hint())); + hint_choice->set_tooltip_text(LimboUtility::get_singleton()->get_property_hint_text(var.get_hint())); + hint_choice->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + hint_choice->set_flat(true); + hint_choice->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + hint_choice->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_show_button_popup).bind(hint_choice, hint_menu, idx)); + + LineEdit *hint_string_edit = memnew(LineEdit); + props_hbox->add_child(hint_string_edit); + hint_string_edit->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE); + hint_string_edit->set_text(var.get_hint_string()); + hint_string_edit->set_placeholder(TTR("Hint string")); + hint_string_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hint_string_edit->set_flat(true); + hint_string_edit->connect(LW_NAME(text_changed), callable_mp(this, &BlackboardPlanEditor::_change_var_hint_string).bind(idx)); + hint_string_edit->connect(LW_NAME(text_submitted), callable_mp(this, &BlackboardPlanEditor::_refresh).unbind(1)); + + Button *trash_button = memnew(Button); + props_hbox->add_child(trash_button); + trash_button->set_custom_minimum_size(Size2(24.0, 0.0) * EDSCALE); + BUTTON_SET_ICON(trash_button, theme_cache.trash_icon); + trash_button->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_trash_var).bind(idx)); + + idx += 1; + } +} + +void BlackboardPlanEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + theme_cache.trash_icon = get_theme_icon(LW_NAME(Remove), LW_NAME(EditorIcons)); + theme_cache.grab_icon = get_theme_icon(LW_NAME(TripleBar), LW_NAME(EditorIcons)); + + BUTTON_SET_ICON(add_var_tool, get_theme_icon(LW_NAME(Add), LW_NAME(EditorIcons))); + + type_menu->clear(); + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + if (i == Variant::RID || i == Variant::CALLABLE || i == Variant::SIGNAL) { + continue; + } + String type = Variant::get_type_name(Variant::Type(i)); + type_menu->add_icon_item(get_theme_icon(type, LW_NAME(EditorIcons)), type, i); + } + + ADD_STYLEBOX_OVERRIDE(scroll_container, LW_NAME(panel), get_theme_stylebox(LW_NAME(panel), LW_NAME(Tree))); + + Color bg_color = get_theme_color(LW_NAME(dark_color_2), LW_NAME(Editor)); + theme_cache.odd_style->set_bg_color(bg_color.darkened(-0.05)); + theme_cache.even_style->set_bg_color(bg_color.darkened(0.05)); + theme_cache.header_style->set_bg_color(bg_color.darkened(-0.2)); + + ADD_STYLEBOX_OVERRIDE(header_row, LW_NAME(panel), theme_cache.header_style); + } break; + case NOTIFICATION_READY: { + add_var_tool->connect(LW_NAME(pressed), callable_mp(this, &BlackboardPlanEditor::_add_var)); + connect(LW_NAME(visibility_changed), callable_mp(this, &BlackboardPlanEditor::_visibility_changed)); + type_menu->connect(LW_NAME(id_pressed), callable_mp(this, &BlackboardPlanEditor::_type_chosen)); + hint_menu->connect(LW_NAME(id_pressed), callable_mp(this, &BlackboardPlanEditor::_hint_chosen)); + } break; + } +} + +BlackboardPlanEditor::BlackboardPlanEditor() { + set_title(TTR("Manage Blackboard Plan")); + + VBoxContainer *vbox = memnew(VBoxContainer); + vbox->add_theme_constant_override(LW_NAME(separation), 8 * EDSCALE); + add_child(vbox); + + HBoxContainer *toolbar = memnew(HBoxContainer); + vbox->add_child(toolbar); + + add_var_tool = memnew(Button); + toolbar->add_child(add_var_tool); + add_var_tool->set_focus_mode(Control::FOCUS_NONE); + add_var_tool->set_text(TTR("Add variable")); + + { + // * Header + header_row = memnew(PanelContainer); + vbox->add_child(header_row); + header_row->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + HBoxContainer *labels_hbox = memnew(HBoxContainer); + header_row->add_child(labels_hbox); + labels_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + Control *offset = memnew(Control); + labels_hbox->add_child(offset); + offset->set_custom_minimum_size(Size2(2.0, 0.0) * EDSCALE); + + Label *drag_header = memnew(Label); + labels_hbox->add_child(drag_header); + drag_header->set_custom_minimum_size(Size2(28.0, 28.0) * EDSCALE); + + Label *name_header = memnew(Label); + labels_hbox->add_child(name_header); + name_header->set_text(TTR("Name")); + name_header->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE); + name_header->set_theme_type_variation(LW_NAME(HeaderSmall)); + + Label *type_header = memnew(Label); + labels_hbox->add_child(type_header); + type_header->set_text(TTR("Type")); + type_header->set_custom_minimum_size(Size2(170, 0.0) * EDSCALE); + type_header->set_theme_type_variation(LW_NAME(HeaderSmall)); + + Label *hint_header = memnew(Label); + labels_hbox->add_child(hint_header); + hint_header->set_text(TTR("Hint")); + hint_header->set_custom_minimum_size(Size2(150.0, 0.0) * EDSCALE); + hint_header->set_theme_type_variation(LW_NAME(HeaderSmall)); + + Label *hint_string_header = memnew(Label); + labels_hbox->add_child(hint_string_header); + hint_string_header->set_text(TTR("Hint string")); + hint_string_header->set_custom_minimum_size(Size2(300.0, 0.0) * EDSCALE); + hint_string_header->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hint_string_header->set_theme_type_variation(LW_NAME(HeaderSmall)); + } + + scroll_container = memnew(ScrollContainer); + vbox->add_child(scroll_container); + scroll_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + scroll_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + scroll_container->set_custom_minimum_size(Size2(0.0, 600.0) * EDSCALE); + + rows_vbox = memnew(VBoxContainer); + scroll_container->add_child(rows_vbox); + rows_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + rows_vbox->add_theme_constant_override(LW_NAME(separation), 0); + + type_menu = memnew(PopupMenu); + add_child(type_menu); + + hint_menu = memnew(PopupMenu); + add_child(hint_menu); + for (int i = 0; i < PropertyHint::PROPERTY_HINT_MAX; i++) { + hint_menu->add_item(LimboUtility::get_singleton()->get_property_hint_text(PropertyHint(i)), i); + } + + theme_cache.odd_style.instantiate(); + theme_cache.even_style.instantiate(); + theme_cache.header_style.instantiate(); +} + +// ***** EditorInspectorPluginBBPlan ***** + +void EditorInspectorPluginBBPlan::_edit_plan(const Ref &p_plan) { + ERR_FAIL_NULL(p_plan); + plan_editor->edit_plan(p_plan); + plan_editor->popup_centered(); +} + +void EditorInspectorPluginBBPlan::_open_base_plan(const Ref &p_plan) { + ERR_FAIL_NULL(p_plan); + ERR_FAIL_NULL(p_plan->get_base_plan()); + EditorInterface::get_singleton()->call_deferred("edit_resource", p_plan->get_base_plan()); +} + +#ifdef LIMBOAI_MODULE +bool EditorInspectorPluginBBPlan::can_handle(Object *p_object) { +#elif LIMBOAI_GDEXTENSION +bool EditorInspectorPluginBBPlan::_can_handle(Object *p_object) const { +#endif + Ref plan = Object::cast_to(p_object); + if (plan.is_valid()) { + plan->sync_with_base_plan(); + } + return plan.is_valid(); +} + +#ifdef LIMBOAI_MODULE +void EditorInspectorPluginBBPlan::parse_begin(Object *p_object) { +#elif LIMBOAI_GDEXTENSION +void EditorInspectorPluginBBPlan::_parse_begin(Object *p_object) { +#endif + Ref plan = Object::cast_to(p_object); + ERR_FAIL_NULL(plan); + + PanelContainer *panel = memnew(PanelContainer); + ADD_STYLEBOX_OVERRIDE(panel, LW_NAME(panel), toolbar_style); + + MarginContainer *margin_container = memnew(MarginContainer); + panel->add_child(margin_container); + margin_container->set_theme_type_variation("MarginContainer4px"); + margin_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + margin_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + VBoxContainer *toolbar = memnew(VBoxContainer); + margin_container->add_child(toolbar); + toolbar->set_h_size_flags(Control::SIZE_EXPAND_FILL); + + if (plan->is_derived()) { + Button *goto_btn = memnew(Button); + toolbar->add_child(goto_btn); + goto_btn->set_text(TTR("Edit Base")); + goto_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + goto_btn->set_custom_minimum_size(Size2(150.0, 0.0) * EDSCALE); + BUTTON_SET_ICON(goto_btn, EditorInterface::get_singleton()->get_editor_theme()->get_icon(LW_NAME(Edit), LW_NAME(EditorIcons))); + goto_btn->connect(LW_NAME(pressed), callable_mp(this, &EditorInspectorPluginBBPlan::_open_base_plan).bind(plan)); + } else { + Button *edit_btn = memnew(Button); + toolbar->add_child(edit_btn); + edit_btn->set_text(TTR("Manage...")); + edit_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + edit_btn->set_custom_minimum_size(Size2(150.0, 0.0) * EDSCALE); + BUTTON_SET_ICON(edit_btn, EditorInterface::get_singleton()->get_editor_theme()->get_icon(LW_NAME(EditAddRemove), LW_NAME(EditorIcons))); + edit_btn->connect(LW_NAME(pressed), callable_mp(this, &EditorInspectorPluginBBPlan::_edit_plan).bind(plan)); + } + + add_custom_control(panel); +} + +EditorInspectorPluginBBPlan::EditorInspectorPluginBBPlan() { + plan_editor = memnew(BlackboardPlanEditor); + EditorInterface::get_singleton()->get_base_control()->add_child(plan_editor); + plan_editor->hide(); + + toolbar_style = Ref(memnew(StyleBoxFlat)); + Color bg = EditorInterface::get_singleton()->get_editor_theme()->get_color(LW_NAME(accent_color), LW_NAME(Editor)); + bg = bg.darkened(-0.1); + bg.a *= 0.2; + toolbar_style->set_bg_color(bg); +} diff --git a/editor/blackboard_plan_editor.h b/editor/blackboard_plan_editor.h new file mode 100644 index 0000000..0cceb3a --- /dev/null +++ b/editor/blackboard_plan_editor.h @@ -0,0 +1,117 @@ +/** + * blackboard_plan_editor.h + * ============================================================================= + * Copyright 2021-2024 Serhii Snitsaruk + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + * ============================================================================= + */ + +#ifndef BLACKBOARD_PLAN_EDITOR_H +#define BLACKBOARD_PLAN_EDITOR_H + +#include "../blackboard/blackboard_plan.h" + +#ifdef LIMBOAI_MODULE +#include "editor/editor_inspector.h" +#include "scene/gui/dialogs.h" +#endif // LIMBOAI_MODULE + +#ifdef LIMBOAI_GDEXTENSION +#include +#include +#include +#include +#include +#include +#include +using namespace godot; +#endif // LIMBOAI_GDEXTENSION + +// ***** + +class BlackboardPlanEditor : public AcceptDialog { + GDCLASS(BlackboardPlanEditor, AcceptDialog); + +private: + struct ThemeCache { + Ref trash_icon; + Ref grab_icon; + Ref odd_style; + Ref even_style; + Ref header_style; + } theme_cache; + + int last_index = 0; + int drag_mouse_y_delta = 0; + int drag_start = -1; + int drag_index = -1; + + Ref plan; + + VBoxContainer *rows_vbox; + Button *add_var_tool; + PanelContainer *header_row; + ScrollContainer *scroll_container; + PopupMenu *type_menu; + PopupMenu *hint_menu; + + void _add_var(); + void _trash_var(int p_index); + void _rename_var(const String &p_new_name, int p_index); + void _change_var_type(Variant::Type p_new_type, int p_index); + void _change_var_hint(PropertyHint p_new_hint, int p_index); + void _change_var_hint_string(const String &p_new_hint_string, int p_index); + + void _show_button_popup(Button *p_button, PopupMenu *p_popup, int p_index); + void _type_chosen(int id); + void _hint_chosen(int id); + + void _drag_button_down(Control *p_row); + void _drag_button_up(); + void _drag_button_gui_input(const Ref &p_event); + + void _refresh(); + void _visibility_changed(); + +protected: + static void _bind_methods() {} + + void _notification(int p_what); + +public: + void edit_plan(const Ref &p_plan); + + BlackboardPlanEditor(); +}; + +// ***** + +class EditorInspectorPluginBBPlan : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginBBPlan, EditorInspectorPlugin); + +private: + BlackboardPlanEditor *plan_editor; + Ref toolbar_style; + + void _edit_plan(const Ref &p_plan); + void _open_base_plan(const Ref &p_plan); + +protected: + static void _bind_methods() {} + +public: +#ifdef LIMBOAI_MODULE + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +#elif LIMBOAI_GDEXTENSION + virtual bool _can_handle(Object *p_object) const override; + virtual void _parse_begin(Object *p_object) override; +#endif + + EditorInspectorPluginBBPlan(); +}; + +#endif // BLACKBOARD_PLAN_EDITOR_H diff --git a/editor/limbo_ai_editor_plugin.cpp b/editor/limbo_ai_editor_plugin.cpp index 08e30ac..788f1fd 100644 --- a/editor/limbo_ai_editor_plugin.cpp +++ b/editor/limbo_ai_editor_plugin.cpp @@ -18,11 +18,12 @@ #include "../bt/tasks/composites/bt_probability_selector.h" #include "../bt/tasks/composites/bt_selector.h" #include "../bt/tasks/decorators/bt_subtree.h" -#include "../editor/debugger/limbo_debugger_plugin.h" -#include "../editor/editor_property_bb_param.h" #include "../util/limbo_compat.h" #include "../util/limbo_utility.h" #include "action_banner.h" +#include "blackboard_plan_editor.h" +#include "debugger/limbo_debugger_plugin.h" +#include "editor_property_bb_param.h" #ifdef LIMBOAI_MODULE #include "core/config/project_settings.h" @@ -183,8 +184,9 @@ void LimboAIEditor::_update_history_buttons() { } void LimboAIEditor::_new_bt() { - BehaviorTree *bt = memnew(BehaviorTree); + Ref bt = memnew(BehaviorTree); bt->set_root_task(memnew(BTSelector)); + bt->set_blackboard_plan(memnew(BlackboardPlan)); EDIT_RESOURCE(bt); } @@ -221,6 +223,11 @@ void LimboAIEditor::edit_bt(Ref p_behavior_tree, bool p_force_refr return; } +#ifdef LIMBOAI_MODULE + p_behavior_tree->editor_set_section_unfold("blackboard_plan", true); + p_behavior_tree->notify_property_list_changed(); +#endif // LIMBOAI_MODULE + task_tree->load_bt(p_behavior_tree); int idx = history.find(p_behavior_tree); @@ -697,6 +704,9 @@ void LimboAIEditor::_on_visibility_changed() { void LimboAIEditor::_on_header_pressed() { _update_header(); task_tree->deselect(); +#ifdef LIMBOAI_MODULE + task_tree->get_bt()->editor_set_section_unfold("blackboard_plan", true); +#endif // LIMBOAI_MODULE EDIT_RESOURCE(task_tree->get_bt()); } @@ -1393,6 +1403,11 @@ void LimboAIEditorPlugin::_notification(int p_notification) { switch (p_notification) { case NOTIFICATION_READY: { add_debugger_plugin(memnew(LimboDebuggerPlugin)); + add_inspector_plugin(memnew(EditorInspectorPluginBBPlan)); +#ifdef LIMBOAI_MODULE + // ! Only used in the module version. + add_inspector_plugin(memnew(EditorInspectorPluginBBParam)); +#endif // LIMBOAI_MODULE } break; case NOTIFICATION_ENTER_TREE: { // Add BehaviorTree to the list of resources that should open in a new inspector. @@ -1446,11 +1461,6 @@ LimboAIEditorPlugin::LimboAIEditorPlugin() { MAIN_SCREEN_CONTROL()->add_child(limbo_ai_editor); limbo_ai_editor->hide(); limbo_ai_editor->set_plugin(this); - -#ifdef LIMBOAI_MODULE - // ! Only used in the module version. - add_inspector_plugin(memnew(EditorInspectorPluginBBParam)); -#endif // LIMBOAI_MODULE } LimboAIEditorPlugin::~LimboAIEditorPlugin() { diff --git a/hsm/limbo_hsm.cpp b/hsm/limbo_hsm.cpp index 1eb8616..8834f52 100644 --- a/hsm/limbo_hsm.cpp +++ b/hsm/limbo_hsm.cpp @@ -190,7 +190,7 @@ bool LimboHSM::dispatch(const String &p_event, const Variant &p_cargo) { void LimboHSM::initialize(Node *p_agent, const Ref &p_parent_scope) { ERR_FAIL_COND(p_agent == nullptr); if (!p_parent_scope.is_null()) { - blackboard->set_parent_scope(p_parent_scope); + blackboard->set_parent(p_parent_scope); } _initialize(p_agent, nullptr); diff --git a/hsm/limbo_state.cpp b/hsm/limbo_state.cpp index 4950e21..bf75f49 100644 --- a/hsm/limbo_state.cpp +++ b/hsm/limbo_state.cpp @@ -69,8 +69,9 @@ void LimboState::_initialize(Node *p_agent, const Ref &p_blackboard) agent = p_agent; if (!p_blackboard.is_null()) { - if (!blackboard->get_data().is_empty()) { - blackboard->set_parent_scope(p_blackboard); + if (blackboard_plan.is_valid() && !blackboard_plan->is_empty()) { + blackboard = blackboard_plan->create_blackboard(); + blackboard->set_parent(p_blackboard); } else { blackboard = p_blackboard; } @@ -179,8 +180,8 @@ void LimboState::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_guard"), &LimboState::clear_guard); ClassDB::bind_method(D_METHOD("get_blackboard"), &LimboState::get_blackboard); - ClassDB::bind_method(D_METHOD("_set_blackboard_data", "p_blackboard"), &LimboState::_set_blackboard_data); - ClassDB::bind_method(D_METHOD("_get_blackboard_data"), &LimboState::_get_blackboard_data); + ClassDB::bind_method(D_METHOD("set_blackboard_plan", "p_plan"), &LimboState::set_blackboard_plan); + ClassDB::bind_method(D_METHOD("get_blackboard_plan"), &LimboState::get_blackboard_plan); #ifdef LIMBOAI_MODULE GDVIRTUAL_BIND(_setup); @@ -194,7 +195,7 @@ void LimboState::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::STRING, "EVENT_FINISHED", PROPERTY_HINT_NONE, "", 0), "", "event_finished"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "agent", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "set_agent", "get_agent"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard", PROPERTY_HINT_RESOURCE_TYPE, "Blackboard", 0), "", "get_blackboard"); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_blackboard_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "_set_blackboard_data", "_get_blackboard_data"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard_plan", PROPERTY_HINT_RESOURCE_TYPE, "BlackboardPlan", PROPERTY_USAGE_DEFAULT), "set_blackboard_plan", "get_blackboard_plan"); ADD_SIGNAL(MethodInfo("setup")); ADD_SIGNAL(MethodInfo("entered")); diff --git a/hsm/limbo_state.h b/hsm/limbo_state.h index 7a908d6..5f337a1 100644 --- a/hsm/limbo_state.h +++ b/hsm/limbo_state.h @@ -13,6 +13,7 @@ #define LIMBO_STATE_H #include "../blackboard/blackboard.h" +#include "../blackboard/blackboard_plan.h" #include "../util/limbo_string_names.h" @@ -37,6 +38,7 @@ class LimboState : public Node { GDCLASS(LimboState, Node); private: + Ref blackboard_plan; Node *agent; Ref blackboard; HashMap handlers; @@ -51,9 +53,6 @@ protected: void _notification(int p_what); - void _set_blackboard_data(Dictionary p_value) { blackboard->set_data(p_value.duplicate()); } - Dictionary _get_blackboard_data() const { return blackboard->get_data(); } - virtual void _initialize(Node *p_agent, const Ref &p_blackboard); virtual void _setup(); @@ -71,6 +70,9 @@ protected: void add_event_handler(const String &p_event, const Callable &p_handler); public: + void set_blackboard_plan(const Ref p_plan) { blackboard_plan = p_plan; } + Ref get_blackboard_plan() const { return blackboard_plan; } + Ref get_blackboard() const { return blackboard; } Node *get_agent() const { return agent; } diff --git a/icons/BlackboardPlan.svg b/icons/BlackboardPlan.svg new file mode 100644 index 0000000..958fba3 --- /dev/null +++ b/icons/BlackboardPlan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/register_types.cpp b/register_types.cpp index 4d6edf9..be7be65 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -47,6 +47,7 @@ #include "blackboard/bb_param/bb_vector4.h" #include "blackboard/bb_param/bb_vector4i.h" #include "blackboard/blackboard.h" +#include "blackboard/blackboard_plan.h" #include "bt/behavior_tree.h" #include "bt/bt_player.h" #include "bt/bt_state.h" @@ -94,6 +95,7 @@ #include "bt/tasks/utility/bt_wait.h" #include "bt/tasks/utility/bt_wait_ticks.h" #include "editor/action_banner.h" +#include "editor/blackboard_plan_editor.h" #include "editor/debugger/behavior_tree_data.h" #include "editor/debugger/limbo_debugger.h" #include "editor/debugger/limbo_debugger_plugin.h" @@ -133,6 +135,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(LimboUtility); GDREGISTER_CLASS(Blackboard); + GDREGISTER_CLASS(BlackboardPlan); GDREGISTER_CLASS(LimboState); GDREGISTER_CLASS(LimboHSM); @@ -251,6 +254,8 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(BehaviorTreeView); GDREGISTER_CLASS(LimboDebuggerTab); GDREGISTER_CLASS(LimboDebuggerPlugin); + GDREGISTER_CLASS(BlackboardPlanEditor); + GDREGISTER_CLASS(EditorInspectorPluginBBPlan); GDREGISTER_CLASS(LimboAIEditor); GDREGISTER_CLASS(LimboAIEditorPlugin); #endif // LIMBOAI_GDEXTENSION diff --git a/tests/test_new_scope.h b/tests/test_new_scope.h index a16a45b..421ffbd 100644 --- a/tests/test_new_scope.h +++ b/tests/test_new_scope.h @@ -50,7 +50,7 @@ TEST_CASE("[Modules][LimboAI] BTNewScope") { CHECK(ns->get_blackboard() != parent->get_blackboard()); CHECK(ns->get_blackboard() == child->get_blackboard()); CHECK(parent->get_blackboard() == parent_bb); - CHECK(ns->get_blackboard()->get_parent_scope() == parent_bb); + CHECK(ns->get_blackboard()->get_parent() == parent_bb); ns->get_blackboard()->set_var("fruit", "pear"); // * override "fruit" diff --git a/util/limbo_compat.cpp b/util/limbo_compat.cpp index a02f34a..1e04f86 100644 --- a/util/limbo_compat.cpp +++ b/util/limbo_compat.cpp @@ -15,6 +15,7 @@ #ifdef TOOLS_ENABLED #include "core/io/resource.h" +#include "core/variant/variant.h" #include "editor/editor_node.h" #include "editor/plugins/script_editor_plugin.h" #endif // TOOLS_ENABLED @@ -85,6 +86,128 @@ Variant _GLOBAL_DEF(const PropertyInfo &p_info, const Variant &p_default, bool p // **** Shared +Variant VARIANT_DEFAULT(Variant::Type p_type) { + switch (p_type) { + case Variant::Type::NIL: { + return Variant(); + } break; + case Variant::Type::BOOL: { + return Variant(false); + } break; + case Variant::Type::INT: { + return Variant(0); + } break; + case Variant::Type::FLOAT: { + return Variant(0.0); + } break; + case Variant::Type::STRING: { + return Variant(""); + } break; + case Variant::Type::VECTOR2: { + return Variant(Vector2()); + } break; + case Variant::Type::VECTOR2I: { + return Variant(Vector2i()); + } break; + case Variant::Type::RECT2: { + return Variant(Rect2()); + } break; + case Variant::Type::RECT2I: { + return Variant(Rect2i()); + } break; + case Variant::Type::VECTOR3: { + return Variant(Vector3()); + } break; + case Variant::Type::VECTOR3I: { + return Variant(Vector3i()); + } break; + case Variant::Type::TRANSFORM2D: { + return Variant(Transform2D()); + } break; + case Variant::Type::VECTOR4: { + return Variant(Vector4()); + } break; + case Variant::Type::VECTOR4I: { + return Variant(Vector4i()); + } break; + case Variant::Type::PLANE: { + return Variant(Plane()); + } break; + case Variant::Type::QUATERNION: { + return Variant(Quaternion()); + } break; + case Variant::Type::AABB: { + return Variant(AABB()); + } break; + case Variant::Type::BASIS: { + return Variant(Basis()); + } break; + case Variant::Type::TRANSFORM3D: { + return Variant(Transform3D()); + } break; + case Variant::Type::PROJECTION: { + return Variant(Projection()); + } break; + case Variant::Type::COLOR: { + return Variant(Color()); + } break; + case Variant::Type::STRING_NAME: { + return Variant(StringName()); + } break; + case Variant::Type::NODE_PATH: { + return Variant(NodePath()); + } break; + case Variant::Type::RID: { + return Variant(RID()); + } break; + case Variant::Type::OBJECT: { + return Variant(); + } break; + case Variant::Type::CALLABLE: { + return Variant(); + } break; + case Variant::Type::SIGNAL: { + return Variant(); + } break; + case Variant::Type::DICTIONARY: { + return Variant(Dictionary()); + } break; + case Variant::Type::ARRAY: { + return Variant(Array()); + } break; + case Variant::Type::PACKED_BYTE_ARRAY: { + return Variant(PackedByteArray()); + } break; + case Variant::Type::PACKED_INT32_ARRAY: { + return Variant(PackedInt32Array()); + } break; + case Variant::Type::PACKED_INT64_ARRAY: { + return Variant(PackedInt64Array()); + } break; + case Variant::Type::PACKED_FLOAT32_ARRAY: { + return Variant(PackedFloat32Array()); + } break; + case Variant::Type::PACKED_FLOAT64_ARRAY: { + return Variant(PackedFloat64Array()); + } break; + case Variant::Type::PACKED_STRING_ARRAY: { + return Variant(PackedStringArray()); + } break; + case Variant::Type::PACKED_VECTOR2_ARRAY: { + return Variant(PackedVector2Array()); + } break; + case Variant::Type::PACKED_VECTOR3_ARRAY: { + return Variant(PackedVector3Array()); + } break; + case Variant::Type::PACKED_COLOR_ARRAY: { + return Variant(PackedColorArray()); + } break; + default: { + return Variant(); + } + } +} + #ifdef TOOLS_ENABLED void SHOW_DOC(const String &p_topic) { diff --git a/util/limbo_compat.h b/util/limbo_compat.h index a382f01..fdfce9a 100644 --- a/util/limbo_compat.h +++ b/util/limbo_compat.h @@ -52,6 +52,7 @@ #define DIR_ACCESS_CREATE() DirAccess::create(DirAccess::ACCESS_RESOURCES) #define PERFORMANCE_ADD_CUSTOM_MONITOR(m_id, m_callable) (Performance::get_singleton()->add_custom_monitor(m_id, m_callable, Variant())) #define GET_SCRIPT(m_obj) (m_obj->get_script_instance() ? m_obj->get_script_instance()->get_script() : nullptr) +#define ADD_STYLEBOX_OVERRIDE(m_control, m_name, m_stylebox) (m_control->add_theme_style_override(m_name, m_stylebox)) #define VARIANT_EVALUATE(m_op, m_lvalue, m_rvalue, r_ret) r_ret = Variant::evaluate(m_op, m_lvalue, m_rvalue) @@ -130,6 +131,7 @@ using namespace godot; #define DIR_ACCESS_CREATE() DirAccess::open("res://") #define PERFORMANCE_ADD_CUSTOM_MONITOR(m_id, m_callable) (Performance::get_singleton()->add_custom_monitor(m_id, m_callable)) #define GET_SCRIPT(m_obj) (m_obj->get_script()) +#define ADD_STYLEBOX_OVERRIDE(m_control, m_name, m_stylebox) (m_control->add_theme_stylebox_override(m_name, m_stylebox)) #define VARIANT_EVALUATE(m_op, m_lvalue, m_rvalue, r_ret) \ { \ @@ -218,6 +220,8 @@ inline void VARIANT_DELETE_IF_OBJECT(Variant m_variant) { } } +Variant VARIANT_DEFAULT(Variant::Type p_type); + #define PROJECT_CONFIG_FILE() GET_PROJECT_SETTINGS_DIR().path_join("limbo_ai.cfg") #define IS_RESOURCE_FILE(m_path) (m_path.begins_with("res://") && m_path.find("::") == -1) #define RESOURCE_TYPE_HINT(m_type) vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, m_type) diff --git a/util/limbo_string_names.cpp b/util/limbo_string_names.cpp index ee045bd..25b55d1 100644 --- a/util/limbo_string_names.cpp +++ b/util/limbo_string_names.cpp @@ -42,6 +42,7 @@ LimboStringNames::LimboStringNames() { _update_banners = SN("_update_banners"); _weight_ = SN("_weight_"); accent_color = SN("accent_color"); + Add = SN("Add"); add_child = SN("add_child"); add_child_at_index = SN("add_child_at_index"); AnimationFilter = SN("AnimationFilter"); @@ -52,13 +53,19 @@ LimboStringNames::LimboStringNames() { bold = SN("bold"); BTAlwaysFail = SN("BTAlwaysFail"); BTAlwaysSucceed = SN("BTAlwaysSucceed"); + button_down = SN("button_down"); + button_up = SN("button_up"); changed = SN("changed"); connect = SN("connect"); + dark_color_1 = SN("dark_color_1"); + dark_color_2 = SN("dark_color_2"); Debug = SN("Debug"); disabled_font_color = SN("disabled_font_color"); doc_italic = SN("doc_italic"); draw = SN("draw"); Duplicate = SN("Duplicate"); + Edit = SN("Edit"); + EditAddRemove = SN("EditAddRemove"); Editor = SN("Editor"); EditorFonts = SN("EditorFonts"); EditorIcons = SN("EditorIcons"); @@ -76,6 +83,7 @@ LimboStringNames::LimboStringNames() { gui_input = SN("gui_input"); GuiTreeArrowDown = SN("GuiTreeArrowDown"); GuiTreeArrowRight = SN("GuiTreeArrowRight"); + HeaderSmall = SN("HeaderSmall"); Help = SN("Help"); icon_max_width = SN("icon_max_width"); id_pressed = SN("id_pressed"); @@ -97,6 +105,7 @@ LimboStringNames::LimboStringNames() { NodeWarning = SN("NodeWarning"); NonFavorite = SN("NonFavorite"); normal = SN("normal"); + panel = SN("panel"); popup_hide = SN("popup_hide"); pressed = SN("pressed"); probability_clicked = SN("probability_clicked"); @@ -111,6 +120,7 @@ LimboStringNames::LimboStringNames() { Script = SN("Script"); ScriptCreate = SN("ScriptCreate"); Search = SN("Search"); + separation = SN("separation"); set_custom_name = SN("set_custom_name"); set_root_task = SN("set_root_task"); setup = SN("setup"); @@ -125,9 +135,12 @@ LimboStringNames::LimboStringNames() { task_meta = SN("task_meta"); task_selected = SN("task_selected"); text_changed = SN("text_changed"); + text_submitted = SN("text_submitted"); timeout = SN("timeout"); toggled = SN("toggled"); Tools = SN("Tools"); + Tree = SN("Tree"); + TripleBar = SN("TripleBar"); update_task = SN("update_task"); update_tree = SN("update_tree"); updated = SN("updated"); diff --git a/util/limbo_string_names.h b/util/limbo_string_names.h index d1bd93f..bccbafd 100644 --- a/util/limbo_string_names.h +++ b/util/limbo_string_names.h @@ -58,6 +58,7 @@ public: StringName accent_color; StringName add_child_at_index; StringName add_child; + StringName Add; StringName AnimationFilter; StringName Back; StringName behavior_tree_finished; @@ -66,13 +67,19 @@ public: StringName bold; StringName BTAlwaysFail; StringName BTAlwaysSucceed; + StringName button_down; + StringName button_up; StringName changed; StringName connect; + StringName dark_color_1; + StringName dark_color_2; StringName Debug; StringName disabled_font_color; StringName doc_italic; StringName draw; StringName Duplicate; + StringName Edit; + StringName EditAddRemove; StringName Editor; StringName EditorFonts; StringName EditorIcons; @@ -90,6 +97,7 @@ public: StringName gui_input; StringName GuiTreeArrowDown; StringName GuiTreeArrowRight; + StringName HeaderSmall; StringName Help; StringName icon_max_width; StringName id_pressed; @@ -112,6 +120,7 @@ public: StringName NodeWarning; StringName NonFavorite; StringName normal; + StringName panel; StringName popup_hide; StringName pressed; StringName probability_clicked; @@ -126,6 +135,7 @@ public: StringName Script; StringName ScriptCreate; StringName Search; + StringName separation; StringName set_custom_name; StringName set_root_task; StringName setup; @@ -140,9 +150,12 @@ public: StringName task_meta; StringName task_selected; StringName text_changed; + StringName text_submitted; StringName timeout; StringName toggled; StringName Tools; + StringName Tree; + StringName TripleBar; StringName update_task; StringName update_tree; StringName updated; diff --git a/util/limbo_utility.cpp b/util/limbo_utility.cpp index 087dcd0..ed2f64f 100644 --- a/util/limbo_utility.cpp +++ b/util/limbo_utility.cpp @@ -288,6 +288,129 @@ Variant LimboUtility::perform_operation(Operation p_operation, const Variant &le return ret; } +String LimboUtility::get_property_hint_text(PropertyHint p_hint) const { + switch (p_hint) { + case PROPERTY_HINT_NONE: { + return "NONE"; + } + case PROPERTY_HINT_RANGE: { + return "RANGE"; + } + case PROPERTY_HINT_ENUM: { + return "ENUM"; + } + case PROPERTY_HINT_ENUM_SUGGESTION: { + return "SUGGESTION"; + } + case PROPERTY_HINT_EXP_EASING: { + return "EXP_EASING"; + } + case PROPERTY_HINT_LINK: { + return "LINK"; + } + case PROPERTY_HINT_FLAGS: { + return "FLAGS"; + } + case PROPERTY_HINT_LAYERS_2D_RENDER: { + return "LAYERS_2D_RENDER"; + } + case PROPERTY_HINT_LAYERS_2D_PHYSICS: { + return "LAYERS_2D_PHYSICS"; + } + case PROPERTY_HINT_LAYERS_2D_NAVIGATION: { + return "LAYERS_2D_NAVIGATION"; + } + case PROPERTY_HINT_LAYERS_3D_RENDER: { + return "LAYERS_3D_RENDER"; + } + case PROPERTY_HINT_LAYERS_3D_PHYSICS: { + return "LAYERS_3D_PHYSICS"; + } + case PROPERTY_HINT_LAYERS_3D_NAVIGATION: { + return "LAYERS_3D_NAVIGATION"; + } + case PROPERTY_HINT_FILE: { + return "FILE"; + } + case PROPERTY_HINT_DIR: { + return "DIR"; + } + case PROPERTY_HINT_GLOBAL_FILE: { + return "GLOBAL_FILE"; + } + case PROPERTY_HINT_GLOBAL_DIR: { + return "GLOBAL_DIR"; + } + case PROPERTY_HINT_RESOURCE_TYPE: { + return "RESOURCE_TYPE"; + } + case PROPERTY_HINT_MULTILINE_TEXT: { + return "MULTILINE_TEXT"; + } + case PROPERTY_HINT_EXPRESSION: { + return "EXPRESSION"; + } + case PROPERTY_HINT_PLACEHOLDER_TEXT: { + return "PLACEHOLDER_TEXT"; + } + case PROPERTY_HINT_COLOR_NO_ALPHA: { + return "COLOR_NO_ALPHA"; + } + case PROPERTY_HINT_OBJECT_ID: { + return "OBJECT_ID"; + } + case PROPERTY_HINT_TYPE_STRING: { + return "TYPE_STRING"; + } + case PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE: { + return "NODE_PATH_TO_EDITED_NODE"; + } + case PROPERTY_HINT_OBJECT_TOO_BIG: { + return "OBJECT_TOO_BIG"; + } + case PROPERTY_HINT_NODE_PATH_VALID_TYPES: { + return "NODE_PATH_VALID_TYPES"; + } + case PROPERTY_HINT_SAVE_FILE: { + return "SAVE_FILE"; + } + case PROPERTY_HINT_GLOBAL_SAVE_FILE: { + return "GLOBAL_SAVE_FILE"; + } + case PROPERTY_HINT_INT_IS_OBJECTID: { + return "INT_IS_OBJECTID"; + } + case PROPERTY_HINT_INT_IS_POINTER: { + return "INT_IS_POINTER"; + } + case PROPERTY_HINT_ARRAY_TYPE: { + return "ARRAY_TYPE"; + } + case PROPERTY_HINT_LOCALE_ID: { + return "LOCALE_ID"; + } + case PROPERTY_HINT_LOCALIZABLE_STRING: { + return "LOCALIZABLE_STRING"; + } + case PROPERTY_HINT_NODE_TYPE: { + return "NODE_TYPE"; + } + case PROPERTY_HINT_HIDE_QUATERNION_EDIT: { + return "HIDE_QUATERNION_EDIT"; + } + case PROPERTY_HINT_PASSWORD: { + return "PASSWORD"; + } + case PROPERTY_HINT_LAYERS_AVOIDANCE: { + return "LAYERS_AVOIDANCE"; + } + case PROPERTY_HINT_MAX: { + return "MAX"; + } + } + return ""; +} + #ifdef TOOLS_ENABLED Ref LimboUtility::add_shortcut(const String &p_path, const String &p_name, Key p_keycode) { diff --git a/util/limbo_utility.h b/util/limbo_utility.h index bcb5a1d..39045a4 100644 --- a/util/limbo_utility.h +++ b/util/limbo_utility.h @@ -88,6 +88,8 @@ public: String get_operation_string(Operation p_operation) const; Variant perform_operation(Operation p_operation, const Variant &left_value, const Variant &right_value); + String get_property_hint_text(PropertyHint p_hint) const; + #ifdef TOOLS_ENABLED Ref add_shortcut(const String &p_path, const String &p_name, Key p_keycode = LW_KEY(NONE)); bool is_shortcut(const String &p_path, const Ref &p_event) const;