Skip to content

Commit

Permalink
Add BTEvaluateExpression
Browse files Browse the repository at this point in the history
  • Loading branch information
Rubonnek committed Feb 18, 2024
1 parent 27327cf commit 9526e73
Show file tree
Hide file tree
Showing 6 changed files with 450 additions and 0 deletions.
168 changes: 168 additions & 0 deletions bt/tasks/utility/bt_evaluate_expression.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
* 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(const String &p_expression_string) {
expression_string = p_expression_string;
emit_changed();
}

void BTEvaluateExpression::set_node_param(Ref<BBNode> 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_values.resize(input_values.size() + int(p_input_include_delta));
}
input_include_delta = p_input_include_delta;
emit_changed();
}

void BTEvaluateExpression::set_input_vars(const PackedStringArray &p_input_vars) {
if (input_vars.size() != p_input_vars.size()) {
}
input_vars = p_input_vars;
emit_changed();
}

void BTEvaluateExpression::set_input_values(const TypedArray<BBVariant> &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() {
parse();
ERR_FAIL_COND_MSG(is_parsed != Error::OK, "BTEvaluateExpression: Failed to parse expression: " + expression.get_error_text());
}

Error BTEvaluateExpression::parse() {
PackedStringArray processed_input_vars;
processed_input_vars.resize(input_vars.size() + int(input_include_delta));
String *processed_input_vars_ptr = processed_input_vars.ptrw();
if (input_include_delta) {
processed_input_vars_ptr[0] = "delta";
}
for (int i = int(input_include_delta); i < input_vars.size(); ++i) {
processed_input_vars_ptr[i] = input_vars[i];
}

is_parsed = expression.parse(expression_string, processed_input_vars);
return is_parsed;
}

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.is_empty() ? 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.is_empty(), 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<BBVariant> &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("parse"), &BTEvaluateExpression::parse);
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_input_delta_included"), &BTEvaluateExpression::is_input_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_input_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() {
}
76 changes: 76 additions & 0 deletions bt/tasks/utility/bt_evaluate_expression.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 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"

#ifdef LIMBOAI_MODULE
#include "core/math/expression.h"
#endif

#ifdef LIMBOAI_GDEXTENSION
#include <godot_cpp/classes/expression.hpp>
#endif

#include "../../../blackboard/bb_param/bb_node.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<BBNode> node_param;
String expression_string;
PackedStringArray input_vars;
TypedArray<BBVariant> input_values;
bool input_include_delta = false;
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:
Error parse();

void set_expression_string(const String &p_expression_string);
String get_expression_string() const { return expression_string; }

void set_node_param(Ref<BBNode> p_object);
Ref<BBNode> get_node_param() const { return node_param; }

void set_input_vars(const PackedStringArray &p_input_vars);
PackedStringArray get_input_vars() const { return input_vars; }

void set_input_values(const TypedArray<BBVariant> &p_input_values);
TypedArray<BBVariant> get_input_values() const { return input_values; }

void set_input_include_delta(bool p_input_include_delta);
bool is_input_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
1 change: 1 addition & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def get_doc_classes():
"BTAlwaysSucceed",
"BTAwaitAnimation",
"BTCallMethod",
"BTEvaluateExpression",
"BTCheckAgentProperty",
"BTCheckTrigger",
"BTCheckVar",
Expand Down
43 changes: 43 additions & 0 deletions doc_classes/BTEvaluateExpression.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="BTEvaluateExpression" inherits="BTAction" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
BT action that evaluates an [Expression] against a specified [Node] or [Object].
</brief_description>
<description>
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.
</description>
<tutorials>
</tutorials>
<methods>
<method name="parse">
<return type="int" enum="Error" />
<description>
Calls [method Expression.parse] considering [member input_include_delta] and [member input_vars] and returns its error code.
</description>
</method>
</methods>
<members>
<member name="expression_string" type="String" setter="set_expression_string" getter="get_expression_string" default="&quot;&quot;">
The expression string to be parsed and executed.
[b]Warning:[/b] Call [method parse] after updating [member expression_string] to update the internal [Expression] as it won't be updated automatically.
</member>
<member name="input_include_delta" type="bool" setter="set_input_include_delta" getter="is_input_delta_included" default="false">
If enabled, the input variable [code]delta[/code] will be added to [member input_names] and [member input_values].
[b]Warning:[/b] Call [method parse] after toggling [member input_include_delta] to update the internal [Expression] as it won't be updated automatically.
</member>
<member name="input_values" type="BBVariant[]" setter="set_input_values" getter="get_input_values" default="[]">
List of values for variables specified in [member input_vars]. The values are mapped to the variables by their array index.
</member>
<member name="input_vars" type="PackedStringArray" setter="set_input_vars" getter="get_input_vars" default="PackedStringArray()">
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 parse] after updating [member input_vars] to update the internal [Expression] as it won't be updated automatically.
</member>
<member name="node" type="BBNode" setter="set_node_param" getter="get_node_param">
Specifies the [Node] or [Object] instance containing the method to be called.
</member>
<member name="result_var" type="String" setter="set_result_var" getter="get_result_var" default="&quot;&quot;">
if non-empty, assign the result of the method call to the blackboard variable specified by this property.
</member>
</members>
</class>
2 changes: 2 additions & 0 deletions register_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 9526e73

Please sign in to comment.