#include "unit_world_state.hpp" #include "entity_health.hpp" #include "unit.hpp" #include "utils/godot_macros.hpp" #include "utils/util_functions.hpp" #include #include #include #include #include void UnitWorldState::_bind_methods() { #define CLASSNAME UnitWorldState GDSIGNAL("attention_changed"); GDFUNCTION(get_weapon_animation); GDFUNCTION(get_can_see_target); GDFUNCTION(get_is_target_dead); GDFUNCTION(get_is_at_target); GDFUNCTION(get_has_target); GDFUNCTION(get_target_node); GDFUNCTION(get_is_target_enemy); GDFUNCTION(get_is_in_range); GDFUNCTION(get_is_health_safe); GDFUNCTION(get_parent_global_position); GDFUNCTION(get_target_global_position); } void UnitWorldState::_enter_tree() { this->parent_unit = gd::Object::cast_to(this->get_parent()); this->agent = this->get_node("%NavigationAgent3D"); this->eye_location = this->get_node("%Eyes"); this->health = this->get_node("%EntityHealth"); } gd::Variant UnitWorldState::get_world_property(gd::String property) { 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); } gd::String UnitWorldState::get_weapon_animation() const { return gd::String("melee_attack"); } 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 gd::Node3D *vision_target{node->get_node("VisionTarget")}; gd::Transform3D const eyes{this->eye_location->get_global_transform()}; gd::Vector3 const target_position{vision_target ? vision_target->get_global_position() : node->get_global_position()}; // construct list for ignored objects including target node and parent unit gd::TypedArray ignore_list{this->parent_unit->get_rid()}; // ignore target if it is a collision object that might obstruct it's own collision test if(gd::CollisionObject3D *as_collision_object{gd::Object::cast_to(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 gd::Ref const query{gd::PhysicsRayQueryParameters3D::create( eyes.origin, target_position, 0x1 | 0x4, ignore_list )}; return this->parent_unit->get_world_3d()->get_direct_space_state()->intersect_ray(query).is_empty(); } bool UnitWorldState::get_is_at_target() const { if(this->target_node == nullptr) return true; gd::Vector3 const target = this->target_node->get_global_position(); gd::Vector3 const current = this->parent_unit->get_global_position(); float const min_dist = this->agent->get_target_desired_distance(); return (target - current).length_squared() <= min_dist * min_dist; } bool UnitWorldState::get_has_target() const { return this->target_node != nullptr; } bool UnitWorldState::get_is_target_dead() const { if(EntityHealth *health{this->target_node->get_node("%EntityHealth")}) return health->get_injury_current() <= 0.f; return false; } bool UnitWorldState::get_is_target_unit() const { return gd::ClassDB::is_parent_class(this->target_node->get_class(), "Unit"); } bool UnitWorldState::get_is_target_enemy() const { Unit *unit{gd::Object::cast_to(this->target_node)}; return unit != nullptr && this->get_is_unit_enemy(unit); } bool UnitWorldState::get_is_unit_enemy(Unit *unit) const { return unit != nullptr && unit->get_team() != UnitTeam::Neutral && unit->get_team() != this->parent_unit->get_team(); } bool UnitWorldState::get_is_in_range() const { return this->target_node != nullptr && this->target_node->get_global_position().distance_squared_to(this->parent_unit->get_global_position()) <= 2.f * 2.f; } bool UnitWorldState::get_is_health_safe() const { return float(this->health->get_injury_current()) > (float(this->health->get_injury_max()) / 2.f); } gd::Vector3 UnitWorldState::get_parent_global_position() const { return this->parent_unit->get_global_position(); } gd::Vector3 UnitWorldState::get_target_global_position() const { return this->target_node == nullptr ? gd::Vector3{0.f, 0.f, 0.f} : this->target_node->get_global_position(); } void UnitWorldState::set_target_node(gd::Node3D *node) { this->cache.clear(); if(this->target_node != nullptr) this->target_node->disconnect("tree_exited", this->target_node_exited_tree.bind(this->target_node)); this->target_node = node; if(node != nullptr) node->connect("tree_exited", this->target_node_exited_tree.bind(node)); this->emit_signal("attention_changed"); } gd::Node3D *UnitWorldState::get_target_node() const { 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) { if(target == this->target_node) this->set_target_node(nullptr); }