diff --git a/bt/tasks/composites/bt_probability_selector.cpp b/bt/tasks/composites/bt_probability_selector.cpp new file mode 100644 index 0000000..74e12fd --- /dev/null +++ b/bt/tasks/composites/bt_probability_selector.cpp @@ -0,0 +1,101 @@ +/** + * bt_probability_selector.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_probability_selector.h" + +#include "modules/limboai/bt/tasks/bt_task.h" + +#include "core/error/error_macros.h" + +double BTProbabilitySelector::get_weight(int p_index) const { + return _get_weight(p_index); +} + +void BTProbabilitySelector::set_weight(int p_index, double p_weight) { + _set_weight(p_index, p_weight); +} + +double BTProbabilitySelector::get_probability(int p_index) const { + ERR_FAIL_INDEX_V(p_index, get_child_count(), 0.0); + return _get_weight(p_index) / _get_total_weight(); +} + +void BTProbabilitySelector::set_probability(int p_index, double p_probability) { + ERR_FAIL_INDEX(p_index, get_child_count()); + ERR_FAIL_COND(p_probability < 0.0); + ERR_FAIL_COND(p_probability > 1.0); + ERR_FAIL_COND(p_probability > 0.99 && get_child_count() > 1); + + double others_total = _get_total_weight() - _get_weight(p_index); + double others_probability = 1.0 - p_probability; + double new_total = others_total / others_probability; + _set_weight(p_index, new_total - others_total); +} + +void BTProbabilitySelector::_enter() { + _select_task(); +} + +void BTProbabilitySelector::_exit() { + failed_tasks.clear(); + selected_task.unref(); +} + +BT::Status BTProbabilitySelector::_tick(double p_delta) { + while (selected_task.is_valid()) { + Status status = selected_task->execute(p_delta); + if (status == FAILURE) { + failed_tasks.insert(selected_task); + _select_task(); + } else { // RUNNING or SUCCESS + return status; + } + } + + return FAILURE; +} + +void BTProbabilitySelector::_select_task() { + selected_task.unref(); + + double remaining_tasks_weight = _get_total_weight(); + for (const Ref &task : failed_tasks) { + remaining_tasks_weight -= _get_weight(task); + } + + double roll = Math::random(0.0, remaining_tasks_weight); + for (int i = 0; i < get_child_count(); i++) { + Ref task = get_child(i); + if (failed_tasks.has(task)) { + continue; + } + double weight = _get_weight(i); + if (weight == 0) { + continue; + } + if (roll > weight) { + roll -= weight; + continue; + } + + selected_task = task; + break; + } +} + +//***** Godot + +void BTProbabilitySelector::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_weight", "p_index"), &BTProbabilitySelector::get_weight); + ClassDB::bind_method(D_METHOD("set_weight", "p_index", "p_weight"), &BTProbabilitySelector::set_weight); + ClassDB::bind_method(D_METHOD("get_probability", "p_index"), &BTProbabilitySelector::get_probability); + ClassDB::bind_method(D_METHOD("set_probability", "p_index", "p_probability"), &BTProbabilitySelector::set_probability); +} diff --git a/bt/tasks/composites/bt_probability_selector.h b/bt/tasks/composites/bt_probability_selector.h new file mode 100644 index 0000000..c45157d --- /dev/null +++ b/bt/tasks/composites/bt_probability_selector.h @@ -0,0 +1,55 @@ +/** + * bt_probability_selector.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. + * ============================================================================= + */ + +#ifndef BT_PROBABILITY_SELECTOR_H +#define BT_PROBABILITY_SELECTOR_H + +#include "modules/limboai/bt/tasks/bt_composite.h" + +#include "core/typedefs.h" + +class BTProbabilitySelector : public BTComposite { + GDCLASS(BTProbabilitySelector, BTComposite); + TASK_CATEGORY(Composites); + +private: + HashSet> failed_tasks; + Ref selected_task; + + void _select_task(); + + _FORCE_INLINE_ double _get_weight(int p_index) const { return get_child(p_index)->get_meta(SNAME("_weight_"), 1.0); } + _FORCE_INLINE_ double _get_weight(Ref p_task) const { return p_task->get_meta(SNAME("_weight_"), 1.0); } + _FORCE_INLINE_ void _set_weight(int p_index, double p_weight) { get_child(p_index)->set_meta(SNAME("_weight_"), Variant(p_weight)); } + _FORCE_INLINE_ double _get_total_weight() const { + double total = 0.0; + for (int i = 0; i < get_child_count(); i++) { + total += _get_weight(i); + } + return total; + } + +protected: + static void _bind_methods(); + + virtual void _enter() override; + virtual void _exit() override; + virtual Status _tick(double p_delta) override; + +public: + double get_weight(int p_index) const; + void set_weight(int p_index, double p_weight); + + double get_probability(int p_index) const; + void set_probability(int p_index, double p_probability); +}; + +#endif // BT_PROBABILITY_SELECTOR_H diff --git a/config.py b/config.py index 8f47e4c..d456416 100644 --- a/config.py +++ b/config.py @@ -84,6 +84,7 @@ def get_doc_classes(): "BTPlayAnimation", "BTPlayer", "BTProbability", + "BTProbabilitySelector", "BTRandomSelector", "BTRandomSequence", "BTRandomWait", diff --git a/register_types.cpp b/register_types.cpp index 9886a78..9f93b7d 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -59,6 +59,7 @@ #include "bt/tasks/composites/bt_dynamic_selector.h" #include "bt/tasks/composites/bt_dynamic_sequence.h" #include "bt/tasks/composites/bt_parallel.h" +#include "bt/tasks/composites/bt_probability_selector.h" #include "bt/tasks/composites/bt_random_selector.h" #include "bt/tasks/composites/bt_random_sequence.h" #include "bt/tasks/composites/bt_selector.h" @@ -131,6 +132,7 @@ void initialize_limboai_module(ModuleInitializationLevel p_level) { LIMBO_REGISTER_TASK(BTParallel); LIMBO_REGISTER_TASK(BTDynamicSequence); LIMBO_REGISTER_TASK(BTDynamicSelector); + LIMBO_REGISTER_TASK(BTProbabilitySelector); LIMBO_REGISTER_TASK(BTRandomSequence); LIMBO_REGISTER_TASK(BTRandomSelector);