
- Death now removes entity from awareness - State failure now causes replan or goal reevaluation
128 lines
4.2 KiB
C++
128 lines
4.2 KiB
C++
#include "enemy_world_state.hpp"
|
|
#include "entity_health.hpp"
|
|
#include "unit.hpp"
|
|
#include "goap/goal.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() { 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));
|
|
this->health = this->get_node<EntityHealth>("%EntityHealth");
|
|
this->health->connect("damage", callable_mp(this, &EnemyWorldState::on_damaged));
|
|
this->parent_unit->connect("plan_interrupted", callable_mp(this, &EnemyWorldState::select_and_set_target));
|
|
}
|
|
|
|
void EnemyWorldState::on_awareness_entered(gd::Node3D *node) {
|
|
Unit *unit{gd::Object::cast_to<Unit>(node)};
|
|
if(unit == nullptr)
|
|
return;
|
|
if(unit == this->parent_unit)
|
|
return;
|
|
if(unit->get_entity_health()->get_is_dead())
|
|
return;
|
|
unit->get_entity_health()->connect("death", this->aware_unit_death.bind(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)};
|
|
if(unit == nullptr)
|
|
return;
|
|
if(this->known_enemies.has(unit)) {
|
|
unit->get_entity_health()->disconnect("death", this->aware_unit_death.bind(unit));
|
|
this->known_enemies.erase(unit);
|
|
}
|
|
if(unit == this->target_node) {
|
|
this->select_and_set_target();
|
|
}
|
|
}
|
|
|
|
void EnemyWorldState::on_damaged(int remaining, int delta, Unit *source) {
|
|
if(!this->known_enemies.has(source)) {
|
|
source->get_entity_health()->connect("death", this->aware_unit_death.bind(source));
|
|
this->known_enemies.push_back(source);
|
|
}
|
|
if(!this->parent_unit->has_plan())
|
|
this->try_set_target(this->select_target_from_known());
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void EnemyWorldState::on_aware_unit_death(Unit *_, Unit *dead_entity) {
|
|
this->on_awareness_exited(dead_entity);
|
|
}
|
|
|
|
void EnemyWorldState::select_and_set_target() {
|
|
this->try_set_target(this->select_target_from_known());
|
|
}
|
|
|
|
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) {
|
|
if(unit == nullptr) {
|
|
this->target_node = nullptr;
|
|
this->parent_unit->stop_plan();
|
|
return;
|
|
}
|
|
gd::Ref<goap::Goal> goal{this->get_goal_for_target(unit)};
|
|
if(goal.is_valid())
|
|
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_front()};
|
|
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;
|
|
}
|