metro-rts/src/unit_world_state.cpp

147 lines
5.7 KiB
C++

#include "unit_world_state.hpp"
#include "entity_health.hpp"
#include "unit.hpp"
#include "utils/godot_macros.hpp"
#include "utils/util_functions.hpp"
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
#include <godot_cpp/classes/world3d.hpp>
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
#include <godot_cpp/variant/callable_method_pointer.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
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<Unit>(this->get_parent());
this->agent = this->get_node<gd::NavigationAgent3D>("%NavigationAgent3D");
this->eye_location = this->get_node<gd::Node3D>("%Eyes");
this->health = this->get_node<EntityHealth>("%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<gd::Node3D>("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<gd::RID> 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<gd::CollisionObject3D>(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<gd::PhysicsRayQueryParameters3D> 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>("%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<Unit>(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);
}