fix: general AI behaviour fixes

This commit is contained in:
Sara 2024-08-05 22:56:55 +02:00
parent b87752c1a9
commit 973ba952c2
25 changed files with 155 additions and 114 deletions

1
.gitignore vendored
View file

@ -56,3 +56,4 @@ metro-rts.kdev4
# Caches # Caches
.cache/ .cache/
godot/.gdb_history godot/.gdb_history
builds

@ -1 +1 @@
Subproject commit 54136ee8357c5140a3775c54f08db5f7deda2058 Subproject commit 51c752c46b44769d3b6c661526c364a18ea64781

View file

@ -30,18 +30,23 @@ void EnemyWorldState::on_awareness_entered(gd::Node3D *node) {
} }
void EnemyWorldState::on_awareness_exited(gd::Node3D *node) { void EnemyWorldState::on_awareness_exited(gd::Node3D *node) {
Unit *unit{gd::Object::cast_to<Unit>(node)}; if(Unit *unit{gd::Object::cast_to<Unit>(node)})
this->remove_aware_unit(unit); this->remove_aware_unit(unit);
} }
void EnemyWorldState::on_damaged(int remaining, int delta, Unit *source) { void EnemyWorldState::on_damaged(EntityHealth *, int, Unit *source) {
if(source != nullptr && !this->known_enemies.has(source))
this->add_aware_unit(source); this->add_aware_unit(source);
else
this->select_and_set_target();
} }
Unit *EnemyWorldState::select_target_from_known_with_priority(float *out_priority) { Unit *EnemyWorldState::select_target_from_known_with_priority(float *out_priority) {
Unit *out{nullptr}; Unit *out{nullptr};
*out_priority = -INFINITY; *out_priority = -INFINITY;
for(Unit *unit : this->known_enemies) { for(Unit *unit : this->known_enemies) {
if(!this->get_can_see_node(unit))
continue;
float const priority{this->calculate_priority(unit)}; float const priority{this->calculate_priority(unit)};
if(priority > *out_priority) { if(priority > *out_priority) {
out = unit; out = unit;

View file

@ -19,7 +19,7 @@ public:
void on_awareness_entered(gd::Node3D *node); void on_awareness_entered(gd::Node3D *node);
void on_awareness_exited(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. /*! Select the target with the highest priority and return it.
* Assigns the priority of found target to out_priority. * Assigns the priority of found target to out_priority.
* \param out_priority Assigned to highest found priority, cannot be nullptr. * \param out_priority Assigned to highest found priority, cannot be nullptr.

View file

@ -4,22 +4,21 @@
void EntityHealth::_bind_methods() { void EntityHealth::_bind_methods() {
#define CLASSNAME EntityHealth #define CLASSNAME EntityHealth
GDFUNCTION_ARGS(damage, "amount");
GDFUNCTION_ARGS(damaged_by, "amount", "source"); GDFUNCTION_ARGS(damaged_by, "amount", "source");
GDFUNCTION_ARGS(healed_by, "amount", "source"); GDFUNCTION_ARGS(healed_by, "amount", "source");
GDPROPERTY(injury_max, gd::Variant::INT); GDPROPERTY(injury_max, gd::Variant::INT);
GDPROPERTY(wounds_max, gd::Variant::INT); GDPROPERTY(wounds_max, gd::Variant::INT);
GDSIGNAL("damage", GDSIGNAL("damage",
gd::PropertyInfo(gd::Variant::INT, "remaining"), gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
gd::PropertyInfo(gd::Variant::INT, "change"), gd::PropertyInfo(gd::Variant::INT, "change"),
gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit")); gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
GDSIGNAL("heal", GDSIGNAL("heal",
gd::PropertyInfo(gd::Variant::INT, "remaining"), gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
gd::PropertyInfo(gd::Variant::INT, "change"), gd::PropertyInfo(gd::Variant::INT, "change"),
gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit")); gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
GDSIGNAL("health_changed", GDSIGNAL("health_changed",
gd::PropertyInfo(gd::Variant::INT, "remaining"), gd::PropertyInfo(gd::Variant::OBJECT, "health_entity", gd::PROPERTY_HINT_NODE_TYPE, "EntityHealth"),
gd::PropertyInfo(gd::Variant::INT, "change")); gd::PropertyInfo(gd::Variant::INT, "change"));
GDSIGNAL("death", gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit")); GDSIGNAL("death", gd::PropertyInfo(gd::Variant::OBJECT, "source", gd::PROPERTY_HINT_NODE_TYPE, "Unit"));
} }
@ -27,29 +26,34 @@ void EntityHealth::_bind_methods() {
void EntityHealth::_enter_tree() { void EntityHealth::_enter_tree() {
this->injury_current = this->injury_max; this->injury_current = this->injury_max;
this->wounds_current = this->wounds_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) { 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); amount = gd::Math::abs(amount);
if(this->injury_current <= 0) return; // do not take damage when already dead this->wounds_current -= amount;
this->injury_current -= amount; this->injury_current -= gd::Math::max(1, int(float(amount) * this->get_wounds_damage_factor()));
this->emit_signal("damage", this->injury_current, amount, source);
this->emit_signal("health_changed", this->injury_current, -amount); this->emit_signal("damage", this, amount, source);
if(this->injury_current <= 0) { this->emit_signal("health_changed", this, -amount);
if(this->injury_current <= 0)
this->emit_signal("death", source); this->emit_signal("death", source);
} }
}
void EntityHealth::healed_by(int amount, Unit *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); amount = gd::Math::abs(amount);
this->injury_current = gd::Math::min(this->injury_max, this->injury_current + 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("heal", this, amount, source);
this->emit_signal("health_changed", this->injury_current, amount); 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 { bool EntityHealth::get_is_dead() const {

View file

@ -13,9 +13,9 @@ class EntityHealth : public gd::Node {
public: public:
virtual void _enter_tree() override; virtual void _enter_tree() override;
void damaged_by(int amount, Unit *source); void damaged_by(int amount, Unit *source);
void damage(int amount);
void healed_by(int amount, Unit *source); void healed_by(int amount, Unit *source);
void heal(int amount);
float get_wounds_damage_factor() const;
bool get_is_dead() const; bool get_is_dead() const;
void set_injury_max(int max_health); void set_injury_max(int max_health);
int get_injury_max() const; int get_injury_max() const;

View file

@ -17,7 +17,7 @@ class ActorWorldState : public gd::Node {
static void _bind_methods(); static void _bind_methods();
public: public:
bool check_property_match(WorldProperty const &property); 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);
}; };
} }

View file

@ -1,6 +1,6 @@
#include "goal.hpp" #include "goal.hpp"
#include "utils/godot_macros.hpp" #include "../utils/godot_macros.hpp"
#include "utils/util_functions.hpp" #include "../utils/util_functions.hpp"
namespace goap { namespace goap {
void Goal::_bind_methods() { void Goal::_bind_methods() {

View file

@ -1,6 +1,6 @@
#include "planner.hpp" #include "planner.hpp"
#include "action_db.hpp" #include "action_db.hpp"
#include "utils/godot_macros.hpp" #include "../utils/godot_macros.hpp"
#include <cstdint> #include <cstdint>
#include <godot_cpp/classes/engine.hpp> #include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/global_constants.hpp> #include <godot_cpp/classes/global_constants.hpp>
@ -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 Planner::unroll_plan(WorldStateNode const &start_node, NodeMap const &from) {
Plan plan{}; Plan plan{};
WorldStateNode node{start_node}; WorldStateNode node{start_node};
#ifdef DEBUG_ENABLED
gd::UtilityFunctions::print("plan (", this->get_path(), "):");
#endif
while(node.last_action != nullptr) { while(node.last_action != nullptr) {
plan.push_back(node.last_action); plan.push_back(node.last_action);
#ifdef DEBUG_ENABLED
gd::UtilityFunctions::print(" (", plan.size(), ") ", node.last_action->get_class());
#endif
node = from.get(node); node = from.get(node);
} }
return plan; return plan;

View file

@ -1,7 +1,7 @@
#include "state.hpp" #include "state.hpp"
#include "goap/action.hpp" #include "../goap/action.hpp"
#include "goap/actor_world_state.hpp" #include "../goap/actor_world_state.hpp"
#include "utils/godot_macros.hpp" #include "../utils/godot_macros.hpp"
namespace goap { namespace goap {
void State::_bind_methods() { void State::_bind_methods() {
@ -15,9 +15,20 @@ void State::_enter_tree() {
this->world_state = this->get_node<ActorWorldState>("../ActorWorldState"); this->world_state = this->get_node<ActorWorldState>("../ActorWorldState");
} }
void State::_process(double delta_time) { void State::_process(double) {
if(this->is_action_done() && this->get_action()->get_require_state_complete()) 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 { Action const *State::get_action() const {
@ -26,11 +37,6 @@ Action const *State::get_action() const {
void State::_end_state() {} 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 { bool State::is_action_done() const {
return this->action->is_completed(this->world_state); 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 { bool State::is_action_done_interrupt() const {
return this->is_action_done() && !this->action->get_require_state_complete(); 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");
}
} }

View file

@ -1,7 +1,7 @@
#ifndef GOAP_STATE_HPP #ifndef GOAP_STATE_HPP
#define GOAP_STATE_HPP #define GOAP_STATE_HPP
#include "goap/actor_world_state.hpp" #include "actor_world_state.hpp"
#include <godot_cpp/classes/node.hpp> #include <godot_cpp/classes/node.hpp>
#include <godot_cpp/classes/node3d.hpp> #include <godot_cpp/classes/node3d.hpp>
@ -16,17 +16,16 @@ friend class Action;
static void _bind_methods(); static void _bind_methods();
public: public:
virtual void _enter_tree() override; virtual void _enter_tree() override;
virtual void _process(double delta_time) override; virtual void _process(double) override;
void interrupt_state();
void end_state();
protected: protected:
Action const *get_action() const; 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; 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; bool is_action_done_interrupt() const;
virtual void _end_state(); virtual void _end_state();
void state_ended();
private:
void end_state();
private: private:
ActorWorldState *world_state{nullptr}; ActorWorldState *world_state{nullptr};
Action const *action{nullptr}; Action const *action{nullptr};

View file

@ -12,7 +12,7 @@ void NavMarker::_bind_methods() {
GDPROPERTY_HINTED(marker_type, gd::Variant::INT, gd::PROPERTY_HINT_ENUM, MarkerType::get_property_hint()); 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 #ifdef DEBUG_ENABLED
GDEDITORONLY() GDEDITORONLY()
if(gd::EditorInterface::get_singleton()->get_selection()->get_selected_nodes().has(this)) { if(gd::EditorInterface::get_singleton()->get_selection()->get_selected_nodes().has(this)) {

View file

@ -8,14 +8,15 @@ namespace gd = godot;
GDENUM(MarkerType, GDENUM(MarkerType,
Generic, Generic,
Cover Cover,
HalfCover
); );
class NavMarker : public gd::Marker3D { class NavMarker : public gd::Marker3D {
GDCLASS(NavMarker, gd::Marker3D); GDCLASS(NavMarker, gd::Marker3D);
static void _bind_methods(); static void _bind_methods();
public: public:
virtual void _process(double delta_time) override; virtual void _process(double) override;
void set_marker_type(int type); void set_marker_type(int type);
int get_marker_type() const; int get_marker_type() const;

View file

@ -32,7 +32,7 @@ FireAtTarget::FireAtTarget()
this->effects.insert("is_target_dead", true); 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<Animate>()}; Animate *state{this->create_state<Animate>()};
state->animation = "fire_weapon"; state->animation = "fire_weapon";
return state; return state;
@ -72,7 +72,7 @@ MeleeAttack::MeleeAttack()
this->effects.insert("is_target_dead", true); 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<Animate>()}; Animate *state{this->create_state<Animate>()};
state->animation = "melee_attack"; state->animation = "melee_attack";
return state; return state;
@ -84,7 +84,7 @@ TankSelfHeal::TankSelfHeal()
this->effects.insert("is_health_safe", true); 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<Animate>()}; Animate *state{this->create_state<Animate>()};
state->animation = "self_heal"; state->animation = "self_heal";
return state; return state;

View file

@ -15,7 +15,7 @@ class FireAtTarget : public goap::Action {
GOAP_ACTION(FireAtTarget); GOAP_ACTION(FireAtTarget);
public: public:
FireAtTarget(); 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 { class FindTarget : public goap::Action {
@ -36,14 +36,14 @@ class MeleeAttack : public goap::Action {
GOAP_ACTION(MeleeAttack); GOAP_ACTION(MeleeAttack);
public: public:
MeleeAttack(); 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 { class TankSelfHeal : public goap::Action {
GOAP_ACTION(TankSelfHeal); GOAP_ACTION(TankSelfHeal);
public: public:
TankSelfHeal(); 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 { class TakeCover : public goap::Action {

View file

@ -1,5 +1,12 @@
#include "rts_game_mode.hpp" #include "rts_game_mode.hpp"
#include "utils/godot_macros.hpp"
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
void RTSGameMode::_bind_methods() { void RTSGameMode::_bind_methods() {
#define CLASSNAME RTSGameMode #define CLASSNAME RTSGameMode
} }
void RTSGameMode::_ready() { GDGAMEONLY();
godot::Engine::get_singleton()->set_max_fps(60);
}

View file

@ -7,7 +7,7 @@ class RTSGameMode : public utils::GameMode {
GDCLASS(RTSGameMode, utils::GameMode); GDCLASS(RTSGameMode, utils::GameMode);
static void _bind_methods(); static void _bind_methods();
public: public:
private: virtual void _ready() override;
}; };
#endif // !RTS_GAME_MODE_HPP #endif // !RTS_GAME_MODE_HPP

View file

@ -131,7 +131,7 @@ void RTSPlayer::select_unit(Unit *unit) {
unit->connect("tree_exited", this->on_selected_unit_destroyed); unit->connect("tree_exited", this->on_selected_unit_destroyed);
} }
void RTSPlayer::on_mouse_horizontal(gd::Ref<gd::InputEvent> event, float value) { void RTSPlayer::on_mouse_horizontal(gd::Ref<gd::InputEvent>, float value) {
if(this->mmb_down) if(this->mmb_down)
this->camera_mouse_motion.x = value; this->camera_mouse_motion.x = value;
else if(this->rmb_down) else if(this->rmb_down)
@ -139,39 +139,39 @@ void RTSPlayer::on_mouse_horizontal(gd::Ref<gd::InputEvent> event, float value)
this->total_cursor_motion.x = value; this->total_cursor_motion.x = value;
} }
void RTSPlayer::on_mouse_vertical(gd::Ref<gd::InputEvent> event, float value) { void RTSPlayer::on_mouse_vertical(gd::Ref<gd::InputEvent>, float value) {
if(this->mmb_down) if(this->mmb_down)
this->camera_mouse_motion.y = value; this->camera_mouse_motion.y = value;
this->total_cursor_motion.y = value; this->total_cursor_motion.y = value;
} }
void RTSPlayer::on_direction_horizontal(gd::Ref<gd::InputEvent> event, float value) { void RTSPlayer::on_direction_horizontal(gd::Ref<gd::InputEvent>, float value) {
this->camera_keys_motion.x = value; this->camera_keys_motion.x = value;
} }
void RTSPlayer::on_direction_vertical(gd::Ref<gd::InputEvent> event, float value) { void RTSPlayer::on_direction_vertical(gd::Ref<gd::InputEvent>, float value) {
this->camera_keys_motion.y = value; this->camera_keys_motion.y = value;
} }
void RTSPlayer::on_rotate_horizontal(gd::Ref<gd::InputEvent> event, float value) { void RTSPlayer::on_rotate_horizontal(gd::Ref<gd::InputEvent>, float value) {
this->camera_keys_rotation = value; this->camera_keys_rotation = value;
} }
void RTSPlayer::on_rclick(gd::Ref<gd::InputEvent> event, float value) { void RTSPlayer::on_rclick(gd::Ref<gd::InputEvent>, float value) {
if(value == 0.f && !this->rmb_is_dragged) if(value == 0.f && !this->rmb_is_dragged)
this->order_activate_object_under_cursor(); this->order_activate_object_under_cursor();
this->rmb_down = value != 0.f; this->rmb_down = value != 0.f;
this->rmb_last_change = utils::time_seconds(); this->rmb_last_change = utils::time_seconds();
} }
void RTSPlayer::on_lclick(gd::Ref<gd::InputEvent> event, float value) { void RTSPlayer::on_lclick(gd::Ref<gd::InputEvent>, float value) {
if(value == 0.f && !this->lmb_is_dragged) if(value == 0.f && !this->lmb_is_dragged)
this->select_unit_under_cursor(); this->select_unit_under_cursor();
this->lmb_down = value != 0.f; this->lmb_down = value != 0.f;
this->lmb_last_change = utils::time_seconds(); this->lmb_last_change = utils::time_seconds();
} }
void RTSPlayer::on_mclick(gd::Ref<gd::InputEvent> event, float value) { void RTSPlayer::on_mclick(gd::Ref<gd::InputEvent>, float value) {
this->mmb_down = value != 0.f; this->mmb_down = value != 0.f;
} }

View file

@ -42,14 +42,14 @@ private:
void select_unit(Unit *unit); void select_unit(Unit *unit);
// input functions // input functions
void on_mouse_horizontal(gd::Ref<gd::InputEvent> event, float value); void on_mouse_horizontal(gd::Ref<gd::InputEvent>, float value);
void on_mouse_vertical(gd::Ref<gd::InputEvent> event, float value); void on_mouse_vertical(gd::Ref<gd::InputEvent>, float value);
void on_direction_horizontal(gd::Ref<gd::InputEvent> event, float value); void on_direction_horizontal(gd::Ref<gd::InputEvent>, float value);
void on_direction_vertical(gd::Ref<gd::InputEvent> event, float value); void on_direction_vertical(gd::Ref<gd::InputEvent>, float value);
void on_rotate_horizontal(gd::Ref<gd::InputEvent> event, float value); void on_rotate_horizontal(gd::Ref<gd::InputEvent>, float value);
void on_lclick(gd::Ref<gd::InputEvent> event, float value); void on_lclick(gd::Ref<gd::InputEvent>, float value);
void on_rclick(gd::Ref<gd::InputEvent> event, float value); void on_rclick(gd::Ref<gd::InputEvent>, float value);
void on_mclick(gd::Ref<gd::InputEvent> event, float value); void on_mclick(gd::Ref<gd::InputEvent>, float value);
// setters & getters // setters & getters
gd::Vector3 get_forward_direction() const; gd::Vector3 get_forward_direction() const;

View file

@ -10,13 +10,12 @@ void MoveTo::_ready() {
this->parent_unit = Object::cast_to<Unit>(this->get_parent()); this->parent_unit = Object::cast_to<Unit>(this->get_parent());
this->agent = this->get_node<gd::NavigationAgent3D>("../NavigationAgent3D"); this->agent = this->get_node<gd::NavigationAgent3D>("../NavigationAgent3D");
if(this->target_node == nullptr) { if(this->target_node == nullptr) {
this->state_ended(); this->end_state();
gd::UtilityFunctions::push_warning("failed to start MoveTo state due to missing target"); gd::UtilityFunctions::push_warning("failed to start MoveTo state due to missing target");
return; return;
} }
this->agent->connect("velocity_computed", this->nav_velocity_computed); this->agent->connect("velocity_computed", this->nav_velocity_computed);
this->calculate_path(); this->calculate_path();
gd::UtilityFunctions::print(this->parent_unit->get_path(), " MoveTo ", this->target_node->get_path());
} }
void MoveTo::_end_state() { void MoveTo::_end_state() {
@ -26,14 +25,14 @@ void MoveTo::_end_state() {
this->parent_unit->set_velocity({0.f, 0.f, 0.f}); 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 pos = this->parent_unit->get_global_position();
gd::Vector3 const target = this->agent->get_next_path_position(); gd::Vector3 const target = this->agent->get_next_path_position();
gd::Vector3 const direction = (target - pos).normalized(); gd::Vector3 const direction = (target - pos).normalized();
this->agent->set_velocity(direction); this->agent->set_velocity(direction);
bool const navigation_finished{this->agent->is_navigation_finished()}; bool const navigation_finished{this->agent->is_navigation_finished()};
if(this->is_action_done_interrupt() || 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()) if((utils::time_seconds() - this->last_repath) > this->get_repath_interval())
this->calculate_path(); this->calculate_path();
} }
@ -78,13 +77,13 @@ void Animate::_ready() {
this->anim = this->get_node<gd::AnimationPlayer>("../AnimationPlayer"); this->anim = this->get_node<gd::AnimationPlayer>("../AnimationPlayer");
this->anim->play(this->animation); this->anim->play(this->animation);
if(!this->anim->has_animation(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}; bool const animation_finished{!this->anim->is_playing() || this->anim->get_current_animation() != this->animation};
if(this->is_action_done_interrupt() || animation_finished) if(this->is_action_done_interrupt() || animation_finished)
this->state_ended(); this->end_state();
} }
void Animate::_end_state() { void Animate::_end_state() {

View file

@ -14,7 +14,7 @@ class MoveTo : public goap::State {
public: public:
virtual void _ready() override; virtual void _ready() override;
virtual void _end_state() override; virtual void _end_state() override;
virtual void _process(double delta_time) override; virtual void _process(double) override;
void calculate_path(); void calculate_path();
void on_velocity_computed(gd::Vector3 vel); void on_velocity_computed(gd::Vector3 vel);
float target_delta_position() const; float target_delta_position() const;

View file

@ -28,7 +28,7 @@ void Unit::_enter_tree() { GDGAMEONLY();
this->health->connect("death", callable_mp(this, &Unit::on_death)); 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(); this->move_and_slide();
} }
@ -131,7 +131,7 @@ void Unit::on_velocity_computed(gd::Vector3 vel) {
void Unit::destroy_state() { void Unit::destroy_state() {
if(this->state == nullptr || this->state->is_queued_for_deletion() || !this->state->is_inside_tree()) if(this->state == nullptr || this->state->is_queued_for_deletion() || !this->state->is_inside_tree())
return; return;
this->state->queue_free(); this->state->interrupt_state();
this->state = nullptr; this->state = nullptr;
} }

View file

@ -26,7 +26,7 @@ class Unit : public gd::CharacterBody3D {
static void _bind_methods(); static void _bind_methods();
public: public:
virtual void _enter_tree() override; virtual void _enter_tree() override;
virtual void _physics_process(double delta_time) override; virtual void _physics_process(double) override;
void stop_plan(); void stop_plan();
void begin_marker_temporary(GoalMarker *marker); void begin_marker_temporary(GoalMarker *marker);

View file

@ -2,6 +2,7 @@
#include "entity_health.hpp" #include "entity_health.hpp"
#include "unit.hpp" #include "unit.hpp"
#include "utils/godot_macros.hpp" #include "utils/godot_macros.hpp"
#include "utils/util_functions.hpp"
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp> #include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
#include <godot_cpp/classes/world3d.hpp> #include <godot_cpp/classes/world3d.hpp>
#include <godot_cpp/classes/physics_direct_space_state3d.hpp> #include <godot_cpp/classes/physics_direct_space_state3d.hpp>
@ -30,25 +31,39 @@ void UnitWorldState::_enter_tree() { GDGAMEONLY();
this->health = this->get_node<EntityHealth>("%EntityHealth"); this->health = this->get_node<EntityHealth>("%EntityHealth");
} }
bool UnitWorldState::get_can_see_target() const { gd::Variant UnitWorldState::get_world_property(gd::String property) {
gd::Transform3D const eyes{this->eye_location->get_global_transform()}; 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 // 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<gd::Node3D>("VisionTarget")}; gd::Node3D *vision_target{node->get_node<gd::Node3D>("VisionTarget")};
gd::Vector3 target_position{ gd::Transform3D const eyes{this->eye_location->get_global_transform()};
vision_target gd::Vector3 const target_position{vision_target ? vision_target->get_global_position() : node->get_global_position()};
? vision_target->get_global_position()
: this->target_node->get_global_position()
};
// construct list for ignored objects including target node and parent unit // construct list for ignored objects including target node and parent unit
gd::TypedArray<gd::RID> ignore_list{this->parent_unit->get_rid()}; gd::TypedArray<gd::RID> ignore_list{this->parent_unit->get_rid()};
if(gd::CollisionObject3D *target_collider{gd::Object::cast_to<gd::CollisionObject3D>(this->target_node)}) // ignore target if it is a collision object that might obstruct it's own collision test
ignore_list.push_back(target_collider->get_rid()); if(gd::CollisionObject3D *as_collision_object{gd::Object::cast_to<gd::CollisionObject3D>(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 // construct raycast query from unit's eye node to vision target. Ignoring parent unit and target if applicable
gd::Ref<gd::PhysicsRayQueryParameters3D> const query{gd::PhysicsRayQueryParameters3D::create( gd::Ref<gd::PhysicsRayQueryParameters3D> const query{gd::PhysicsRayQueryParameters3D::create(
eyes.origin, eyes.origin,
target_position, 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(); 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 { bool UnitWorldState::get_is_in_melee_range() const {
return this->target_node != nullptr return this->target_node != nullptr
&& this->target_node->get_global_position() && 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 { 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) { void UnitWorldState::set_target_node(gd::Node3D *node) {
this->cache.clear();
if(this->target_node != nullptr) if(this->target_node != nullptr)
this->target_node->disconnect("tree_exited", this->target_node_exited_tree.bind(this->target_node)); this->target_node->disconnect("tree_exited", this->target_node_exited_tree.bind(this->target_node));
this->target_node = node; this->target_node = node;
@ -116,6 +132,10 @@ gd::Node3D *UnitWorldState::get_target_node() const {
return this->target_node; 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) { void UnitWorldState::target_destroyed(gd::Node3D *target) {
if(target == this->target_node) if(target == this->target_node)
this->set_target_node(nullptr); this->set_target_node(nullptr);

View file

@ -6,14 +6,23 @@
#include <godot_cpp/classes/navigation_agent3d.hpp> #include <godot_cpp/classes/navigation_agent3d.hpp>
#include <godot_cpp/classes/node3d.hpp> #include <godot_cpp/classes/node3d.hpp>
namespace gd = godot;
class Unit; class Unit;
struct CachedWorldProperty {
gd::Variant value{};
double auto_invalidate_time{0.0};
};
class UnitWorldState : public goap::ActorWorldState { class UnitWorldState : public goap::ActorWorldState {
GDCLASS(UnitWorldState, goap::ActorWorldState); GDCLASS(UnitWorldState, goap::ActorWorldState);
static void _bind_methods(); static void _bind_methods();
public: public:
virtual void _enter_tree() override; 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_is_at_target() const;
bool get_has_target() const; bool get_has_target() const;
bool get_is_target_dead() const; bool get_is_target_dead() const;
@ -28,6 +37,7 @@ public:
void set_target_node(gd::Node3D *node); void set_target_node(gd::Node3D *node);
gd::Node3D *get_target_node() const; gd::Node3D *get_target_node() const;
private: private:
void cache_property(gd::String property, gd::Variant value, double auto_invalidate_time);
void target_destroyed(gd::Node3D *target); void target_destroyed(gd::Node3D *target);
private: private:
gd::Callable const target_node_exited_tree{callable_mp(this, &UnitWorldState::target_destroyed)}; gd::Callable const target_node_exited_tree{callable_mp(this, &UnitWorldState::target_destroyed)};
@ -37,6 +47,7 @@ protected:
gd::NavigationAgent3D *agent{nullptr}; gd::NavigationAgent3D *agent{nullptr};
gd::Node3D *target_node{nullptr}; gd::Node3D *target_node{nullptr};
gd::Node3D *eye_location{nullptr}; gd::Node3D *eye_location{nullptr};
gd::HashMap<gd::String, CachedWorldProperty> cache;
}; };
#endif // !UNIT_WORLD_STATE_HPP #endif // !UNIT_WORLD_STATE_HPP