#include "turret.hpp" #include "beam.hpp" #include "utils/godot_macros.h" #include #include #include #include #include #include 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("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("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 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(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 scene) { this->beam_scene = scene; } Ref Turret::get_beam_scene() const { return this->beam_scene; } }