Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BTEvaluateExpression #43

Merged
merged 1 commit into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions bt/tasks/utility/bt_evaluate_expression.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/**
* bt_evaluate_expression.cpp
* =============================================================================
* Copyright 2021-2023 Serhii Snitsaruk
Rubonnek marked this conversation as resolved.
Show resolved Hide resolved
* Copyright 2024 Wilson E. Alvarez
*
* 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_names(const PackedStringArray &p_input_names) {
input_names = p_input_names;
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_names;
processed_input_names.resize(input_names.size() + int(input_include_delta));
String *processed_input_names_ptr = processed_input_names.ptrw();
if (input_include_delta) {
processed_input_names_ptr[0] = "delta";
}
for (int i = 0; i < input_names.size(); ++i) {
processed_input_names_ptr[i + int(input_include_delta)] = input_names[i];
}

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

String BTEvaluateExpression::_generate_name() {
return vformat("EvaluateExpression %s node: %s %s",
!expression_string.is_empty() ? expression_string : "???",
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 = 0; i < input_values.size(); ++i) {
const Ref<BBVariant> &bb_variant = input_values[i];
processed_input_values[i + int(input_include_delta)] = 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_names", "p_input_names"), &BTEvaluateExpression::set_input_names);
ClassDB::bind_method(D_METHOD("get_input_names"), &BTEvaluateExpression::get_input_names);
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_names", PROPERTY_HINT_ARRAY_TYPE, "String"), "set_input_names", "get_input_names");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "input_values", PROPERTY_HINT_ARRAY_TYPE, RESOURCE_TYPE_HINT("BBVariant")), "set_input_values", "get_input_values");
}

BTEvaluateExpression::BTEvaluateExpression() {
}
77 changes: 77 additions & 0 deletions bt/tasks/utility/bt_evaluate_expression.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
Rubonnek marked this conversation as resolved.
Show resolved Hide resolved
* bt_evaluate_expression.h
* =============================================================================
* Copyright 2021-2023 Serhii Snitsaruk
* Copyright 2024 Wilson E. Alvarez
*
* 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_names;
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_names(const PackedStringArray &p_input_names);
PackedStringArray get_input_names() const { return input_names; }

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_names] 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_names" type="PackedStringArray" setter="set_input_names" getter="get_input_names" default="PackedStringArray()">
List of variable names within [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_names] 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_names]. The values are mapped to the variables by their array index.
</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
Loading