From f9b3c6eb3fd7dc2fdd8960d2d43704dce0716312 Mon Sep 17 00:00:00 2001 From: Sara Date: Sun, 21 Jul 2024 22:18:10 +0200 Subject: [PATCH] feat: implemented heal action and goal --- godot/AI/maintain_health.tres | 9 +++++ godot/Animation/bean_characters.res | Bin 1361 -> 1546 bytes godot/GameObjects/enemy_unit.tscn | 9 ++--- src/enemy_world_state.cpp | 13 +++---- src/entity_health.cpp | 8 +++++ src/goap/action.cpp | 4 +++ src/goap/action.hpp | 2 ++ src/goap/state.cpp | 17 +++++---- src/goap/state.hpp | 6 ++-- src/register_types.cpp | 1 + src/rts_actions.cpp | 13 +++++++ src/rts_actions.hpp | 7 ++++ src/rts_states.cpp | 53 +++++++++++++++++++++------- src/rts_states.hpp | 6 ++++ src/unit.cpp | 13 ++++--- src/unit.hpp | 1 + src/unit_world_state.cpp | 8 ++++- src/unit_world_state.hpp | 3 ++ 18 files changed, 131 insertions(+), 42 deletions(-) create mode 100644 godot/AI/maintain_health.tres diff --git a/godot/AI/maintain_health.tres b/godot/AI/maintain_health.tres new file mode 100644 index 0000000..6126e85 --- /dev/null +++ b/godot/AI/maintain_health.tres @@ -0,0 +1,9 @@ +[gd_resource type="Goal" format=3 uid="uid://btv8ultseri3q"] + +[resource] +requirements_dict = { +"is_health_safe": false +} +desired_state_dict = { +"is_health_safe": true +} diff --git a/godot/Animation/bean_characters.res b/godot/Animation/bean_characters.res index 2b007e8b93b9269b20618f6a0f9d9acc6dc322bf..122437412da1b051ca9ccfb8b8095a254b3e7b36 100644 GIT binary patch delta 1504 zcmV<61t0p+3W^MWQd2`i0ssI201yBGIu-x`y8{3KGXekrD77#BU;qz|9RPNrc}Vci zHXY1}I?{1ctkxQzxxsqzy74)>NcM^B#N7R??S4Vq?b?)4(pG%Hi>XoK8Ce@7>zas+ zwx-5U&JOUzz)a|j&FQE#odQu>V4(?Mwa!GDkcsN0N)07WP%mH*gVl`D=|GpTr)6z78f zAOAJ}U;i`yr<_2il@W9u6vsQ>=@i44LGM3+uIsw4>uxF5&vG2caU8d8+qP|+rfHg{ zX|~lXt~?-rFKgc!UM|5Nadj(ygjzat*QEHYr{ckB_`r zBkvISj}e0*qraiCp^5OXTx93AD~zpuKdu~p-sHxAIhisR*ICi3pd;w398`r>sKdXI zr=$Wb1fdTPQ3pq8_3^#zb&pND{HAT^Gsd5(xpxel#?gQXfK$ zQZGDz_+Z!!5Ks#2YLHam#l;kW`KWg^ zNC85qM#522QczHkWDHhM8FFEaip0`HOvY1zgQhZeh0(|({}HP)JH8vKolKnC1T>5lP<=c(hEzoHm43vfY;S;q=mBes zCXY_z1h5WepdjY3(CiQXX~XM2KZbrT!r#A_YWQ6?ji9Bp<@Hx!Jpl`b??%CERA%IV zkUX?qaEttv1^CDqc4HbOGTCjwW#uc8-SLQlNvt{+6oU01{%{k*ro^De|41b9G(0M; zS6_^;=1sNt&e$-6*><@B;U|fEtM&Ok$Wi4XEuh0}^|2vGh?sm6lral{ZMLB(v|ruV z3tH*n+Fs&viaj}=eB@({5k2Enne~{6{0Dv^@zjr@R>~X$8Z-77a z4H+OIA|oUvCX-eKAAcxvZ**ZPDl054E*K{Y0000%ZfR`?b7gF1Uub1vYyb-k4Gs?w z5dZ)n|2DqO%uEs!6crX17~m*;JqG{)0BvP-Xm14u0001VX>Db5baG*Cc42IFWpia@ zUt)P-a%Tb&Ole|rVRCsd1yV&*MN|M7WMyG=XaQzva%Eq4Wq)CCZ*Br@Wo%_-Utx4~ zVPk6p1X5E{Lx72>!QDVcG)Y?74B!HhAu)Lp0CX5oWyDq@6%-VgM~yTG!!511c*3Kt*OH)*}j!{2O45QNce?iD9gGiG&|U{#qqX|tzrsqzC- GQ$s_kYL^87 delta 1316 zcmV+<1>5?H4ABaIQd2`i0ssI201yBGRTBUJy8{3KdI10cD77#BU;qz|9RQ}_c1Hls zC>{L!4D{d8j@k?q(Y8P-vum*MzpC0XKnLvayYIcM9c`h6km3A;RavMdQHLlpAyA0L z8T^c!k--OdWB=wT00)Up0aXBA0BZpA^VVg_e*F*d9$AKe-m*da&HKF=oUgCJ3Y)e! ze!_QF(M3Dy{{iO|bPY;OM^PsDKl%^g zfA)Xk4=&Jnuth_sfR;~2AQPLoxeQ9jGhZr|!k5GEKZWOcp67X&iWRh7*L7XjaU92S z9Jg)Twr!h#jkSv_4aRBhD_fUo*smCt9rmr0jw0zzV>gA8vM!M8rG0IObNuKGBD4!p z$O2UH>F`9rqy$AWA`)@}`S9rA*jUw=N*Y-P#;7lc6sFp7Fk)2!gn7M4}QsD6oRk`CJ91^XV!= z7@3ryPBJ1Af>T3BD-sL^6&no}v=hjOM+e8oDuf3KS(cef8d(MgX@o*pXY*}QoKpG>!2r4n)9{C z6%5|vys&Skm<{fyQX+%cPm~xjn$Hy*Bm|;=DWTzH5R;yi$S=>uxPQV#jBN6ak?TR= zv1t!GFd7ZxKVnsey~LIF$p9QM`k|p<)yGR>Nktmp@K;*OzF~W@tZh1Vc1|aN^(X@y zVNQW&ry)GuXq``B#m`^xH+Mgj ziUDYJfL&%of;eDl|IYN?eT*flC_$E87KI1V(G(L_b!e5M^>p6V%@T=qLx#WTMk;7~ zp>=D_EHy~%60GyOW1af1Ba1A5U(D+==Bd9Id9X5T#3zfb@-#Qbo+pF$pOkrX zr19Rd8fUh=_`#el_+VevFo->A&MiGQJ5VCa(Qd?+3MO42bdO4%4aLKiK`q@GG%f@= zCGl2NuztR3C}b$LFa2Ou0)-0zR1N?D02TlM00IC20096102crN03iSX02lz1`vWC8 z1poj53;+NCaBp*IbZKvHUo&I?85$cL000039Uc?_0Dv^@zjr@R>~X$8Z-77a4H+LG zAtECrC6ieMAAcuuZ**ZODJm;0Ef^*d0000%ZfR{yX<~9=a(ONU1^@s6QbkimQ~(%c zWnpw^0cL4(WnXt?VQ_D50u2BF0BvP#Wo2JsbaY{3Yf@8ELqn)hWr55DHEApv#?S(h zA<-QY0CN-;h63G8sIahH|2w5Ak-&z`Gp;`{Qt(%41zn^F!IFh>LSZAB*E#A7k#DW% zr2G84<+@?hw|M>!-B=v~Qbj&*x`0l>ecSQVXs2~K{H7ttGce$YTlU#KGj5e%^3Na; aKKbyh8CucD1EkhpL`Eu;p-dH0Q$s@yM?-i3 diff --git a/godot/GameObjects/enemy_unit.tscn b/godot/GameObjects/enemy_unit.tscn index d25cf9f..f408f0d 100644 --- a/godot/GameObjects/enemy_unit.tscn +++ b/godot/GameObjects/enemy_unit.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=8 format=3 uid="uid://ba17jrcaduowj"] +[gd_scene load_steps=9 format=3 uid="uid://ba17jrcaduowj"] [ext_resource type="Goal" uid="uid://b4i4e34046n44" path="res://AI/defeat_enemy_unit.tres" id="1_b1qo1"] +[ext_resource type="Goal" uid="uid://btv8ultseri3q" path="res://AI/maintain_health.tres" id="2_k42dl"] [ext_resource type="AnimationLibrary" uid="uid://crkh5gahl2ci6" path="res://Animation/bean_characters.res" id="2_lrpu6"] [sub_resource type="SphereShape3D" id="SphereShape3D_5pqvg"] @@ -24,11 +25,11 @@ collision_layer = 6 collision_mask = 0 [node name="ActorWorldState" type="EnemyWorldState" parent="."] -editor_available_goals = [ExtResource("1_b1qo1")] +editor_available_goals = [ExtResource("2_k42dl"), ExtResource("1_b1qo1")] unique_name_in_owner = true [node name="Planner" type="Planner" parent="."] -actions_inspector = [3, 2, 4] +actions_inspector = [3, 2, 4, 5] unique_name_in_owner = true [node name="EntityHealth" type="EntityHealth" parent="."] @@ -63,7 +64,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.4512, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.31501, 0) [node name="CollisionShape3D" type="CollisionShape3D" parent="."] -transform = Transform3D(1, 0, 0, 0, 0.999222, -0.0394342, 0, 0.0394342, 0.999222, 0, 1.01253, 0.443459) +transform = Transform3D(1, 0, 0, 0, 0.999222, -0.0394342, 0, 0.0394342, 0.999222, 0, 1.013, 0) shape = SubResource("SphereShape3D_drlm2") [node name="MeshInstance3D" type="MeshInstance3D" parent="."] diff --git a/src/enemy_world_state.cpp b/src/enemy_world_state.cpp index 1bd3f45..bd2d296 100644 --- a/src/enemy_world_state.cpp +++ b/src/enemy_world_state.cpp @@ -19,16 +19,13 @@ void EnemyWorldState::_ready() { this->awareness_area->connect("body_entered", callable_mp(this, &EnemyWorldState::on_awareness_entered)); this->awareness_area->connect("body_exited", callable_mp(this, &EnemyWorldState::on_awareness_exited)); this->health = this->get_node("%EntityHealth"); - if(this->health == nullptr) - this->health->connect("damage", callable_mp(this, &EnemyWorldState::on_damaged)); + this->health->connect("damage", callable_mp(this, &EnemyWorldState::on_damaged)); } void EnemyWorldState::on_awareness_entered(gd::Node3D *node) { - gd::UtilityFunctions::print("1) object entered awareness"); Unit *unit{gd::Object::cast_to(node)}; if(unit == nullptr) return; if(unit == this->parent_unit) return; - gd::UtilityFunctions::print("2) object was Unit"); this->known_enemies.push_back(unit); this->try_set_target(this->select_target_from_known()); } @@ -82,17 +79,15 @@ gd::Ref EnemyWorldState::get_goal_for_target(Unit *unit) { } void EnemyWorldState::try_set_target(Unit *unit) { - gd::UtilityFunctions::print("3) selecting goal"); gd::Ref goal{this->get_goal_for_target(unit)}; - if(!goal.is_valid()) return; - gd::UtilityFunctions::print("4) selected goal ", goal->get_path()); - this->parent_unit->set_target_goal(unit, goal); + if(goal.is_valid()) + this->parent_unit->set_target_goal(unit, goal); } void EnemyWorldState::set_editor_available_goals(gd::Array array) { this->available_goals.clear(); while(!array.is_empty()) { - gd::Ref goal{array.pop_back()}; + gd::Ref goal{array.pop_front()}; this->available_goals.push_back(goal); } } diff --git a/src/entity_health.cpp b/src/entity_health.cpp index a88e240..ef289ac 100644 --- a/src/entity_health.cpp +++ b/src/entity_health.cpp @@ -4,6 +4,9 @@ void EntityHealth::_bind_methods() { #define CLASSNAME EntityHealth + GDFUNCTION_ARGS(damage, "amount"); + GDFUNCTION_ARGS(damaged_by, "amount", "source"); + GDFUNCTION_ARGS(healed_by, "amount", "source"); GDPROPERTY(injury_max, gd::Variant::INT); GDPROPERTY(wounds_max, gd::Variant::INT); @@ -27,8 +30,13 @@ void EntityHealth::_enter_tree() { this->emit_signal("damage", this->injury_current, 0, nullptr); } +void EntityHealth::damage(int amount) { + this->damaged_by(amount, nullptr); +} + void EntityHealth::damaged_by(int amount, Unit *source) { amount = gd::Math::abs(amount); + if(this->injury_current <= 0) return; // do not take damage when already dead this->injury_current -= amount; this->emit_signal("damage", this->injury_current, amount, source); this->emit_signal("health_changed", this->injury_current, -amount); diff --git a/src/goap/action.cpp b/src/goap/action.cpp index b9be10a..2f5c77e 100644 --- a/src/goap/action.cpp +++ b/src/goap/action.cpp @@ -29,6 +29,10 @@ ActionID Action::get_id() const { return this->id; } +bool Action::get_require_state_complete() const { + return this->require_state_complete; +} + bool Action::procedural_is_possible(ActorWorldState *context) const { return true; } diff --git a/src/goap/action.hpp b/src/goap/action.hpp index 944775c..36f3664 100644 --- a/src/goap/action.hpp +++ b/src/goap/action.hpp @@ -43,6 +43,7 @@ public: WorldState const &get_effects() const; ActionID get_id() const; + bool get_require_state_complete() const; protected: Action() = default; template @@ -54,6 +55,7 @@ protected: WorldState required{}; WorldState effects{}; bool only_proc_is_completed{false}; + bool require_state_complete{true}; private: ActionID id{-1}; }; diff --git a/src/goap/state.cpp b/src/goap/state.cpp index 1c2b90d..2ee25f0 100644 --- a/src/goap/state.cpp +++ b/src/goap/state.cpp @@ -16,8 +16,8 @@ void State::_enter_tree() { } void State::_process(double delta_time) { - if(this->is_action_done()) - this->state_finished(); + if(this->is_action_done() && this->get_action()->get_require_state_complete()) + this->state_ended(); } Action const *State::get_action() const { @@ -26,20 +26,19 @@ Action const *State::get_action() const { void State::_end_state() {} -void State::state_finished() { +void State::state_ended() { this->end_state(); - this->emit_signal("state_finished"); -} - -void State::state_failed() { - this->end_state(); - this->emit_signal("state_failed"); + this->emit_signal(this->is_action_done() ? "state_finished" : "state_failed"); } bool State::is_action_done() const { return this->action->is_completed(this->world_state); } +bool State::is_action_done_interrupt() const { + return this->is_action_done() && !this->action->get_require_state_complete(); +} + void State::end_state() { this->_end_state(); this->queue_free(); diff --git a/src/goap/state.hpp b/src/goap/state.hpp index 2f08f57..ab3764c 100644 --- a/src/goap/state.hpp +++ b/src/goap/state.hpp @@ -19,10 +19,12 @@ public: virtual void _process(double delta_time) override; protected: Action const *get_action() const; + // \returns True if the Action's requirements are complete. Without evaluating Action::require_state_complete. bool is_action_done() const; + // \returns True if the Action's requirements are complete. Including Action::require_state_complete. + bool is_action_done_interrupt() const; virtual void _end_state(); - void state_finished(); - void state_failed(); + void state_ended(); private: void end_state(); private: diff --git a/src/register_types.cpp b/src/register_types.cpp index 28f6053..6a57c8c 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -35,6 +35,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level) goap::ActionDB::register_action(); goap::ActionDB::register_action(); goap::ActionDB::register_action(); + goap::ActionDB::register_action(); ClassDB::register_class(); ClassDB::register_class(); diff --git a/src/rts_actions.cpp b/src/rts_actions.cpp index 38a953f..1a3a17b 100644 --- a/src/rts_actions.cpp +++ b/src/rts_actions.cpp @@ -36,6 +36,7 @@ goap::State *FireAtTarget::get_apply_state(goap::ActorWorldState *context) const FindTarget::FindTarget() : Action() { + this->require_state_complete = false; this->effects.insert("can_see_target", true); } @@ -48,6 +49,7 @@ goap::State *FindTarget::get_apply_state(goap::ActorWorldState *context) const { GetInMeleeRange::GetInMeleeRange() : Action() { + this->require_state_complete = false; this->effects.insert("is_in_melee_range", true); this->required.insert("can_see_target", true); } @@ -71,3 +73,14 @@ goap::State *MeleeAttack::get_apply_state(goap::ActorWorldState *context) const state->animation = "melee_attack"; return state; } + +TankSelfHeal::TankSelfHeal() +: Action() { + this->effects.insert("is_health_safe", true); +} + +goap::State *TankSelfHeal::get_apply_state(goap::ActorWorldState *context) const { + Animate *state{this->create_state()}; + state->animation = "self_heal"; + return state; +} diff --git a/src/rts_actions.hpp b/src/rts_actions.hpp index 0a7119c..3e77448 100644 --- a/src/rts_actions.hpp +++ b/src/rts_actions.hpp @@ -39,4 +39,11 @@ public: virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; }; +class TankSelfHeal : public goap::Action { + GOAP_ACTION(SelfHeal); +public: + TankSelfHeal(); + virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; +}; + #endif // !RTS_ACTIONS_HPP diff --git a/src/rts_states.cpp b/src/rts_states.cpp index 348e013..5ca4ceb 100644 --- a/src/rts_states.cpp +++ b/src/rts_states.cpp @@ -1,5 +1,8 @@ #include "rts_states.hpp" +#include "utils/util_functions.hpp" +#include #include +#include void MoveTo::_bind_methods() {} @@ -7,6 +10,7 @@ void MoveTo::_ready() { this->agent = this->get_node("../NavigationAgent3D"); this->agent->set_target_position(this->target_node->get_global_position()); this->parent_node3d = Object::cast_to(this->get_parent()); + this->calculate_path(); } void MoveTo::_end_state() { @@ -20,10 +24,38 @@ void MoveTo::_process(double delta_time) { this->parent_node3d->set_global_position(pos + direction * delta_time); this->parent_node3d->look_at(pos - gd::Vector3{direction.x, 0.f, direction.z}); - if(this->is_action_done()) - this->state_finished(); - else if(this->agent->is_navigation_finished()) - this->state_failed(); + bool const navigation_finished{this->agent->is_navigation_finished()}; + if(this->is_action_done_interrupt() || navigation_finished) + this->state_ended(); + + if((utils::time_seconds() - this->last_repath) > this->get_repath_interval()) + this->calculate_path(); +} + +void MoveTo::calculate_path() { + gd::Vector3 const target_pos{this->target_node->get_global_position()}; + this->agent->set_target_position(target_pos); + this->last_repath = utils::time_seconds(); + this->target_position_at_last = target_pos; +} + +float MoveTo::target_delta_position() const { + return this->target_position_at_last + .distance_to(this->target_node->get_global_position()); +} + +float MoveTo::distance_to_target() const { + return this->target_position_at_last + .distance_to(this->parent_node3d->get_global_position()); +} + +double MoveTo::get_repath_interval() const { + float const target_delta_position{this->target_delta_position()}; + if(target_delta_position == 0.f) + return INFINITY; + else + return gd::Math::max(gd::Math::min(double(this->distance_to_target()), 5.0) + - target_delta_position, 0.2); } void Activate::_bind_methods() {} @@ -32,18 +64,15 @@ void Animate::_bind_methods() {} void Animate::_ready() { this->anim = this->get_node("../AnimationPlayer"); - this->anim->queue(this->animation); + this->anim->play(this->animation); } void Animate::_process(double delta_time) { - if(this->is_action_done()) { - this->state_finished(); - } else if(!this->anim->is_playing() || this->anim->get_current_animation() != this->animation) { - this->state_failed(); - } + bool const animation_finished{!this->anim->is_playing() || this->anim->get_current_animation() != this->animation}; + if(this->is_action_done_interrupt() || animation_finished) + this->state_ended(); } void Animate::_end_state() { - if(this->anim->get_current_animation() == this->animation) - this->anim->stop(); + this->anim->stop(); } diff --git a/src/rts_states.hpp b/src/rts_states.hpp index e2cd532..de1f7f0 100644 --- a/src/rts_states.hpp +++ b/src/rts_states.hpp @@ -14,11 +14,17 @@ public: virtual void _ready() override; virtual void _end_state() override; virtual void _process(double delta_time) override; + void calculate_path(); + float target_delta_position() const; + float distance_to_target() const; + double get_repath_interval() const; public: gd::Node3D *target_node{nullptr}; private: gd::Node3D *parent_node3d{nullptr}; gd::NavigationAgent3D *agent{nullptr}; + gd::Vector3 target_position_at_last{}; + double last_repath{0.0}; }; class Activate : public goap::State { diff --git a/src/unit.cpp b/src/unit.cpp index 66e991c..8e58a74 100644 --- a/src/unit.cpp +++ b/src/unit.cpp @@ -20,8 +20,8 @@ void Unit::_enter_tree() { GDGAMEONLY(); this->world_state = this->get_node("%ActorWorldState"); this->world_state->connect("attention_changed", callable_mp(this, &Unit::stop_plan)); this->anim_player = this->get_node("%AnimationPlayer"); - EntityHealth *health{this->get_node("%EntityHealth")}; - health->connect("death", callable_mp(this, &Unit::on_death)); + this->health = this->get_node("%EntityHealth"); + this->health->connect("death", callable_mp(this, &Unit::on_death)); } void Unit::stop_plan() { @@ -34,9 +34,7 @@ void Unit::stop_plan() { } void Unit::begin_marker_temporary(GoalMarker *marker) { - this->destroy_state(); this->world_state->set_target_node(marker); - this->destroy_state(); this->set_goal_and_plan(marker->get_goal()); // destroy temporary marker if goal is already achieved or failed // connect observers if a plan was formed @@ -80,6 +78,7 @@ void Unit::aim_at(gd::Node3D *target) { } void Unit::on_death(Unit *damage_source) { + this->destroy_state(); this->anim_player->stop(); this->anim_player->play("death"); } @@ -122,11 +121,16 @@ void Unit::state_finished() { } void Unit::next_action() { + // cannot perform actions while dead + if(this->health->get_injury_current() <= 0) + return; + // destroy active state if(this->state != nullptr && !this->state->is_queued_for_deletion()) this->destroy_state(); this->state = nullptr; if(this->current_plan.is_empty()) return; + // pop next action and apply state this->state = this->current_plan.get(0)->get_apply_state(this->world_state); if(state == nullptr) { this->stop_plan(); @@ -135,7 +139,6 @@ void Unit::next_action() { } this->current_plan.remove_at(0); this->add_child(this->state); - this->state->connect("state_finished", this->on_state_finished); this->state->connect("state_failed", this->on_plan_failed); } diff --git a/src/unit.hpp b/src/unit.hpp index bcad7c0..400aea3 100644 --- a/src/unit.hpp +++ b/src/unit.hpp @@ -60,6 +60,7 @@ protected: gd::NavigationAgent3D *agent{nullptr}; gd::AnimationPlayer *anim_player{nullptr}; goap::Planner *planner{nullptr}; + EntityHealth *health{nullptr}; UnitWorldState *world_state{nullptr}; }; diff --git a/src/unit_world_state.cpp b/src/unit_world_state.cpp index c910f66..d453a33 100644 --- a/src/unit_world_state.cpp +++ b/src/unit_world_state.cpp @@ -17,12 +17,14 @@ void UnitWorldState::_bind_methods() { GDFUNCTION(get_target_node); GDFUNCTION(get_is_target_enemy); GDFUNCTION(get_is_in_melee_range); + GDFUNCTION(get_is_health_safe); } void UnitWorldState::_enter_tree() { GDGAMEONLY(); this->parent_unit = gd::Object::cast_to(this->get_parent()); this->agent = this->get_node("%NavigationAgent3D"); this->eye_location = this->get_node("%Eyes"); + this->health = this->get_node("%EntityHealth"); if(this->parent_unit == nullptr) gd::UtilityFunctions::push_warning("UnitWorldState needs to be a child node of a Unit"); } @@ -77,7 +79,11 @@ bool UnitWorldState::get_is_target_enemy() const { bool UnitWorldState::get_is_in_melee_range() const { return this->target_node != nullptr && this->target_node->get_global_position() - .distance_squared_to(this->parent_unit->get_global_position()) <= 4.f; + .distance_squared_to(this->parent_unit->get_global_position()) <= 4.f; +} + +bool UnitWorldState::get_is_health_safe() const { + return float(this->health->get_injury_current()) > (float(this->health->get_injury_max()) / 2.f); } void UnitWorldState::set_target_node(gd::Node3D *node) { diff --git a/src/unit_world_state.hpp b/src/unit_world_state.hpp index 4970565..bd3db95 100644 --- a/src/unit_world_state.hpp +++ b/src/unit_world_state.hpp @@ -1,6 +1,7 @@ #ifndef UNIT_WORLD_STATE_HPP #define UNIT_WORLD_STATE_HPP +#include "entity_health.hpp" #include "goap/actor_world_state.hpp" #include #include @@ -18,6 +19,7 @@ public: bool get_is_target_unit() const; bool get_is_target_enemy() const; bool get_is_in_melee_range() const; + bool get_is_health_safe() const; void set_target_node(gd::Node3D *node); gd::Node3D *get_target_node() const; @@ -27,6 +29,7 @@ private: gd::Callable const target_node_exited_tree{callable_mp(this, &UnitWorldState::target_destroyed)}; protected: Unit *parent_unit{nullptr}; + EntityHealth *health{nullptr}; gd::NavigationAgent3D *agent{nullptr}; gd::Node3D *target_node{nullptr}; gd::Node3D *eye_location{nullptr};