Compare commits

..

10 Commits

Author SHA1 Message Date
Serhii Snitsaruk 8a23496265
Bump version to 1.1.1 2024-08-06 21:41:09 +02:00
Serhii Snitsaruk 820663099d Merge branch “cherrypicks-4.2” 2024-08-06 21:36:10 +02:00
Serhii Snitsaruk ad821c4514 Fix C# exports print errors due to missing ClassDB binding 2024-08-06 19:58:05 +02:00
Serhii Snitsaruk 5a58eef4f1 Fix error if `changed` signal is already connected to a BBParam in a bunch of tasks 2024-08-06 19:20:25 +02:00
Serhii Snitsaruk 997f9572e2 Fix process_input is not enabled in active substate 2024-08-06 19:18:52 +02:00
Serhii Snitsaruk 01ae6a46de Fix BTPlayer.restart() crash 2024-08-06 19:17:41 +02:00
yds b5602169d4 Fix new task script path don't update after changing the setting 2024-08-06 19:16:48 +02:00
Serhii Snitsaruk d999e33efa Defer banner actions to not crash upon restart 2024-08-06 19:15:10 +02:00
Serhii Snitsaruk 9da78e52e7 Fix BlackboardPlan arrays shouldn't be shared between instanced agents
Variable values are deep copied now using Variant.duplicate(true). Note that currently it doesn't duplicate objects.
2024-08-06 19:14:42 +02:00
Serhii Snitsaruk e6d1e0d076 Fix connected signal error when loading BT for the second time 2024-08-06 19:12:54 +02:00
21 changed files with 70 additions and 42 deletions

View File

@ -78,12 +78,16 @@ String BBVariable::get_hint_string() const {
return data->hint_string; return data->hint_string;
} }
BBVariable BBVariable::duplicate() const { BBVariable BBVariable::duplicate(bool p_deep) const {
BBVariable var; BBVariable var;
var.data->hint = data->hint; var.data->hint = data->hint;
var.data->hint_string = data->hint_string; var.data->hint_string = data->hint_string;
var.data->type = data->type; var.data->type = data->type;
var.data->value = data->value; if (p_deep) {
var.data->value = data->value.duplicate(p_deep);
} else {
var.data->value = data->value;
}
var.data->binding_path = data->binding_path; var.data->binding_path = data->binding_path;
var.data->bound_object = data->bound_object; var.data->bound_object = data->bound_object;
var.data->bound_property = data->bound_property; var.data->bound_property = data->bound_property;

View File

@ -54,7 +54,7 @@ public:
void set_hint_string(const String &p_hint_string); void set_hint_string(const String &p_hint_string);
String get_hint_string() const; String get_hint_string() const;
BBVariable duplicate() const; BBVariable duplicate(bool p_deep = false) const;
_FORCE_INLINE_ bool is_value_changed() const { return data->value_changed; } _FORCE_INLINE_ bool is_value_changed() const { return data->value_changed; }
_FORCE_INLINE_ void reset_value_changed() { data->value_changed = false; } _FORCE_INLINE_ void reset_value_changed() { data->value_changed = false; }

View File

@ -391,7 +391,7 @@ void BlackboardPlan::sync_with_base_plan() {
inline void bb_add_var_dup_with_prefetch(const Ref<Blackboard> &p_blackboard, const StringName &p_name, const BBVariable &p_var, bool p_prefetch, Node *p_node) { inline void bb_add_var_dup_with_prefetch(const Ref<Blackboard> &p_blackboard, const StringName &p_name, const BBVariable &p_var, bool p_prefetch, Node *p_node) {
if (unlikely(p_prefetch && p_var.get_type() == Variant::NODE_PATH)) { if (unlikely(p_prefetch && p_var.get_type() == Variant::NODE_PATH)) {
Node *n = p_node->get_node_or_null(p_var.get_value()); Node *n = p_node->get_node_or_null(p_var.get_value());
BBVariable var = p_var.duplicate(); BBVariable var = p_var.duplicate(true);
if (n != nullptr) { if (n != nullptr) {
var.set_value(n); var.set_value(n);
} else { } else {
@ -404,7 +404,7 @@ inline void bb_add_var_dup_with_prefetch(const Ref<Blackboard> &p_blackboard, co
} }
p_blackboard->assign_var(p_name, var); p_blackboard->assign_var(p_name, var);
} else { } else {
p_blackboard->assign_var(p_name, p_var.duplicate()); p_blackboard->assign_var(p_name, p_var.duplicate(true));
} }
} }

View File

@ -144,6 +144,7 @@ void BTPlayer::update(double p_delta) {
} }
void BTPlayer::restart() { void BTPlayer::restart() {
ERR_FAIL_COND_MSG(tree_instance.is_null(), "BTPlayer: Restart failed - no valid tree instance. Make sure the BTPlayer has a valid behavior tree with a valid root task.");
tree_instance->abort(); tree_instance->abort();
set_active(true); set_active(true);
} }

View File

@ -24,8 +24,9 @@ void BTCheckVar::set_check_type(LimboUtility::CheckType p_check_type) {
void BTCheckVar::set_value(const Ref<BBVariant> &p_value) { void BTCheckVar::set_value(const Ref<BBVariant> &p_value) {
value = p_value; value = p_value;
emit_changed(); emit_changed();
if (Engine::get_singleton()->is_editor_hint() && value.is_valid()) { if (Engine::get_singleton()->is_editor_hint() && value.is_valid() &&
value->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); !value->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
value->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
} }
} }

View File

@ -48,8 +48,9 @@ void BTSetVar::set_variable(const StringName &p_variable) {
void BTSetVar::set_value(const Ref<BBVariant> &p_value) { void BTSetVar::set_value(const Ref<BBVariant> &p_value) {
value = p_value; value = p_value;
emit_changed(); emit_changed();
if (Engine::get_singleton()->is_editor_hint() && value.is_valid()) { if (Engine::get_singleton()->is_editor_hint() && value.is_valid() &&
value->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); !value->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
value->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
} }
} }

View File

@ -377,16 +377,21 @@ void BTTask::print_tree(int p_initial_tabs) {
} }
} }
#ifdef TOOLS_ENABLED
Ref<BehaviorTree> BTTask::editor_get_behavior_tree() { Ref<BehaviorTree> BTTask::editor_get_behavior_tree() {
#ifdef TOOLS_ENABLED
BTTask *task = this; BTTask *task = this;
while (task->data.behavior_tree_id.is_null() && task->get_parent().is_valid()) { while (task->data.behavior_tree_id.is_null() && task->get_parent().is_valid()) {
task = task->data.parent; task = task->data.parent;
} }
return Object::cast_to<BehaviorTree>(ObjectDB::get_instance(task->data.behavior_tree_id)); return Object::cast_to<BehaviorTree>(ObjectDB::get_instance(task->data.behavior_tree_id));
#else
ERR_PRINT("BTTask::editor_get_behavior_tree: Not available in release builds.");
return Ref<BehaviorTree>();
#endif
} }
#ifdef TOOLS_ENABLED
void BTTask::editor_set_behavior_tree(const Ref<BehaviorTree> &p_bt) { void BTTask::editor_set_behavior_tree(const Ref<BehaviorTree> &p_bt) {
data.behavior_tree_id = p_bt->get_instance_id(); data.behavior_tree_id = p_bt->get_instance_id();
} }
@ -414,9 +419,7 @@ void BTTask::_bind_methods() {
ClassDB::bind_method(D_METHOD("print_tree", "initial_tabs"), &BTTask::print_tree, Variant(0)); ClassDB::bind_method(D_METHOD("print_tree", "initial_tabs"), &BTTask::print_tree, Variant(0));
ClassDB::bind_method(D_METHOD("get_task_name"), &BTTask::get_task_name); ClassDB::bind_method(D_METHOD("get_task_name"), &BTTask::get_task_name);
ClassDB::bind_method(D_METHOD("abort"), &BTTask::abort); ClassDB::bind_method(D_METHOD("abort"), &BTTask::abort);
#ifdef TOOLS_ENABLED
ClassDB::bind_method(D_METHOD("editor_get_behavior_tree"), &BTTask::editor_get_behavior_tree); ClassDB::bind_method(D_METHOD("editor_get_behavior_tree"), &BTTask::editor_get_behavior_tree);
#endif // TOOLS_ENABLED
// Properties, setters and getters. // Properties, setters and getters.
ClassDB::bind_method(D_METHOD("get_agent"), &BTTask::get_agent); ClassDB::bind_method(D_METHOD("get_agent"), &BTTask::get_agent);

View File

@ -167,8 +167,8 @@ public:
void print_tree(int p_initial_tabs = 0); void print_tree(int p_initial_tabs = 0);
#ifdef TOOLS_ENABLED
Ref<BehaviorTree> editor_get_behavior_tree(); Ref<BehaviorTree> editor_get_behavior_tree();
#ifdef TOOLS_ENABLED
void editor_set_behavior_tree(const Ref<BehaviorTree> &p_bt); void editor_set_behavior_tree(const Ref<BehaviorTree> &p_bt);
#endif #endif

View File

@ -16,8 +16,9 @@
void BTAwaitAnimation::set_animation_player(Ref<BBNode> p_animation_player) { void BTAwaitAnimation::set_animation_player(Ref<BBNode> p_animation_player) {
animation_player_param = p_animation_player; animation_player_param = p_animation_player;
emit_changed(); emit_changed();
if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid()) { if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid() &&
animation_player_param->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); !animation_player_param->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
animation_player_param->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
} }
} }

View File

@ -24,8 +24,9 @@ void BTCheckAgentProperty::set_check_type(LimboUtility::CheckType p_check_type)
void BTCheckAgentProperty::set_value(Ref<BBVariant> p_value) { void BTCheckAgentProperty::set_value(Ref<BBVariant> p_value) {
value = p_value; value = p_value;
emit_changed(); emit_changed();
if (Engine::get_singleton()->is_editor_hint() && value.is_valid()) { if (Engine::get_singleton()->is_editor_hint() && value.is_valid() &&
value->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); !value->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
value->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
} }
} }

View File

@ -16,8 +16,9 @@
void BTPauseAnimation::set_animation_player(Ref<BBNode> p_animation_player) { void BTPauseAnimation::set_animation_player(Ref<BBNode> p_animation_player) {
animation_player_param = p_animation_player; animation_player_param = p_animation_player;
emit_changed(); emit_changed();
if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid()) { if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid() &&
animation_player_param->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); !animation_player_param->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
animation_player_param->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
} }
} }

View File

@ -16,8 +16,9 @@
void BTPlayAnimation::set_animation_player(Ref<BBNode> p_animation_player) { void BTPlayAnimation::set_animation_player(Ref<BBNode> p_animation_player) {
animation_player_param = p_animation_player; animation_player_param = p_animation_player;
emit_changed(); emit_changed();
if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid()) { if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid() &&
animation_player_param->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); !animation_player_param->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
animation_player_param->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
} }
} }

View File

@ -19,8 +19,9 @@ void BTSetAgentProperty::set_property(StringName p_prop) {
void BTSetAgentProperty::set_value(Ref<BBVariant> p_value) { void BTSetAgentProperty::set_value(Ref<BBVariant> p_value) {
value = p_value; value = p_value;
emit_changed(); emit_changed();
if (Engine::get_singleton()->is_editor_hint() && value.is_valid()) { if (Engine::get_singleton()->is_editor_hint() && value.is_valid() &&
value->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); !value->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
value->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
} }
} }

View File

@ -16,8 +16,9 @@
void BTStopAnimation::set_animation_player(Ref<BBNode> p_animation_player) { void BTStopAnimation::set_animation_player(Ref<BBNode> p_animation_player) {
animation_player_param = p_animation_player; animation_player_param = p_animation_player;
emit_changed(); emit_changed();
if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid()) { if (Engine::get_singleton()->is_editor_hint() && animation_player_param.is_valid() &&
animation_player_param->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); !animation_player_param->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
animation_player_param->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
} }
} }

View File

@ -28,8 +28,9 @@ void BTCallMethod::set_method(const StringName &p_method_name) {
void BTCallMethod::set_node_param(const Ref<BBNode> &p_object) { void BTCallMethod::set_node_param(const Ref<BBNode> &p_object) {
node_param = p_object; node_param = p_object;
emit_changed(); emit_changed();
if (Engine::get_singleton()->is_editor_hint() && node_param.is_valid()) { if (Engine::get_singleton()->is_editor_hint() && node_param.is_valid() &&
node_param->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); !node_param->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
node_param->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
} }
} }

View File

@ -29,8 +29,9 @@ void BTEvaluateExpression::set_expression_string(const String &p_expression_stri
void BTEvaluateExpression::set_node_param(Ref<BBNode> p_object) { void BTEvaluateExpression::set_node_param(Ref<BBNode> p_object) {
node_param = p_object; node_param = p_object;
emit_changed(); emit_changed();
if (Engine::get_singleton()->is_editor_hint() && node_param.is_valid()) { if (Engine::get_singleton()->is_editor_hint() && node_param.is_valid() &&
node_param->connect(LW_NAME(changed), Callable(this, LW_NAME(emit_changed))); !node_param->is_connected(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed))) {
node_param->connect(LW_NAME(changed), callable_mp((Resource *)this, &Resource::emit_changed));
} }
} }

View File

@ -41,7 +41,7 @@ void ActionBanner::close() {
void ActionBanner::add_action(const String &p_name, const Callable &p_action, bool p_auto_close) { void ActionBanner::add_action(const String &p_name, const Callable &p_action, bool p_auto_close) {
Button *action_btn = memnew(Button); Button *action_btn = memnew(Button);
action_btn->set_text(p_name); action_btn->set_text(p_name);
action_btn->connect(LW_NAME(pressed), callable_mp(this, &ActionBanner::_execute_action).bind(p_action, p_auto_close)); action_btn->connect(LW_NAME(pressed), callable_mp(this, &ActionBanner::_execute_action).bind(p_action, p_auto_close), CONNECT_DEFERRED);
hbox->add_child(action_btn); hbox->add_child(action_btn);
} }

View File

@ -220,6 +220,8 @@ void LimboAIEditor::_load_bt(String p_path) {
void LimboAIEditor::_disable_editing() { void LimboAIEditor::_disable_editing() {
task_tree->unload(); task_tree->unload();
task_palette->hide(); task_palette->hide();
task_tree->hide();
usage_hint->show();
} }
void LimboAIEditor::edit_bt(Ref<BehaviorTree> p_behavior_tree, bool p_force_refresh) { void LimboAIEditor::edit_bt(Ref<BehaviorTree> p_behavior_tree, bool p_force_refresh) {
@ -234,14 +236,9 @@ void LimboAIEditor::edit_bt(Ref<BehaviorTree> p_behavior_tree, bool p_force_refr
p_behavior_tree->notify_property_list_changed(); p_behavior_tree->notify_property_list_changed();
#endif // LIMBOAI_MODULE #endif // LIMBOAI_MODULE
if (task_tree->get_bt().is_valid() &&
task_tree->get_bt()->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty).bind(true))) {
task_tree->get_bt()->disconnect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty).bind(true));
}
task_tree->load_bt(p_behavior_tree); task_tree->load_bt(p_behavior_tree);
if (task_tree->get_bt().is_valid()) { if (task_tree->get_bt().is_valid() && !task_tree->get_bt()->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty))) {
task_tree->get_bt()->connect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty).bind(true)); task_tree->get_bt()->connect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty).bind(true));
} }
@ -888,6 +885,10 @@ void LimboAIEditor::_on_resources_reload(const PackedStringArray &p_resources) {
#endif #endif
} }
void LimboAIEditor::_on_new_script_pressed() {
SCRIPT_EDITOR()->open_script_create_dialog("BTAction", String(GLOBAL_GET("limbo_ai/behavior_tree/user_task_dir_1")).path_join("new_task"));
}
void LimboAIEditor::_task_type_selected(const String &p_class_or_path) { void LimboAIEditor::_task_type_selected(const String &p_class_or_path) {
change_type_popup->hide(); change_type_popup->hide();
@ -958,6 +959,10 @@ void LimboAIEditor::_tab_clicked(int p_tab) {
void LimboAIEditor::_tab_closed(int p_tab) { void LimboAIEditor::_tab_closed(int p_tab) {
ERR_FAIL_INDEX(p_tab, history.size()); ERR_FAIL_INDEX(p_tab, history.size());
Ref<BehaviorTree> history_bt = history[p_tab];
if (history_bt.is_valid() && history_bt->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty))) {
history_bt->disconnect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty));
}
history.remove_at(p_tab); history.remove_at(p_tab);
idx_history = MIN(idx_history, history.size() - 1); idx_history = MIN(idx_history, history.size() - 1);
if (idx_history < 0) { if (idx_history < 0) {
@ -1296,9 +1301,11 @@ void LimboAIEditor::_notification(int p_what) {
cf->set_value("bt_editor", "bteditor_hsplit", hsc->get_split_offset()); cf->set_value("bt_editor", "bteditor_hsplit", hsc->get_split_offset());
cf->save(conf_path); cf->save(conf_path);
if (task_tree->get_bt().is_valid() && task_tree->unload();
task_tree->get_bt()->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty).bind(true))) { for (int i = 0; i < history.size(); i++) {
task_tree->get_bt()->disconnect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty).bind(true)); if (history[i]->is_connected(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty))) {
history[i]->disconnect(LW_NAME(changed), callable_mp(this, &LimboAIEditor::_mark_as_dirty));
}
} }
} break; } break;
case NOTIFICATION_READY: { case NOTIFICATION_READY: {
@ -1329,7 +1336,7 @@ void LimboAIEditor::_notification(int p_what) {
disk_changed->connect("confirmed", callable_mp(this, &LimboAIEditor::_reload_modified)); disk_changed->connect("confirmed", callable_mp(this, &LimboAIEditor::_reload_modified));
disk_changed->connect("custom_action", callable_mp(this, &LimboAIEditor::_resave_modified)); disk_changed->connect("custom_action", callable_mp(this, &LimboAIEditor::_resave_modified));
rename_dialog->connect("confirmed", callable_mp(this, &LimboAIEditor::_rename_task_confirmed)); rename_dialog->connect("confirmed", callable_mp(this, &LimboAIEditor::_rename_task_confirmed));
new_script_btn->connect(LW_NAME(pressed), callable_mp(SCRIPT_EDITOR(), &ScriptEditor::open_script_create_dialog).bind("BTAction", String(GLOBAL_GET("limbo_ai/behavior_tree/user_task_dir_1")).path_join("new_task"))); new_script_btn->connect(LW_NAME(pressed), callable_mp(this, &LimboAIEditor::_on_new_script_pressed));
tab_bar->connect("tab_clicked", callable_mp(this, &LimboAIEditor::_tab_clicked)); tab_bar->connect("tab_clicked", callable_mp(this, &LimboAIEditor::_tab_clicked));
tab_bar->connect("active_tab_rearranged", callable_mp(this, &LimboAIEditor::_move_active_tab)); tab_bar->connect("active_tab_rearranged", callable_mp(this, &LimboAIEditor::_move_active_tab));
tab_bar->connect("tab_close_pressed", callable_mp(this, &LimboAIEditor::_tab_closed)); tab_bar->connect("tab_close_pressed", callable_mp(this, &LimboAIEditor::_tab_closed));

View File

@ -227,6 +227,7 @@ private:
void _on_history_forward(); void _on_history_forward();
void _on_task_dragged(Ref<BTTask> p_task, Ref<BTTask> p_to_task, int p_type); void _on_task_dragged(Ref<BTTask> p_task, Ref<BTTask> p_to_task, int p_type);
void _on_resources_reload(const PackedStringArray &p_resources); void _on_resources_reload(const PackedStringArray &p_resources);
void _on_new_script_pressed();
void _task_type_selected(const String &p_class_or_path); void _task_type_selected(const String &p_class_or_path);
void _copy_version_info(); void _copy_version_info();

View File

@ -51,11 +51,13 @@ void LimboHSM::_change_state(LimboState *p_state) {
if (active_state) { if (active_state) {
active_state->_exit(); active_state->_exit();
active_state->set_process_input(false);
previous_active = active_state; previous_active = active_state;
} }
active_state = p_state; active_state = p_state;
active_state->_enter(); active_state->_enter();
active_state->set_process_input(true);
emit_signal(LimboStringNames::get_singleton()->active_state_changed, active_state, previous_active); emit_signal(LimboStringNames::get_singleton()->active_state_changed, active_state, previous_active);
} }

View File

@ -2,7 +2,7 @@
major = 1 major = 1
minor = 1 minor = 1
patch = 0 patch = 1
status = "" status = ""
doc_branch = "v1.1.0" doc_branch = "v1.1.0"