From 973ba952c205fcff4741c0d223a3c04f8fbc7b81 Mon Sep 17 00:00:00 2001 From: Sara Date: Mon, 5 Aug 2024 22:56:55 +0200 Subject: [PATCH] fix: general AI behaviour fixes --- .gitignore | 1 + godot-cpp | 2 +- src/enemy_world_state.cpp | 13 ++++++--- src/enemy_world_state.hpp | 2 +- src/entity_health.cpp | 48 ++++++++++++++++++---------------- src/entity_health.hpp | 4 +-- src/goap/actor_world_state.hpp | 2 +- src/goap/goal.cpp | 4 +-- src/goap/planner.cpp | 8 +----- src/goap/state.cpp | 32 +++++++++++------------ src/goap/state.hpp | 13 +++++---- src/nav_marker.cpp | 2 +- src/nav_marker.hpp | 5 ++-- src/rts_actions.cpp | 6 ++--- src/rts_actions.hpp | 6 ++--- src/rts_game_mode.cpp | 7 +++++ src/rts_game_mode.hpp | 2 +- src/rts_player.cpp | 16 ++++++------ src/rts_player.hpp | 16 ++++++------ src/rts_states.cpp | 13 +++++---- src/rts_states.hpp | 2 +- src/unit.cpp | 4 +-- src/unit.hpp | 2 +- src/unit_world_state.cpp | 46 +++++++++++++++++++++++--------- src/unit_world_state.hpp | 13 ++++++++- 25 files changed, 155 insertions(+), 114 deletions(-) diff --git a/.gitignore b/.gitignore index 86015af..bb1adae 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ metro-rts.kdev4 # Caches .cache/ godot/.gdb_history +builds diff --git a/godot-cpp b/godot-cpp index 54136ee..51c752c 160000 --- a/godot-cpp +++ b/godot-cpp @@ -1 +1 @@ -Subproject commit 54136ee8357c5140a3775c54f08db5f7deda2058 +Subproject commit 51c752c46b44769d3b6c661526c364a18ea64781 diff --git a/src/enemy_world_state.cpp b/src/enemy_world_state.cpp index 13d59b8..de875fc 100644 --- a/src/enemy_world_state.cpp +++ b/src/enemy_world_state.cpp @@ -30,18 +30,23 @@ void EnemyWorldState::on_awareness_entered(gd::Node3D *node) { } void EnemyWorldState::on_awareness_exited(gd::Node3D *node) { - Unit *unit{gd::Object::cast_to(node)}; - this->remove_aware_unit(unit); + if(Unit *unit{gd::Object::cast_to(node)}) + this->remove_aware_unit(unit); } -void EnemyWorldState::on_damaged(int remaining, int delta, Unit *source) { - this->add_aware_unit(source); +void EnemyWorldState::on_damaged(EntityHealth *, int, Unit *source) { + if(source != nullptr && !this->known_enemies.has(source)) + this->add_aware_unit(source); + else + this->select_and_set_target(); } Unit *EnemyWorldState::select_target_from_known_with_priority(float *out_priority) { Unit *out{nullptr}; *out_priority = -INFINITY; for(Unit *unit : this->known_enemies) { + if(!this->get_can_see_node(unit)) + continue; float const priority{this->calculate_priority(unit)}; if(priority > *out_priority) { out = unit; diff --git a/src/enemy_world_state.hpp b/src/enemy_world_state.hpp index ea8cb3b..9fc9edc 100644 --- a/src/enemy_world_state.hpp +++ b/src/enemy_world_state.hpp @@ -19,7 +19,7 @@ public: void on_awareness_entered(gd::Node3D *node); void on_awareness_exited(gd::Node3D *node); - void on_damaged(int remaining, int delta, Unit *source); + void on_damaged(EntityHealth *, int, Unit *source); /*! Select the target with the highest priority and return it. * Assigns the priority of found target to out_priority. * \param out_priority Assigned to highest found priority, cannot be nullptr. diff --git a/src/entity_health.cpp b/src/entity_health.cpp index bc0f729..1c93f4f 100644 --- a/src/entity_health.cpp +++ b/src/entity_health.cpp @@ -4,52 +4,56 @@ 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); GDSIGNAL("damage", - gd::PropertyInfo(gd::Variant::INT, "remaining"), - gd::PropertyInfo(gd::Variant::INT, "change"), - gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit")); + gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"), + gd::PropertyInfo(gd::Variant::INT, "change"), + gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit")); GDSIGNAL("heal", - gd::PropertyInfo(gd::Variant::INT, "remaining"), - gd::PropertyInfo(gd::Variant::INT, "change"), - gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit")); + gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"), + gd::PropertyInfo(gd::Variant::INT, "change"), + gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit")); GDSIGNAL("health_changed", - gd::PropertyInfo(gd::Variant::INT, "remaining"), - gd::PropertyInfo(gd::Variant::INT, "change")); + gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"), + gd::PropertyInfo(gd::Variant::INT, "change")); GDSIGNAL("death", gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit")); } void EntityHealth::_enter_tree() { this->injury_current = this->injury_max; this->wounds_current = this->wounds_max; - 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) { + // do not take damage when already dead + if(this->injury_current <= 0) + return; 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); - if(this->injury_current <= 0) { + this->wounds_current -= amount; + this->injury_current -= gd::Math::max(1, int(float(amount) * this->get_wounds_damage_factor())); + + this->emit_signal("damage", this, amount, source); + this->emit_signal("health_changed", this, -amount); + if(this->injury_current <= 0) this->emit_signal("death", source); - } } void EntityHealth::healed_by(int amount, Unit *source) { + // reviving takes a different action + if(this->injury_current <= 0) + return; amount = gd::Math::abs(amount); this->injury_current = gd::Math::min(this->injury_max, this->injury_current + amount); - this->emit_signal("heal", this->injury_current, amount, source); - this->emit_signal("health_changed", this->injury_current, amount); + this->emit_signal("heal", this, amount, source); + this->emit_signal("health_changed", this, amount); +} + +float EntityHealth::get_wounds_damage_factor() const { + return float(this->injury_current) / float(this->injury_max); } bool EntityHealth::get_is_dead() const { diff --git a/src/entity_health.hpp b/src/entity_health.hpp index c074a1e..9cba59e 100644 --- a/src/entity_health.hpp +++ b/src/entity_health.hpp @@ -13,9 +13,9 @@ class EntityHealth : public gd::Node { public: virtual void _enter_tree() override; void damaged_by(int amount, Unit *source); - void damage(int amount); void healed_by(int amount, Unit *source); - void heal(int amount); + + float get_wounds_damage_factor() const; bool get_is_dead() const; void set_injury_max(int max_health); int get_injury_max() const; diff --git a/src/goap/actor_world_state.hpp b/src/goap/actor_world_state.hpp index 5172fd4..4d68b17 100644 --- a/src/goap/actor_world_state.hpp +++ b/src/goap/actor_world_state.hpp @@ -17,7 +17,7 @@ class ActorWorldState : public gd::Node { static void _bind_methods(); public: bool check_property_match(WorldProperty const &property); - gd::Variant get_world_property(gd::String world_prop_name); + virtual gd::Variant get_world_property(gd::String world_prop_name); }; } diff --git a/src/goap/goal.cpp b/src/goap/goal.cpp index 2c96483..7b81323 100644 --- a/src/goap/goal.cpp +++ b/src/goap/goal.cpp @@ -1,6 +1,6 @@ #include "goal.hpp" -#include "utils/godot_macros.hpp" -#include "utils/util_functions.hpp" +#include "../utils/godot_macros.hpp" +#include "../utils/util_functions.hpp" namespace goap { void Goal::_bind_methods() { diff --git a/src/goap/planner.cpp b/src/goap/planner.cpp index 4580763..e44a3b8 100644 --- a/src/goap/planner.cpp +++ b/src/goap/planner.cpp @@ -1,6 +1,6 @@ #include "planner.hpp" #include "action_db.hpp" -#include "utils/godot_macros.hpp" +#include "../utils/godot_macros.hpp" #include #include #include @@ -192,14 +192,8 @@ bool Planner::does_action_contribute(Action const *action, WorldStateNode const Plan Planner::unroll_plan(WorldStateNode const &start_node, NodeMap const &from) { Plan plan{}; WorldStateNode node{start_node}; -#ifdef DEBUG_ENABLED - gd::UtilityFunctions::print("plan (", this->get_path(), "):"); -#endif while(node.last_action != nullptr) { plan.push_back(node.last_action); -#ifdef DEBUG_ENABLED - gd::UtilityFunctions::print(" (", plan.size(), ") ", node.last_action->get_class()); -#endif node = from.get(node); } return plan; diff --git a/src/goap/state.cpp b/src/goap/state.cpp index 2ee25f0..29b2a32 100644 --- a/src/goap/state.cpp +++ b/src/goap/state.cpp @@ -1,7 +1,7 @@ #include "state.hpp" -#include "goap/action.hpp" -#include "goap/actor_world_state.hpp" -#include "utils/godot_macros.hpp" +#include "../goap/action.hpp" +#include "../goap/actor_world_state.hpp" +#include "../utils/godot_macros.hpp" namespace goap { void State::_bind_methods() { @@ -15,9 +15,20 @@ void State::_enter_tree() { this->world_state = this->get_node("../ActorWorldState"); } -void State::_process(double delta_time) { +void State::_process(double) { if(this->is_action_done() && this->get_action()->get_require_state_complete()) - this->state_ended(); + this->end_state(); +} + +void State::interrupt_state() { + this->_end_state(); + this->queue_free(); +} + +void State::end_state() { + this->interrupt_state(); + this->emit_signal(this->is_action_done() ? "state_finished" : "state_failed"); + this->emit_signal("end_state"); } Action const *State::get_action() const { @@ -26,11 +37,6 @@ Action const *State::get_action() const { void State::_end_state() {} -void State::state_ended() { - this->end_state(); - 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); } @@ -38,10 +44,4 @@ bool State::is_action_done() const { 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(); - this->emit_signal("end_state"); -} } diff --git a/src/goap/state.hpp b/src/goap/state.hpp index ab3764c..f991557 100644 --- a/src/goap/state.hpp +++ b/src/goap/state.hpp @@ -1,7 +1,7 @@ #ifndef GOAP_STATE_HPP #define GOAP_STATE_HPP -#include "goap/actor_world_state.hpp" +#include "actor_world_state.hpp" #include #include @@ -16,17 +16,16 @@ friend class Action; static void _bind_methods(); public: virtual void _enter_tree() override; - virtual void _process(double delta_time) override; + virtual void _process(double) override; + void interrupt_state(); + void end_state(); protected: Action const *get_action() const; - // \returns True if the Action's requirements are complete. Without evaluating Action::require_state_complete. + //! \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. + //! \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_ended(); -private: - void end_state(); private: ActorWorldState *world_state{nullptr}; Action const *action{nullptr}; diff --git a/src/nav_marker.cpp b/src/nav_marker.cpp index 4e255b3..aad5cbc 100644 --- a/src/nav_marker.cpp +++ b/src/nav_marker.cpp @@ -12,7 +12,7 @@ void NavMarker::_bind_methods() { GDPROPERTY_HINTED(marker_type, gd::Variant::INT, gd::PROPERTY_HINT_ENUM, MarkerType::get_property_hint()); } -void NavMarker::_process(double delta_time) { +void NavMarker::_process(double) { #ifdef DEBUG_ENABLED GDEDITORONLY() if(gd::EditorInterface::get_singleton()->get_selection()->get_selected_nodes().has(this)) { diff --git a/src/nav_marker.hpp b/src/nav_marker.hpp index 95ef037..ca5a6fa 100644 --- a/src/nav_marker.hpp +++ b/src/nav_marker.hpp @@ -8,14 +8,15 @@ namespace gd = godot; GDENUM(MarkerType, Generic, - Cover + Cover, + HalfCover ); class NavMarker : public gd::Marker3D { GDCLASS(NavMarker, gd::Marker3D); static void _bind_methods(); public: - virtual void _process(double delta_time) override; + virtual void _process(double) override; void set_marker_type(int type); int get_marker_type() const; diff --git a/src/rts_actions.cpp b/src/rts_actions.cpp index ed5e441..c9a2658 100644 --- a/src/rts_actions.cpp +++ b/src/rts_actions.cpp @@ -32,7 +32,7 @@ FireAtTarget::FireAtTarget() this->effects.insert("is_target_dead", true); } -goap::State *FireAtTarget::get_apply_state(goap::ActorWorldState *context) const { +goap::State *FireAtTarget::get_apply_state(goap::ActorWorldState *) const { Animate *state{this->create_state()}; state->animation = "fire_weapon"; return state; @@ -72,7 +72,7 @@ MeleeAttack::MeleeAttack() this->effects.insert("is_target_dead", true); } -goap::State *MeleeAttack::get_apply_state(goap::ActorWorldState *context) const { +goap::State *MeleeAttack::get_apply_state(goap::ActorWorldState *) const { Animate *state{this->create_state()}; state->animation = "melee_attack"; return state; @@ -84,7 +84,7 @@ TankSelfHeal::TankSelfHeal() this->effects.insert("is_health_safe", true); } -goap::State *TankSelfHeal::get_apply_state(goap::ActorWorldState *context) const { +goap::State *TankSelfHeal::get_apply_state(goap::ActorWorldState *) 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 122ebc1..fcaf321 100644 --- a/src/rts_actions.hpp +++ b/src/rts_actions.hpp @@ -15,7 +15,7 @@ class FireAtTarget : public goap::Action { GOAP_ACTION(FireAtTarget); public: FireAtTarget(); - virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; + virtual goap::State *get_apply_state(goap::ActorWorldState *) const override; }; class FindTarget : public goap::Action { @@ -36,14 +36,14 @@ class MeleeAttack : public goap::Action { GOAP_ACTION(MeleeAttack); public: MeleeAttack(); - virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; + virtual goap::State *get_apply_state(goap::ActorWorldState *) const override; }; class TankSelfHeal : public goap::Action { GOAP_ACTION(TankSelfHeal); public: TankSelfHeal(); - virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; + virtual goap::State *get_apply_state(goap::ActorWorldState *) const override; }; class TakeCover : public goap::Action { diff --git a/src/rts_game_mode.cpp b/src/rts_game_mode.cpp index 5a282d8..49c2985 100644 --- a/src/rts_game_mode.cpp +++ b/src/rts_game_mode.cpp @@ -1,5 +1,12 @@ #include "rts_game_mode.hpp" +#include "utils/godot_macros.hpp" +#include +#include void RTSGameMode::_bind_methods() { #define CLASSNAME RTSGameMode } + +void RTSGameMode::_ready() { GDGAMEONLY(); + godot::Engine::get_singleton()->set_max_fps(60); +} diff --git a/src/rts_game_mode.hpp b/src/rts_game_mode.hpp index ee2a316..e37fafb 100644 --- a/src/rts_game_mode.hpp +++ b/src/rts_game_mode.hpp @@ -7,7 +7,7 @@ class RTSGameMode : public utils::GameMode { GDCLASS(RTSGameMode, utils::GameMode); static void _bind_methods(); public: -private: + virtual void _ready() override; }; #endif // !RTS_GAME_MODE_HPP diff --git a/src/rts_player.cpp b/src/rts_player.cpp index cbc9c47..bf4fadd 100644 --- a/src/rts_player.cpp +++ b/src/rts_player.cpp @@ -131,7 +131,7 @@ void RTSPlayer::select_unit(Unit *unit) { unit->connect("tree_exited", this->on_selected_unit_destroyed); } -void RTSPlayer::on_mouse_horizontal(gd::Ref event, float value) { +void RTSPlayer::on_mouse_horizontal(gd::Ref, float value) { if(this->mmb_down) this->camera_mouse_motion.x = value; else if(this->rmb_down) @@ -139,39 +139,39 @@ void RTSPlayer::on_mouse_horizontal(gd::Ref event, float value) this->total_cursor_motion.x = value; } -void RTSPlayer::on_mouse_vertical(gd::Ref event, float value) { +void RTSPlayer::on_mouse_vertical(gd::Ref, float value) { if(this->mmb_down) this->camera_mouse_motion.y = value; this->total_cursor_motion.y = value; } -void RTSPlayer::on_direction_horizontal(gd::Ref event, float value) { +void RTSPlayer::on_direction_horizontal(gd::Ref, float value) { this->camera_keys_motion.x = value; } -void RTSPlayer::on_direction_vertical(gd::Ref event, float value) { +void RTSPlayer::on_direction_vertical(gd::Ref, float value) { this->camera_keys_motion.y = value; } -void RTSPlayer::on_rotate_horizontal(gd::Ref event, float value) { +void RTSPlayer::on_rotate_horizontal(gd::Ref, float value) { this->camera_keys_rotation = value; } -void RTSPlayer::on_rclick(gd::Ref event, float value) { +void RTSPlayer::on_rclick(gd::Ref, float value) { if(value == 0.f && !this->rmb_is_dragged) this->order_activate_object_under_cursor(); this->rmb_down = value != 0.f; this->rmb_last_change = utils::time_seconds(); } -void RTSPlayer::on_lclick(gd::Ref event, float value) { +void RTSPlayer::on_lclick(gd::Ref, float value) { if(value == 0.f && !this->lmb_is_dragged) this->select_unit_under_cursor(); this->lmb_down = value != 0.f; this->lmb_last_change = utils::time_seconds(); } -void RTSPlayer::on_mclick(gd::Ref event, float value) { +void RTSPlayer::on_mclick(gd::Ref, float value) { this->mmb_down = value != 0.f; } diff --git a/src/rts_player.hpp b/src/rts_player.hpp index 1797f0e..e30f562 100644 --- a/src/rts_player.hpp +++ b/src/rts_player.hpp @@ -42,14 +42,14 @@ private: void select_unit(Unit *unit); // input functions - void on_mouse_horizontal(gd::Ref event, float value); - void on_mouse_vertical(gd::Ref event, float value); - void on_direction_horizontal(gd::Ref event, float value); - void on_direction_vertical(gd::Ref event, float value); - void on_rotate_horizontal(gd::Ref event, float value); - void on_lclick(gd::Ref event, float value); - void on_rclick(gd::Ref event, float value); - void on_mclick(gd::Ref event, float value); + void on_mouse_horizontal(gd::Ref, float value); + void on_mouse_vertical(gd::Ref, float value); + void on_direction_horizontal(gd::Ref, float value); + void on_direction_vertical(gd::Ref, float value); + void on_rotate_horizontal(gd::Ref, float value); + void on_lclick(gd::Ref, float value); + void on_rclick(gd::Ref, float value); + void on_mclick(gd::Ref, float value); // setters & getters gd::Vector3 get_forward_direction() const; diff --git a/src/rts_states.cpp b/src/rts_states.cpp index a83f965..54ee50d 100644 --- a/src/rts_states.cpp +++ b/src/rts_states.cpp @@ -10,13 +10,12 @@ void MoveTo::_ready() { this->parent_unit = Object::cast_to(this->get_parent()); this->agent = this->get_node("../NavigationAgent3D"); if(this->target_node == nullptr) { - this->state_ended(); + this->end_state(); gd::UtilityFunctions::push_warning("failed to start MoveTo state due to missing target"); return; } this->agent->connect("velocity_computed", this->nav_velocity_computed); this->calculate_path(); - gd::UtilityFunctions::print(this->parent_unit->get_path(), " MoveTo ", this->target_node->get_path()); } void MoveTo::_end_state() { @@ -26,14 +25,14 @@ void MoveTo::_end_state() { this->parent_unit->set_velocity({0.f, 0.f, 0.f}); } -void MoveTo::_process(double delta_time) { +void MoveTo::_process(double) { gd::Vector3 const pos = this->parent_unit->get_global_position(); gd::Vector3 const target = this->agent->get_next_path_position(); gd::Vector3 const direction = (target - pos).normalized(); this->agent->set_velocity(direction); bool const navigation_finished{this->agent->is_navigation_finished()}; if(this->is_action_done_interrupt() || navigation_finished) - this->state_ended(); + this->end_state(); if((utils::time_seconds() - this->last_repath) > this->get_repath_interval()) this->calculate_path(); } @@ -78,13 +77,13 @@ void Animate::_ready() { this->anim = this->get_node("../AnimationPlayer"); this->anim->play(this->animation); if(!this->anim->has_animation(this->animation)) - this->state_ended(); + this->end_state(); } -void Animate::_process(double delta_time) { +void Animate::_process(double) { 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(); + this->end_state(); } void Animate::_end_state() { diff --git a/src/rts_states.hpp b/src/rts_states.hpp index 7504760..b1f444a 100644 --- a/src/rts_states.hpp +++ b/src/rts_states.hpp @@ -14,7 +14,7 @@ class MoveTo : public goap::State { public: virtual void _ready() override; virtual void _end_state() override; - virtual void _process(double delta_time) override; + virtual void _process(double) override; void calculate_path(); void on_velocity_computed(gd::Vector3 vel); float target_delta_position() const; diff --git a/src/unit.cpp b/src/unit.cpp index 33574c8..93d219b 100644 --- a/src/unit.cpp +++ b/src/unit.cpp @@ -28,7 +28,7 @@ void Unit::_enter_tree() { GDGAMEONLY(); this->health->connect("death", callable_mp(this, &Unit::on_death)); } -void Unit::_physics_process(double delta_time) { +void Unit::_physics_process(double) { this->move_and_slide(); } @@ -131,7 +131,7 @@ void Unit::on_velocity_computed(gd::Vector3 vel) { void Unit::destroy_state() { if(this->state == nullptr || this->state->is_queued_for_deletion() || !this->state->is_inside_tree()) return; - this->state->queue_free(); + this->state->interrupt_state(); this->state = nullptr; } diff --git a/src/unit.hpp b/src/unit.hpp index 32cdff3..d73b92c 100644 --- a/src/unit.hpp +++ b/src/unit.hpp @@ -26,7 +26,7 @@ class Unit : public gd::CharacterBody3D { static void _bind_methods(); public: virtual void _enter_tree() override; - virtual void _physics_process(double delta_time) override; + virtual void _physics_process(double) override; void stop_plan(); void begin_marker_temporary(GoalMarker *marker); diff --git a/src/unit_world_state.cpp b/src/unit_world_state.cpp index 3904027..cf93525 100644 --- a/src/unit_world_state.cpp +++ b/src/unit_world_state.cpp @@ -2,6 +2,7 @@ #include "entity_health.hpp" #include "unit.hpp" #include "utils/godot_macros.hpp" +#include "utils/util_functions.hpp" #include #include #include @@ -30,25 +31,39 @@ void UnitWorldState::_enter_tree() { GDGAMEONLY(); this->health = this->get_node("%EntityHealth"); } -bool UnitWorldState::get_can_see_target() const { - gd::Transform3D const eyes{this->eye_location->get_global_transform()}; +gd::Variant UnitWorldState::get_world_property(gd::String property) { + if(this->cache.has(property)) { + CachedWorldProperty prop{this->cache[property]}; + if(prop.auto_invalidate_time > utils::time_seconds()) { + return prop.value; + } + } + return ActorWorldState::get_world_property(property); +} + +bool UnitWorldState::get_can_see_target() { + bool value{this->get_can_see_node(this->target_node)}; + this->cache_property("can_see_target", value, utils::time_seconds() + 0.5); + return value; +} + +bool UnitWorldState::get_can_see_node(gd::Node3D *node) const { // check if the target has a separate vision target, or is it's own vision target - gd::Node3D *vision_target{this->target_node->get_node("VisionTarget")}; - gd::Vector3 target_position{ - vision_target - ? vision_target->get_global_position() - : this->target_node->get_global_position() - }; + gd::Node3D *vision_target{node->get_node("VisionTarget")}; + gd::Transform3D const eyes{this->eye_location->get_global_transform()}; + gd::Vector3 const target_position{vision_target ? vision_target->get_global_position() : node->get_global_position()}; + // construct list for ignored objects including target node and parent unit gd::TypedArray ignore_list{this->parent_unit->get_rid()}; - if(gd::CollisionObject3D *target_collider{gd::Object::cast_to(this->target_node)}) - ignore_list.push_back(target_collider->get_rid()); + // ignore target if it is a collision object that might obstruct it's own collision test + if(gd::CollisionObject3D *as_collision_object{gd::Object::cast_to(node)}) + ignore_list.push_back(as_collision_object->get_rid()); // construct raycast query from unit's eye node to vision target. Ignoring parent unit and target if applicable gd::Ref const query{gd::PhysicsRayQueryParameters3D::create( eyes.origin, target_position, - 0x1 | 0x4, ignore_list) - }; + 0x1 | 0x4, ignore_list + )}; return this->parent_unit->get_world_3d()->get_direct_space_state()->intersect_ray(query).is_empty(); } @@ -88,7 +103,7 @@ bool UnitWorldState::get_is_unit_enemy(Unit *unit) 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()) <= 2.f * 2.f; } bool UnitWorldState::get_is_health_safe() const { @@ -104,6 +119,7 @@ gd::Vector3 UnitWorldState::get_target_global_position() const { } void UnitWorldState::set_target_node(gd::Node3D *node) { + this->cache.clear(); if(this->target_node != nullptr) this->target_node->disconnect("tree_exited", this->target_node_exited_tree.bind(this->target_node)); this->target_node = node; @@ -116,6 +132,10 @@ gd::Node3D *UnitWorldState::get_target_node() const { return this->target_node; } +void UnitWorldState::cache_property(gd::String property, gd::Variant value, double auto_invalidate_time) { + this->cache[property] = {.value=value, .auto_invalidate_time=auto_invalidate_time}; +} + void UnitWorldState::target_destroyed(gd::Node3D *target) { if(target == this->target_node) this->set_target_node(nullptr); diff --git a/src/unit_world_state.hpp b/src/unit_world_state.hpp index a24d104..44584e9 100644 --- a/src/unit_world_state.hpp +++ b/src/unit_world_state.hpp @@ -6,14 +6,23 @@ #include #include +namespace gd = godot; + class Unit; +struct CachedWorldProperty { + gd::Variant value{}; + double auto_invalidate_time{0.0}; +}; + class UnitWorldState : public goap::ActorWorldState { GDCLASS(UnitWorldState, goap::ActorWorldState); static void _bind_methods(); public: virtual void _enter_tree() override; - bool get_can_see_target() const; + virtual gd::Variant get_world_property(gd::String property) override; + bool get_can_see_target(); + bool get_can_see_node(gd::Node3D *node) const; bool get_is_at_target() const; bool get_has_target() const; bool get_is_target_dead() const; @@ -28,6 +37,7 @@ public: void set_target_node(gd::Node3D *node); gd::Node3D *get_target_node() const; private: + void cache_property(gd::String property, gd::Variant value, double auto_invalidate_time); void target_destroyed(gd::Node3D *target); private: gd::Callable const target_node_exited_tree{callable_mp(this, &UnitWorldState::target_destroyed)}; @@ -37,6 +47,7 @@ protected: gd::NavigationAgent3D *agent{nullptr}; gd::Node3D *target_node{nullptr}; gd::Node3D *eye_location{nullptr}; + gd::HashMap cache; }; #endif // !UNIT_WORLD_STATE_HPP