147 lines
5.7 KiB
C++
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);
|
|
}
|