#include "enemy_world_state.hpp" #include "entity_health.hpp" #include "unit.hpp" #include "goap/goal.hpp" #include "utils/godot_macros.hpp" #include 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("%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"); 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(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(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 EnemyWorldState::get_goal_for_target(Unit *unit) { gd::Node3D *store{this->target_node}; this->target_node = unit; for(gd::Ref 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 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 goal{array.pop_front()}; this->available_goals.push_back(goal); } } gd::Array EnemyWorldState::get_editor_available_goals() const { gd::Array a{}; for(gd::Ref const &goal : this->available_goals) a.push_back(goal); return a; }