dirt-racer/src/turret.cpp
2024-05-21 15:03:36 +02:00

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;
}
}