fix: self preservation plan in the wrong order

This commit is contained in:
Sara 2024-07-28 23:17:09 +02:00
parent 4ba6869a30
commit a2240797b8
17 changed files with 304 additions and 26 deletions

View file

@ -1,8 +1,7 @@
#include "enemy_world_state.hpp"
#include "entity_health.hpp"
#include "goap/goal.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
#include "unit.hpp"
#include "goap/goal.hpp"
#include "utils/godot_macros.hpp"
#include <cmath>
@ -14,7 +13,7 @@ void EnemyWorldState::_bind_methods() {
GDRESOURCETYPE("Goal"));
}
void EnemyWorldState::_ready() {
void EnemyWorldState::_ready() { GDGAMEONLY();
this->awareness_area = this->get_node<gd::Area3D>("%AwarenessArea");
this->awareness_area->connect("body_entered", callable_mp(this, &EnemyWorldState::on_awareness_entered));
this->awareness_area->connect("body_exited", callable_mp(this, &EnemyWorldState::on_awareness_exited));
@ -36,6 +35,8 @@ void EnemyWorldState::on_awareness_exited(gd::Node3D *node) {
}
void EnemyWorldState::on_damaged(int remaining, int delta, Unit *source) {
if(this->parent_unit->has_plan())
return;
float highest_priority{0.f};
Unit *highest_priority_unit{this->select_target_from_known_with_priority(&highest_priority)};
float const priority{this->calculate_priority(source)};

View file

@ -1,10 +1,10 @@
#include "planner.hpp"
#include "action_db.hpp"
#include "godot_cpp/classes/engine.hpp"
#include "godot_cpp/classes/global_constants.hpp"
#include "godot_cpp/templates/hashfuncs.hpp"
#include "utils/godot_macros.hpp"
#include <cstdint>
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
namespace goap {
@ -16,18 +16,20 @@ WorldStateNode::WorldStateNode(WorldStateNode const &last_state, Action const *l
, open_requirements{last_state.open_requirements}
, last_action{last_action}
, context{last_state.context} {
for(WorldProperty const &req : last_action->get_required())
for(WorldProperty const &req : last_action->get_required()) {
this->open_requirements[req.key] = req.value;
this->state[req.key] = this->context->get_world_property(req.key);
}
for(WorldProperty const &effect : last_action->get_effects())
if(this->open_requirements.has(effect.key))
this->state[effect.key] = effect.value;
this->state[effect.key] = effect.value;
}
int WorldStateNode::requirements_unmet() const {
int requirements = this->open_requirements.size();
for(WorldProperty const &req : this->open_requirements) {
if(this->state.has(req.key) && this->state[req.key] == req.value) {
--requirements;
if(this->state.has(req.key)) {
if(this->state[req.key] == req.value)
--requirements;
} else if(this->context->check_property_match(req)) {
--requirements;
}
@ -63,19 +65,19 @@ bool operator!=(goap::WorldStateNode const &lhs, goap::WorldStateNode const &rhs
}
bool operator<(goap::WorldStateNode const &lhs, goap::WorldStateNode const &rhs) {
return lhs.state.size() < rhs.state.size();
return lhs.requirements_unmet() < rhs.requirements_unmet();
}
bool operator<=(goap::WorldStateNode const &lhs, goap::WorldStateNode const &rhs) {
return lhs.state.size() <= rhs.state.size();
return lhs.requirements_unmet() <= rhs.requirements_unmet();
}
bool operator>(goap::WorldStateNode const &lhs, goap::WorldStateNode const &rhs) {
return lhs.state.size() > rhs.state.size();
return lhs.requirements_unmet() > rhs.requirements_unmet();
}
bool operator>=(goap::WorldStateNode const &lhs, goap::WorldStateNode const &rhs) {
return lhs.state.size() >= rhs.state.size();
return lhs.requirements_unmet() >= rhs.requirements_unmet();
}
void Planner::_bind_methods() {

32
src/nav_marker.cpp Normal file
View file

@ -0,0 +1,32 @@
#include "nav_marker.hpp"
#include "utils/godot_macros.hpp"
#include <godot_cpp/classes/editor_interface.hpp>
#include <godot_cpp/classes/editor_selection.hpp>
#include <godot_cpp/classes/engine.hpp>
#include <godot_cpp/classes/navigation_server3d.hpp>
#include <godot_cpp/classes/world3d.hpp>
#include <godot_cpp/variant/rid.hpp>
void NavMarker::_bind_methods() {
#define CLASSNAME NavMarker
GDPROPERTY_HINTED(marker_type, gd::Variant::INT, gd::PROPERTY_HINT_ENUM, MarkerType::get_property_hint());
}
void NavMarker::_process(double delta_time) {
#ifdef DEBUG_ENABLED
GDEDITORONLY()
if(gd::EditorInterface::get_singleton()->get_selection()->get_selected_nodes().has(this)) {
gd::RID const map_id{this->get_world_3d()->get_navigation_map()};
gd::Vector3 const new_point{gd::NavigationServer3D::get_singleton()->map_get_closest_point(map_id, this->get_global_position())};
this->set_global_position(new_point);
}
#endif // DEBUG
}
void NavMarker::set_marker_type(int type) {
this->type = type;
}
int NavMarker::get_marker_type() const {
return this->type;
}

26
src/nav_marker.hpp Normal file
View file

@ -0,0 +1,26 @@
#ifndef NAV_MARKER_HPP
#define NAV_MARKER_HPP
#include "utils/godot_macros.hpp"
#include <godot_cpp/classes/marker3d.hpp>
namespace gd = godot;
GDENUM(MarkerType,
Generic,
Cover
);
class NavMarker : public gd::Marker3D {
GDCLASS(NavMarker, gd::Marker3D);
static void _bind_methods();
public:
virtual void _process(double delta_time) override;
void set_marker_type(int type);
int get_marker_type() const;
private:
MarkerType type{MarkerType::Generic};
};
#endif // !NAV_MARKER_HPP

60
src/nav_room.cpp Normal file
View file

@ -0,0 +1,60 @@
#include "nav_room.hpp"
#include "godot_cpp/classes/global_constants.hpp"
#include "nav_marker.hpp"
#include "utils/godot_macros.hpp"
#include <cmath>
void NavRoom::_bind_methods() {
#define CLASSNAME NavRoom
GDPROPERTY_HINTED(editor_neighbours, gd::Variant::ARRAY, gd::PROPERTY_HINT_ARRAY_TYPE, GDNODETYPE("NavRoom"));
}
NavRoom *NavRoom::get_closest_room(gd::Vector3 const &closest_to) {
NavRoom *candidate{nullptr};
float shortest_distance{INFINITY};
for(NavRoom *room : NavRoom::rooms) {
float const distance{room->get_global_position().distance_squared_to(closest_to)};
if(distance < shortest_distance) {
shortest_distance = distance;
candidate = room;
}
}
return candidate;
}
void NavRoom::_enter_tree() { GDGAMEONLY()
NavRoom::rooms.push_back(this);
this->connect("child_entered_tree", callable_mp(this, &NavRoom::on_child_entered));
}
void NavRoom::_exit_tree() { GDGAMEONLY()
NavRoom::rooms.erase(this);
}
void NavRoom::on_child_entered(gd::Node *child) {
if(NavMarker *marker{gd::Object::cast_to<NavMarker>(child)})
this->markers.push_back(marker);
}
gd::Vector<NavRoom*> const &NavRoom::get_neighbours() const {
return this->neighbours;
}
gd::Vector<NavMarker*> const &NavRoom::get_markers() const {
return this->markers;
}
gd::Array NavRoom::get_editor_neighbours() const {
gd::Array a{};
for(NavRoom *room : this->neighbours)
a.push_back(a);
return a;
}
void NavRoom::set_editor_neighbours(gd::Array array) {
this->neighbours.clear();
while(!array.is_empty()) if(NavRoom *room{gd::Object::cast_to<NavRoom>(array.pop_front())})
this->neighbours.push_back(room);
}
gd::Vector<NavRoom*> NavRoom::rooms{};

33
src/nav_room.hpp Normal file
View file

@ -0,0 +1,33 @@
#ifndef NAV_ROOM_HPP
#define NAV_ROOM_HPP
#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/templates/vector.hpp>
namespace gd = godot;
class NavMarker;
class NavRoom : public gd::Node3D {
GDCLASS(NavRoom, gd::Node3D);
static void _bind_methods();
public:
static NavRoom *get_closest_room(gd::Vector3 const &closest_to);
virtual void _enter_tree() override;
virtual void _exit_tree() override;
virtual void on_child_entered(gd::Node *child);
gd::Vector<NavRoom*> const &get_neighbours() const;
gd::Vector<NavMarker*> const &get_markers() const;
private:
void set_editor_neighbours(gd::Array array);
gd::Array get_editor_neighbours() const;
private:
float radius{1.f};
gd::Vector3 centre{0.f, 0.f, 0.f};
gd::Vector<NavMarker*> markers{};
gd::Vector<NavRoom*> neighbours{};
static gd::Vector<NavRoom*> rooms;
};
#endif // !NAV_ROOM_HPP

View file

@ -2,6 +2,8 @@
#include "enemy_world_state.hpp"
#include "entity_health.hpp"
#include "goap/state.hpp"
#include "nav_marker.hpp"
#include "nav_room.hpp"
#include "rts_actions.hpp"
#include "rts_game_mode.hpp"
#include "rts_player.hpp"
@ -36,6 +38,7 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
goap::ActionDB::register_action<GetInMeleeRange>();
goap::ActionDB::register_action<MeleeAttack>();
goap::ActionDB::register_action<TankSelfHeal>();
goap::ActionDB::register_action<TakeCover>();
ClassDB::register_class<goap::ActorWorldState>();
ClassDB::register_class<goap::Goal>();
@ -53,6 +56,8 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
ClassDB::register_class<RTSGameMode>();
ClassDB::register_class<RTSPlayer>();
ClassDB::register_class<EntityHealth>();
ClassDB::register_class<NavMarker>();
ClassDB::register_class<NavRoom>();
}
extern "C"

View file

@ -1,9 +1,13 @@
#include "rts_actions.hpp"
#include "nav_marker.hpp"
#include "nav_room.hpp"
#include "rts_states.hpp"
#include "goap/actor_world_state.hpp"
#include "goap/state.hpp"
#include <godot_cpp/core/memory.hpp>
#include <godot_cpp/variant/basis.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <cmath>
MoveToTarget::MoveToTarget()
: Action() {
@ -24,8 +28,8 @@ goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const
FireAtTarget::FireAtTarget()
: Action() {
this->effects.insert("is_target_dead", true);
this->required.insert("can_see_target", true);
this->effects.insert("is_target_dead", true);
}
goap::State *FireAtTarget::get_apply_state(goap::ActorWorldState *context) const {
@ -50,8 +54,8 @@ goap::State *FindTarget::get_apply_state(goap::ActorWorldState *context) const {
GetInMeleeRange::GetInMeleeRange()
: Action() {
this->require_state_complete = false;
this->effects.insert("is_in_melee_range", true);
this->required.insert("can_see_target", true);
this->effects.insert("is_in_melee_range", true);
}
goap::State *GetInMeleeRange::get_apply_state(goap::ActorWorldState *context) const {
@ -63,9 +67,9 @@ goap::State *GetInMeleeRange::get_apply_state(goap::ActorWorldState *context) co
MeleeAttack::MeleeAttack()
: Action() {
this->effects.insert("is_target_dead", true);
this->required.insert("can_see_target", true);
this->required.insert("is_in_melee_range", true);
this->effects.insert("is_target_dead", true);
}
goap::State *MeleeAttack::get_apply_state(goap::ActorWorldState *context) const {
@ -76,6 +80,7 @@ goap::State *MeleeAttack::get_apply_state(goap::ActorWorldState *context) const
TankSelfHeal::TankSelfHeal()
: Action() {
this->required.insert("can_see_target", false);
this->effects.insert("is_health_safe", true);
}
@ -84,3 +89,48 @@ goap::State *TankSelfHeal::get_apply_state(goap::ActorWorldState *context) const
state->animation = "self_heal";
return state;
}
TakeCover::TakeCover()
: Action () {
this->require_state_complete = false;
this->effects.insert("can_see_target", false);
}
bool TakeCover::procedural_is_possible(goap::ActorWorldState *context) const {
gd::Vector3 const global_position{context->get_world_property("parent_global_position")};
gd::Vector3 const target_position{context->get_world_property("target_global_position")};
NavRoom *room{NavRoom::get_closest_room(global_position)};
if(room == nullptr)
return false;
for(NavMarker *marker : room->get_markers())
if(TakeCover::is_marker_cover_from(marker, target_position))
return true;
return false;
}
goap::State *TakeCover::get_apply_state(goap::ActorWorldState *context) const {
MoveTo *state{this->create_state<MoveTo>()};
gd::Vector3 const global_position{context->get_world_property("parent_global_position")};
gd::Vector3 const target_position{context->get_world_property("target_global_position")};
NavRoom *room{NavRoom::get_closest_room(global_position)};
if(room == nullptr)
return nullptr;
float best_score{-INFINITY};
for(NavMarker *marker : room->get_markers()) {
gd::Vector3 const marker_position{marker->get_global_position()};
float const score{100.f - marker_position.distance_to(global_position)};
if(score > best_score && TakeCover::is_marker_cover_from(marker, target_position)) {
best_score = score;
state->target_node = marker;
gd::UtilityFunctions::print("!!! best cover so far: ", marker->get_path());
}
}
return state;
}
bool TakeCover::is_marker_cover_from(NavMarker *marker, gd::Vector3 const &target) {
return marker->get_marker_type() == MarkerType::Cover
&& marker->get_global_basis()
.get_column(2)
.dot((marker->get_global_position() - target).normalized()) < -0.7f;
}

View file

@ -40,10 +40,20 @@ public:
};
class TankSelfHeal : public goap::Action {
GOAP_ACTION(SelfHeal);
GOAP_ACTION(TankSelfHeal);
public:
TankSelfHeal();
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
};
class TakeCover : public goap::Action {
GOAP_ACTION(TakeCover);
public:
TakeCover();
virtual bool procedural_is_possible(goap::ActorWorldState *context) const override;
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
private:
static bool is_marker_cover_from(class NavMarker *marker, gd::Vector3 const &target);
};
#endif // !RTS_ACTIONS_HPP

View file

@ -7,10 +7,16 @@
void MoveTo::_bind_methods() {}
void MoveTo::_ready() {
this->agent = this->get_node<gd::NavigationAgent3D>("../NavigationAgent3D");
this->agent->set_target_position(this->target_node->get_global_position());
this->parent_node3d = Object::cast_to<gd::Node3D>(this->get_parent());
this->agent = this->get_node<gd::NavigationAgent3D>("../NavigationAgent3D");
if(this->target_node == nullptr) {
this->state_ended();
gd::UtilityFunctions::push_warning("failed to start MoveTo state due to missing target");
return;
}
this->agent->set_target_position(this->target_node->get_global_position());
this->calculate_path();
gd::UtilityFunctions::print(this->parent_node3d->get_path(), " MoveTo ", this->target_node->get_path());
}
void MoveTo::_end_state() {
@ -65,6 +71,8 @@ void Animate::_bind_methods() {}
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();
}
void Animate::_process(double delta_time) {

View file

@ -50,7 +50,9 @@ bool UnitWorldState::get_can_see_target() const {
target_position,
0x1 | 0x4, ignore_list)
};
return this->parent_unit->get_world_3d()->get_direct_space_state()->intersect_ray(query).is_empty();
bool const can_see{this->parent_unit->get_world_3d()->get_direct_space_state()->intersect_ray(query).is_empty()};
gd::UtilityFunctions::print(this->parent_unit->get_path(), can_see ? " can see " : " can't see ", this->target_node->get_path());
return can_see;
}
bool UnitWorldState::get_is_at_target() const {