Fix drag & drop position in the same branch

Also, refactor get_child_index() into get_index().
This commit is contained in:
Serhii Snitsaruk 2023-11-22 13:05:55 +01:00
parent 6347311749
commit 60012d984b
6 changed files with 83 additions and 53 deletions

View File

@ -67,6 +67,7 @@ void BTTask::_set_children(Array p_children) {
Variant task_var = p_children[i];
Ref<BTTask> task_ref = task_var;
task_ref->data.parent = this;
task_ref->data.index = i;
data.children.set(i, task_var);
}
}
@ -117,6 +118,7 @@ Ref<BTTask> BTTask::clone() const {
Ref<BTTask> c = get_child(i)->clone();
if (c.is_valid()) {
c->data.parent = inst.ptr();
c->data.index = i;
inst->data.children.set(i - num_null, c);
} else {
num_null += 1;
@ -219,6 +221,7 @@ int BTTask::get_child_count_excluding_comments() const {
void BTTask::add_child(Ref<BTTask> p_child) {
ERR_FAIL_COND_MSG(p_child->get_parent().is_valid(), "p_child already has a parent!");
p_child->data.parent = this;
p_child->data.index = data.children.size();
data.children.push_back(p_child);
emit_changed();
}
@ -230,23 +233,34 @@ void BTTask::add_child_at_index(Ref<BTTask> p_child, int p_idx) {
}
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;
}
emit_changed();
}
void BTTask::remove_child(Ref<BTTask> p_child) {
int idx = data.children.find(p_child);
if (idx == -1) {
ERR_FAIL_MSG("p_child not found!");
} else {
data.children.remove_at(idx);
p_child->data.parent = nullptr;
emit_changed();
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;
}
emit_changed();
}
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;
data.children.remove_at(p_idx);
for (int i = p_idx; i < data.children.size(); i++) {
get_child(i)->data.index = i;
}
emit_changed();
}
bool BTTask::has_child(const Ref<BTTask> &p_child) const {
@ -264,15 +278,10 @@ bool BTTask::is_descendant_of(const Ref<BTTask> &p_task) const {
return false;
}
int BTTask::get_child_index(const Ref<BTTask> &p_child) const {
return data.children.find(p_child);
}
Ref<BTTask> BTTask::next_sibling() const {
if (data.parent != nullptr) {
int idx = data.parent->get_child_index(Ref<BTTask>(this));
if (idx != -1 && data.parent->get_child_count() > (idx + 1)) {
return data.parent->get_child(idx + 1);
if (get_index() != -1 && data.parent->get_child_count() > (get_index() + 1)) {
return data.parent->get_child(get_index() + 1);
}
}
return Ref<BTTask>();
@ -316,7 +325,7 @@ void BTTask::_bind_methods() {
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_child_index", "p_child"), &BTTask::get_child_index);
ClassDB::bind_method(D_METHOD("get_index"), &BTTask::get_index);
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);
@ -351,12 +360,6 @@ void BTTask::_bind_methods() {
}
BTTask::BTTask() {
data.custom_name = String();
data.agent = nullptr;
data.parent = nullptr;
data.children = Vector<Ref<BTTask>>();
data.status = FRESH;
data.elapsed = 0.0;
}
BTTask::~BTTask() {

View File

@ -20,6 +20,7 @@
#include "core/object/ref_counted.h"
#include "core/string/ustring.h"
#include "core/templates/vector.h"
#include "core/typedefs.h"
#include "core/variant/array.h"
#include "core/variant/binder_common.h"
#include "core/variant/dictionary.h"
@ -55,13 +56,14 @@ private:
// Avoid namespace pollution in derived classes.
struct Data {
int index = -1;
String custom_name;
Node *agent;
Node *agent = nullptr;
Ref<Blackboard> blackboard;
BTTask *parent;
BTTask *parent = nullptr;
Vector<Ref<BTTask>> children;
Status status;
double elapsed;
Status status = FRESH;
double elapsed = 0.0;
} data;
Array _get_children() const;
@ -116,7 +118,7 @@ public:
void remove_child_at_index(int p_idx);
bool has_child(const Ref<BTTask> &p_child) const;
bool is_descendant_of(const Ref<BTTask> &p_task) const;
int get_child_index(const Ref<BTTask> &p_child) const;
_FORCE_INLINE_ int get_index() const { return data.index; }
Ref<BTTask> next_sibling() const;
void print_tree(int p_initial_tabs = 0) const;

View File

@ -108,11 +108,10 @@
Returns the number of child tasks not counting [BTComment] tasks.
</description>
</method>
<method name="get_child_index" qualifiers="const">
<method name="get_index" qualifiers="const">
<return type="int" />
<param index="0" name="p_child" type="BTTask" />
<description>
Returns the child task's index. If [code]p_child[/code] is not a child of the task, [code]-1[/code] is returned instead.
Returns the task's position in the behavior tree branch. Returns [code]-1[/code] if the task doesn't belong to a task tree, i.e. doesn't have a parent.
</description>
</method>
<method name="get_parent" qualifiers="const">

View File

@ -105,7 +105,7 @@ void LimboAIEditor::_remove_task(const Ref<BTTask> &p_task) {
undo_redo->add_undo_method(task_tree->get_bt().ptr(), SNAME("set_root_task"), task_tree->get_bt()->get_root_task());
} else {
undo_redo->add_do_method(p_task->get_parent().ptr(), SNAME("remove_child"), p_task);
undo_redo->add_undo_method(p_task->get_parent().ptr(), SNAME("add_child_at_index"), p_task, p_task->get_parent()->get_child_index(p_task));
undo_redo->add_undo_method(p_task->get_parent().ptr(), SNAME("add_child_at_index"), p_task, p_task->get_index());
}
undo_redo->add_do_method(task_tree, SNAME("update_tree"));
undo_redo->add_undo_method(task_tree, SNAME("update_tree"));
@ -347,7 +347,7 @@ void LimboAIEditor::_action_selected(int p_id) {
Ref<BTTask> sel = task_tree->get_selected();
if (sel.is_valid() && sel->get_parent().is_valid()) {
Ref<BTTask> parent = sel->get_parent();
int idx = parent->get_child_index(sel);
int idx = sel->get_index();
if (idx > 0 && idx < parent->get_child_count()) {
undo_redo->create_action(TTR("Move BT Task"));
undo_redo->add_do_method(parent.ptr(), SNAME("remove_child"), sel);
@ -365,7 +365,7 @@ void LimboAIEditor::_action_selected(int p_id) {
Ref<BTTask> sel = task_tree->get_selected();
if (sel.is_valid() && sel->get_parent().is_valid()) {
Ref<BTTask> parent = sel->get_parent();
int idx = parent->get_child_index(sel);
int idx = sel->get_index();
if (idx >= 0 && idx < (parent->get_child_count() - 1)) {
undo_redo->create_action(TTR("Move BT Task"));
undo_redo->add_do_method(parent.ptr(), SNAME("remove_child"), sel);
@ -388,7 +388,7 @@ void LimboAIEditor::_action_selected(int p_id) {
parent = sel;
}
const Ref<BTTask> &sel_dup = sel->clone();
undo_redo->add_do_method(parent.ptr(), SNAME("add_child_at_index"), sel_dup, parent->get_child_index(sel) + 1);
undo_redo->add_do_method(parent.ptr(), SNAME("add_child_at_index"), sel_dup, sel->get_index() + 1);
undo_redo->add_undo_method(parent.ptr(), SNAME("remove_child"), sel_dup);
undo_redo->add_do_method(task_tree, SNAME("update_tree"));
undo_redo->add_undo_method(task_tree, SNAME("update_tree"));
@ -408,7 +408,7 @@ void LimboAIEditor::_action_selected(int p_id) {
undo_redo->add_do_method(sel.ptr(), SNAME("add_child"), old_root);
undo_redo->add_undo_method(sel.ptr(), SNAME("remove_child"), old_root);
undo_redo->add_undo_method(task_tree->get_bt().ptr(), SNAME("set_root_task"), old_root);
undo_redo->add_undo_method(parent.ptr(), SNAME("add_child_at_index"), sel, parent->get_child_index(sel));
undo_redo->add_undo_method(parent.ptr(), SNAME("add_child_at_index"), sel, sel->get_index());
undo_redo->add_do_method(task_tree, SNAME("update_tree"));
undo_redo->add_undo_method(task_tree, SNAME("update_tree"));
undo_redo->commit_action();
@ -424,7 +424,7 @@ void LimboAIEditor::_action_selected(int p_id) {
undo_redo->add_undo_method(task_tree->get_bt().ptr(), SNAME("set_root_task"), task_tree->get_bt()->get_root_task());
} else {
undo_redo->add_do_method(sel->get_parent().ptr(), SNAME("remove_child"), sel);
undo_redo->add_undo_method(sel->get_parent().ptr(), SNAME("add_child_at_index"), sel, sel->get_parent()->get_child_index(sel));
undo_redo->add_undo_method(sel->get_parent().ptr(), SNAME("add_child_at_index"), sel, sel->get_index());
}
undo_redo->add_do_method(task_tree, SNAME("update_tree"));
undo_redo->add_undo_method(task_tree, SNAME("update_tree"));
@ -442,9 +442,9 @@ void LimboAIEditor::_on_probability_edited(double p_value) {
Ref<BTProbabilitySelector> probability_selector = selected->get_parent();
ERR_FAIL_COND(probability_selector.is_null());
if (percent_mode->is_pressed()) {
probability_selector->set_probability(probability_selector->get_child_index(selected), p_value * 0.01);
probability_selector->set_probability(selected->get_index(), p_value * 0.01);
} else {
probability_selector->set_weight(probability_selector->get_child_index(selected), p_value);
probability_selector->set_weight(selected->get_index(), p_value);
}
}
@ -453,7 +453,7 @@ void LimboAIEditor::_update_probability_edit() {
ERR_FAIL_COND(selected.is_null());
Ref<BTProbabilitySelector> prob = selected->get_parent();
ERR_FAIL_COND(prob.is_null());
double others_weight = prob->get_total_weight() - prob->get_weight(prob->get_child_index(selected));
double others_weight = prob->get_total_weight() - prob->get_weight(selected->get_index());
bool cannot_edit_percent = others_weight == 0.0;
percent_mode->set_disabled(cannot_edit_percent);
if (cannot_edit_percent && percent_mode->is_pressed()) {
@ -611,15 +611,21 @@ void LimboAIEditor::_on_task_dragged(Ref<BTTask> p_task, Ref<BTTask> p_to_task,
if (p_type == 0) {
undo_redo->add_do_method(p_to_task.ptr(), SNAME("add_child"), p_task);
undo_redo->add_undo_method(p_to_task.ptr(), SNAME("remove_child"), p_task);
} else if (p_type == -1) {
undo_redo->add_do_method(p_to_task->get_parent().ptr(), SNAME("add_child_at_index"), p_task, p_to_task->get_parent()->get_child_index(p_to_task));
undo_redo->add_undo_method(p_to_task->get_parent().ptr(), SNAME("remove_child"), p_task);
} else if (p_type == 1) {
undo_redo->add_do_method(p_to_task->get_parent().ptr(), SNAME("add_child_at_index"), p_task, p_to_task->get_parent()->get_child_index(p_to_task) + 1);
undo_redo->add_undo_method(p_to_task->get_parent().ptr(), SNAME("remove_child"), p_task);
} else {
int drop_idx = p_to_task->get_index();
if (p_to_task->get_parent() == p_task->get_parent() && drop_idx > p_task->get_index()) {
drop_idx -= 1;
}
if (p_type == -1) {
undo_redo->add_do_method(p_to_task->get_parent().ptr(), SNAME("add_child_at_index"), p_task, drop_idx);
undo_redo->add_undo_method(p_to_task->get_parent().ptr(), SNAME("remove_child"), p_task);
} else if (p_type == 1) {
undo_redo->add_do_method(p_to_task->get_parent().ptr(), SNAME("add_child_at_index"), p_task, drop_idx + 1);
undo_redo->add_undo_method(p_to_task->get_parent().ptr(), SNAME("remove_child"), p_task);
}
}
undo_redo->add_undo_method(p_task->get_parent().ptr(), "add_child_at_index", p_task, p_task->get_parent()->get_child_index(p_task));
undo_redo->add_undo_method(p_task->get_parent().ptr(), "add_child_at_index", p_task, p_task->get_index());
undo_redo->add_do_method(task_tree, SNAME("update_tree"));
undo_redo->add_undo_method(task_tree, SNAME("update_tree"));

View File

@ -223,7 +223,7 @@ double TaskTree::get_selected_probability_weight() const {
ERR_FAIL_COND_V(selected.is_null(), 0.0);
Ref<BTProbabilitySelector> probability_selector = selected->get_parent();
ERR_FAIL_COND_V(probability_selector.is_null(), 0.0);
return probability_selector->get_weight(probability_selector->get_child_index(selected));
return probability_selector->get_weight(selected->get_index());
}
double TaskTree::get_selected_probability_percent() const {
@ -231,7 +231,7 @@ double TaskTree::get_selected_probability_percent() const {
ERR_FAIL_COND_V(selected.is_null(), 0.0);
Ref<BTProbabilitySelector> probability_selector = selected->get_parent();
ERR_FAIL_COND_V(probability_selector.is_null(), 0.0);
return probability_selector->get_probability(probability_selector->get_child_index(selected)) * 100.0;
return probability_selector->get_probability(selected->get_index()) * 100.0;
}
bool TaskTree::selected_has_probability() const {

View File

@ -33,6 +33,8 @@ TEST_CASE("[Modules][LimboAI] BTTask") {
REQUIRE(task->get_child_count() == 2);
REQUIRE(task->get_child(0) == child1);
REQUIRE(task->get_child(1) == child3);
CHECK(child1->get_index() == 0);
CHECK(child3->get_index() == 1);
// * add_child_at_index
Ref<BTTask> child2 = memnew(BTTask);
@ -57,14 +59,14 @@ TEST_CASE("[Modules][LimboAI] BTTask") {
Ref<BTTask> other = memnew(BTTask);
CHECK_FALSE(task->has_child(other));
}
SUBCASE("Test get_child_index()") {
CHECK(task->get_child_index(child1) == 0);
CHECK(task->get_child_index(child2) == 1);
CHECK(task->get_child_index(child3) == 2);
SUBCASE("Test get_index()") {
CHECK(child1->get_index() == 0);
CHECK(child2->get_index() == 1);
CHECK(child3->get_index() == 2);
}
SUBCASE("Test get_child_index() with an out-of-hierarchy task") {
SUBCASE("Test get_index() with an out-of-hierarchy task") {
Ref<BTTask> other = memnew(BTTask);
CHECK(task->get_child_index(other) == -1);
CHECK(other->get_index() == -1);
}
SUBCASE("Test is_descendant_of()") {
Ref<BTTask> grandchild = memnew(BTTask);
@ -83,13 +85,22 @@ TEST_CASE("[Modules][LimboAI] BTTask") {
REQUIRE(task->get_child_count() == 2);
CHECK(task->get_child(0) == child1);
CHECK(task->get_child(1) == child3);
CHECK(child1->get_index() == 0);
CHECK(child2->get_index() == -1);
CHECK(child3->get_index() == 1);
task->remove_child(child3);
REQUIRE(task->get_child_count() == 1);
CHECK(task->get_child(0) == child1);
CHECK(child1->get_index() == 0);
CHECK(child2->get_index() == -1);
CHECK(child3->get_index() == -1);
task->remove_child(child1);
REQUIRE(task->get_child_count() == 0);
CHECK(child1->get_index() == -1);
CHECK(child2->get_index() == -1);
CHECK(child3->get_index() == -1);
}
SUBCASE("Test remove_child() with an out-of-hierarchy task") {
Ref<BTTask> other = memnew(BTTask);
@ -98,18 +109,27 @@ TEST_CASE("[Modules][LimboAI] BTTask") {
task->remove_child(other);
ERR_PRINT_ON;
}
SUBCASE("Test remove_at_index()") {
SUBCASE("Test remove_child_at_index()") {
task->remove_child_at_index(1);
REQUIRE(task->get_child_count() == 2);
CHECK(task->get_child(0) == child1);
CHECK(task->get_child(1) == child3);
CHECK(child1->get_index() == 0);
CHECK(child2->get_index() == -1);
CHECK(child3->get_index() == 1);
task->remove_child_at_index(1);
REQUIRE(task->get_child_count() == 1);
CHECK(task->get_child(0) == child1);
CHECK(child1->get_index() == 0);
CHECK(child2->get_index() == -1);
CHECK(child3->get_index() == -1);
task->remove_child_at_index(0);
REQUIRE(task->get_child_count() == 0);
CHECK(child1->get_index() == -1);
CHECK(child2->get_index() == -1);
CHECK(child3->get_index() == -1);
}
SUBCASE("Test remove_child_at_index() with an out-of-bounds index") {
// * Must not crash.