From a236aee22b1edd0df770848d86c938d3cd1adc18 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Tue, 30 Aug 2022 18:48:49 +0200 Subject: [PATCH] Add BTPlayer and BehaviorTree classes, and fix leaking refs --- limboai/bt/behavior_tree.cpp | 45 +++++++++++ limboai/bt/behavior_tree.h | 38 +++++++++ limboai/bt/bt_player.cpp | 111 +++++++++++++++++++++++++++ limboai/bt/bt_player.h | 57 ++++++++++++++ limboai/bt/bt_task.cpp | 35 +++++---- limboai/bt/bt_task.h | 16 ++-- limboai/icons/icon_b_t_player.svg | 1 + limboai/icons/icon_behavior_tree.svg | 1 + limboai/limbo_string_names.cpp | 2 + limboai/limbo_string_names.h | 1 + limboai/register_types.cpp | 4 + test/Agent.gd | 19 +++++ test/Agent.tscn | 9 +++ test/Test.tscn | 7 ++ test/ai/tasks/BTPrintLine.gd | 14 ++++ test/ai/trees/test.tres | 6 ++ test/default_env.tres | 7 ++ test/icon.png | Bin 0 -> 3305 bytes test/icon.png.import | 35 +++++++++ test/project.godot | 37 +++++++++ 20 files changed, 423 insertions(+), 22 deletions(-) create mode 100644 limboai/bt/behavior_tree.cpp create mode 100644 limboai/bt/behavior_tree.h create mode 100644 limboai/bt/bt_player.cpp create mode 100644 limboai/bt/bt_player.h create mode 100644 limboai/icons/icon_b_t_player.svg create mode 100644 limboai/icons/icon_behavior_tree.svg create mode 100644 test/Agent.gd create mode 100644 test/Agent.tscn create mode 100644 test/Test.tscn create mode 100644 test/ai/tasks/BTPrintLine.gd create mode 100644 test/ai/trees/test.tres create mode 100644 test/default_env.tres create mode 100644 test/icon.png create mode 100644 test/icon.png.import create mode 100644 test/project.godot diff --git a/limboai/bt/behavior_tree.cpp b/limboai/bt/behavior_tree.cpp new file mode 100644 index 0000000..c89252b --- /dev/null +++ b/limboai/bt/behavior_tree.cpp @@ -0,0 +1,45 @@ +/* behavior_tree.cpp */ + +#include "behavior_tree.h" +#include "core/class_db.h" +#include "core/list.h" +#include "core/object.h" +#include "core/variant.h" +#include + +void BehaviorTree::init() { + List stack; + BTTask *task = root_task.ptr(); + while (task != nullptr) { + for (int i = 0; i < task->get_child_count(); i++) { + task->get_child(i)->_parent = task; + stack.push_back(task->get_child(i).ptr()); + } + task = nullptr; + if (!stack.empty()) { + task = stack.front()->get(); + stack.pop_front(); + } + } +} + +Ref BehaviorTree::clone() const { + Ref copy = duplicate(false); + copy->set_path(""); + if (root_task.is_valid()) { + copy->root_task = root_task->clone(); + } + return copy; +} + +void BehaviorTree::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_description", "p_value"), &BehaviorTree::set_description); + ClassDB::bind_method(D_METHOD("get_description"), &BehaviorTree::get_description); + ClassDB::bind_method(D_METHOD("set_root_task", "p_value"), &BehaviorTree::set_root_task); + ClassDB::bind_method(D_METHOD("get_root_task"), &BehaviorTree::get_root_task); + ClassDB::bind_method(D_METHOD("init"), &BehaviorTree::init); + ClassDB::bind_method(D_METHOD("clone"), &BehaviorTree::clone); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "description", PROPERTY_HINT_MULTILINE_TEXT), "set_description", "get_description"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_task", PROPERTY_HINT_RESOURCE_TYPE, "BTTask"), "set_root_task", "get_root_task"); +} \ No newline at end of file diff --git a/limboai/bt/behavior_tree.h b/limboai/bt/behavior_tree.h new file mode 100644 index 0000000..23dffa4 --- /dev/null +++ b/limboai/bt/behavior_tree.h @@ -0,0 +1,38 @@ +/* behavior_tree.h */ + +#ifndef BEHAVIOR_TREE_H +#define BEHAVIOR_TREE_H + +#include "core/object.h" +#include "core/resource.h" + +#include "bt_task.h" + +class BehaviorTree : public Resource { + GDCLASS(BehaviorTree, Resource); + +private: + String description; + Ref root_task; + +protected: + static void _bind_methods(); + +public: + void set_description(String p_value) { + description = p_value; + emit_changed(); + } + String get_description() const { return description; } + + void set_root_task(const Ref &p_value) { + root_task = p_value; + emit_changed(); + } + Ref get_root_task() const { return root_task; } + + void init(); + Ref clone() const; +}; + +#endif // BEHAVIOR_TREE_H \ No newline at end of file diff --git a/limboai/bt/bt_player.cpp b/limboai/bt/bt_player.cpp new file mode 100644 index 0000000..971f891 --- /dev/null +++ b/limboai/bt/bt_player.cpp @@ -0,0 +1,111 @@ +/* bt_player.cpp */ + +#include "bt_player.h" + +#include "../limbo_string_names.h" +#include "bt_task.h" +#include "core/class_db.h" +#include "core/engine.h" +#include "core/io/resource_loader.h" +#include "core/object.h" +#include + +VARIANT_ENUM_CAST(BTPlayer::UpdateMode); + +void BTPlayer::_load_tree() { + _loaded_tree.unref(); + _root_task.unref(); + ERR_FAIL_COND_MSG(!behavior_tree.is_valid(), "BTPlayer needs a valid behavior tree."); + ERR_FAIL_COND_MSG(!behavior_tree->get_root_task().is_valid(), "Behavior tree has no valid root task."); + _loaded_tree = behavior_tree; + _root_task = _loaded_tree->get_root_task()->clone(); + _root_task->initialize(get_owner(), blackboard); +} + +void BTPlayer::set_behavior_tree(const Ref &p_tree) { + behavior_tree = p_tree; + if (Engine::get_singleton()->is_editor_hint() == false) { + _load_tree(); + set_update_mode(update_mode); + } +} + +void BTPlayer::set_update_mode(UpdateMode p_mode) { + update_mode = p_mode; + set_active(active); +} + +void BTPlayer::set_active(bool p_active) { + active = p_active; + if (!Engine::get_singleton()->is_editor_hint()) { + set_process(update_mode == UpdateMode::IDLE); + set_physics_process(update_mode == UpdateMode::PHYSICS); + } +} + +void BTPlayer::update(float p_delta) { + if (!_root_task.is_valid()) { + ERR_PRINT_ONCE(vformat("BTPlayer has no root task to update (owner: %s)", get_owner())); + return; + } + if (active) { + int status = _root_task->execute(p_delta); + if (status == BTTask::SUCCESS || status == BTTask::FAILURE) { + set_active(auto_restart); + emit_signal(LimboStringNames::get_singleton()->behavior_tree_finished, status); + } + } +} + +void BTPlayer::restart() { + _root_task->cancel(); + set_active(true); +} + +void BTPlayer::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_PROCESS: { + if (active) { + Variant time = get_process_delta_time(); + update(time); + } + } break; + case NOTIFICATION_PHYSICS_PROCESS: { + if (active) { + Variant time = get_process_delta_time(); + update(time); + } + } break; + case NOTIFICATION_READY: { + if (!Engine::get_singleton()->is_editor_hint()) { + if (behavior_tree.is_valid()) { + _load_tree(); + } + set_active(active); + } + } break; + } +} + +void BTPlayer::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_behavior_tree", "p_path"), &BTPlayer::set_behavior_tree); + ClassDB::bind_method(D_METHOD("get_behavior_tree"), &BTPlayer::get_behavior_tree); + ClassDB::bind_method(D_METHOD("set_update_mode", "p_mode"), &BTPlayer::set_update_mode); + ClassDB::bind_method(D_METHOD("get_update_mode"), &BTPlayer::get_update_mode); + ClassDB::bind_method(D_METHOD("set_active", "p_active"), &BTPlayer::set_active); + ClassDB::bind_method(D_METHOD("get_active"), &BTPlayer::get_active); + ClassDB::bind_method(D_METHOD("set_auto_restart", "p_value"), &BTPlayer::set_auto_restart); + ClassDB::bind_method(D_METHOD("get_auto_restart"), &BTPlayer::get_auto_restart); + + ClassDB::bind_method(D_METHOD("update", "p_delta"), &BTPlayer::update); + ClassDB::bind_method(D_METHOD("restart"), &BTPlayer::restart); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "behavior_tree", PROPERTY_HINT_RESOURCE_TYPE, "BehaviorTree"), "set_behavior_tree", "get_behavior_tree"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "Idle,Physics,Manual"), "set_update_mode", "get_update_mode"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "get_active"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_restart"), "set_auto_restart", "get_auto_restart"); + + BIND_ENUM_CONSTANT(IDLE); + BIND_ENUM_CONSTANT(PHYSICS); + BIND_ENUM_CONSTANT(MANUAL); +} \ No newline at end of file diff --git a/limboai/bt/bt_player.h b/limboai/bt/bt_player.h new file mode 100644 index 0000000..d17aacb --- /dev/null +++ b/limboai/bt/bt_player.h @@ -0,0 +1,57 @@ +/* bt_player.h */ + +#ifndef BT_PLAYER_H +#define BT_PLAYER_H + +#include "core/object.h" +#include "scene/main/node.h" + +#include "behavior_tree.h" +#include "bt_task.h" +#include + +class BTPlayer : public Node { + GDCLASS(BTPlayer, Node); + +public: + enum UpdateMode : unsigned int { + IDLE, // automatically call update() during NOTIFICATION_PROCESS + PHYSICS, //# automatically call update() during NOTIFICATION_PHYSICS + MANUAL, // manually update state machine, user must call update(delta) + }; + +private: + Ref behavior_tree; + UpdateMode update_mode = UpdateMode::IDLE; + bool active = false; + bool auto_restart = false; + Dictionary blackboard; + + Ref _loaded_tree; + Ref _root_task; + + void _load_tree(); + +protected: + static void _bind_methods(); + + void _notification(int p_notification); + +public: + void set_behavior_tree(const Ref &p_tree); + Ref get_behavior_tree() const { return behavior_tree; }; + + void set_update_mode(UpdateMode p_mode); + UpdateMode get_update_mode() const { return update_mode; } + + void set_active(bool p_active); + bool get_active() const { return active; } + + void set_auto_restart(bool p_value) { auto_restart = p_value; } + bool get_auto_restart() const { return auto_restart; } + + void update(float p_delta); + void restart(); +}; + +#endif // BT_PLAYER_H \ No newline at end of file diff --git a/limboai/bt/bt_task.cpp b/limboai/bt/bt_task.cpp index 94ca285..3fc7cb7 100644 --- a/limboai/bt/bt_task.cpp +++ b/limboai/bt/bt_task.cpp @@ -2,14 +2,14 @@ #include "bt_task.h" +#include "../limbo_string_names.h" +#include "../limbo_utility.h" #include "core/class_db.h" #include "core/object.h" #include "core/script_language.h" #include "core/variant.h" #include "editor/editor_node.h" - -#include "../limbo_string_names.h" -#include "../limbo_utility.h" +#include String BTTask::_generate_name() const { if (get_script_instance()) { @@ -58,7 +58,7 @@ String BTTask::get_task_name() const { Ref BTTask::get_root() const { const BTTask *task = this; while (!task->is_root()) { - task = task->get_parent().ptr(); + task = task->_parent; } return Ref(task); } @@ -86,11 +86,11 @@ void BTTask::initialize(Object *p_agent, Dictionary p_blackboard) { Ref BTTask::clone() const { Ref inst = duplicate(true); - inst.ptr()->_parent.unref(); - CRASH_COND(inst.ptr()->get_parent().is_valid()); + inst->_parent = nullptr; + CRASH_COND(inst->get_parent().is_valid()); for (int i = 0; i < _children.size(); i++) { Ref c = get_child(i)->clone(); - c->_parent = inst; + c->_parent = inst.ptr(); inst->_children.set(i, c); } return inst; @@ -152,19 +152,19 @@ int BTTask::get_child_count() const { } void BTTask::add_child(Ref p_child) { - ERR_FAIL_COND_MSG(p_child.ptr()->get_parent().is_valid(), "p_child already has a parent!"); - p_child->_parent = Ref(this); + ERR_FAIL_COND_MSG(p_child->get_parent().is_valid(), "p_child already has a parent!"); + p_child->_parent = this; _children.push_back(p_child); emit_changed(); } void BTTask::add_child_at_index(Ref p_child, int p_idx) { - ERR_FAIL_COND_MSG(p_child.ptr()->get_parent().is_valid(), "p_child already has a parent!"); + ERR_FAIL_COND_MSG(p_child->get_parent().is_valid(), "p_child already has a parent!"); if (p_idx < 0 || p_idx > _children.size()) { p_idx = _children.size(); } _children.insert(p_idx, p_child); - p_child->_parent = Ref(this); + p_child->_parent = this; emit_changed(); } @@ -174,7 +174,7 @@ void BTTask::remove_child(Ref p_child) { ERR_FAIL_MSG("p_child not found!"); } else { _children.remove(idx); - p_child->_parent.unref(); + p_child->_parent = nullptr; emit_changed(); } } @@ -188,7 +188,7 @@ int BTTask::get_child_index(const Ref &p_child) const { } Ref BTTask::next_sibling() const { - if (_parent.is_valid()) { + if (_parent != nullptr) { int idx = _parent->get_child_index(Ref(this)); if (idx != -1 && _parent->get_child_count() > (idx + 1)) { return _parent->get_child(idx + 1); @@ -276,7 +276,16 @@ void BTTask::_bind_methods() { BTTask::BTTask() { _custom_name = String(); _agent = nullptr; + _parent = nullptr; _blackboard = Dictionary(); _children = Vector>(); _status = FRESH; +} + +BTTask::~BTTask() { + for (int i = 0; i < get_child_count(); i++) { + ERR_FAIL_COND(!get_child(i).is_valid()); + get_child(i)->_parent = nullptr; + get_child(i).unref(); + } } \ No newline at end of file diff --git a/limboai/bt/bt_task.h b/limboai/bt/bt_task.h index 368016d..07441a1 100644 --- a/limboai/bt/bt_task.h +++ b/limboai/bt/bt_task.h @@ -10,6 +10,7 @@ #include "core/ustring.h" #include "core/vector.h" #include "scene/resources/texture.h" +#include class BTTask : public Resource { GDCLASS(BTTask, Resource); @@ -21,18 +22,14 @@ public: FAILURE, SUCCESS, }; - enum TaskType { - ACTION, - CONDITION, - COMPOSITE, - DECORATOR, - }; private: + friend class BehaviorTree; + String _custom_name; Object *_agent; Dictionary _blackboard; - Ref _parent; + BTTask *_parent; Vector> _children; int _status; @@ -51,8 +48,8 @@ protected: public: Object *get_agent() const { return _agent; }; Dictionary get_blackboard() const { return _blackboard; }; - Ref get_parent() const { return _parent; }; - bool is_root() const { return _parent.is_null(); }; + Ref get_parent() const { return Ref(_parent); }; + bool is_root() const { return _parent == nullptr; }; Ref get_root() const; int get_status() const { return _status; }; String get_custom_name() const { return _custom_name; }; @@ -76,6 +73,7 @@ public: void print_tree(int p_initial_tabs = 0) const; BTTask(); + ~BTTask(); }; #endif // BTTASK_H \ No newline at end of file diff --git a/limboai/icons/icon_b_t_player.svg b/limboai/icons/icon_b_t_player.svg new file mode 100644 index 0000000..166edff --- /dev/null +++ b/limboai/icons/icon_b_t_player.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/limboai/icons/icon_behavior_tree.svg b/limboai/icons/icon_behavior_tree.svg new file mode 100644 index 0000000..f8cd473 --- /dev/null +++ b/limboai/icons/icon_behavior_tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/limboai/limbo_string_names.cpp b/limboai/limbo_string_names.cpp index f3d6beb..abe8fd1 100644 --- a/limboai/limbo_string_names.cpp +++ b/limboai/limbo_string_names.cpp @@ -1,6 +1,7 @@ /* limbo_string_names.cpp */ #include "limbo_string_names.h" +#include "core/string_name.h" LimboStringNames *LimboStringNames::singleton = nullptr; @@ -10,4 +11,5 @@ LimboStringNames::LimboStringNames() { _enter = StaticCString::create("_enter"); _exit = StaticCString::create("_exit"); _tick = StaticCString::create("_tick"); + behavior_tree_finished = StaticCString::create("behavior_tree_finished"); } \ No newline at end of file diff --git a/limboai/limbo_string_names.h b/limboai/limbo_string_names.h index d29211b..f3ef0f3 100644 --- a/limboai/limbo_string_names.h +++ b/limboai/limbo_string_names.h @@ -28,6 +28,7 @@ public: StringName _enter; StringName _exit; StringName _tick; + StringName behavior_tree_finished; }; #endif // LIMBO_STRING_NAMES_H \ No newline at end of file diff --git a/limboai/register_types.cpp b/limboai/register_types.cpp index 8ec1b90..13f2be5 100644 --- a/limboai/register_types.cpp +++ b/limboai/register_types.cpp @@ -4,6 +4,7 @@ #include "core/class_db.h" +#include "bt/behavior_tree.h" #include "bt/bt_action.h" #include "bt/bt_always_fail.h" #include "bt/bt_always_succeed.h" @@ -17,6 +18,7 @@ #include "bt/bt_fail.h" #include "bt/bt_invert.h" #include "bt/bt_parallel.h" +#include "bt/bt_player.h" #include "bt/bt_probability.h" #include "bt/bt_random_selector.h" #include "bt/bt_random_sequence.h" @@ -36,6 +38,8 @@ void register_limboai_types() { ClassDB::register_class(); + ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); diff --git a/test/Agent.gd b/test/Agent.gd new file mode 100644 index 0000000..f32da4d --- /dev/null +++ b/test/Agent.gd @@ -0,0 +1,19 @@ +extends KinematicBody2D + + +onready var bt_player: BTPlayer = $BTPlayer + + +func _ready() -> void: + _configure_ai() + + +func _configure_ai() -> void: + var tree := BehaviorTree.new() + var seq := BTSequence.new() + var print_task := BTPrintLine.new("Hello world!") + seq.add_child(print_task) + tree.root_task = seq + bt_player.behavior_tree = tree + print("Assigning tree second time") + bt_player.behavior_tree = tree diff --git a/test/Agent.tscn b/test/Agent.tscn new file mode 100644 index 0000000..c67aa37 --- /dev/null +++ b/test/Agent.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://Agent.gd" type="Script" id=2] + +[node name="Agent" type="KinematicBody2D"] +script = ExtResource( 2 ) + +[node name="BTPlayer" type="BTPlayer" parent="."] +active = true diff --git a/test/Test.tscn b/test/Test.tscn new file mode 100644 index 0000000..3311850 --- /dev/null +++ b/test/Test.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://Agent.tscn" type="PackedScene" id=1] + +[node name="Test" type="Node2D"] + +[node name="Agent" parent="." instance=ExtResource( 1 )] diff --git a/test/ai/tasks/BTPrintLine.gd b/test/ai/tasks/BTPrintLine.gd new file mode 100644 index 0000000..e376612 --- /dev/null +++ b/test/ai/tasks/BTPrintLine.gd @@ -0,0 +1,14 @@ +class_name BTPrintLine +extends BTTask + + +export var line: String + + +func _init(p_line: String = "") -> void: + line = p_line + + +func _tick(_delta: float) -> int: + print(line) + return SUCCESS diff --git a/test/ai/trees/test.tres b/test/ai/trees/test.tres new file mode 100644 index 0000000..174eb38 --- /dev/null +++ b/test/ai/trees/test.tres @@ -0,0 +1,6 @@ +[gd_resource type="BehaviorTree" load_steps=2 format=2] + +[sub_resource type="BTSequence" id=1] + +[resource] +root_task = SubResource( 1 ) diff --git a/test/default_env.tres b/test/default_env.tres new file mode 100644 index 0000000..20207a4 --- /dev/null +++ b/test/default_env.tres @@ -0,0 +1,7 @@ +[gd_resource type="Environment" load_steps=2 format=2] + +[sub_resource type="ProceduralSky" id=1] + +[resource] +background_mode = 2 +background_sky = SubResource( 1 ) diff --git a/test/icon.png b/test/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c98fbb601c83c81ec8c22b1dba7d1d57c62b323c GIT binary patch literal 3305 zcmVNc=P)Px>qe(&U$es`gSqKCHF-lq>v1vga#%UF>TTrLR zW%{UNJKZi|Pj@Rc9GyPBD1CamMMf6SL~V^ag9~Vzut^L^0!Tv0LK0FTdnJ`x->EF(MZIP5kY*1-@^egP~7mH>({qi7{6 zQF;bN-XMq~+RzA8lI9AtJuz@PY*+{SP-Gbd@mZ(r*eE&`XO5!C>w#-pcmS28K^qzY zfTGCjor*I@ltgKb03nh#Fh$KpDL=o}gj-g4v6{}ZR1*mvXv?|gEA&Yr#r;Zw*d zUabIx8iHf+WoIO_c11Ba&!34XihSMF&C#YFDjU0)mmbXz3ex!D&t9UYp>;&R%(O(_ z*z^;&A84SWzKiQpqsdQ+Vs?rFS(f?R;c8xg_ft;Roec_~1KsVww}wzq5D}*5x6k|& zf~2A3@L4|ix|Q=L>rnmKE;B3UB=OMQxAK$Ce;LvDp?hwn-{Rn}Uo~U4IXTs4V%MQY zCWULcZFU0R%gbU;_Ef(A#76r1%|YWis0t`9$R{cyjFnsV(POrI)SGQi-l{mu{e?5R zepcp?AQ54D3g_mswd@RLn{z~;^Cl}>%j@}TWixL+audY``MmSV{-E(3R0Ws^U9%mk zmAond;N8k*{(f!}e^~d(i1Hq@jdv@XN2MLAl}3yaECf{nz5N3KMCjDCFzB_7)gkjj z>2Z={^e74l7u>P4oo1{Kc~sgFI`xP#f`uR}z_p~qLwws5)h)eLxAX=?+fB2_6kG)a zeE3U}YSi;Qc}gq*;kw|Tu5Oy{F)l`0;$$RA6)@d^I9>n9N^W1g0D!WJYJT&d@6p`W zfmWmD=^x$2@|)+=&@n(wn<-#M#zIY-iH42=UU>XI3i7l0^?#ILwb@CU63f5b_jeS| zn+d@CpB>^?Ti*1WuHSaRniWO-^Xl8!b+D0stAl$BQjr8G`KX-vGpCc0lEAKmjl6lN z5r?ddL)6hBi2|!`NM+@MRO*^qsi>~y`%4$%P+-S_M#8ibt8Pf;m7O23?cF^-X$52l zEV@3AM^`Q9vy(=)?W+gi)8lPCP&k!)Z(Bsa#m@S7j#1gzJx&pQ!yzlYvA==iExkN@ zTMnz!68Wg=9Ius~p?A=A>P(5$@#w1MG`6<$`Il8=(j0RI#KlIj>!qL4)MMjk|8*3* zbL8w!iwnbSb<*17eb=8TBt(Uv*Qz*e>>p9CRtapnJD-#&4Xd8ojIpD~Yk&6&7;_U` z|L{sgNzJAYPkIOsaN5{^*@Xva?HTkC9>DHY*!1B^L`lv1hgXhC$EO1BSh9fYXU*VG zpVwjRvs^m2ml?)B3xE2&j_YU5;Ep8=e75zefN3cSw04`>U3D&~3|AIJAJnEseqE*p>uF=1Cv$SfvI z!(+vnRMj+4vb)@8Tb~MW$}-RYemjyN^W@U3pfWj;cyehLk|6W*KkUFMkM3W9AE!Wb zTL-_}Udr6GXl}`!5;P_!3b*7=VQyM9zuR6)b6dxl?fo)@-u`$$Pu#bHB*W+#Gp!_Y z*ZdUbq#B3_QPbElK4*QE)$x+;qpGazKD1C!=jx=^ta=2+!&oRjmg4Jf{ z?T`J78TjoBD9Y&OtwFEhrIq<48uS2IEEbY8C$TVd5`X!kj*`Qd7RI`3elib!C*xb1 z(UIgPMzT12GEcpEly0*vU|ugqP(r~!E}l-JK~G&>9S_|9Aj@uD&azvVQ&RF4YZp!> zJ3hi|zlabu5u>=y+3^vqT{xAJlDCHFJ#hbn)Ya9IXwdWH;_1O)ef$at)k@qrEf%ZQ z%DU&)(a_KUxMpn2t6Mm@e?LVzaUT6LCWo=>;TzfYZ~+;U!#wJXa^g66-~d}*-Gas9 zGQt`f8d&$-daPC}H%^NkiV}?n<5oawj2=M{sHv&JXl(bWFDox6HP$o6KRY=Jl_;PR zMP?^QdD4vyrL3&XqugjTQd3idAPA(!=*P?c_!Z!e`f9aWuk~t4qQew;9IwMq>%w#92+*iNN#Qp zadB}J6)j=I#urf#czO3X!C*Z&LD5rfCLY^S$>ZP6}eFW#%-2L)+t{`cPyqLD6))yK1?m7F>6=?Y&8f)>3zbH1O)cT}QNtB4KL(A@1i zMzF88gDrb&hn~H`?o`-XUeDI@dXfwwboAS>*qvV6UMhkfzO~q$V+s%8loj4P(&9H= ze`sC`uI?L9L4e;YK&2A7XF)0}u1lh+%Z$S*Q{ORwtSHpAyWYpI>bqzU!p`gqlf$*l zO^*g(+T?Hq0n%ebkyIin(R#FM6&9;^6WJU5R)By&tZQ6PV zS^MWhqtcj}7)kON#>?4Gv(K#2=6mv)5;@W->l(1q*>9t&xfesIn$&3j4WxkffXaq0 zwwBkAD2vjoi4E8CK;cwoC3#wO!|}v-XOJ`obIo05{&DMQIRyHAd5@%-0xA%uA0UK2qng>xb(kvMzX)7t^ z);-|T`mgSsHKM$+a{!w|Mt5QLwD>sA+;u-+k%z_ZL?el$#&|kX?ygLfm zxZ^Fo^bOhx)w*6In?vS{Q|uk08cKRK}t+0ukQSCOyP$^HEC+zzX51M#=e-?*xHWMDRcLdIV41daHy{HimwDo z6!_O=*(}MK!YeyJpmgu(cF1tpEv}m;0s8{4z4HlHyMxDncn8zs!g+OXEk`CeEj}9N zq#Ag1$#jyV_5AjYQg*!mS->;`S^;iU)ih9D+eks)H2z`1RHny;F<^CEwk+}d^k^Ph zl);*XQ|ayL;rZWh=fA(G2#AJz1&r&as9I8S@9m3Owftrb5n*)pTluK^9LHOFIo{G2 zG}l$9R*{<+L2hCsOJ~Lt6Q-rRub*8X{*4{)e}>%=_&DxOFeq1LRia4Yyj*Tyynw>F zxkKf(MiaG0*L|V-^Zhtvg-(-|F0&1rU8bqab*n5TT8~C860O$|6Rt%P1=1(EjIQZ% z;Y^PU2VC*~^2!sG?mbBPS0~0yd-+086)+rHjhfk6>CB$t`o%;=kdYF9NwiKkwbIpN z;_FlOuHQHHSZ&@fUuSI-S*t`DjsiIB z{=1M@JKVC$a8z{2;xCPfRb{~T>uo#5rL4L+z9n`rSUt3Tt nAZ`TZm+q1gPVN84&*%Ra7her>#-hHS00000NkvXXu0mjf|6N@O literal 0 HcmV?d00001 diff --git a/test/icon.png.import b/test/icon.png.import new file mode 100644 index 0000000..a4c02e6 --- /dev/null +++ b/test/icon.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.png" +dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/test/project.godot b/test/project.godot new file mode 100644 index 0000000..2bb9304 --- /dev/null +++ b/test/project.godot @@ -0,0 +1,37 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=4 + +_global_script_classes=[ { +"base": "BTTask", +"class": "BTPrintLine", +"language": "GDScript", +"path": "res://ai/tasks/BTPrintLine.gd" +} ] +_global_script_class_icons={ +"BTPrintLine": "" +} + +[application] + +config/name="LimboAI Test" +run/main_scene="res://Test.tscn" +config/icon="res://icon.png" + +[gui] + +common/drop_mouse_on_gui_input_disabled=true + +[physics] + +common/enable_pause_aware_picking=true + +[rendering] + +environment/default_environment="res://default_env.tres"