limboai/bt/tasks/bt_task.cpp

470 lines
14 KiB
C++
Raw Normal View History

/**
* bt_task.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.
* =============================================================================
*/
2022-08-28 10:54:34 +00:00
#include "bt_task.h"
#ifdef LIMBOAI_MODULE
#include "bt_comment.h"
2023-07-20 16:35:36 +00:00
#include "modules/limboai/blackboard/blackboard.h"
#include "modules/limboai/util/limbo_string_names.h"
#include "modules/limboai/util/limbo_utility.h"
#include "core/error/error_macros.h"
#include "core/io/resource.h"
#include "core/object/class_db.h"
#include "core/object/object.h"
#include "core/object/ref_counted.h"
#include "core/object/script_language.h"
#include "core/string/ustring.h"
#include "core/templates/hash_map.h"
#include "core/variant/variant.h"
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION
#include "blackboard/blackboard.h"
#include "bt/tasks/bt_comment.h"
#include "util/limbo_string_names.h"
2024-01-09 20:47:22 +00:00
#include "godot_cpp/classes/global_constants.hpp"
#include "godot_cpp/core/class_db.hpp"
#include "godot_cpp/variant/dictionary.hpp"
#include "godot_cpp/variant/string_name.hpp"
#include "godot_cpp/variant/typed_array.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
#include "godot_cpp/variant/variant.hpp"
#include <godot_cpp/classes/ref.hpp>
#endif // LIMBOAI_GDEXTENSION
2022-08-28 10:54:34 +00:00
void BT::_bind_methods() {
BIND_ENUM_CONSTANT(FRESH);
BIND_ENUM_CONSTANT(RUNNING);
BIND_ENUM_CONSTANT(FAILURE);
BIND_ENUM_CONSTANT(SUCCESS);
}
String BTTask::_generate_name() {
#ifdef LIMBOAI_MODULE
2022-08-28 10:54:34 +00:00
if (get_script_instance()) {
if (get_script_instance()->has_method(LimboStringNames::get_singleton()->_generate_name)) {
ERR_FAIL_COND_V_MSG(!get_script_instance()->get_script()->is_tool(), "ERROR: not a tool script", "Task script should be a \"tool\" script!");
2022-08-28 10:54:34 +00:00
return get_script_instance()->call(LimboStringNames::get_singleton()->_generate_name);
}
2023-07-20 18:10:02 +00:00
String script_path = get_script_instance()->get_script()->get_path();
if (!script_path.is_empty()) {
2022-08-28 10:54:34 +00:00
// Generate name based on script file
2023-07-20 18:10:02 +00:00
return script_path.get_basename().get_file().trim_prefix("BT").to_pascal_case();
2022-08-28 10:54:34 +00:00
}
}
return get_class().trim_prefix("BT");
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION
if (!get_path().is_empty()) {
return get_path().get_basename().get_file().trim_prefix("BT").to_pascal_case();
}
return get_class().trim_prefix("BT");
#endif // LIMBOAI_GDEXTENSION
2022-08-28 10:54:34 +00:00
}
Array BTTask::_get_children() const {
Array arr;
int num_children = get_child_count();
arr.resize(num_children);
for (int i = 0; i < num_children; i++) {
arr[i] = get_child(i).ptr();
}
return arr;
}
void BTTask::_set_children(Array p_children) {
2023-07-20 18:10:02 +00:00
data.children.clear();
2022-08-28 10:54:34 +00:00
const int num_children = p_children.size();
2023-07-20 18:10:02 +00:00
data.children.resize(num_children);
2022-08-28 10:54:34 +00:00
for (int i = 0; i < num_children; i++) {
Variant task_var = p_children[i];
Ref<BTTask> task_ref = task_var;
2023-07-20 18:10:02 +00:00
task_ref->data.parent = this;
task_ref->data.index = i;
2023-07-20 18:10:02 +00:00
data.children.set(i, task_var);
2022-08-28 10:54:34 +00:00
}
}
String BTTask::get_task_name() {
2023-07-20 18:10:02 +00:00
if (data.custom_name.is_empty()) {
return call(LimboStringNames::get_singleton()->_generate_name);
2022-08-28 10:54:34 +00:00
}
2023-07-20 18:10:02 +00:00
return data.custom_name;
2022-08-28 10:54:34 +00:00
}
Ref<BTTask> BTTask::get_root() const {
const BTTask *task = this;
while (!task->is_root()) {
2023-07-20 18:10:02 +00:00
task = task->data.parent;
2022-08-28 10:54:34 +00:00
}
return Ref<BTTask>(task);
}
void BTTask::set_custom_name(const String &p_name) {
2023-07-20 18:10:02 +00:00
if (data.custom_name != p_name) {
data.custom_name = p_name;
2022-08-28 10:54:34 +00:00
emit_changed();
}
};
2022-12-17 07:33:18 +00:00
void BTTask::initialize(Node *p_agent, const Ref<Blackboard> &p_blackboard) {
ERR_FAIL_COND(p_agent == nullptr);
ERR_FAIL_COND(p_blackboard == nullptr);
2023-07-20 18:10:02 +00:00
data.agent = p_agent;
data.blackboard = p_blackboard;
for (int i = 0; i < data.children.size(); i++) {
2022-08-28 10:54:34 +00:00
get_child(i)->initialize(p_agent, p_blackboard);
}
#ifdef LIMBOAI_MODULE
if (!GDVIRTUAL_CALL(_setup)) {
2022-08-28 10:54:34 +00:00
_setup();
}
#endif
#ifdef LIMBOAI_GDEXTENSION
call(LimboStringNames::get_singleton()->_setup);
#endif
2022-08-28 10:54:34 +00:00
}
Ref<BTTask> BTTask::clone() const {
2022-09-02 22:08:10 +00:00
Ref<BTTask> inst = duplicate(false);
2023-07-20 18:10:02 +00:00
inst->data.parent = nullptr;
inst->data.agent = nullptr;
inst->data.blackboard.unref();
2023-08-18 19:38:28 +00:00
int num_null = 0;
2023-07-20 18:10:02 +00:00
for (int i = 0; i < data.children.size(); i++) {
2022-08-28 10:54:34 +00:00
Ref<BTTask> c = get_child(i)->clone();
2023-08-18 19:38:28 +00:00
if (c.is_valid()) {
c->data.parent = inst.ptr();
c->data.index = i;
2023-08-18 19:38:28 +00:00
inst->data.children.set(i - num_null, c);
} else {
num_null += 1;
}
}
if (num_null > 0) {
// * BTComment tasks return nullptr at runtime - we remove those.
inst->data.children.resize(data.children.size() - num_null);
2022-08-28 10:54:34 +00:00
}
#ifdef LIMBOAI_MODULE
// Make BBParam properties unique.
List<PropertyInfo> props;
inst->get_property_list(&props);
HashMap<Ref<Resource>, Ref<Resource>> duplicates;
for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
continue;
}
Variant v = inst->get(E->get().name);
if (v.is_ref_counted()) {
Ref<RefCounted> ref = v;
if (ref.is_valid()) {
Ref<Resource> res = ref;
if (res.is_valid() && res->is_class("BBParam")) {
if (!duplicates.has(res)) {
duplicates[res] = res->duplicate();
}
res = duplicates[res];
inst->set(E->get().name, res);
}
}
}
}
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION
// Make BBParam properties unique.
TypedArray<Dictionary> props = inst->get_property_list();
HashMap<Ref<Resource>, Ref<Resource>> duplicates;
for (int i = 0; i < props.size(); i++) {
Dictionary prop = props[i];
if (!(int(prop["usage"]) & PROPERTY_USAGE_STORAGE)) {
continue;
}
StringName prop_name = prop["name"];
Variant v = inst->get(prop_name);
if (v.get_type() == Variant::OBJECT && int(prop["hint"]) == PROPERTY_HINT_RESOURCE_TYPE) {
Ref<RefCounted> ref = v;
if (ref.is_valid()) {
Ref<Resource> res = ref;
if (res.is_valid() && res->is_class("BBParam")) {
if (!duplicates.has(res)) {
duplicates[res] = res->duplicate();
}
res = duplicates[res];
inst->set(prop_name, res);
}
}
}
}
#endif // LIMBOAI_GDEXTENSION
2022-08-28 10:54:34 +00:00
return inst;
}
BT::Status BTTask::execute(double p_delta) {
2023-07-20 18:10:02 +00:00
if (data.status != RUNNING) {
// Reset children status.
2023-07-20 18:10:02 +00:00
if (data.status != FRESH) {
for (int i = 0; i < get_child_count(); i++) {
data.children.get(i)->abort();
}
}
#ifdef LIMBOAI_MODULE
if (!GDVIRTUAL_CALL(_enter)) {
2022-08-28 10:54:34 +00:00
_enter();
}
#endif
#ifdef LIMBOAI_GDEXTENSION
call(LimboStringNames::get_singleton()->_enter);
#endif
} else {
2023-07-20 18:10:02 +00:00
data.elapsed += p_delta;
2022-08-28 10:54:34 +00:00
}
2022-08-31 15:05:25 +00:00
#ifdef LIMBOAI_MODULE
2023-07-20 18:10:02 +00:00
if (!GDVIRTUAL_CALL(_tick, p_delta, data.status)) {
data.status = _tick(p_delta);
2022-08-28 10:54:34 +00:00
}
#endif
#ifdef LIMBOAI_GDEXTENSION
data.status = (Status)(int)call(LimboStringNames::get_singleton()->_tick, p_delta);
#endif
2022-08-28 10:54:34 +00:00
2023-07-20 18:10:02 +00:00
if (data.status != RUNNING) {
#ifdef LIMBOAI_MODULE
if (!GDVIRTUAL_CALL(_exit)) {
2022-08-28 10:54:34 +00:00
_exit();
}
#endif
#ifdef LIMBOAI_GDEXTENSION
call(LimboStringNames::get_singleton()->_exit);
#endif
2023-07-20 18:10:02 +00:00
data.elapsed = 0.0;
2022-08-28 10:54:34 +00:00
}
2023-07-20 18:10:02 +00:00
return data.status;
2022-08-28 10:54:34 +00:00
}
void BTTask::abort() {
2023-07-20 18:10:02 +00:00
for (int i = 0; i < data.children.size(); i++) {
get_child(i)->abort();
2022-08-28 10:54:34 +00:00
}
2023-07-20 18:10:02 +00:00
if (data.status == RUNNING) {
#ifdef LIMBOAI_MODULE
if (!GDVIRTUAL_CALL(_exit)) {
2022-08-28 10:54:34 +00:00
_exit();
}
#endif
#ifdef LIMBOAI_GDEXTENSION
call(LimboStringNames::get_singleton()->_exit);
#endif
2022-08-28 10:54:34 +00:00
}
2023-07-20 18:10:02 +00:00
data.status = FRESH;
data.elapsed = 0.0;
2022-08-28 10:54:34 +00:00
}
int BTTask::get_child_count_excluding_comments() const {
int count = 0;
for (int i = 0; i < data.children.size(); i++) {
if (!IS_CLASS(data.children[i], BTComment)) {
count += 1;
}
}
return count;
}
2022-08-28 10:54:34 +00:00
void BTTask::add_child(Ref<BTTask> p_child) {
ERR_FAIL_COND_MSG(p_child->get_parent().is_valid(), "p_child already has a parent!");
2023-07-20 18:10:02 +00:00
p_child->data.parent = this;
p_child->data.index = data.children.size();
2023-07-20 18:10:02 +00:00
data.children.push_back(p_child);
2022-08-28 10:54:34 +00:00
emit_changed();
}
void BTTask::add_child_at_index(Ref<BTTask> p_child, int p_idx) {
ERR_FAIL_COND_MSG(p_child->get_parent().is_valid(), "p_child already has a parent!");
2023-07-20 18:10:02 +00:00
if (p_idx < 0 || p_idx > data.children.size()) {
p_idx = data.children.size();
2022-08-28 10:54:34 +00:00
}
2023-07-20 18:10:02 +00:00
data.children.insert(p_idx, p_child);
p_child->data.parent = this;
p_child->data.index = p_idx;
for (int i = p_idx + 1; i < data.children.size(); i++) {
get_child(i)->data.index = i;
}
2022-08-28 10:54:34 +00:00
emit_changed();
}
void BTTask::remove_child(Ref<BTTask> p_child) {
2023-07-20 18:10:02 +00:00
int idx = data.children.find(p_child);
ERR_FAIL_COND_MSG(idx == -1, "p_child not found!");
data.children.remove_at(idx);
p_child->data.parent = nullptr;
p_child->data.index = -1;
for (int i = idx; i < data.children.size(); i++) {
get_child(i)->data.index = i;
2022-08-28 10:54:34 +00:00
}
emit_changed();
2022-08-28 10:54:34 +00:00
}
2022-09-21 14:13:17 +00:00
void BTTask::remove_child_at_index(int p_idx) {
ERR_FAIL_INDEX(p_idx, get_child_count());
data.children[p_idx]->data.parent = nullptr;
data.children[p_idx]->data.index = -1;
2023-07-20 18:10:02 +00:00
data.children.remove_at(p_idx);
for (int i = p_idx; i < data.children.size(); i++) {
get_child(i)->data.index = i;
}
emit_changed();
2022-09-21 14:13:17 +00:00
}
bool BTTask::is_descendant_of(const Ref<BTTask> &p_task) const {
const BTTask *task = this;
while (task != nullptr) {
2023-07-20 18:10:02 +00:00
task = task->data.parent;
if (task == p_task.ptr()) {
return true;
}
}
return false;
}
2022-08-28 10:54:34 +00:00
Ref<BTTask> BTTask::next_sibling() const {
2023-07-20 18:10:02 +00:00
if (data.parent != nullptr) {
if (get_index() != -1 && data.parent->get_child_count() > (get_index() + 1)) {
return data.parent->get_child(get_index() + 1);
2022-08-28 10:54:34 +00:00
}
}
return Ref<BTTask>();
}
PackedStringArray BTTask::get_configuration_warnings() {
PackedStringArray ret;
PackedStringArray warnings;
#ifdef LIMBOAI_MODULE
if (GDVIRTUAL_CALL(_get_configuration_warning, warnings)) {
ret.append_array(warnings);
}
#endif
#ifdef LIMBOAI_GDEXTENSION
warnings = call(LimboStringNames::get_singleton()->_get_configuration_warning);
ret.append_array(warnings);
#endif
return ret;
2022-08-28 10:54:34 +00:00
}
void BTTask::print_tree(int p_initial_tabs) {
2022-08-28 10:54:34 +00:00
String tabs = "--";
for (int i = 0; i < p_initial_tabs; i++) {
tabs += "--";
}
2024-01-09 12:34:24 +00:00
PRINT_LINE(vformat("%s Name: %s Instance: %s", tabs, get_task_name(), Ref<BTTask>(this)));
2022-08-28 10:54:34 +00:00
for (int i = 0; i < get_child_count(); i++) {
get_child(i)->print_tree(p_initial_tabs + 1);
}
}
void BTTask::_bind_methods() {
2022-12-17 07:33:18 +00:00
// Public Methods.
ClassDB::bind_method(D_METHOD("is_root"), &BTTask::is_root);
ClassDB::bind_method(D_METHOD("get_root"), &BTTask::get_root);
ClassDB::bind_method(D_METHOD("initialize", "p_agent", "p_blackboard"), &BTTask::initialize);
ClassDB::bind_method(D_METHOD("clone"), &BTTask::clone);
ClassDB::bind_method(D_METHOD("execute", "p_delta"), &BTTask::execute);
ClassDB::bind_method(D_METHOD("get_child", "p_idx"), &BTTask::get_child);
ClassDB::bind_method(D_METHOD("get_child_count"), &BTTask::get_child_count);
ClassDB::bind_method(D_METHOD("get_child_count_excluding_comments"), &BTTask::get_child_count_excluding_comments);
2022-12-17 07:33:18 +00:00
ClassDB::bind_method(D_METHOD("add_child", "p_child"), &BTTask::add_child);
ClassDB::bind_method(D_METHOD("add_child_at_index", "p_child", "p_idx"), &BTTask::add_child_at_index);
ClassDB::bind_method(D_METHOD("remove_child", "p_child"), &BTTask::remove_child);
ClassDB::bind_method(D_METHOD("remove_child_at_index", "p_idx"), &BTTask::remove_child_at_index);
ClassDB::bind_method(D_METHOD("has_child", "p_child"), &BTTask::has_child);
ClassDB::bind_method(D_METHOD("is_descendant_of", "p_task"), &BTTask::is_descendant_of);
ClassDB::bind_method(D_METHOD("get_index"), &BTTask::get_index);
2022-12-17 07:33:18 +00:00
ClassDB::bind_method(D_METHOD("next_sibling"), &BTTask::next_sibling);
ClassDB::bind_method(D_METHOD("print_tree", "p_initial_tabs"), &BTTask::print_tree, Variant(0));
ClassDB::bind_method(D_METHOD("get_task_name"), &BTTask::get_task_name);
ClassDB::bind_method(D_METHOD("abort"), &BTTask::abort);
2022-10-24 22:47:22 +00:00
2022-12-17 07:33:18 +00:00
// Properties, setters and getters.
2022-08-28 10:54:34 +00:00
ClassDB::bind_method(D_METHOD("get_agent"), &BTTask::get_agent);
2022-10-24 22:47:22 +00:00
ClassDB::bind_method(D_METHOD("set_agent", "p_agent"), &BTTask::set_agent);
ClassDB::bind_method(D_METHOD("_get_children"), &BTTask::_get_children);
ClassDB::bind_method(D_METHOD("_set_children", "p_children"), &BTTask::_set_children);
2022-08-28 10:54:34 +00:00
ClassDB::bind_method(D_METHOD("get_blackboard"), &BTTask::get_blackboard);
ClassDB::bind_method(D_METHOD("get_parent"), &BTTask::get_parent);
2022-10-24 22:47:22 +00:00
ClassDB::bind_method(D_METHOD("get_status"), &BTTask::get_status);
ClassDB::bind_method(D_METHOD("get_elapsed_time"), &BTTask::get_elapsed_time);
ClassDB::bind_method(D_METHOD("get_custom_name"), &BTTask::get_custom_name);
ClassDB::bind_method(D_METHOD("set_custom_name", "p_name"), &BTTask::set_custom_name);
2022-10-24 22:47:22 +00:00
ADD_PROPERTY(PropertyInfo(Variant::STRING, "custom_name"), "set_custom_name", "get_custom_name");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "agent", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_agent", "get_agent");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "blackboard", PROPERTY_HINT_RESOURCE_TYPE, "Blackboard", PROPERTY_USAGE_NONE), "", "get_blackboard");
// ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "BTTask", 0), "", "get_parent");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "children", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_children", "_get_children");
ADD_PROPERTY(PropertyInfo(Variant::INT, "status", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_status");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "elapsed_time", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_elapsed_time");
2022-08-28 10:54:34 +00:00
#ifdef LIMBOAI_MODULE
GDVIRTUAL_BIND(_setup);
GDVIRTUAL_BIND(_enter);
GDVIRTUAL_BIND(_exit);
GDVIRTUAL_BIND(_tick, "p_delta");
GDVIRTUAL_BIND(_generate_name);
GDVIRTUAL_BIND(_get_configuration_warning);
#endif // LIMBOAI_MODULE
#ifdef LIMBOAI_GDEXTENSION
2024-01-09 12:34:24 +00:00
// TODO: Until virtual functions are implemented in godot-cpp, we do this. Replace this code when possible.
ClassDB::bind_method(D_METHOD("_setup"), &BTTask::_setup);
ClassDB::bind_method(D_METHOD("_enter"), &BTTask::_enter);
ClassDB::bind_method(D_METHOD("_exit"), &BTTask::_exit);
ClassDB::bind_method(D_METHOD("_tick", "p_delta"), &BTTask::_tick);
ClassDB::bind_method(D_METHOD("_generate_name"), &BTTask::_generate_name);
ClassDB::bind_method(D_METHOD("_get_configuration_warnings"), &BTTask::get_configuration_warnings);
2024-01-09 20:47:22 +00:00
// BIND_VIRTUAL_METHOD(BTTask, _setup);
// BIND_VIRTUAL_METHOD(BTTask, _enter);
// BIND_VIRTUAL_METHOD(BTTask, _exit);
// BIND_VIRTUAL_METHOD(BTTask, _tick);
// BIND_VIRTUAL_METHOD(BTTask, _generate_name);
// BIND_VIRTUAL_METHOD(BTTask, get_configuration_warnings);
#endif
2022-08-28 10:54:34 +00:00
}
BTTask::BTTask() {
}
BTTask::~BTTask() {
for (int i = 0; i < get_child_count(); i++) {
ERR_FAIL_COND(!get_child(i).is_valid());
2023-07-20 18:10:02 +00:00
get_child(i)->data.parent = nullptr;
get_child(i).unref();
}
}