From fd6d1ea93c680e43402bbaaff4cbe7fbec3bdf13 Mon Sep 17 00:00:00 2001 From: "Wilson E. Alvarez" Date: Sat, 17 Feb 2024 16:00:25 -0500 Subject: [PATCH] Add BTEvaluateExpression --- bt/tasks/utility/bt_evaluate_expression.cpp | 163 ++++++++++++++++++++ bt/tasks/utility/bt_evaluate_expression.h | 69 +++++++++ config.py | 1 + doc_classes/BTEvaluateExpression.xml | 35 +++++ register_types.cpp | 2 + 5 files changed, 270 insertions(+) create mode 100644 bt/tasks/utility/bt_evaluate_expression.cpp create mode 100644 bt/tasks/utility/bt_evaluate_expression.h create mode 100644 doc_classes/BTEvaluateExpression.xml diff --git a/bt/tasks/utility/bt_evaluate_expression.cpp b/bt/tasks/utility/bt_evaluate_expression.cpp new file mode 100644 index 00000000..bcc1fe86 --- /dev/null +++ b/bt/tasks/utility/bt_evaluate_expression.cpp @@ -0,0 +1,163 @@ +/** + * bt_call_method.cpp + * ============================================================================= + * Copyright 2021-2023 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 "bt_evaluate_expression.h" + +#include "../../../util/limbo_compat.h" +#include "../../../util/limbo_utility.h" + +#ifdef LIMBOAI_GDEXTENSION +#include "godot_cpp/classes/global_constants.hpp" +#endif // LIMBOAI_GDEXTENSION + +//**** Setters / Getters + +void BTEvaluateExpression::set_expression_string(String p_expression_string) { + expression_string = p_expression_string; + emit_changed(); +} + +void BTEvaluateExpression::set_node_param(Ref p_object) { + node_param = p_object; + emit_changed(); + if (Engine::get_singleton()->is_editor_hint() && node_param.is_valid()) { + node_param->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); + } +} + +void BTEvaluateExpression::set_input_include_delta(bool p_input_include_delta) { + if (input_include_delta != p_input_include_delta) { + processed_input_vars.resize(input_vars.size() + int(p_input_include_delta)); + processed_input_values.resize(input_values.size() + int(p_input_include_delta)); + } + input_include_delta = p_input_include_delta; + emit_changed(); +} + +void BTEvaluateExpression::set_input_vars(Vector p_input_vars) { + if (input_vars.size() != p_input_vars.size()) { + processed_input_vars.resize(p_input_vars.size() + int(input_include_delta)); + } + input_vars = p_input_vars; + emit_changed(); +} + +void BTEvaluateExpression::set_input_values(TypedArray p_input_values) { + if (input_values.size() != p_input_values.size()) { + processed_input_values.resize(p_input_values.size() + int(input_include_delta)); + } + input_values = p_input_values; + emit_changed(); +} + +void BTEvaluateExpression::set_result_var(const String &p_result_var) { + result_var = p_result_var; + emit_changed(); +} + +//**** Task Implementation + +PackedStringArray BTEvaluateExpression::get_configuration_warnings() { + PackedStringArray warnings = BTAction::get_configuration_warnings(); + if (expression_string.is_empty()) { + warnings.append("Expression string is not set."); + } + if (node_param.is_null()) { + warnings.append("Node parameter is not set."); + } else if (node_param->get_value_source() == BBParam::SAVED_VALUE && node_param->get_saved_value() == Variant()) { + warnings.append("Path to node is not set."); + } else if (node_param->get_value_source() == BBParam::BLACKBOARD_VAR && node_param->get_variable() == String()) { + warnings.append("Node blackboard variable is not set."); + } + return warnings; +} + +void BTEvaluateExpression::_setup() { + ERR_FAIL_COND_MSG(expression_string == String(), "BTEvaluateExpression: Expression String is not set."); + String *processed_input_vars_ptr = processed_input_vars.ptrw(); + if (input_include_delta) { + processed_input_vars_ptr[0] = "delta"; + } + for (int i = 0; i < input_vars.size(); ++i) { + processed_input_vars_ptr[i] = input_vars[i]; + } + + is_parsed = expression.parse(expression_string, processed_input_vars); + ERR_FAIL_COND_MSG(is_parsed != Error::OK, "BTEvaluateExpression: Failed to parse expression: " + expression.get_error_text()); +} + +String BTEvaluateExpression::_generate_name() { + String input_vars_str = input_include_delta ? "delta" : ""; + if (input_vars.size() > 0) { + if (!input_vars_str.is_empty()) { + input_vars_str += ", "; + } + input_vars_str += vformat("%s", input_vars).trim_prefix("[").trim_suffix("]").replace("\"", ""); + } + return vformat("EvaluateExpression %s with vars [%s] node: %s %s", + expression_string != String() ? expression_string : "???", + input_vars_str, + node_param.is_valid() && !node_param->to_string().is_empty() ? node_param->to_string() : "???", + result_var.is_empty() ? "" : LimboUtility::get_singleton()->decorate_output_var(result_var)); +} + +BT::Status BTEvaluateExpression::_tick(double p_delta) { + ERR_FAIL_COND_V_MSG(expression_string == String(), FAILURE, "BTEvaluateExpression: Expression String is not set."); + ERR_FAIL_COND_V_MSG(node_param.is_null(), FAILURE, "BTEvaluateExpression: Node parameter is not set."); + Object *obj = node_param->get_value(get_agent(), get_blackboard()); + ERR_FAIL_COND_V_MSG(obj == nullptr, FAILURE, "BTEvaluateExpression: Failed to get object: " + node_param->to_string()); + ERR_FAIL_COND_V_MSG(is_parsed != Error::OK, FAILURE, "BTEvaluateExpression: Failed to parse expression: " + expression.get_error_text()); + + if (input_include_delta) { + processed_input_values[0] = p_delta; + } + for (int i = int(input_include_delta); i < input_values.size(); ++i) { + const Ref &bb_variant = input_values[i]; + processed_input_values[i] = bb_variant->get_value(get_agent(), get_blackboard()); + } + + Variant result = expression.execute(processed_input_values, obj, false); + ERR_FAIL_COND_V_MSG(expression.has_execute_failed(), FAILURE, "BTEvaluateExpression: Failed to execute: " + expression.get_error_text()); + + if (!result_var.is_empty()) { + get_blackboard()->set_var(result_var, result); + } + + return SUCCESS; +} + +//**** Godot + +void BTEvaluateExpression::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_expression_string", "p_method"), &BTEvaluateExpression::set_expression_string); + ClassDB::bind_method(D_METHOD("get_expression_string"), &BTEvaluateExpression::get_expression_string); + ClassDB::bind_method(D_METHOD("set_node_param", "p_param"), &BTEvaluateExpression::set_node_param); + ClassDB::bind_method(D_METHOD("get_node_param"), &BTEvaluateExpression::get_node_param); + ClassDB::bind_method(D_METHOD("set_input_vars", "p_input_vars"), &BTEvaluateExpression::set_input_vars); + ClassDB::bind_method(D_METHOD("get_input_vars"), &BTEvaluateExpression::get_input_vars); + ClassDB::bind_method(D_METHOD("set_input_values", "p_input_values"), &BTEvaluateExpression::set_input_values); + ClassDB::bind_method(D_METHOD("get_input_values"), &BTEvaluateExpression::get_input_values); + ClassDB::bind_method(D_METHOD("set_input_include_delta", "p_input_include_delta"), &BTEvaluateExpression::set_input_include_delta); + ClassDB::bind_method(D_METHOD("is_delta_included"), &BTEvaluateExpression::is_delta_included); + ClassDB::bind_method(D_METHOD("set_result_var", "p_result_var"), &BTEvaluateExpression::set_result_var); + ClassDB::bind_method(D_METHOD("get_result_var"), &BTEvaluateExpression::get_result_var); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "BBNode"), "set_node_param", "get_node_param"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "expression_string"), "set_expression_string", "get_expression_string"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "result_var"), "set_result_var", "get_result_var"); + ADD_GROUP("Inputs", "input_"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_include_delta"), "set_input_include_delta", "is_delta_included"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "input_vars", PROPERTY_HINT_ARRAY_TYPE, "String"), "set_input_vars", "get_input_vars"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "input_values", PROPERTY_HINT_ARRAY_TYPE, RESOURCE_TYPE_HINT("BBVariant")), "set_input_values", "get_input_values"); +} + +BTEvaluateExpression::BTEvaluateExpression() { +} diff --git a/bt/tasks/utility/bt_evaluate_expression.h b/bt/tasks/utility/bt_evaluate_expression.h new file mode 100644 index 00000000..24891ade --- /dev/null +++ b/bt/tasks/utility/bt_evaluate_expression.h @@ -0,0 +1,69 @@ +/** + * bt_call_method.h + * ============================================================================= + * Copyright 2021-2023 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 BT_EVALUATE_EXPRESSION_H +#define BT_EVALUATE_EXPRESSION_H + +#include "../bt_action.h" +#include "core/math/expression.h" + +#include "../../../blackboard/bb_param/bb_node.h" +#include "../../../blackboard/bb_param/bb_string.h" +#include "../../../blackboard/bb_param/bb_variant.h" + +class BTEvaluateExpression : public BTAction { + GDCLASS(BTEvaluateExpression, BTAction); + TASK_CATEGORY(Utility); + +private: + Expression expression; + Error is_parsed = FAILED; + Ref node_param; + String expression_string; + Vector input_vars; + TypedArray input_values; + bool input_include_delta = false; + Vector processed_input_vars; + Array processed_input_values; + String result_var; + +protected: + static void _bind_methods(); + + virtual String _generate_name() override; + virtual void _setup() override; + virtual Status _tick(double p_delta) override; + +public: + void set_expression_string(String p_expression_string); + String get_expression_string() const { return expression_string; } + + void set_node_param(Ref p_object); + Ref get_node_param() const { return node_param; } + + void set_input_vars(Vector p_input_vars); + Vector get_input_vars() const { return input_vars; } + + void set_input_values(TypedArray p_input_values); + TypedArray get_input_values() const { return input_values; } + + void set_input_include_delta(bool p_input_include_delta); + bool is_delta_included() const { return input_include_delta; } + + void set_result_var(const String &p_result_var); + String get_result_var() const { return result_var; } + + virtual PackedStringArray get_configuration_warnings() override; + + BTEvaluateExpression(); +}; + +#endif // BT_EVALUATE_EXPRESSION_H diff --git a/config.py b/config.py index 875d0d31..fe81b90f 100644 --- a/config.py +++ b/config.py @@ -68,6 +68,7 @@ def get_doc_classes(): "BTAlwaysSucceed", "BTAwaitAnimation", "BTCallMethod", + "BTEvaluateExpression", "BTCheckAgentProperty", "BTCheckTrigger", "BTCheckVar", diff --git a/doc_classes/BTEvaluateExpression.xml b/doc_classes/BTEvaluateExpression.xml new file mode 100644 index 00000000..7880d8f9 --- /dev/null +++ b/doc_classes/BTEvaluateExpression.xml @@ -0,0 +1,35 @@ + + + + BT action that evaluates an [Expression] against a specified [Node] or [Object]. + + + BTEvaluateExpression action evaluates an [member expression_string] on the specified [Node] or [Object] instance and returns [code]SUCCESS[/code] when the [Expression] executes successfully. + Returns [code]FAILURE[/code] if the action encounters an issue during the [Expression] parsing or execution. + + + + + + The expression string to be parsed and executed. + [b]Warning:[/b] Call [method _setup] after updating [member expression_string] to update the internal [Expression] as it won't be updated automatically. + + + If enabled, the input variable [code]delta[/code] will be added to [member input_names] and [member input_values]. + [b]Warning:[/b] Call [method _setup] after toggling [member input_include_delta] to update the internal [Expression] as it won't be updated automatically. + + + List of values for variables specified in [member input_vars]. The values are mapped to the variables by their array index. + + + List of variables within the [member expression_string] for which the user will provide values for through [member input_values]. + [b]Warning:[/b] Call [method _setup] after updating [member input_vars] to update the internal [Expression] as it won't be updated automatically. + + + Specifies the [Node] or [Object] instance containing the method to be called. + + + if non-empty, assign the result of the method call to the blackboard variable specified by this property. + + + diff --git a/register_types.cpp b/register_types.cpp index d74a015b..d209c9f5 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -90,6 +90,7 @@ #include "bt/tasks/scene/bt_stop_animation.h" #include "bt/tasks/utility/bt_call_method.h" #include "bt/tasks/utility/bt_console_print.h" +#include "bt/tasks/utility/bt_evaluate_expression.h" #include "bt/tasks/utility/bt_fail.h" #include "bt/tasks/utility/bt_random_wait.h" #include "bt/tasks/utility/bt_wait.h" @@ -182,6 +183,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(BTCondition); LIMBO_REGISTER_TASK(BTAwaitAnimation); LIMBO_REGISTER_TASK(BTCallMethod); + LIMBO_REGISTER_TASK(BTEvaluateExpression); LIMBO_REGISTER_TASK(BTConsolePrint); LIMBO_REGISTER_TASK(BTFail); LIMBO_REGISTER_TASK(BTPauseAnimation);