Merge branch 'qol-improvements'
This commit is contained in:
commit
26b9327090
|
@ -13,7 +13,7 @@
|
|||
|
||||
PackedStringArray BTAction::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = BTTask::get_configuration_warnings();
|
||||
if (get_child_count() != 0) {
|
||||
if (get_child_count_excluding_comments() != 0) {
|
||||
warnings.append("Action can't have child tasks.");
|
||||
}
|
||||
return warnings;
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* bt_comment.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.
|
||||
* =============================================================================
|
||||
*/
|
||||
|
||||
#include "bt_comment.h"
|
||||
|
||||
#include "bt_task.h"
|
||||
|
||||
Ref<BTTask> BTComment::clone() const {
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return BTTask::clone();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PackedStringArray BTComment::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = BTTask::get_configuration_warnings();
|
||||
if (get_child_count_excluding_comments() > 0) {
|
||||
warnings.append("Can only have other comment tasks as children.");
|
||||
}
|
||||
if (get_parent() == nullptr) {
|
||||
warnings.append("Can't be the root task.");
|
||||
}
|
||||
return warnings;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* bt_comment.h
|
||||
* =============================================================================
|
||||
* 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.
|
||||
* =============================================================================
|
||||
*/
|
||||
/* bt_comment.h */
|
||||
|
||||
#ifndef BT_COMMENT_H
|
||||
#define BT_COMMENT_H
|
||||
|
||||
#include "bt_task.h"
|
||||
|
||||
class BTComment : public BTTask {
|
||||
GDCLASS(BTComment, BTTask);
|
||||
|
||||
private:
|
||||
public:
|
||||
virtual Ref<BTTask> clone() const override;
|
||||
virtual PackedStringArray get_configuration_warnings() const override;
|
||||
};
|
||||
|
||||
#endif // BT_COMMENT
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
PackedStringArray BTComposite::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = BTTask::get_configuration_warnings();
|
||||
if (get_child_count() < 1) {
|
||||
if (get_child_count_excluding_comments() < 1) {
|
||||
warnings.append("Composite should have at least one child task.");
|
||||
}
|
||||
return warnings;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
PackedStringArray BTCondition::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = BTTask::get_configuration_warnings();
|
||||
if (get_child_count() != 0) {
|
||||
if (get_child_count_excluding_comments() != 0) {
|
||||
warnings.append("Condition task can't have child tasks.");
|
||||
}
|
||||
return warnings;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
PackedStringArray BTDecorator::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = BTTask::get_configuration_warnings();
|
||||
if (get_child_count() != 1) {
|
||||
if (get_child_count_excluding_comments() != 1) {
|
||||
warnings.append("Decorator should have a single child task.");
|
||||
}
|
||||
return warnings;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "bt_task.h"
|
||||
|
||||
#include "bt_comment.h"
|
||||
#include "modules/limboai/blackboard/blackboard.h"
|
||||
#include "modules/limboai/util/limbo_string_names.h"
|
||||
#include "modules/limboai/util/limbo_utility.h"
|
||||
|
@ -104,10 +105,19 @@ Ref<BTTask> BTTask::clone() const {
|
|||
inst->data.parent = nullptr;
|
||||
inst->data.agent = nullptr;
|
||||
inst->data.blackboard.unref();
|
||||
int num_null = 0;
|
||||
for (int i = 0; i < data.children.size(); i++) {
|
||||
Ref<BTTask> c = get_child(i)->clone();
|
||||
if (c.is_valid()) {
|
||||
c->data.parent = inst.ptr();
|
||||
inst->data.children.set(i, c);
|
||||
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);
|
||||
}
|
||||
|
||||
// Make BBParam properties unique.
|
||||
|
@ -189,6 +199,16 @@ int BTTask::get_child_count() const {
|
|||
return data.children.size();
|
||||
}
|
||||
|
||||
int BTTask::get_child_count_excluding_comments() const {
|
||||
int count = 0;
|
||||
for (int i = 0; i < data.children.size(); i++) {
|
||||
if (!data.children[i]->is_class_ptr(BTComment::get_class_ptr_static())) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -282,6 +302,7 @@ void BTTask::_bind_methods() {
|
|||
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);
|
||||
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);
|
||||
|
|
|
@ -93,6 +93,7 @@ public:
|
|||
|
||||
Ref<BTTask> get_child(int p_idx) const;
|
||||
int get_child_count() const;
|
||||
int get_child_count_excluding_comments() const;
|
||||
void add_child(Ref<BTTask> p_child);
|
||||
void add_child_at_index(Ref<BTTask> p_child, int p_idx);
|
||||
void remove_child(Ref<BTTask> p_child);
|
||||
|
|
|
@ -65,6 +65,7 @@ def get_doc_classes():
|
|||
"BTCheckAgentProperty",
|
||||
"BTCheckTrigger",
|
||||
"BTCheckVar",
|
||||
"BTComment",
|
||||
"BTComposite",
|
||||
"BTCondition",
|
||||
"BTConsolePrint",
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="BTParallel" inherits="BTComposite" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
BT composite that executes tasks simultaneously.
|
||||
BT composite that executes child tasks until one of the criteria is met.
|
||||
</brief_description>
|
||||
<description>
|
||||
BT composite that executes tasks simultaneously until one of the criterea is met. BTParallel will execute each task from first to last at least once before returning a result.
|
||||
If set to [member repeat], the tasks will be executed again, even if they returned [code]SUCCESS[/code] or [code]FAILURE[/code] on the previous tick.
|
||||
Returns [code]FAILURE[/code] when a required number of tasks return [code]FAILURE[/code]. When [member repeat] is set to [code]false[/code], if none of the criteria were met, and all child tasks returned [code]SUCCESS[/code] or [code]FAILURE[/code], [BTParallel] will return [code]FAILURE[/code].
|
||||
Returns [code]SUCCESS[/code] when a required number of tasks return [code]SUCCESS[/code].
|
||||
Returns [code]RUNNING[/code] after executing all tasks from first to last, and for as long as the above criterea are not met.
|
||||
BTParallel executes its child tasks until one of the criterea is met. It will execute each task at least once, from the first to the last, before returning a result.
|
||||
If set to [member repeat], the child tasks will be re-executed, regardless of whether they previously resulted in a [code]SUCCESS[/code] or [code]FAILURE[/code].
|
||||
Returns [code]FAILURE[/code] when the required number of tasks return [code]FAILURE[/code]. When [member repeat] is set to [code]false[/code], if none of the criteria were met, and all child tasks resulted in a [code]SUCCESS[/code] or [code]FAILURE[/code], BTParallel will return [code]FAILURE[/code].
|
||||
Returns [code]SUCCESS[/code] when the required number of tasks return [code]SUCCESS[/code].
|
||||
Returns [code]RUNNING[/code] if none of the criterea were fulfilled, and either [member repeat] is set to [code]true[/code] or a child task resulted in [code]RUNNING[/code].
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
<members>
|
||||
<member name="num_failures_required" type="int" setter="set_num_failures_required" getter="get_num_failures_required" default="1">
|
||||
When the specified number of child tasks return [code]SUCCESS[/code], [BTParallel] will also return [code]SUCCESS[/code].
|
||||
When the specified number of child tasks return [code]SUCCESS[/code], BTParallel will also return [code]SUCCESS[/code].
|
||||
</member>
|
||||
<member name="num_successes_required" type="int" setter="set_num_successes_required" getter="get_num_successes_required" default="1">
|
||||
When the specified number of child tasks return [code]FAILURE[/code], [BTParallel] will also return [code]FAILURE[/code].
|
||||
When the specified number of child tasks return [code]FAILURE[/code], BTParallel will also return [code]FAILURE[/code].
|
||||
</member>
|
||||
<member name="repeat" type="bool" setter="set_repeat" getter="get_repeat" default="false">
|
||||
When [code]true[/code], the tasks will be executed again, even if they returned [code]SUCCESS[/code] or [code]FAILURE[/code] on the previous tick.
|
||||
When [code]false[/code], if none of the criteria were met, and all child tasks returned [code]SUCCESS[/code] or [code]FAILURE[/code], [BTParallel] will return [code]FAILURE[/code].
|
||||
When [code]true[/code], the child tasks will be executed again, regardless of whether they previously resulted in a [code]SUCCESS[/code] or [code]FAILURE[/code].
|
||||
When [code]false[/code], if none of the criteria were met, and all child tasks resulted in a [code]SUCCESS[/code] or [code]FAILURE[/code], BTParallel will return [code]FAILURE[/code].
|
||||
</member>
|
||||
</members>
|
||||
</class>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="BTSelector" inherits="BTComposite" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
BT composite that executes tasks in turn until first [code]SUCCESS[/code].
|
||||
BT composite that sequentially executes tasks until first [code]SUCCESS[/code].
|
||||
</brief_description>
|
||||
<description>
|
||||
BT composite that executes child tasks from first to last until any child returns [code]SUCCESS[/code].
|
||||
Returns [code]RUNNING[/code] if a task returns [code]RUNNING[/code], and remembers last [code]RUNNING[/code] child. BTSelector will continue where it left off on the next execution tick.
|
||||
Returns [code]FAILURE[/code] if all tasks return [code]FAILURE[/code].
|
||||
Returns [code]SUCCESS[/code] if any task returns [code]SUCCESS[/code].
|
||||
BTSelector executes its child tasks sequentially, from first to last, until any child returns [code]SUCCESS[/code].
|
||||
Returns [code]RUNNING[/code] if any child task returns [code]RUNNING[/code]. The composite will also remember the last child task that returned [code]RUNNING[/code], ensuring it resumes from that point in the next execution tick.
|
||||
Returns [code]FAILURE[/code] if all child tasks return [code]FAILURE[/code].
|
||||
Returns [code]SUCCESS[/code] if a child task returns [code]SUCCESS[/code].
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="BTSequence" inherits="BTComposite" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
|
||||
<brief_description>
|
||||
BT composite that executes tasks in turn for as long as they return [code]SUCCESS[/code].
|
||||
BT composite that sequentially executes tasks as long as they return [code]SUCCESS[/code].
|
||||
</brief_description>
|
||||
<description>
|
||||
BT composite that executes child tasks from first to last for as long as they return [code]SUCCESS[/code].
|
||||
Returns [code]RUNNING[/code] if a task returns [code]RUNNING[/code], and remembers last [code]RUNNING[/code] child. BTSequence will continue where it left off on the next execution tick.
|
||||
Returns [code]FAILURE[/code] if any task returns [code]FAILURE[/code].
|
||||
Returns [code]SUCCESS[/code] if all tasks return [code]SUCCESS[/code].
|
||||
BTSequence executes its child tasks sequentially, from first to last, as long as they return [code]SUCCESS[/code].
|
||||
Returns [code]RUNNING[/code] if any child task returns [code]RUNNING[/code]. The composite will also remember the last child task that returned [code]RUNNING[/code], ensuring it resumes from that point in the next execution tick.
|
||||
Returns [code]FAILURE[/code] if a child task returns [code]FAILURE[/code].
|
||||
Returns [code]SUCCESS[/code] if all child tasks return [code]SUCCESS[/code].
|
||||
</description>
|
||||
<tutorials>
|
||||
</tutorials>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* action_banner.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.
|
||||
* =============================================================================
|
||||
*/
|
||||
|
||||
#include "action_banner.h"
|
||||
|
||||
#include "scene/gui/button.h"
|
||||
|
||||
void ActionBanner::set_text(const String &p_text) {
|
||||
message->set_text(p_text);
|
||||
}
|
||||
|
||||
String ActionBanner::get_text() const {
|
||||
return message->get_text();
|
||||
}
|
||||
|
||||
void ActionBanner::close() {
|
||||
queue_free();
|
||||
}
|
||||
|
||||
void ActionBanner::add_action(const String &p_name, const Callable &p_action, bool p_auto_close) {
|
||||
Button *action_btn = memnew(Button);
|
||||
action_btn->set_text(p_name);
|
||||
action_btn->connect(SNAME("pressed"), callable_mp(this, &ActionBanner::_execute_action).bind(p_action, p_auto_close));
|
||||
hbox->add_child(action_btn);
|
||||
}
|
||||
|
||||
void ActionBanner::_execute_action(const Callable &p_action, bool p_auto_close) {
|
||||
Callable::CallError ce;
|
||||
Variant ret;
|
||||
p_action.callp(nullptr, 0, ret, ce);
|
||||
|
||||
if (p_auto_close) {
|
||||
queue_free();
|
||||
}
|
||||
}
|
||||
|
||||
void ActionBanner::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
icon->set_texture(get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void ActionBanner::_bind_methods() {
|
||||
}
|
||||
|
||||
ActionBanner::ActionBanner() {
|
||||
add_theme_constant_override("margin_bottom", 4);
|
||||
add_theme_constant_override("margin_top", 4);
|
||||
add_theme_constant_override("margin_left", 10);
|
||||
add_theme_constant_override("margin_right", 10);
|
||||
|
||||
hbox = memnew(HBoxContainer);
|
||||
hbox->add_theme_constant_override("hseparation", 8);
|
||||
add_child(hbox);
|
||||
|
||||
icon = memnew(TextureRect);
|
||||
icon->set_expand_mode(TextureRect::ExpandMode::EXPAND_KEEP_SIZE);
|
||||
icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED);
|
||||
hbox->add_child(icon);
|
||||
|
||||
message = memnew(Label);
|
||||
message->set_text(vformat(TTR("User task folder doesn't exist")));
|
||||
message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
||||
hbox->add_child(message);
|
||||
|
||||
Control *spacer = memnew(Control);
|
||||
spacer->set_custom_minimum_size(Size2(0, 16));
|
||||
hbox->add_child(spacer);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* action_banner.h
|
||||
* =============================================================================
|
||||
* 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.
|
||||
* =============================================================================
|
||||
*/
|
||||
/* action_banner.h */
|
||||
|
||||
#ifndef ACTION_BANNER_H
|
||||
#define ACTION_BANNER_H
|
||||
|
||||
#include "scene/gui/margin_container.h"
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
|
||||
class ActionBanner : public MarginContainer {
|
||||
GDCLASS(ActionBanner, MarginContainer);
|
||||
|
||||
private:
|
||||
TextureRect *icon;
|
||||
Label *message;
|
||||
HBoxContainer *hbox;
|
||||
|
||||
void _execute_action(const Callable &p_action, bool p_auto_close);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void set_text(const String &p_text);
|
||||
String get_text() const;
|
||||
|
||||
void add_action(const String &p_name, const Callable &p_action, bool p_auto_close = false);
|
||||
|
||||
void close();
|
||||
|
||||
ActionBanner();
|
||||
};
|
||||
|
||||
#endif // ACTION_BANNER
|
|
@ -32,9 +32,10 @@
|
|||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/tab_container.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
|
||||
/////////////////////// LimboDebuggerTab
|
||||
//**** LimboDebuggerTab
|
||||
|
||||
void LimboDebuggerTab::start_session() {
|
||||
bt_player_list->clear();
|
||||
|
@ -209,7 +210,9 @@ LimboDebuggerTab::LimboDebuggerTab(Ref<EditorDebuggerSession> p_session, WindowW
|
|||
stop_session();
|
||||
}
|
||||
|
||||
//////////////////////// LimboDebuggerPlugin
|
||||
//**** LimboDebuggerPlugin
|
||||
|
||||
LimboDebuggerPlugin *LimboDebuggerPlugin::singleton = nullptr;
|
||||
|
||||
void LimboDebuggerPlugin::_window_visibility_changed(bool p_visible) {
|
||||
}
|
||||
|
@ -259,8 +262,23 @@ bool LimboDebuggerPlugin::has_capture(const String &p_capture) const {
|
|||
return p_capture == "limboai";
|
||||
}
|
||||
|
||||
WindowWrapper *LimboDebuggerPlugin::get_session_tab() const {
|
||||
return window_wrapper;
|
||||
}
|
||||
|
||||
int LimboDebuggerPlugin::get_session_tab_index() const {
|
||||
TabContainer *c = Object::cast_to<TabContainer>(window_wrapper->get_parent());
|
||||
ERR_FAIL_COND_V(c == nullptr, -1);
|
||||
return c->get_tab_idx_from_control(window_wrapper);
|
||||
}
|
||||
|
||||
LimboDebuggerPlugin::LimboDebuggerPlugin() {
|
||||
tab = nullptr;
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
LimboDebuggerPlugin::~LimboDebuggerPlugin() {
|
||||
singleton = nullptr;
|
||||
}
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
|
|
|
@ -70,17 +70,25 @@ class LimboDebuggerPlugin : public EditorDebuggerPlugin {
|
|||
GDCLASS(LimboDebuggerPlugin, EditorDebuggerPlugin);
|
||||
|
||||
private:
|
||||
static LimboDebuggerPlugin *singleton;
|
||||
|
||||
LimboDebuggerTab *tab = nullptr;
|
||||
WindowWrapper *window_wrapper = nullptr;
|
||||
|
||||
void _window_visibility_changed(bool p_visible);
|
||||
|
||||
public:
|
||||
static _FORCE_INLINE_ LimboDebuggerPlugin *get_singleton() { return singleton; }
|
||||
|
||||
void setup_session(int p_idx) override;
|
||||
bool has_capture(const String &p_capture) const override;
|
||||
bool capture(const String &p_message, const Array &p_data, int p_session) override;
|
||||
|
||||
WindowWrapper *get_session_tab() const;
|
||||
int get_session_tab_index() const;
|
||||
|
||||
LimboDebuggerPlugin();
|
||||
~LimboDebuggerPlugin();
|
||||
};
|
||||
|
||||
#endif // LIMBO_DEBUGGER_PLUGIN
|
||||
|
|
|
@ -13,8 +13,10 @@
|
|||
|
||||
#include "limbo_ai_editor_plugin.h"
|
||||
|
||||
#include "action_banner.h"
|
||||
#include "modules/limboai/bt/behavior_tree.h"
|
||||
#include "modules/limboai/bt/tasks/bt_action.h"
|
||||
#include "modules/limboai/bt/tasks/bt_comment.h"
|
||||
#include "modules/limboai/bt/tasks/bt_task.h"
|
||||
#include "modules/limboai/bt/tasks/composites/bt_parallel.h"
|
||||
#include "modules/limboai/bt/tasks/composites/bt_selector.h"
|
||||
|
@ -49,7 +51,10 @@
|
|||
#include "core/variant/callable.h"
|
||||
#include "core/variant/dictionary.h"
|
||||
#include "core/variant/variant.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_inspector.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_paths.h"
|
||||
|
@ -59,6 +64,7 @@
|
|||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/inspector_dock.h"
|
||||
#include "editor/plugins/script_editor_plugin.h"
|
||||
#include "editor/project_settings_editor.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/control.h"
|
||||
|
@ -94,6 +100,16 @@ void TaskTree::_update_item(TreeItem *p_item) {
|
|||
Ref<BTTask> task = p_item->get_metadata(0);
|
||||
ERR_FAIL_COND_MSG(!task.is_valid(), "Invalid task reference in metadata.");
|
||||
p_item->set_text(0, task->get_task_name());
|
||||
if (task->is_class_ptr(BTComment::get_class_ptr_static())) {
|
||||
p_item->set_custom_font(0, (get_theme_font(SNAME("doc_italic"), SNAME("EditorFonts"))));
|
||||
p_item->set_custom_color(0, get_theme_color(SNAME("disabled_font_color"), SNAME("Editor")));
|
||||
} else if (task->get_custom_name().is_empty()) {
|
||||
p_item->set_custom_font(0, nullptr);
|
||||
p_item->clear_custom_color(0);
|
||||
} else {
|
||||
p_item->set_custom_font(0, (get_theme_font(SNAME("bold"), SNAME("EditorFonts"))));
|
||||
// p_item->set_custom_color(0, get_theme_color(SNAME("warning_color"), SNAME("Editor")));
|
||||
}
|
||||
String type_arg;
|
||||
if (task->get_script_instance() && !task->get_script_instance()->get_script()->get_path().is_empty()) {
|
||||
type_arg = task->get_script_instance()->get_script()->get_path();
|
||||
|
@ -341,12 +357,43 @@ TaskTree::~TaskTree() {
|
|||
|
||||
//**** TaskTree ^
|
||||
|
||||
//**** TaskButton
|
||||
|
||||
Control *TaskButton::make_custom_tooltip(const String &p_text) const {
|
||||
EditorHelpBit *help_bit = memnew(EditorHelpBit);
|
||||
help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1));
|
||||
|
||||
String help_text;
|
||||
if (!p_text.is_empty()) {
|
||||
help_text = p_text;
|
||||
} else {
|
||||
help_text = "[i]" + TTR("No description.") + "[/i]";
|
||||
}
|
||||
|
||||
help_bit->set_text(help_text);
|
||||
|
||||
return help_bit;
|
||||
}
|
||||
|
||||
//**** TaskButton ^
|
||||
|
||||
//**** TaskSection
|
||||
|
||||
void TaskSection::_on_task_button_pressed(const StringName &p_task) {
|
||||
void TaskSection::_on_task_button_pressed(const String &p_task) {
|
||||
emit_signal(SNAME("task_button_pressed"), p_task);
|
||||
}
|
||||
|
||||
void TaskSection::_on_task_button_gui_input(const Ref<InputEvent> &p_event, const String &p_task) {
|
||||
if (!p_event->is_pressed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<InputEventMouseButton> mb = p_event;
|
||||
if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT) {
|
||||
emit_signal(SNAME("task_button_rmb"), p_task);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskSection::_on_header_pressed() {
|
||||
set_collapsed(!is_collapsed());
|
||||
}
|
||||
|
@ -368,12 +415,14 @@ void TaskSection::set_filter(String p_filter_text) {
|
|||
}
|
||||
}
|
||||
|
||||
void TaskSection::add_task_button(String p_name, const Ref<Texture> &icon, Variant p_meta) {
|
||||
Button *btn = memnew(Button);
|
||||
void TaskSection::add_task_button(const String &p_name, const Ref<Texture> &icon, const String &p_tooltip, Variant p_meta) {
|
||||
TaskButton *btn = memnew(TaskButton);
|
||||
btn->set_text(p_name);
|
||||
btn->set_icon(icon);
|
||||
btn->set_tooltip_text(p_tooltip);
|
||||
btn->add_theme_constant_override(SNAME("icon_max_width"), 16 * EDSCALE); // Force user icons to be of the proper size.
|
||||
btn->connect("pressed", callable_mp(this, &TaskSection::_on_task_button_pressed).bind(p_meta));
|
||||
btn->connect(SNAME("pressed"), callable_mp(this, &TaskSection::_on_task_button_pressed).bind(p_meta));
|
||||
btn->connect(SNAME("gui_input"), callable_mp(this, &TaskSection::_on_task_button_gui_input).bind(p_meta));
|
||||
tasks_container->add_child(btn);
|
||||
}
|
||||
|
||||
|
@ -395,6 +444,7 @@ void TaskSection::_notification(int p_what) {
|
|||
|
||||
void TaskSection::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("task_button_pressed"));
|
||||
ADD_SIGNAL(MethodInfo("task_button_rmb"));
|
||||
}
|
||||
|
||||
TaskSection::TaskSection(String p_category_name) {
|
||||
|
@ -415,11 +465,68 @@ TaskSection::~TaskSection() {
|
|||
|
||||
//**** TaskPanel
|
||||
|
||||
void TaskPanel::_on_task_button_pressed(const StringName &p_task) {
|
||||
void TaskPanel::_menu_action_selected(int p_id) {
|
||||
ERR_FAIL_COND(context_task.is_empty());
|
||||
switch (p_id) {
|
||||
case MENU_OPEN_DOC: {
|
||||
String help_class;
|
||||
if (context_task.begins_with("res://")) {
|
||||
Ref<Script> s = ResourceLoader::load(context_task, "Script");
|
||||
help_class = s->get_language()->get_global_class_name(context_task);
|
||||
}
|
||||
if (help_class.is_empty()) {
|
||||
// Assuming context task is core class.
|
||||
help_class = context_task;
|
||||
}
|
||||
ScriptEditor::get_singleton()->goto_help("class_name:" + help_class);
|
||||
EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
|
||||
} break;
|
||||
case MENU_EDIT_SCRIPT: {
|
||||
ERR_FAIL_COND(!context_task.begins_with("res://"));
|
||||
ScriptEditor::get_singleton()->open_file(context_task);
|
||||
} break;
|
||||
case MENU_FAVORITE: {
|
||||
PackedStringArray favorite_tasks = GLOBAL_GET("limbo_ai/behavior_tree/favorite_tasks");
|
||||
if (favorite_tasks.has(context_task)) {
|
||||
favorite_tasks.erase(context_task);
|
||||
} else {
|
||||
favorite_tasks.append(context_task);
|
||||
}
|
||||
ProjectSettings::get_singleton()->set_setting("limbo_ai/behavior_tree/favorite_tasks", favorite_tasks);
|
||||
ProjectSettings::get_singleton()->save();
|
||||
emit_signal(SNAME("favorite_tasks_changed"));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void TaskPanel::_on_task_button_pressed(const String &p_task) {
|
||||
emit_signal(SNAME("task_selected"), p_task);
|
||||
}
|
||||
|
||||
void TaskPanel::_on_filter_text_changed(String p_text) {
|
||||
void TaskPanel::_on_task_button_rmb(const String &p_task) {
|
||||
ERR_FAIL_COND(p_task.is_empty());
|
||||
|
||||
context_task = p_task;
|
||||
menu->clear();
|
||||
|
||||
menu->add_icon_item(get_theme_icon(SNAME("Script"), SNAME("EditorIcons")), TTR("Edit Script"), MENU_EDIT_SCRIPT);
|
||||
menu->set_item_disabled(MENU_EDIT_SCRIPT, !context_task.begins_with("res://"));
|
||||
menu->add_icon_item(get_theme_icon(SNAME("Help"), SNAME("EditorIcons")), TTR("Open Documentation"), MENU_OPEN_DOC);
|
||||
|
||||
menu->add_separator();
|
||||
Array favorite_tasks = GLOBAL_GET("limbo_ai/behavior_tree/favorite_tasks");
|
||||
if (favorite_tasks.has(context_task)) {
|
||||
menu->add_icon_item(get_theme_icon(SNAME("NonFavorite"), SNAME("EditorIcons")), TTR("Remove from Favorites"), MENU_FAVORITE);
|
||||
} else {
|
||||
menu->add_icon_item(get_theme_icon(SNAME("Favorites"), SNAME("EditorIcons")), TTR("Add to Favorites"), MENU_FAVORITE);
|
||||
}
|
||||
|
||||
menu->reset_size();
|
||||
menu->set_position(get_screen_position() + get_local_mouse_position());
|
||||
menu->popup();
|
||||
}
|
||||
|
||||
void TaskPanel::_apply_filter(const String &p_text) {
|
||||
for (int i = 0; i < sections->get_child_count(); i++) {
|
||||
TaskSection *sec = Object::cast_to<TaskSection>(sections->get_child(i));
|
||||
ERR_FAIL_COND(sec == nullptr);
|
||||
|
@ -470,7 +577,7 @@ void TaskPanel::refresh() {
|
|||
categorized_tasks["Conditions"] = List<String>();
|
||||
_populate_core_tasks_from_class("BTCondition", &categorized_tasks["Conditions"]);
|
||||
|
||||
categorized_tasks["User"] = List<String>();
|
||||
categorized_tasks["Uncategorized"] = List<String>();
|
||||
|
||||
String dir1 = GLOBAL_GET("limbo_ai/behavior_tree/user_task_dir_1");
|
||||
_populate_from_user_dir(dir1, &categorized_tasks);
|
||||
|
@ -497,19 +604,42 @@ void TaskPanel::refresh() {
|
|||
TaskSection *sec = memnew(TaskSection(cat));
|
||||
for (String task_meta : tasks) {
|
||||
Ref<Texture2D> icon = LimboUtility::get_singleton()->get_task_icon(task_meta);
|
||||
|
||||
String tname;
|
||||
DocTools *dd = EditorHelp::get_doc_data();
|
||||
HashMap<String, DocData::ClassDoc>::Iterator E;
|
||||
if (task_meta.begins_with("res:")) {
|
||||
tname = task_meta.get_file().get_basename().trim_prefix("BT").to_pascal_case();
|
||||
E = dd->class_list.find(vformat("\"%s\"", task_meta.trim_prefix("res://")));
|
||||
if (!E) {
|
||||
E = dd->class_list.find(tname);
|
||||
}
|
||||
} else {
|
||||
tname = task_meta.trim_prefix("BT");
|
||||
E = dd->class_list.find(task_meta);
|
||||
}
|
||||
sec->add_task_button(tname, icon, task_meta);
|
||||
|
||||
String descr;
|
||||
if (E) {
|
||||
if (E->value.description.is_empty() || E->value.description.length() > 1000) {
|
||||
descr = DTR(E->value.brief_description);
|
||||
} else {
|
||||
descr = DTR(E->value.description);
|
||||
}
|
||||
}
|
||||
|
||||
sec->add_task_button(tname, icon, descr, task_meta);
|
||||
}
|
||||
sec->set_filter("");
|
||||
sec->connect("task_button_pressed", callable_mp(this, &TaskPanel::_on_task_button_pressed));
|
||||
sec->connect(SNAME("task_button_pressed"), callable_mp(this, &TaskPanel::_on_task_button_pressed));
|
||||
sec->connect(SNAME("task_button_rmb"), callable_mp(this, &TaskPanel::_on_task_button_rmb));
|
||||
sections->add_child(sec);
|
||||
sec->set_collapsed(collapsed_sections.has(cat));
|
||||
}
|
||||
|
||||
if (!filter_edit->get_text().is_empty()) {
|
||||
_apply_filter(filter_edit->get_text());
|
||||
}
|
||||
}
|
||||
|
||||
void TaskPanel::_populate_core_tasks_from_class(const StringName &p_base_class, List<String> *p_task_classes) {
|
||||
|
@ -535,7 +665,7 @@ void TaskPanel::_populate_from_user_dir(String p_path, HashMap<String, List<Stri
|
|||
String category;
|
||||
if (fn == ".") {
|
||||
full_path = p_path;
|
||||
category = "User";
|
||||
category = "Uncategorized";
|
||||
} else {
|
||||
full_path = p_path.path_join(fn);
|
||||
category = fn.capitalize();
|
||||
|
@ -596,6 +726,7 @@ void TaskPanel::_notification(int p_what) {
|
|||
conf.save(conf_path);
|
||||
} break;
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
refresh_btn->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")));
|
||||
if (is_visible_in_tree()) {
|
||||
refresh();
|
||||
}
|
||||
|
@ -607,17 +738,29 @@ void TaskPanel::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("refresh"), &TaskPanel::refresh);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("task_selected"));
|
||||
ADD_SIGNAL(MethodInfo("favorite_tasks_changed"));
|
||||
}
|
||||
|
||||
TaskPanel::TaskPanel() {
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
add_child(vb);
|
||||
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
vb->add_child(hb);
|
||||
|
||||
filter_edit = memnew(LineEdit);
|
||||
filter_edit->set_clear_button_enabled(true);
|
||||
filter_edit->set_placeholder(TTR("Filter tasks"));
|
||||
filter_edit->connect("text_changed", callable_mp(this, &TaskPanel::_on_filter_text_changed));
|
||||
vb->add_child(filter_edit);
|
||||
filter_edit->connect("text_changed", callable_mp(this, &TaskPanel::_apply_filter));
|
||||
filter_edit->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
hb->add_child(filter_edit);
|
||||
|
||||
refresh_btn = memnew(Button);
|
||||
refresh_btn->set_tooltip_text(TTR("Refresh tasks"));
|
||||
refresh_btn->set_flat(true);
|
||||
refresh_btn->set_focus_mode(FocusMode::FOCUS_NONE);
|
||||
refresh_btn->connect("pressed", callable_mp(this, &TaskPanel::refresh));
|
||||
hb->add_child(refresh_btn);
|
||||
|
||||
ScrollContainer *sc = memnew(ScrollContainer);
|
||||
sc->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
|
@ -628,6 +771,10 @@ TaskPanel::TaskPanel() {
|
|||
sections->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
sections->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
sc->add_child(sections);
|
||||
|
||||
menu = memnew(PopupMenu);
|
||||
add_child(menu);
|
||||
menu->connect("id_pressed", callable_mp(this, &TaskPanel::_menu_action_selected));
|
||||
}
|
||||
|
||||
TaskPanel::~TaskPanel() {
|
||||
|
@ -637,6 +784,11 @@ TaskPanel::~TaskPanel() {
|
|||
|
||||
//**** 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");
|
||||
}
|
||||
|
||||
void LimboAIEditor::_add_task(const Ref<BTTask> &p_task) {
|
||||
ERR_FAIL_COND(p_task.is_null());
|
||||
ERR_FAIL_COND(task_tree->get_bt().is_null());
|
||||
|
@ -659,6 +811,33 @@ void LimboAIEditor::_add_task(const Ref<BTTask> &p_task) {
|
|||
_mark_as_dirty(true);
|
||||
}
|
||||
|
||||
void LimboAIEditor::_add_task_by_class_or_path(String p_class_or_path) {
|
||||
Ref<BTTask> task;
|
||||
|
||||
if (p_class_or_path.begins_with("res:")) {
|
||||
Ref<Script> s = ResourceLoader::load(p_class_or_path, "Script");
|
||||
ERR_FAIL_COND_MSG(s.is_null() || !s->is_valid(), vformat("LimboAI: Failed to instantiate task. Bad script: %s", p_class_or_path));
|
||||
Variant inst = ClassDB::instantiate(s->get_instance_base_type());
|
||||
ERR_FAIL_COND_MSG(inst.is_zero(), vformat("LimboAI: Failed to instantiate base type \"%s\".", s->get_instance_base_type()));
|
||||
|
||||
if (unlikely(!((Object *)inst)->is_class("BTTask"))) {
|
||||
if (!inst.is_ref_counted()) {
|
||||
memdelete((Object *)inst);
|
||||
}
|
||||
ERR_PRINT(vformat("LimboAI: Failed to instantiate task. Script is not a BTTask: %s", p_class_or_path));
|
||||
return;
|
||||
}
|
||||
|
||||
if (inst && s.is_valid()) {
|
||||
((Object *)inst)->set_script(s);
|
||||
task = inst;
|
||||
}
|
||||
} else {
|
||||
task = ClassDB::instantiate(p_class_or_path);
|
||||
}
|
||||
_add_task(task);
|
||||
}
|
||||
|
||||
void LimboAIEditor::_remove_task(const Ref<BTTask> &p_task) {
|
||||
ERR_FAIL_COND(p_task.is_null());
|
||||
ERR_FAIL_COND(task_tree->get_bt().is_null());
|
||||
|
@ -764,40 +943,137 @@ void LimboAIEditor::_mark_as_dirty(bool p_dirty) {
|
|||
}
|
||||
}
|
||||
|
||||
void LimboAIEditor::_create_user_task_dir() {
|
||||
String task_dir = GLOBAL_GET("limbo_ai/behavior_tree/user_task_dir_1");
|
||||
ERR_FAIL_COND_MSG(DirAccess::exists(task_dir), "LimboAIEditor: Directory already exists: " + task_dir);
|
||||
|
||||
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
Error err;
|
||||
err = dir->make_dir_recursive(task_dir);
|
||||
ERR_FAIL_COND_MSG(err != OK, "LimboAIEditor: Failed to create directory: " + task_dir);
|
||||
|
||||
EditorFileSystem::get_singleton()->scan_changes();
|
||||
_update_banners();
|
||||
}
|
||||
|
||||
void LimboAIEditor::_edit_project_settings() {
|
||||
ProjectSettingsEditor::get_singleton()->set_general_page("limbo_ai/behavior_tree");
|
||||
ProjectSettingsEditor::get_singleton()->popup_project_settings();
|
||||
ProjectSettingsEditor::get_singleton()->connect(SNAME("visibility_changed"), callable_mp(this, &LimboAIEditor::_update_banners), CONNECT_ONE_SHOT);
|
||||
}
|
||||
|
||||
void LimboAIEditor::_remove_task_from_favorite(const String &p_task) {
|
||||
PackedStringArray favorite_tasks = GLOBAL_GET("limbo_ai/behavior_tree/favorite_tasks");
|
||||
favorite_tasks.erase(p_task);
|
||||
ProjectSettings::get_singleton()->set_setting("limbo_ai/behavior_tree/favorite_tasks", favorite_tasks);
|
||||
ProjectSettings::get_singleton()->save();
|
||||
}
|
||||
|
||||
void LimboAIEditor::shortcut_input(const Ref<InputEvent> &p_event) {
|
||||
if (!p_event->is_pressed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// * Global shortcuts.
|
||||
|
||||
if (ED_IS_SHORTCUT("limbo_ai/open_debugger", p_event)) {
|
||||
_misc_option_selected(MISC_OPEN_DEBUGGER);
|
||||
accept_event();
|
||||
}
|
||||
|
||||
// * Local shortcuts.
|
||||
|
||||
if (!(has_focus() || get_viewport()->gui_get_focus_owner() == nullptr || is_ancestor_of(get_viewport()->gui_get_focus_owner()))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ED_IS_SHORTCUT("limbo_ai/rename_task", p_event)) {
|
||||
_action_selected(ACTION_RENAME);
|
||||
} else if (ED_IS_SHORTCUT("limbo_ai/move_task_up", p_event)) {
|
||||
_action_selected(ACTION_MOVE_UP);
|
||||
} else if (ED_IS_SHORTCUT("limbo_ai/move_task_down", p_event)) {
|
||||
_action_selected(ACTION_MOVE_DOWN);
|
||||
} else if (ED_IS_SHORTCUT("limbo_ai/duplicate_task", p_event)) {
|
||||
_action_selected(ACTION_DUPLICATE);
|
||||
} else if (ED_IS_SHORTCUT("limbo_ai/remove_task", p_event)) {
|
||||
_action_selected(ACTION_REMOVE);
|
||||
} else if (ED_IS_SHORTCUT("limbo_ai/new_behavior_tree", p_event)) {
|
||||
_new_bt();
|
||||
} else if (ED_IS_SHORTCUT("limbo_ai/save_behavior_tree", p_event)) {
|
||||
_on_save_pressed();
|
||||
} else if (ED_IS_SHORTCUT("limbo_ai/load_behavior_tree", p_event)) {
|
||||
load_dialog->popup_file_dialog();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
accept_event();
|
||||
}
|
||||
|
||||
void LimboAIEditor::_on_tree_rmb(const Vector2 &p_menu_pos) {
|
||||
menu->clear();
|
||||
menu->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Remove"), ACTION_REMOVE);
|
||||
|
||||
Ref<BTTask> task = task_tree->get_selected();
|
||||
ERR_FAIL_COND_MSG(task.is_null(), "LimboAIEditor: get_selected() returned null");
|
||||
|
||||
menu->add_icon_shortcut(get_theme_icon(SNAME("Rename"), SNAME("EditorIcons")), ED_GET_SHORTCUT("limbo_ai/rename_task"), ACTION_RENAME);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("Script"), SNAME("EditorIcons")), TTR("Edit Script"), ACTION_EDIT_SCRIPT);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("Help"), SNAME("EditorIcons")), TTR("Open Documentation"), ACTION_OPEN_DOC);
|
||||
menu->set_item_disabled(ACTION_EDIT_SCRIPT, task->get_script().is_null());
|
||||
|
||||
menu->add_separator();
|
||||
menu->add_icon_item(get_theme_icon(SNAME("MoveUp"), SNAME("EditorIcons")), TTR("Move Up"), ACTION_MOVE_UP);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("MoveDown"), SNAME("EditorIcons")), TTR("Move Down"), ACTION_MOVE_DOWN);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Duplicate"), ACTION_DUPLICATE);
|
||||
menu->add_icon_shortcut(get_theme_icon(SNAME("MoveUp"), SNAME("EditorIcons")), ED_GET_SHORTCUT("limbo_ai/move_task_up"), ACTION_MOVE_UP);
|
||||
menu->add_icon_shortcut(get_theme_icon(SNAME("MoveDown"), SNAME("EditorIcons")), ED_GET_SHORTCUT("limbo_ai/move_task_down"), ACTION_MOVE_DOWN);
|
||||
menu->add_icon_shortcut(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), ED_GET_SHORTCUT("limbo_ai/duplicate_task"), ACTION_DUPLICATE);
|
||||
menu->add_icon_item(get_theme_icon(SNAME("NewRoot"), SNAME("EditorIcons")), TTR("Make Root"), ACTION_MAKE_ROOT);
|
||||
|
||||
menu->add_separator();
|
||||
menu->add_icon_shortcut(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), ED_GET_SHORTCUT("limbo_ai/remove_task"), ACTION_REMOVE);
|
||||
|
||||
menu->reset_size();
|
||||
menu->set_position(p_menu_pos);
|
||||
menu->popup();
|
||||
}
|
||||
|
||||
void LimboAIEditor::_on_action_selected(int p_id) {
|
||||
void LimboAIEditor::_action_selected(int p_id) {
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
switch (p_id) {
|
||||
case ACTION_REMOVE: {
|
||||
Ref<BTTask> sel = task_tree->get_selected();
|
||||
if (sel.is_valid()) {
|
||||
undo_redo->create_action(TTR("Remove BT Task"));
|
||||
if (sel->get_parent().is_null()) {
|
||||
undo_redo->add_do_method(task_tree->get_bt().ptr(), SNAME("set_root_task"), Variant());
|
||||
undo_redo->add_undo_method(task_tree->get_bt().ptr(), SNAME("set_root_task"), task_tree->get_bt()->get_root_task());
|
||||
case ACTION_RENAME: {
|
||||
if (!task_tree->get_selected().is_valid()) {
|
||||
return;
|
||||
}
|
||||
Ref<BTTask> task = task_tree->get_selected();
|
||||
if (task->is_class_ptr(BTComment::get_class_ptr_static())) {
|
||||
rename_dialog->set_title(TTR("Edit Comment"));
|
||||
rename_dialog->get_ok_button()->set_text(TTR("OK"));
|
||||
rename_edit->set_placeholder(TTR("Comment"));
|
||||
} 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));
|
||||
rename_dialog->set_title(TTR("Rename Task"));
|
||||
rename_dialog->get_ok_button()->set_text(TTR("Rename"));
|
||||
rename_edit->set_placeholder(TTR("Custom Name"));
|
||||
}
|
||||
undo_redo->add_do_method(task_tree, SNAME("update_tree"));
|
||||
undo_redo->add_undo_method(task_tree, SNAME("update_tree"));
|
||||
undo_redo->commit_action();
|
||||
EditorNode::get_singleton()->edit_resource(task_tree->get_selected());
|
||||
_mark_as_dirty(true);
|
||||
rename_edit->set_text(task->get_custom_name());
|
||||
rename_dialog->popup_centered();
|
||||
rename_edit->select_all();
|
||||
rename_edit->grab_focus();
|
||||
} break;
|
||||
case ACTION_EDIT_SCRIPT: {
|
||||
ERR_FAIL_COND(task_tree->get_selected().is_null());
|
||||
EditorNode::get_singleton()->edit_resource(task_tree->get_selected()->get_script());
|
||||
} break;
|
||||
case ACTION_OPEN_DOC: {
|
||||
Ref<BTTask> task = task_tree->get_selected();
|
||||
ERR_FAIL_COND(task.is_null());
|
||||
String help_class;
|
||||
if (!task->get_script().is_null()) {
|
||||
Ref<Script> s = task->get_script();
|
||||
help_class = s->get_language()->get_global_class_name(s->get_path());
|
||||
}
|
||||
if (help_class.is_empty()) {
|
||||
help_class = task->get_class();
|
||||
}
|
||||
ScriptEditor::get_singleton()->goto_help("class_name:" + help_class);
|
||||
EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
|
||||
} break;
|
||||
case ACTION_MOVE_UP: {
|
||||
Ref<BTTask> sel = task_tree->get_selected();
|
||||
|
@ -871,6 +1147,92 @@ void LimboAIEditor::_on_action_selected(int p_id) {
|
|||
_mark_as_dirty(true);
|
||||
}
|
||||
} break;
|
||||
case ACTION_REMOVE: {
|
||||
Ref<BTTask> sel = task_tree->get_selected();
|
||||
if (sel.is_valid()) {
|
||||
undo_redo->create_action(TTR("Remove BT Task"));
|
||||
if (sel->get_parent().is_null()) {
|
||||
undo_redo->add_do_method(task_tree->get_bt().ptr(), SNAME("set_root_task"), Variant());
|
||||
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_do_method(task_tree, SNAME("update_tree"));
|
||||
undo_redo->add_undo_method(task_tree, SNAME("update_tree"));
|
||||
undo_redo->commit_action();
|
||||
EditorNode::get_singleton()->edit_resource(task_tree->get_selected());
|
||||
_mark_as_dirty(true);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void LimboAIEditor::_misc_option_selected(int p_id) {
|
||||
switch (p_id) {
|
||||
case MISC_OPEN_DEBUGGER: {
|
||||
ERR_FAIL_COND(LimboDebuggerPlugin::get_singleton() == nullptr);
|
||||
if (LimboDebuggerPlugin::get_singleton()->get_session_tab()->get_window_enabled()) {
|
||||
LimboDebuggerPlugin::get_singleton()->get_session_tab()->set_window_enabled(true);
|
||||
} else {
|
||||
EditorNode::get_singleton()->make_bottom_panel_item_visible(EditorDebuggerNode::get_singleton());
|
||||
EditorDebuggerNode::get_singleton()->get_default_debugger()->switch_to_debugger(
|
||||
LimboDebuggerPlugin::get_singleton()->get_session_tab_index());
|
||||
}
|
||||
} break;
|
||||
case MISC_PROJECT_SETTINGS: {
|
||||
_edit_project_settings();
|
||||
} break;
|
||||
case MISC_CREATE_SCRIPT_TEMPLATE: {
|
||||
String template_path = _get_script_template_path();
|
||||
String template_dir = template_path.get_base_dir();
|
||||
|
||||
if (!FileAccess::exists(template_path)) {
|
||||
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
Error err;
|
||||
if (!dir->exists(template_dir)) {
|
||||
err = dir->make_dir_recursive(template_dir);
|
||||
ERR_FAIL_COND(err != OK);
|
||||
}
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(template_path, FileAccess::WRITE, &err);
|
||||
ERR_FAIL_COND(err != OK);
|
||||
|
||||
String script_template =
|
||||
"# meta-name: Custom Task\n"
|
||||
"# meta-description: Custom task to be used in a BehaviorTree\n"
|
||||
"# meta-default: true\n"
|
||||
"@tool\n"
|
||||
"extends _BASE_\n"
|
||||
"## _CLASS_\n"
|
||||
"\n\n"
|
||||
"# Display a customized name (requires @tool).\n"
|
||||
"func _generate_name() -> String:\n"
|
||||
"_TS_return \"_CLASS_\"\n"
|
||||
"\n\n"
|
||||
"# Called once during initialization.\n"
|
||||
"func _setup() -> void:\n"
|
||||
"_TS_pass\n"
|
||||
"\n\n"
|
||||
"# Called each time this task is entered.\n"
|
||||
"func _enter() -> void:\n"
|
||||
"_TS_pass\n"
|
||||
"\n\n"
|
||||
"# Called each time this task is exited.\n"
|
||||
"func _exit() -> void:\n"
|
||||
"_TS_pass\n"
|
||||
"\n\n"
|
||||
"# Called each time this task is ticked (aka executed).\n"
|
||||
"func _tick(delta: float) -> int:\n"
|
||||
"_TS_return SUCCESS\n";
|
||||
|
||||
f->store_string(script_template);
|
||||
f->close();
|
||||
}
|
||||
|
||||
ScriptEditor::get_singleton()->open_file(template_path);
|
||||
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -879,41 +1241,7 @@ void LimboAIEditor::_on_tree_task_selected(const Ref<BTTask> &p_task) {
|
|||
}
|
||||
|
||||
void LimboAIEditor::_on_tree_task_double_clicked() {
|
||||
if (!task_tree->get_selected().is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
rename_dialog->popup_centered();
|
||||
rename_edit->set_text(task_tree->get_selected()->get_custom_name());
|
||||
rename_edit->select_all();
|
||||
rename_edit->grab_focus();
|
||||
}
|
||||
|
||||
void LimboAIEditor::_on_panel_task_selected(String p_task) {
|
||||
Ref<BTTask> task;
|
||||
|
||||
if (p_task.begins_with("res:")) {
|
||||
Ref<Script> s = ResourceLoader::load(p_task, "Script");
|
||||
ERR_FAIL_COND_MSG(s.is_null() || !s->is_valid(), vformat("LimboAI: Failed to instance task. Bad script: %s", p_task));
|
||||
Variant inst = ClassDB::instantiate(s->get_instance_base_type());
|
||||
ERR_FAIL_COND_MSG(inst.is_zero(), vformat("LimboAI: Failed to instance base type \"%s\".", s->get_instance_base_type()));
|
||||
|
||||
if (unlikely(!((Object *)inst)->is_class("BTTask"))) {
|
||||
if (!inst.is_ref_counted()) {
|
||||
memdelete((Object *)inst);
|
||||
}
|
||||
ERR_PRINT(vformat("LimboAI: Failed to instance task. Script is not a BTTask: %s", p_task));
|
||||
return;
|
||||
}
|
||||
|
||||
if (inst && s.is_valid()) {
|
||||
((Object *)inst)->set_script(s);
|
||||
task = inst;
|
||||
}
|
||||
} else {
|
||||
task = ClassDB::instantiate(p_task);
|
||||
}
|
||||
_add_task(task);
|
||||
_action_selected(ACTION_RENAME);
|
||||
}
|
||||
|
||||
void LimboAIEditor::_on_visibility_changed() {
|
||||
|
@ -927,6 +1255,7 @@ void LimboAIEditor::_on_visibility_changed() {
|
|||
|
||||
task_panel->refresh();
|
||||
}
|
||||
_update_favorite_tasks();
|
||||
}
|
||||
|
||||
void LimboAIEditor::_on_header_pressed() {
|
||||
|
@ -1073,6 +1402,86 @@ void LimboAIEditor::apply_changes() {
|
|||
}
|
||||
}
|
||||
|
||||
void LimboAIEditor::_update_favorite_tasks() {
|
||||
for (int i = 0; i < fav_tasks_hbox->get_child_count(); i++) {
|
||||
fav_tasks_hbox->get_child(i)->queue_free();
|
||||
}
|
||||
Array favorite_tasks = GLOBAL_GET("limbo_ai/behavior_tree/favorite_tasks");
|
||||
for (int i = 0; i < favorite_tasks.size(); i++) {
|
||||
String task_meta = favorite_tasks[i];
|
||||
|
||||
if (task_meta.is_empty() || (!FileAccess::exists(task_meta) && !ClassDB::class_exists(task_meta))) {
|
||||
call_deferred(SNAME("_update_banners"));
|
||||
continue;
|
||||
}
|
||||
|
||||
Button *btn = memnew(Button);
|
||||
String task_name;
|
||||
if (task_meta.begins_with("res:")) {
|
||||
task_name = task_meta.get_file().get_basename().trim_prefix("BT").to_pascal_case();
|
||||
} else {
|
||||
task_name = task_meta.trim_prefix("BT");
|
||||
}
|
||||
btn->set_text(task_name);
|
||||
btn->set_meta(SNAME("task_meta"), task_meta);
|
||||
btn->set_icon(LimboUtility::get_singleton()->get_task_icon(task_meta));
|
||||
btn->set_tooltip_text(vformat(TTR("Add %s task."), task_name));
|
||||
btn->set_flat(true);
|
||||
btn->add_theme_constant_override(SNAME("icon_max_width"), 16 * EDSCALE); // Force user icons to be of the proper size.
|
||||
btn->set_focus_mode(Control::FOCUS_NONE);
|
||||
btn->connect(SNAME("pressed"), callable_mp(this, &LimboAIEditor::_add_task_by_class_or_path).bind(task_meta));
|
||||
fav_tasks_hbox->add_child(btn);
|
||||
}
|
||||
}
|
||||
|
||||
void LimboAIEditor::_update_misc_menu() {
|
||||
PopupMenu *misc_menu = misc_btn->get_popup();
|
||||
|
||||
misc_menu->clear();
|
||||
|
||||
misc_menu->add_icon_shortcut(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Debug"), SNAME("EditorIcons")), ED_GET_SHORTCUT("limbo_ai/open_debugger"), MISC_OPEN_DEBUGGER);
|
||||
misc_menu->add_item(TTR("Project Settings..."), MISC_PROJECT_SETTINGS);
|
||||
|
||||
misc_menu->add_separator();
|
||||
misc_menu->add_item(
|
||||
FileAccess::exists(_get_script_template_path()) ? TTR("Edit Script Template") : TTR("Create Script Template"),
|
||||
MISC_CREATE_SCRIPT_TEMPLATE);
|
||||
}
|
||||
|
||||
void LimboAIEditor::_update_banners() {
|
||||
for (int i = 0; i < banners->get_child_count(); i++) {
|
||||
if (banners->get_child(i)->has_meta(SNAME("managed"))) {
|
||||
banners->get_child(i)->queue_free();
|
||||
}
|
||||
}
|
||||
|
||||
for (String dir_setting : { "limbo_ai/behavior_tree/user_task_dir_1", "limbo_ai/behavior_tree/user_task_dir_2", "limbo_ai/behavior_tree/user_task_dir_3" }) {
|
||||
String task_dir = GLOBAL_GET(dir_setting);
|
||||
if (!task_dir.is_empty() && !DirAccess::exists(task_dir)) {
|
||||
ActionBanner *banner = memnew(ActionBanner);
|
||||
banner->set_text(vformat(TTR("Task folder not found: %s"), task_dir));
|
||||
banner->add_action(TTR("Create"), callable_mp(this, &LimboAIEditor::_create_user_task_dir), true);
|
||||
banner->add_action(TTR("Edit Path..."), callable_mp(this, &LimboAIEditor::_edit_project_settings));
|
||||
banner->set_meta(SNAME("managed"), Variant(true));
|
||||
banners->call_deferred(SNAME("add_child"), banner);
|
||||
}
|
||||
}
|
||||
|
||||
Array favorite_tasks = GLOBAL_GET("limbo_ai/behavior_tree/favorite_tasks");
|
||||
for (int i = 0; i < favorite_tasks.size(); i++) {
|
||||
String task_meta = favorite_tasks[i];
|
||||
|
||||
if (task_meta.is_empty() || (!FileAccess::exists(task_meta) && !ClassDB::class_exists(task_meta))) {
|
||||
ActionBanner *banner = memnew(ActionBanner);
|
||||
banner->set_text(vformat(TTR("Favorite task not found: %s"), task_meta));
|
||||
banner->add_action(TTR("Remove"), callable_mp(this, &LimboAIEditor::_remove_task_from_favorite).bind(task_meta), true);
|
||||
banner->add_action(TTR("Edit Favorite Tasks..."), callable_mp(this, &LimboAIEditor::_edit_project_settings));
|
||||
banner->set_meta(SNAME("managed"), Variant(true));
|
||||
banners->call_deferred(SNAME("add_child"), banner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LimboAIEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
|
@ -1090,9 +1499,6 @@ void LimboAIEditor::_notification(int p_what) {
|
|||
conf.save(conf_path);
|
||||
} break;
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
selector_btn->set_icon(EditorNode::get_singleton()->get_class_icon("BTSelector"));
|
||||
sequence_btn->set_icon(EditorNode::get_singleton()->get_class_icon("BTSequence"));
|
||||
parallel_btn->set_icon(EditorNode::get_singleton()->get_class_icon("BTParallel"));
|
||||
new_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("New"), SNAME("EditorIcons")));
|
||||
load_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Load"), SNAME("EditorIcons")));
|
||||
save_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Save"), SNAME("EditorIcons")));
|
||||
|
@ -1100,6 +1506,9 @@ void LimboAIEditor::_notification(int p_what) {
|
|||
history_back->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Back"), SNAME("EditorIcons")));
|
||||
history_forward->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Forward"), SNAME("EditorIcons")));
|
||||
|
||||
misc_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Tools"), SNAME("EditorIcons")));
|
||||
|
||||
_update_favorite_tasks();
|
||||
_update_header();
|
||||
}
|
||||
}
|
||||
|
@ -1120,6 +1529,20 @@ void LimboAIEditor::_bind_methods() {
|
|||
LimboAIEditor::LimboAIEditor() {
|
||||
idx_history = 0;
|
||||
|
||||
ED_SHORTCUT("limbo_ai/rename_task", TTR("Rename"), Key::F2);
|
||||
ED_SHORTCUT_OVERRIDE("limbo_ai/rename_task", "macos", Key::ENTER);
|
||||
ED_SHORTCUT("limbo_ai/move_task_up", TTR("Move Up"), KeyModifierMask::CMD_OR_CTRL | Key::UP);
|
||||
ED_SHORTCUT("limbo_ai/move_task_down", TTR("Move Down"), KeyModifierMask::CMD_OR_CTRL | Key::DOWN);
|
||||
ED_SHORTCUT("limbo_ai/duplicate_task", TTR("Duplicate"), KeyModifierMask::CMD_OR_CTRL | Key::D);
|
||||
ED_SHORTCUT("limbo_ai/remove_task", TTR("Remove"), Key::KEY_DELETE);
|
||||
|
||||
ED_SHORTCUT("limbo_ai/new_behavior_tree", TTR("New Behavior Tree"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::N);
|
||||
ED_SHORTCUT("limbo_ai/save_behavior_tree", TTR("Save Behavior Tree"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::S);
|
||||
ED_SHORTCUT("limbo_ai/load_behavior_tree", TTR("Load Behavior Tree"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::L);
|
||||
ED_SHORTCUT("limbo_ai/open_debugger", TTR("Open Debugger"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::D);
|
||||
|
||||
set_process_shortcut_input(true);
|
||||
|
||||
save_dialog = memnew(FileDialog);
|
||||
save_dialog->set_file_mode(FileDialog::FILE_MODE_SAVE_FILE);
|
||||
save_dialog->set_title("Save Behavior Tree");
|
||||
|
@ -1136,76 +1559,80 @@ LimboAIEditor::LimboAIEditor() {
|
|||
load_dialog->hide();
|
||||
add_child(load_dialog);
|
||||
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
vb->set_anchor(SIDE_RIGHT, ANCHOR_END);
|
||||
vb->set_anchor(SIDE_BOTTOM, ANCHOR_END);
|
||||
add_child(vb);
|
||||
vbox = memnew(VBoxContainer);
|
||||
vbox->set_anchor(SIDE_RIGHT, ANCHOR_END);
|
||||
vbox->set_anchor(SIDE_BOTTOM, ANCHOR_END);
|
||||
add_child(vbox);
|
||||
|
||||
HBoxContainer *panel = memnew(HBoxContainer);
|
||||
vb->add_child(panel);
|
||||
HBoxContainer *toolbar = memnew(HBoxContainer);
|
||||
vbox->add_child(toolbar);
|
||||
|
||||
selector_btn = memnew(Button);
|
||||
selector_btn->set_text(TTR("Selector"));
|
||||
selector_btn->set_tooltip_text(TTR("Add Selector task."));
|
||||
selector_btn->set_flat(true);
|
||||
selector_btn->set_focus_mode(Control::FOCUS_NONE);
|
||||
selector_btn->connect("pressed", callable_mp(this, &LimboAIEditor::_add_task_with_prototype).bind(Ref<BTTask>(memnew(BTSelector))));
|
||||
panel->add_child(selector_btn);
|
||||
PackedStringArray favorite_tasks_default;
|
||||
favorite_tasks_default.append("BTSelector");
|
||||
favorite_tasks_default.append("BTSequence");
|
||||
favorite_tasks_default.append("BTParallel");
|
||||
GLOBAL_DEF(PropertyInfo(Variant::PACKED_STRING_ARRAY, "limbo_ai/behavior_tree/favorite_tasks", PROPERTY_HINT_ARRAY_TYPE, "String"), favorite_tasks_default);
|
||||
|
||||
sequence_btn = memnew(Button);
|
||||
sequence_btn->set_text(TTR("Sequence"));
|
||||
sequence_btn->set_tooltip_text(TTR("Add Sequence task."));
|
||||
sequence_btn->set_flat(true);
|
||||
sequence_btn->set_focus_mode(Control::FOCUS_NONE);
|
||||
sequence_btn->connect("pressed", callable_mp(this, &LimboAIEditor::_add_task_with_prototype).bind(Ref<BTTask>(memnew(BTSequence))));
|
||||
panel->add_child(sequence_btn);
|
||||
fav_tasks_hbox = memnew(HBoxContainer);
|
||||
toolbar->add_child(fav_tasks_hbox);
|
||||
|
||||
parallel_btn = memnew(Button);
|
||||
parallel_btn->set_text(TTR("Parallel"));
|
||||
parallel_btn->set_tooltip_text(TTR("Add Parallel task."));
|
||||
parallel_btn->set_flat(true);
|
||||
parallel_btn->set_focus_mode(Control::FOCUS_NONE);
|
||||
parallel_btn->connect("pressed", callable_mp(this, &LimboAIEditor::_add_task_with_prototype).bind(Ref<BTTask>(memnew(BTParallel))));
|
||||
panel->add_child(parallel_btn);
|
||||
comment_btn = memnew(Button);
|
||||
comment_btn->set_text(TTR("Comment"));
|
||||
comment_btn->set_icon(LimboUtility::get_singleton()->get_task_icon("BTComment"));
|
||||
comment_btn->set_tooltip_text(TTR("Add a BTComment task."));
|
||||
comment_btn->set_flat(true);
|
||||
comment_btn->set_focus_mode(Control::FOCUS_NONE);
|
||||
comment_btn->connect("pressed", callable_mp(this, &LimboAIEditor::_add_task_by_class_or_path).bind("BTComment"));
|
||||
toolbar->add_child(comment_btn);
|
||||
|
||||
panel->add_child(memnew(VSeparator));
|
||||
toolbar->add_child(memnew(VSeparator));
|
||||
|
||||
new_btn = memnew(Button);
|
||||
new_btn->set_text(TTR("New"));
|
||||
new_btn->set_tooltip_text(TTR("Create new behavior tree."));
|
||||
new_btn->set_tooltip_text(TTR("Create a new behavior tree."));
|
||||
new_btn->set_shortcut(ED_GET_SHORTCUT("limbo_ai/new_behavior_tree"));
|
||||
new_btn->set_flat(true);
|
||||
new_btn->set_focus_mode(Control::FOCUS_NONE);
|
||||
new_btn->connect("pressed", callable_mp(this, &LimboAIEditor::_new_bt));
|
||||
panel->add_child(new_btn);
|
||||
toolbar->add_child(new_btn);
|
||||
|
||||
load_btn = memnew(Button);
|
||||
load_btn->set_text(TTR("Load"));
|
||||
load_btn->set_tooltip_text(TTR("Load behavior tree."));
|
||||
load_btn->set_tooltip_text(TTR("Load behavior tree from a resource file."));
|
||||
load_btn->set_shortcut(ED_GET_SHORTCUT("limbo_ai/load_behavior_tree"));
|
||||
load_btn->set_flat(true);
|
||||
load_btn->set_focus_mode(Control::FOCUS_NONE);
|
||||
load_btn->connect("pressed", callable_mp(load_dialog, &FileDialog::popup_file_dialog));
|
||||
panel->add_child(load_btn);
|
||||
toolbar->add_child(load_btn);
|
||||
|
||||
save_btn = memnew(Button);
|
||||
save_btn->set_text(TTR("Save"));
|
||||
save_btn->set_tooltip_text(TTR("Save current behavior tree."));
|
||||
save_btn->set_tooltip_text(TTR("Save edited behavior tree to a resource file."));
|
||||
save_btn->set_shortcut(ED_GET_SHORTCUT("limbo_ai/save_behavior_tree"));
|
||||
save_btn->set_flat(true);
|
||||
save_btn->set_focus_mode(Control::FOCUS_NONE);
|
||||
save_btn->connect("pressed", callable_mp(this, &LimboAIEditor::_on_save_pressed));
|
||||
panel->add_child(save_btn);
|
||||
toolbar->add_child(save_btn);
|
||||
|
||||
panel->add_child(memnew(VSeparator));
|
||||
toolbar->add_child(memnew(VSeparator));
|
||||
|
||||
new_script_btn = memnew(Button);
|
||||
new_script_btn->set_text(TTR("New Task"));
|
||||
new_script_btn->set_tooltip_text(TTR("Create new task script and edit it."));
|
||||
new_script_btn->set_flat(true);
|
||||
new_script_btn->set_focus_mode(Control::FOCUS_NONE);
|
||||
panel->add_child(new_script_btn);
|
||||
toolbar->add_child(new_script_btn);
|
||||
|
||||
misc_btn = memnew(MenuButton);
|
||||
misc_btn->set_text(TTR("Misc"));
|
||||
misc_btn->set_flat(true);
|
||||
misc_btn->connect("pressed", callable_mp(this, &LimboAIEditor::_update_misc_menu));
|
||||
misc_btn->get_popup()->connect("id_pressed", callable_mp(this, &LimboAIEditor::_misc_option_selected));
|
||||
toolbar->add_child(misc_btn);
|
||||
|
||||
HBoxContainer *nav = memnew(HBoxContainer);
|
||||
nav->set_h_size_flags(SIZE_EXPAND | SIZE_SHRINK_END);
|
||||
panel->add_child(nav);
|
||||
toolbar->add_child(nav);
|
||||
|
||||
history_back = memnew(Button);
|
||||
history_back->set_flat(true);
|
||||
|
@ -1223,22 +1650,24 @@ LimboAIEditor::LimboAIEditor() {
|
|||
header->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
|
||||
header->add_theme_constant_override("hseparation", 8);
|
||||
header->connect("pressed", callable_mp(this, &LimboAIEditor::_on_header_pressed));
|
||||
vb->add_child(header);
|
||||
vbox->add_child(header);
|
||||
|
||||
hsc = memnew(HSplitContainer);
|
||||
hsc->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
hsc->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
vb->add_child(hsc);
|
||||
hsc->set_focus_mode(FOCUS_NONE);
|
||||
vbox->add_child(hsc);
|
||||
|
||||
task_tree = memnew(TaskTree);
|
||||
task_tree->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
task_tree->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
task_tree->hide();
|
||||
task_tree->connect("rmb_pressed", callable_mp(this, &LimboAIEditor::_on_tree_rmb));
|
||||
task_tree->connect("task_selected", callable_mp(this, &LimboAIEditor::_on_tree_task_selected));
|
||||
task_tree->connect("visibility_changed", callable_mp(this, &LimboAIEditor::_on_visibility_changed));
|
||||
task_tree->connect("task_dragged", callable_mp(this, &LimboAIEditor::_on_task_dragged));
|
||||
task_tree->connect("task_double_clicked", callable_mp(this, &LimboAIEditor::_on_tree_task_double_clicked));
|
||||
task_tree->hide();
|
||||
task_tree->connect("visibility_changed", callable_mp(this, &LimboAIEditor::_on_visibility_changed));
|
||||
task_tree->connect("visibility_changed", callable_mp(this, &LimboAIEditor::_update_banners));
|
||||
hsc->add_child(task_tree);
|
||||
|
||||
usage_hint = memnew(Panel);
|
||||
|
@ -1256,29 +1685,29 @@ LimboAIEditor::LimboAIEditor() {
|
|||
|
||||
task_panel = memnew(TaskPanel());
|
||||
hsc->set_split_offset(-300);
|
||||
task_panel->connect("task_selected", callable_mp(this, &LimboAIEditor::_on_panel_task_selected));
|
||||
task_panel->connect("task_selected", callable_mp(this, &LimboAIEditor::_add_task_by_class_or_path));
|
||||
task_panel->connect("favorite_tasks_changed", callable_mp(this, &LimboAIEditor::_update_favorite_tasks));
|
||||
task_panel->hide();
|
||||
hsc->add_child(task_panel);
|
||||
|
||||
banners = memnew(VBoxContainer);
|
||||
vbox->add_child(banners);
|
||||
|
||||
menu = memnew(PopupMenu);
|
||||
add_child(menu);
|
||||
menu->connect("id_pressed", callable_mp(this, &LimboAIEditor::_on_action_selected));
|
||||
menu->connect("id_pressed", callable_mp(this, &LimboAIEditor::_action_selected));
|
||||
|
||||
rename_dialog = memnew(ConfirmationDialog);
|
||||
{
|
||||
rename_dialog->set_title("Rename Task");
|
||||
|
||||
VBoxContainer *vbc = memnew(VBoxContainer);
|
||||
rename_dialog->add_child(vbc);
|
||||
|
||||
rename_edit = memnew(LineEdit);
|
||||
vbc->add_child(rename_edit);
|
||||
rename_edit->set_placeholder("Custom Name");
|
||||
rename_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
rename_edit->set_custom_minimum_size(Size2(350.0, 0.0));
|
||||
|
||||
rename_dialog->register_text_enter(rename_edit);
|
||||
rename_dialog->get_ok_button()->set_text(TTR("Rename"));
|
||||
rename_dialog->connect("confirmed", callable_mp(this, &LimboAIEditor::_rename_task_confirmed));
|
||||
}
|
||||
add_child(rename_dialog);
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "scene/gui/file_dialog.h"
|
||||
#include "scene/gui/flow_container.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/gui/popup_menu.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
|
@ -75,6 +76,13 @@ public:
|
|||
~TaskTree();
|
||||
};
|
||||
|
||||
class TaskButton : public Button {
|
||||
GDCLASS(TaskButton, Button);
|
||||
|
||||
public:
|
||||
virtual Control *make_custom_tooltip(const String &p_text) const override;
|
||||
};
|
||||
|
||||
class TaskSection : public VBoxContainer {
|
||||
GDCLASS(TaskSection, VBoxContainer);
|
||||
|
||||
|
@ -82,7 +90,8 @@ private:
|
|||
FlowContainer *tasks_container;
|
||||
Button *section_header;
|
||||
|
||||
void _on_task_button_pressed(const StringName &p_task);
|
||||
void _on_task_button_pressed(const String &p_task);
|
||||
void _on_task_button_gui_input(const Ref<InputEvent> &p_event, const String &p_task);
|
||||
void _on_header_pressed();
|
||||
|
||||
protected:
|
||||
|
@ -92,7 +101,7 @@ protected:
|
|||
|
||||
public:
|
||||
void set_filter(String p_filter);
|
||||
void add_task_button(String p_name, const Ref<Texture> &icon, Variant p_meta);
|
||||
void add_task_button(const String &p_name, const Ref<Texture> &icon, const String &p_tooltip, Variant p_meta);
|
||||
|
||||
void set_collapsed(bool p_collapsed);
|
||||
bool is_collapsed() const;
|
||||
|
@ -107,14 +116,26 @@ class TaskPanel : public PanelContainer {
|
|||
GDCLASS(TaskPanel, PanelContainer)
|
||||
|
||||
private:
|
||||
enum MenuAction {
|
||||
MENU_EDIT_SCRIPT,
|
||||
MENU_OPEN_DOC,
|
||||
MENU_FAVORITE,
|
||||
};
|
||||
|
||||
LineEdit *filter_edit;
|
||||
VBoxContainer *sections;
|
||||
PopupMenu *menu;
|
||||
Button *refresh_btn;
|
||||
|
||||
String context_task;
|
||||
|
||||
void _populate_core_tasks_from_class(const StringName &p_base_class, List<String> *p_task_classes);
|
||||
void _populate_from_user_dir(String p_path, HashMap<String, List<String>> *p_categories);
|
||||
void _populate_scripted_tasks_from_dir(String p_path, List<String> *p_task_classes);
|
||||
void _on_task_button_pressed(const StringName &p_task);
|
||||
void _on_filter_text_changed(String p_text);
|
||||
void _menu_action_selected(int p_id);
|
||||
void _on_task_button_pressed(const String &p_task);
|
||||
void _on_task_button_rmb(const String &p_task);
|
||||
void _apply_filter(const String &p_text);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
@ -133,20 +154,31 @@ class LimboAIEditor : public Control {
|
|||
|
||||
private:
|
||||
enum Action {
|
||||
ACTION_REMOVE,
|
||||
ACTION_RENAME,
|
||||
ACTION_EDIT_SCRIPT,
|
||||
ACTION_OPEN_DOC,
|
||||
ACTION_MOVE_UP,
|
||||
ACTION_MOVE_DOWN,
|
||||
ACTION_DUPLICATE,
|
||||
ACTION_MAKE_ROOT,
|
||||
ACTION_REMOVE,
|
||||
};
|
||||
|
||||
enum MiscMenu {
|
||||
MISC_OPEN_DEBUGGER,
|
||||
MISC_PROJECT_SETTINGS,
|
||||
MISC_CREATE_SCRIPT_TEMPLATE,
|
||||
};
|
||||
|
||||
Vector<Ref<BehaviorTree>> history;
|
||||
int idx_history;
|
||||
HashSet<Ref<BehaviorTree>> dirty;
|
||||
|
||||
VBoxContainer *vbox;
|
||||
Button *header;
|
||||
HSplitContainer *hsc;
|
||||
TaskTree *task_tree;
|
||||
VBoxContainer *banners;
|
||||
Panel *usage_hint;
|
||||
PopupMenu *menu;
|
||||
FileDialog *save_dialog;
|
||||
|
@ -154,14 +186,14 @@ private:
|
|||
Button *history_back;
|
||||
Button *history_forward;
|
||||
TaskPanel *task_panel;
|
||||
HBoxContainer *fav_tasks_hbox;
|
||||
|
||||
Button *selector_btn;
|
||||
Button *sequence_btn;
|
||||
Button *parallel_btn;
|
||||
Button *comment_btn;
|
||||
Button *new_btn;
|
||||
Button *load_btn;
|
||||
Button *save_btn;
|
||||
Button *new_script_btn;
|
||||
MenuButton *misc_btn;
|
||||
|
||||
ConfirmationDialog *rename_dialog;
|
||||
LineEdit *rename_edit;
|
||||
|
@ -171,14 +203,21 @@ private:
|
|||
HashSet<String> disk_changed_files;
|
||||
|
||||
void _add_task(const Ref<BTTask> &p_task);
|
||||
void _add_task_by_class_or_path(String p_class_or_path);
|
||||
void _remove_task(const Ref<BTTask> &p_task);
|
||||
_FORCE_INLINE_ void _add_task_with_prototype(const Ref<BTTask> &p_prototype) { _add_task(p_prototype->clone()); }
|
||||
void _update_header() const;
|
||||
void _update_history_buttons();
|
||||
void _update_favorite_tasks();
|
||||
void _update_misc_menu();
|
||||
void _update_banners();
|
||||
void _new_bt();
|
||||
void _save_bt(String p_path);
|
||||
void _load_bt(String p_path);
|
||||
void _mark_as_dirty(bool p_dirty);
|
||||
void _create_user_task_dir();
|
||||
void _edit_project_settings();
|
||||
void _remove_task_from_favorite(const String &p_task);
|
||||
|
||||
void _reload_modified();
|
||||
void _resave_modified(String _str = "");
|
||||
|
@ -186,18 +225,20 @@ private:
|
|||
void _rename_task_confirmed();
|
||||
|
||||
void _on_tree_rmb(const Vector2 &p_menu_pos);
|
||||
void _on_action_selected(int p_id);
|
||||
void _action_selected(int p_id);
|
||||
void _misc_option_selected(int p_id);
|
||||
void _on_tree_task_selected(const Ref<BTTask> &p_task);
|
||||
void _on_tree_task_double_clicked();
|
||||
void _on_visibility_changed();
|
||||
void _on_header_pressed();
|
||||
void _on_save_pressed();
|
||||
void _on_panel_task_selected(String p_task);
|
||||
void _on_history_back();
|
||||
void _on_history_forward();
|
||||
void _on_task_dragged(Ref<BTTask> p_task, Ref<BTTask> p_to_task, int p_type);
|
||||
void _on_resources_reload(const Vector<String> &p_resources);
|
||||
|
||||
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg enable-background="new 0 0 16 16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m11.77 6.91-.75 2.22h2.8l-.21 1.97h-3.22l-1.26 3.91h-2.38l1.27-3.91h-2.1l-1.25 3.91h-2.33l1.22-3.91h-2.57l.2-1.97h3l.76-2.22h-2.74l.2-2h3.13l1.29-3.9h2.35l-1.27 3.9h2.16l1.26-3.9h2.33l-1.26 3.9h2.59l-.2 2zm-5.23 2.22h2.13l.75-2.22h-2.12z" fill="#808080"/></svg>
|
After Width: | Height: | Size: 364 B |
|
@ -60,6 +60,7 @@
|
|||
#include "bt/tasks/actions/bt_wait.h"
|
||||
#include "bt/tasks/actions/bt_wait_ticks.h"
|
||||
#include "bt/tasks/bt_action.h"
|
||||
#include "bt/tasks/bt_comment.h"
|
||||
#include "bt/tasks/bt_composite.h"
|
||||
#include "bt/tasks/bt_condition.h"
|
||||
#include "bt/tasks/bt_decorator.h"
|
||||
|
@ -120,6 +121,8 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) {
|
|||
GDREGISTER_CLASS(BTPlayer);
|
||||
GDREGISTER_CLASS(BTState);
|
||||
|
||||
GDREGISTER_CLASS(BTComment);
|
||||
|
||||
GDREGISTER_CLASS(BTComposite);
|
||||
GDREGISTER_CLASS(BTSequence);
|
||||
GDREGISTER_CLASS(BTSelector);
|
||||
|
|
Loading…
Reference in New Issue