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
|
# Caches
|
||||||
.cache/
|
.cache/
|
||||||
godot/.gdb_history
|
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) {
|
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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue