/** * limbo_ai_editor_plugin.cpp * ============================================================================= * Copyright 2021-2024 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. * ============================================================================= */ #ifdef TOOLS_ENABLED #include "limbo_ai_editor_plugin.h" #include "../bt/behavior_tree.h" #include "../bt/tasks/bt_comment.h" #include "../bt/tasks/composites/bt_probability_selector.h" #include "../bt/tasks/composites/bt_selector.h" #include "../bt/tasks/decorators/bt_subtree.h" #include "../util/limbo_compat.h" #include "../util/limbo_utility.h" #include "../util/limboai_version.h" #include "action_banner.h" #include "blackboard_plan_editor.h" #include "debugger/limbo_debugger_plugin.h" #include "editor_property_bb_param.h" #include "editor_property_property_path.h" #ifdef LIMBOAI_MODULE #include "core/config/project_settings.h" #include "core/error/error_macros.h" #include "core/input/input.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/script_editor_debugger.h" #include "editor/editor_file_system.h" #include "editor/editor_help.h" #include "editor/editor_interface.h" #include "editor/editor_paths.h" #include "editor/editor_settings.h" #include "editor/filesystem_dock.h" #include "editor/gui/editor_bottom_panel.h" #include "editor/inspector_dock.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/project_settings_editor.h" #include "editor/themes/editor_scale.h" #include "scene/gui/panel_container.h" #include "scene/gui/separator.h" #endif // LIMBOAI_MODULE #ifdef LIMBOAI_GDEXTENSION #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif // LIMBOAI_GDEXTENSION namespace { // If built-in resource - switch to the owner scene (open it if not already). inline void _switch_to_owner_scene_if_builtin(const Ref &p_behavior_tree) { if (p_behavior_tree.is_valid() && p_behavior_tree->get_path().contains("::")) { String current_scene = SCENE_TREE()->get_edited_scene_root()->get_scene_file_path(); String scene_path = p_behavior_tree->get_path().get_slice("::", 0); if (current_scene != scene_path) { EditorInterface::get_singleton()->open_scene_from_path(scene_path); } } } } // unnamed namespace //**** LimboAIEditor _FORCE_INLINE_ String _get_script_template_path() { String templates_search_path = GLOBAL_GET("editor/script/templates_search_path"); return templates_search_path.path_join("BTTask").path_join("custom_task.gd"); } EditorUndoRedoManager *LimboAIEditor::_new_undo_redo_action(const String &p_name, UndoRedo::MergeMode p_mode) { #ifdef LIMBOAI_MODULE EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); #elif LIMBOAI_GDEXTENSION EditorUndoRedoManager *undo_redo = plugin->get_undo_redo(); #endif // ! HACK: Force global history to be used for resources without a set path. undo_redo->create_action(p_name, p_mode, dummy_history_context); undo_redo->force_fixed_history(); return undo_redo; } void LimboAIEditor::_commit_action_with_update(EditorUndoRedoManager *p_undo_redo) { ERR_FAIL_NULL(p_undo_redo); p_undo_redo->add_do_method(this, LW_NAME(_update_task_tree), task_tree->get_bt()); p_undo_redo->add_undo_method(this, LW_NAME(_update_task_tree), task_tree->get_bt()); p_undo_redo->commit_action(); _set_as_dirty(task_tree->get_bt(), true); } void LimboAIEditor::_add_task(const Ref &p_task, bool p_as_sibling) { if (task_tree->get_bt().is_null()) { return; } ERR_FAIL_COND(p_task.is_null()); EditorUndoRedoManager *undo_redo = _new_undo_redo_action(TTR("Add BT Task")); int insert_idx = -1; Ref selected = task_tree->get_selected(); Ref parent = selected; if (parent.is_null()) { // When no task is selected, use the root task. parent = task_tree->get_bt()->get_root_task(); selected = parent; } if (parent.is_null()) { // When tree is empty. undo_redo->add_do_method(task_tree->get_bt().ptr(), LW_NAME(set_root_task), p_task); undo_redo->add_undo_method(task_tree->get_bt().ptr(), LW_NAME(set_root_task), task_tree->get_bt()->get_root_task()); } else { if (p_as_sibling && selected.is_valid() && selected->get_parent().is_valid()) { // Insert task after the currently selected and on the same level (usually when shift is pressed). parent = selected->get_parent(); insert_idx = selected->get_index() + 1; } undo_redo->add_do_method(parent.ptr(), LW_NAME(add_child_at_index), p_task, insert_idx); undo_redo->add_undo_method(parent.ptr(), LW_NAME(remove_child), p_task); } _commit_action_with_update(undo_redo); } void LimboAIEditor::_add_task_with_prototype(const Ref &p_prototype) { Ref selected = task_tree->get_selected(); bool as_sibling = Input::get_singleton()->is_key_pressed(LW_KEY(SHIFT)); _add_task(p_prototype->clone(), as_sibling); } Ref LimboAIEditor::_create_task_by_class_or_path(const String &p_class_or_path) const { ERR_FAIL_COND_V(p_class_or_path.is_empty(), nullptr); Ref ret; if (p_class_or_path.begins_with("res:")) { Ref