157 lines
5.1 KiB
C++
157 lines
5.1 KiB
C++
#include "turret.hpp"
|
|
#include "beam.hpp"
|
|
#include "utils/godot_macros.h"
|
|
#include <godot_cpp/classes/global_constants.hpp>
|
|
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
|
|
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
|
|
#include <godot_cpp/classes/time.hpp>
|
|
#include <godot_cpp/classes/world3d.hpp>
|
|
#include <godot_cpp/variant/utility_functions.hpp>
|
|
|
|
namespace godot {
|
|
void Turret::_bind_methods() {
|
|
#define CLASSNAME Turret
|
|
GDPROPERTY_HINTED(attack_classes, Variant::ARRAY, PROPERTY_HINT_ARRAY_TYPE, "StringName");
|
|
GDPROPERTY_HINTED(beam_scene, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene");
|
|
}
|
|
|
|
void Turret::_enter_tree() { GDGAMEONLY();
|
|
Area3D *awareness_area = this->get_node<Area3D>("AwarenessArea");
|
|
awareness_area->connect("body_entered", callable_mp(this, &Turret::detect_node));
|
|
awareness_area->connect("body_exited", callable_mp(this, &Turret::lose_node));
|
|
this->gun_node = this->get_node<Node3D>("Gun");
|
|
}
|
|
|
|
void Turret::_process(double delta_time) { GDGAMEONLY();
|
|
switch(state) {
|
|
default:
|
|
break;
|
|
case TurretState::LOCKING:
|
|
this->lead_target(delta_time);
|
|
break;
|
|
}
|
|
this->try_next_state();
|
|
}
|
|
|
|
void Turret::awareness_changed() {
|
|
if(this->current_target != nullptr)
|
|
return;
|
|
|
|
this->current_target = this->select_target();
|
|
if(this->current_target == nullptr)
|
|
this->state = TurretState::WAITING;
|
|
else
|
|
this->state = TurretState::LOCKING;
|
|
this->last_state_switch = Time::get_singleton()->get_ticks_msec() * 0.001f;
|
|
}
|
|
|
|
Node3D *Turret::select_target() {
|
|
for(Node3D *node : this->awareness)
|
|
if(this->attack_classes.has(node->get_class()) ^ this->invert_attack_classes)
|
|
return node;
|
|
return nullptr;
|
|
}
|
|
|
|
void Turret::lead_target(double delta_time) {
|
|
if(!this->current_target)
|
|
return;
|
|
Vector3 const target_position = this->current_target->get_global_position();
|
|
Vector3 const target_heading = (target_position - this->last_target_position) / delta_time;
|
|
this->aim_position = target_position + (target_heading * this->lead_distance);
|
|
|
|
Vector3 const z = (this->aim_position - this->gun_node->get_global_position()).normalized();
|
|
Vector3 const x = z.cross({0.f, 1.f, 0.f});
|
|
Vector3 const y = z.cross(x);
|
|
|
|
this->gun_node->set_global_basis({x,y,z});
|
|
this->last_target_position = target_position;
|
|
}
|
|
|
|
void Turret::detect_node(Node3D *node) {
|
|
if(!this->awareness.has(node)) {
|
|
this->awareness.insert(node);
|
|
}
|
|
this->awareness_changed();
|
|
UtilityFunctions::print(this->get_path(), " detected node ", node->get_path());
|
|
}
|
|
|
|
void Turret::lose_node(Node3D *node) {
|
|
if(this->awareness.has(node)) {
|
|
this->awareness.erase(node);
|
|
}
|
|
if(node == this->current_target) {
|
|
this->current_target = nullptr;
|
|
}
|
|
this->awareness_changed();
|
|
UtilityFunctions::print(this->get_path(), " lost track of node ", node->get_path());
|
|
}
|
|
|
|
void Turret::try_next_state() {
|
|
TurretState next_state = this->state;
|
|
float time = Time::get_singleton()->get_ticks_msec() * 0.001f;
|
|
switch(this->state) {
|
|
case TurretState::LOCKING:
|
|
if(time >= this->last_state_switch + this->lock_time)
|
|
next_state = TurretState::CHARGING;
|
|
break;
|
|
case TurretState::CHARGING:
|
|
if(time >= this->last_state_switch + this->charge_time)
|
|
next_state = TurretState::FIRING;
|
|
break;
|
|
case TurretState::WAITING:
|
|
case TurretState::FIRING:
|
|
return;
|
|
}
|
|
if(this->state == next_state)
|
|
return;
|
|
else this->state = next_state;
|
|
this->last_state_switch = time;
|
|
if(state == TurretState::FIRING) {
|
|
this->create_beam();
|
|
}
|
|
}
|
|
|
|
void Turret::create_beam() {
|
|
Vector3 const from = this->gun_node->get_global_position();
|
|
Vector3 const to = from + this->gun_node->get_global_basis().get_column(2) * 1000.f;
|
|
Ref<PhysicsRayQueryParameters3D> query{PhysicsRayQueryParameters3D::create(from, to)};
|
|
Dictionary result = this->get_world_3d()->get_direct_space_state()->intersect_ray(query);
|
|
if(result.is_empty())
|
|
return;
|
|
Beam *beam = Object::cast_to<Beam>(this->beam_scene->instantiate());
|
|
this->add_child(beam);
|
|
beam->set_from_to(from, result["position"]);
|
|
beam->set_end_time(this->fire_time);
|
|
beam->connect("tree_exited", callable_mp(this, &Turret::beam_ended));
|
|
}
|
|
|
|
void Turret::beam_ended() {
|
|
this->state = TurretState::LOCKING;
|
|
this->last_state_switch = Time::get_singleton()->get_ticks_msec() * 0.001f;
|
|
}
|
|
|
|
void Turret::set_attack_classes(Array array) {
|
|
this->attack_classes.clear();
|
|
for(int i = 0; i < array.size(); ++i)
|
|
if(array[i].get_type() == Variant::STRING_NAME && !this->attack_classes.has(array[i]))
|
|
this->attack_classes.push_back(array[i]);
|
|
else
|
|
this->attack_classes.push_back("");
|
|
}
|
|
|
|
Array Turret::get_attack_classes() const {
|
|
Array array{};
|
|
for(StringName const &str : this->attack_classes)
|
|
array.push_back(str);
|
|
return array;
|
|
}
|
|
|
|
void Turret::set_beam_scene(Ref<PackedScene> scene) {
|
|
this->beam_scene = scene;
|
|
}
|
|
|
|
Ref<PackedScene> Turret::get_beam_scene() const {
|
|
return this->beam_scene;
|
|
}
|
|
}
|