Skip to content

Commit fb958c7

Browse files
committed
Add BTEvaluateExpression
1 parent 8176d5c commit fb958c7

File tree

6 files changed

+498
-0
lines changed

6 files changed

+498
-0
lines changed
+159
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/**
2+
* bt_evaluate_expression.cpp
3+
* =============================================================================
4+
* Copyright 2021-2023 Serhii Snitsaruk
5+
* Copyright 2024 Wilson E. Alvarez
6+
*
7+
* Use of this source code is governed by an MIT-style
8+
* license that can be found in the LICENSE file or at
9+
* https://opensource.org/licenses/MIT.
10+
* =============================================================================
11+
*/
12+
13+
#include "bt_evaluate_expression.h"
14+
15+
#include "../../../util/limbo_compat.h"
16+
#include "../../../util/limbo_utility.h"
17+
18+
#ifdef LIMBOAI_GDEXTENSION
19+
#include "godot_cpp/classes/global_constants.hpp"
20+
#endif // LIMBOAI_GDEXTENSION
21+
22+
//**** Setters / Getters
23+
24+
void BTEvaluateExpression::set_expression_string(const String &p_expression_string) {
25+
expression_string = p_expression_string;
26+
emit_changed();
27+
}
28+
29+
void BTEvaluateExpression::set_node_param(Ref<BBNode> p_object) {
30+
node_param = p_object;
31+
emit_changed();
32+
if (Engine::get_singleton()->is_editor_hint() && node_param.is_valid()) {
33+
node_param->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed)));
34+
}
35+
}
36+
37+
void BTEvaluateExpression::set_input_include_delta(bool p_input_include_delta) {
38+
if (input_include_delta != p_input_include_delta) {
39+
processed_input_values.resize(input_values.size() + int(p_input_include_delta));
40+
}
41+
input_include_delta = p_input_include_delta;
42+
emit_changed();
43+
}
44+
45+
void BTEvaluateExpression::set_input_names(const PackedStringArray &p_input_names) {
46+
input_names = p_input_names;
47+
emit_changed();
48+
}
49+
50+
void BTEvaluateExpression::set_input_values(const TypedArray<BBVariant> &p_input_values) {
51+
if (input_values.size() != p_input_values.size()) {
52+
processed_input_values.resize(p_input_values.size() + int(input_include_delta));
53+
}
54+
input_values = p_input_values;
55+
emit_changed();
56+
}
57+
58+
void BTEvaluateExpression::set_result_var(const String &p_result_var) {
59+
result_var = p_result_var;
60+
emit_changed();
61+
}
62+
63+
//**** Task Implementation
64+
65+
PackedStringArray BTEvaluateExpression::get_configuration_warnings() {
66+
PackedStringArray warnings = BTAction::get_configuration_warnings();
67+
if (expression_string.is_empty()) {
68+
warnings.append("Expression string is not set.");
69+
}
70+
if (node_param.is_null()) {
71+
warnings.append("Node parameter is not set.");
72+
} else if (node_param->get_value_source() == BBParam::SAVED_VALUE && node_param->get_saved_value() == Variant()) {
73+
warnings.append("Path to node is not set.");
74+
} else if (node_param->get_value_source() == BBParam::BLACKBOARD_VAR && node_param->get_variable() == String()) {
75+
warnings.append("Node blackboard variable is not set.");
76+
}
77+
return warnings;
78+
}
79+
80+
void BTEvaluateExpression::_setup() {
81+
parse();
82+
ERR_FAIL_COND_MSG(is_parsed != Error::OK, "BTEvaluateExpression: Failed to parse expression: " + expression.get_error_text());
83+
}
84+
85+
Error BTEvaluateExpression::parse() {
86+
PackedStringArray processed_input_names;
87+
processed_input_names.resize(input_names.size() + int(input_include_delta));
88+
String *processed_input_names_ptr = processed_input_names.ptrw();
89+
if (input_include_delta) {
90+
processed_input_names_ptr[0] = "delta";
91+
}
92+
for (int i = 0; i < input_names.size(); ++i) {
93+
processed_input_names_ptr[i + int(input_include_delta)] = input_names[i];
94+
}
95+
96+
is_parsed = expression.parse(expression_string, processed_input_names);
97+
return is_parsed;
98+
}
99+
100+
String BTEvaluateExpression::_generate_name() {
101+
return vformat("EvaluateExpression %s node: %s %s",
102+
!expression_string.is_empty() ? expression_string : "???",
103+
node_param.is_valid() && !node_param->to_string().is_empty() ? node_param->to_string() : "???",
104+
result_var.is_empty() ? "" : LimboUtility::get_singleton()->decorate_output_var(result_var));
105+
}
106+
107+
BT::Status BTEvaluateExpression::_tick(double p_delta) {
108+
ERR_FAIL_COND_V_MSG(expression_string.is_empty(), FAILURE, "BTEvaluateExpression: Expression String is not set.");
109+
ERR_FAIL_COND_V_MSG(node_param.is_null(), FAILURE, "BTEvaluateExpression: Node parameter is not set.");
110+
Object *obj = node_param->get_value(get_agent(), get_blackboard());
111+
ERR_FAIL_COND_V_MSG(obj == nullptr, FAILURE, "BTEvaluateExpression: Failed to get object: " + node_param->to_string());
112+
ERR_FAIL_COND_V_MSG(is_parsed != Error::OK, FAILURE, "BTEvaluateExpression: Failed to parse expression: " + expression.get_error_text());
113+
114+
if (input_include_delta) {
115+
processed_input_values[0] = p_delta;
116+
}
117+
for (int i = 0; i < input_values.size(); ++i) {
118+
const Ref<BBVariant> &bb_variant = input_values[i];
119+
processed_input_values[i + int(input_include_delta)] = bb_variant->get_value(get_agent(), get_blackboard());
120+
}
121+
122+
Variant result = expression.execute(processed_input_values, obj, false);
123+
ERR_FAIL_COND_V_MSG(expression.has_execute_failed(), FAILURE, "BTEvaluateExpression: Failed to execute: " + expression.get_error_text());
124+
125+
if (!result_var.is_empty()) {
126+
get_blackboard()->set_var(result_var, result);
127+
}
128+
129+
return SUCCESS;
130+
}
131+
132+
//**** Godot
133+
134+
void BTEvaluateExpression::_bind_methods() {
135+
ClassDB::bind_method(D_METHOD("parse"), &BTEvaluateExpression::parse);
136+
ClassDB::bind_method(D_METHOD("set_expression_string", "p_method"), &BTEvaluateExpression::set_expression_string);
137+
ClassDB::bind_method(D_METHOD("get_expression_string"), &BTEvaluateExpression::get_expression_string);
138+
ClassDB::bind_method(D_METHOD("set_node_param", "p_param"), &BTEvaluateExpression::set_node_param);
139+
ClassDB::bind_method(D_METHOD("get_node_param"), &BTEvaluateExpression::get_node_param);
140+
ClassDB::bind_method(D_METHOD("set_input_names", "p_input_names"), &BTEvaluateExpression::set_input_names);
141+
ClassDB::bind_method(D_METHOD("get_input_names"), &BTEvaluateExpression::get_input_names);
142+
ClassDB::bind_method(D_METHOD("set_input_values", "p_input_values"), &BTEvaluateExpression::set_input_values);
143+
ClassDB::bind_method(D_METHOD("get_input_values"), &BTEvaluateExpression::get_input_values);
144+
ClassDB::bind_method(D_METHOD("set_input_include_delta", "p_input_include_delta"), &BTEvaluateExpression::set_input_include_delta);
145+
ClassDB::bind_method(D_METHOD("is_input_delta_included"), &BTEvaluateExpression::is_input_delta_included);
146+
ClassDB::bind_method(D_METHOD("set_result_var", "p_result_var"), &BTEvaluateExpression::set_result_var);
147+
ClassDB::bind_method(D_METHOD("get_result_var"), &BTEvaluateExpression::get_result_var);
148+
149+
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "BBNode"), "set_node_param", "get_node_param");
150+
ADD_PROPERTY(PropertyInfo(Variant::STRING, "expression_string"), "set_expression_string", "get_expression_string");
151+
ADD_PROPERTY(PropertyInfo(Variant::STRING, "result_var"), "set_result_var", "get_result_var");
152+
ADD_GROUP("Inputs", "input_");
153+
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_include_delta"), "set_input_include_delta", "is_input_delta_included");
154+
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "input_names", PROPERTY_HINT_ARRAY_TYPE, "String"), "set_input_names", "get_input_names");
155+
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "input_values", PROPERTY_HINT_ARRAY_TYPE, RESOURCE_TYPE_HINT("BBVariant")), "set_input_values", "get_input_values");
156+
}
157+
158+
BTEvaluateExpression::BTEvaluateExpression() {
159+
}
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* bt_evaluate_expression.h
3+
* =============================================================================
4+
* Copyright 2021-2023 Serhii Snitsaruk
5+
* Copyright 2024 Wilson E. Alvarez
6+
*
7+
* Use of this source code is governed by an MIT-style
8+
* license that can be found in the LICENSE file or at
9+
* https://opensource.org/licenses/MIT.
10+
* =============================================================================
11+
*/
12+
13+
#ifndef BT_EVALUATE_EXPRESSION_H
14+
#define BT_EVALUATE_EXPRESSION_H
15+
16+
#include "../bt_action.h"
17+
18+
#ifdef LIMBOAI_MODULE
19+
#include "core/math/expression.h"
20+
#endif
21+
22+
#ifdef LIMBOAI_GDEXTENSION
23+
#include <godot_cpp/classes/expression.hpp>
24+
#endif
25+
26+
#include "../../../blackboard/bb_param/bb_node.h"
27+
#include "../../../blackboard/bb_param/bb_variant.h"
28+
29+
class BTEvaluateExpression : public BTAction {
30+
GDCLASS(BTEvaluateExpression, BTAction);
31+
TASK_CATEGORY(Utility);
32+
33+
private:
34+
Expression expression;
35+
Error is_parsed = FAILED;
36+
Ref<BBNode> node_param;
37+
String expression_string;
38+
PackedStringArray input_names;
39+
TypedArray<BBVariant> input_values;
40+
bool input_include_delta = false;
41+
Array processed_input_values;
42+
String result_var;
43+
44+
protected:
45+
static void _bind_methods();
46+
47+
virtual String _generate_name() override;
48+
virtual void _setup() override;
49+
virtual Status _tick(double p_delta) override;
50+
51+
public:
52+
Error parse();
53+
54+
void set_expression_string(const String &p_expression_string);
55+
String get_expression_string() const { return expression_string; }
56+
57+
void set_node_param(Ref<BBNode> p_object);
58+
Ref<BBNode> get_node_param() const { return node_param; }
59+
60+
void set_input_names(const PackedStringArray &p_input_names);
61+
PackedStringArray get_input_names() const { return input_names; }
62+
63+
void set_input_values(const TypedArray<BBVariant> &p_input_values);
64+
TypedArray<BBVariant> get_input_values() const { return input_values; }
65+
66+
void set_input_include_delta(bool p_input_include_delta);
67+
bool is_input_delta_included() const { return input_include_delta; }
68+
69+
void set_result_var(const String &p_result_var);
70+
String get_result_var() const { return result_var; }
71+
72+
virtual PackedStringArray get_configuration_warnings() override;
73+
74+
BTEvaluateExpression();
75+
};
76+
77+
#endif // BT_EVALUATE_EXPRESSION_H

config.py

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def get_doc_classes():
6868
"BTAlwaysSucceed",
6969
"BTAwaitAnimation",
7070
"BTCallMethod",
71+
"BTEvaluateExpression",
7172
"BTCheckAgentProperty",
7273
"BTCheckTrigger",
7374
"BTCheckVar",

doc_classes/BTEvaluateExpression.xml

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<class name="BTEvaluateExpression" inherits="BTAction" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
3+
<brief_description>
4+
BT action that evaluates an [Expression] against a specified [Node] or [Object].
5+
</brief_description>
6+
<description>
7+
BTEvaluateExpression action evaluates an [member expression_string] on the specified [Node] or [Object] instance and returns [code]SUCCESS[/code] when the [Expression] executes successfully.
8+
Returns [code]FAILURE[/code] if the action encounters an issue during the [Expression] parsing or execution.
9+
</description>
10+
<tutorials>
11+
</tutorials>
12+
<methods>
13+
<method name="parse">
14+
<return type="int" enum="Error" />
15+
<description>
16+
Calls [method Expression.parse] considering [member input_include_delta] and [member input_names] and returns its error code.
17+
</description>
18+
</method>
19+
</methods>
20+
<members>
21+
<member name="expression_string" type="String" setter="set_expression_string" getter="get_expression_string" default="&quot;&quot;">
22+
The expression string to be parsed and executed.
23+
[b]Warning:[/b] Call [method parse] after updating [member expression_string] to update the internal [Expression] as it won't be updated automatically.
24+
</member>
25+
<member name="input_include_delta" type="bool" setter="set_input_include_delta" getter="is_input_delta_included" default="false">
26+
If enabled, the input variable [code]delta[/code] will be added to [member input_names] and [member input_values].
27+
[b]Warning:[/b] Call [method parse] after toggling [member input_include_delta] to update the internal [Expression] as it won't be updated automatically.
28+
</member>
29+
<member name="input_names" type="PackedStringArray" setter="set_input_names" getter="get_input_names" default="PackedStringArray()">
30+
List of variable names within [member expression_string] for which the user will provide values for through [member input_values].
31+
[b]Warning:[/b] Call [method parse] after updating [member input_names] to update the internal [Expression] as it won't be updated automatically.
32+
</member>
33+
<member name="input_values" type="BBVariant[]" setter="set_input_values" getter="get_input_values" default="[]">
34+
List of values for variables specified in [member input_names]. The values are mapped to the variables by their array index.
35+
</member>
36+
<member name="node" type="BBNode" setter="set_node_param" getter="get_node_param">
37+
Specifies the [Node] or [Object] instance containing the method to be called.
38+
</member>
39+
<member name="result_var" type="String" setter="set_result_var" getter="get_result_var" default="&quot;&quot;">
40+
if non-empty, assign the result of the method call to the blackboard variable specified by this property.
41+
</member>
42+
</members>
43+
</class>

register_types.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
#include "bt/tasks/scene/bt_stop_animation.h"
9191
#include "bt/tasks/utility/bt_call_method.h"
9292
#include "bt/tasks/utility/bt_console_print.h"
93+
#include "bt/tasks/utility/bt_evaluate_expression.h"
9394
#include "bt/tasks/utility/bt_fail.h"
9495
#include "bt/tasks/utility/bt_random_wait.h"
9596
#include "bt/tasks/utility/bt_wait.h"
@@ -182,6 +183,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) {
182183
GDREGISTER_CLASS(BTCondition);
183184
LIMBO_REGISTER_TASK(BTAwaitAnimation);
184185
LIMBO_REGISTER_TASK(BTCallMethod);
186+
LIMBO_REGISTER_TASK(BTEvaluateExpression);
185187
LIMBO_REGISTER_TASK(BTConsolePrint);
186188
LIMBO_REGISTER_TASK(BTFail);
187189
LIMBO_REGISTER_TASK(BTPauseAnimation);

0 commit comments

Comments
 (0)