metro-rts/src/enemy_world_state.cpp

106 lines
3.6 KiB
C++

#include "enemy_world_state.hpp"
#include "entity_health.hpp"
#include "goap/goal.hpp"
#include "godot_cpp/variant/utility_functions.hpp"
#include "unit.hpp"
#include "utils/godot_macros.hpp"
#include <cmath>
void EnemyWorldState::_bind_methods() {
#define CLASSNAME EnemyWorldState
GDPROPERTY_HINTED(editor_available_goals,
gd::Variant::ARRAY,
gd::PROPERTY_HINT_ARRAY_TYPE,
GDRESOURCETYPE("Goal"));
}
void EnemyWorldState::_ready() {
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));
this->health = this->get_node<EntityHealth>("%EntityHealth");
if(this->health == nullptr)
this->health->connect("damage", callable_mp(this, &EnemyWorldState::on_damaged));
}
void EnemyWorldState::on_awareness_entered(gd::Node3D *node) {
gd::UtilityFunctions::print("1) object entered awareness");
Unit *unit{gd::Object::cast_to<Unit>(node)};
if(unit == nullptr) return;
if(unit == this->parent_unit) return;
gd::UtilityFunctions::print("2) object was Unit");
this->known_enemies.push_back(unit);
this->try_set_target(this->select_target_from_known());
}
void EnemyWorldState::on_awareness_exited(gd::Node3D *node) {
Unit *unit{gd::Object::cast_to<Unit>(node)};
this->known_enemies.erase(unit);
}
void EnemyWorldState::on_damaged(int remaining, int delta, Unit *source) {
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)};
if(priority >= highest_priority)
this->try_set_target(source);
}
Unit *EnemyWorldState::select_target_from_known_with_priority(float *out_priority) {
Unit *out{nullptr};
*out_priority = -INFINITY;
for(Unit *unit : this->known_enemies) {
float const priority{this->calculate_priority(unit)};
if(priority > *out_priority) {
out = unit;
*out_priority = priority;
}
}
return out;
}
Unit *EnemyWorldState::select_target_from_known() {
float dummy{};
return this->select_target_from_known_with_priority(&dummy);
}
float EnemyWorldState::calculate_priority(Unit *target) {
return target->get_global_position().distance_squared_to(this->parent_unit->get_global_position());
}
gd::Ref<goap::Goal> EnemyWorldState::get_goal_for_target(Unit *unit) {
gd::Node3D *store{this->target_node};
this->target_node = unit;
for(gd::Ref<goap::Goal> const &goal : this->available_goals) {
if(goal->check_requirements_met(this)) {
this->target_node = store;
return goal;
}
}
this->target_node = store;
return nullptr;
}
void EnemyWorldState::try_set_target(Unit *unit) {
gd::UtilityFunctions::print("3) selecting goal");
gd::Ref<goap::Goal> goal{this->get_goal_for_target(unit)};
if(!goal.is_valid()) return;
gd::UtilityFunctions::print("4) selected goal ", goal->get_path());
this->parent_unit->set_target_goal(unit, goal);
}
void EnemyWorldState::set_editor_available_goals(gd::Array array) {
this->available_goals.clear();
while(!array.is_empty()) {
gd::Ref<goap::Goal> goal{array.pop_back()};
this->available_goals.push_back(goal);
}
}
gd::Array EnemyWorldState::get_editor_available_goals() const {
gd::Array a{};
for(gd::Ref<goap::Goal> const &goal : this->available_goals)
a.push_back(goal);
return a;
}