fix: general AI behaviour fixes
This commit is contained in:
parent
b87752c1a9
commit
973ba952c2
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -56,3 +56,4 @@ metro-rts.kdev4
|
|||
# Caches
|
||||
.cache/
|
||||
godot/.gdb_history
|
||||
builds
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 54136ee8357c5140a3775c54f08db5f7deda2058
|
||||
Subproject commit 51c752c46b44769d3b6c661526c364a18ea64781
|
|
@ -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<Unit>(node)};
|
||||
this->remove_aware_unit(unit);
|
||||
if(Unit *unit{gd::Object::cast_to<Unit>(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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#include "planner.hpp"
|
||||
#include "action_db.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
#include "../utils/godot_macros.hpp"
|
||||
#include <cstdint>
|
||||
#include <godot_cpp/classes/engine.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 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;
|
||||
|
|
|
@ -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>("../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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef 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/node3d.hpp>
|
||||
|
||||
|
@ -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};
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Animate>()};
|
||||
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<Animate>()};
|
||||
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<Animate>()};
|
||||
state->animation = "self_heal";
|
||||
return state;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
#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() {
|
||||
#define CLASSNAME RTSGameMode
|
||||
}
|
||||
|
||||
void RTSGameMode::_ready() { GDGAMEONLY();
|
||||
godot::Engine::get_singleton()->set_max_fps(60);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<gd::InputEvent> event, float value) {
|
||||
void RTSPlayer::on_mouse_horizontal(gd::Ref<gd::InputEvent>, 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<gd::InputEvent> event, float 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)
|
||||
this->camera_mouse_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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
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<gd::InputEvent> event, float value) {
|
||||
void RTSPlayer::on_lclick(gd::Ref<gd::InputEvent>, 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<gd::InputEvent> event, float value) {
|
||||
void RTSPlayer::on_mclick(gd::Ref<gd::InputEvent>, float value) {
|
||||
this->mmb_down = value != 0.f;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,14 +42,14 @@ private:
|
|||
void select_unit(Unit *unit);
|
||||
|
||||
// input functions
|
||||
void on_mouse_horizontal(gd::Ref<gd::InputEvent> event, float value);
|
||||
void on_mouse_vertical(gd::Ref<gd::InputEvent> event, float value);
|
||||
void on_direction_horizontal(gd::Ref<gd::InputEvent> event, float value);
|
||||
void on_direction_vertical(gd::Ref<gd::InputEvent> event, float value);
|
||||
void on_rotate_horizontal(gd::Ref<gd::InputEvent> event, float value);
|
||||
void on_lclick(gd::Ref<gd::InputEvent> event, float value);
|
||||
void on_rclick(gd::Ref<gd::InputEvent> event, float value);
|
||||
void on_mclick(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>, float value);
|
||||
void on_direction_horizontal(gd::Ref<gd::InputEvent>, float value);
|
||||
void on_direction_vertical(gd::Ref<gd::InputEvent>, float value);
|
||||
void on_rotate_horizontal(gd::Ref<gd::InputEvent>, float value);
|
||||
void on_lclick(gd::Ref<gd::InputEvent>, float value);
|
||||
void on_rclick(gd::Ref<gd::InputEvent>, float value);
|
||||
void on_mclick(gd::Ref<gd::InputEvent>, float value);
|
||||
|
||||
// setters & getters
|
||||
gd::Vector3 get_forward_direction() const;
|
||||
|
|
|
@ -10,13 +10,12 @@ void MoveTo::_ready() {
|
|||
this->parent_unit = Object::cast_to<Unit>(this->get_parent());
|
||||
this->agent = this->get_node<gd::NavigationAgent3D>("../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<gd::AnimationPlayer>("../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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "entity_health.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
#include "utils/util_functions.hpp"
|
||||
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
|
||||
#include <godot_cpp/classes/world3d.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");
|
||||
}
|
||||
|
||||
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<gd::Node3D>("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<gd::Node3D>("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<gd::RID> ignore_list{this->parent_unit->get_rid()};
|
||||
if(gd::CollisionObject3D *target_collider{gd::Object::cast_to<gd::CollisionObject3D>(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<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
|
||||
gd::Ref<gd::PhysicsRayQueryParameters3D> 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);
|
||||
|
|
|
@ -6,14 +6,23 @@
|
|||
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
|
||||
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<gd::String, CachedWorldProperty> cache;
|
||||
};
|
||||
|
||||
#endif // !UNIT_WORLD_STATE_HPP
|
||||
|
|
Loading…
Reference in a new issue