#include "enemy.hpp" #include "utils/godot_macros.hpp" #include #include #include #include #include void Enemy::_bind_methods() { #define CLASSNAME Enemy GDFUNCTION_ARGS(notice_player, "player"); GDPROPERTY(update_interval, gd::Variant::FLOAT); } void Enemy::_ready() { this->anim_tree = this->get_node("CharacterModel/AnimationTree"); this->agent = this->get_node("%NavigationAgent3D"); gd::Timer *timer{memnew(gd::Timer)}; this->add_child(timer); timer->start(this->update_interval); timer->connect("timeout", callable_mp(this, &Enemy::update)); this->target_rotation = this->get_rotation().y; } void Enemy::_process(double delta) { float const angle_left{this->target_rotation - this->get_rotation().y}; float const step(gd::Math::sign(angle_left) * delta * this->TURN_SPEED); if(gd::Math::abs(angle_left) <= gd::Math::abs(step)) { this->rotate_y(angle_left); this->at_target_angle = true; } else { this->rotate_y(step); this->at_target_angle = false; } } void Enemy::update() { if(this->can_see_player) this->last_known_player_position = this->player->get_global_position(); if(this->current_action_fn != nullptr) this->current_action_fn = (ActionFn)(this->*current_action_fn)(); } Enemy::ActionFn Enemy::wait_line_of_sight() { if(this->can_see_player) return (ActionFn)&Enemy::take_aim; else return (ActionFn)&Enemy::wait_line_of_sight; } Enemy::ActionFn Enemy::take_aim() { this->target_rotation = this->get_global_basis().get_column(2).signed_angle_to(this->last_known_player_position - this->get_global_position(), {0.f, 1.f, 0.f}); this->target_rotation -= gd::Math::sign(this->target_rotation) * this->MISS_ANGLE; this->target_rotation += this->get_global_rotation().y; this->anim_tree->set_aim_weapon(true); if(this->anim_tree->get_current_state().begins_with("Aim") && this->at_target_angle) return (ActionFn)&Enemy::fire; else return(ActionFn)&Enemy::take_aim; } Enemy::ActionFn Enemy::fire() { this->anim_tree->set_fire_weapon(); this->missed_shots = (this->missed_shots + 1) % (this->SHOTS_BEFORE_HIT + 1); return (ActionFn)&Enemy::wait_end_of_shot; } Enemy::ActionFn Enemy::wait_end_of_shot() { float const target_diff{this->get_global_basis().get_column(2).signed_angle_to(this->last_known_player_position - this->get_global_position(), {0.f, 1.f, 0.f})}; this->target_rotation = this->get_global_rotation().y + target_diff; if(this->missed_shots < this->SHOTS_BEFORE_HIT) this->target_rotation -= gd::Math::sign(target_diff) * this->MISS_ANGLE; else if(this->missed_shots >= this->SHOTS_BEFORE_HIT) this->target_rotation -= gd::Math::sign(target_diff) * this->HIT_ANGLE; if(this->at_target_angle && !this->anim_tree->get_current_state().begins_with("Fire") && !this->anim_tree->get_fire_weapon()) return (ActionFn)&Enemy::fire; else return (ActionFn)&Enemy::wait_end_of_shot; } void Enemy::_physics_process(double delta [[maybe_unused]]) { this->update_can_see_player(); gd::Basis const basis{this->get_global_basis()}; gd::Vector3 const motion{this->anim_tree->get_root_motion_position()}; this->set_velocity({ basis.get_column(0) * motion.x + basis.get_column(1) * motion.y + basis.get_column(2) * motion.z });\ this->move_and_slide(); } void Enemy::damage() { this->anim_tree->death_animation(); this->set_collision_mask(0x0); this->set_collision_layer(0x0); this->set_process(false); this->set_physics_process(false); } void Enemy::notice_player(Player *player) { this->player = player; this->current_action_fn = (ActionFn)&Enemy::wait_line_of_sight; } void Enemy::update_can_see_player() { if(this->player == nullptr) return; gd::Vector3 const origin{this->get_global_position() + gd::Vector3{0.f, 1.8f, 0.f}}; gd::Vector3 const target{this->player->get_global_position() + gd::Vector3{0.f, 1.8f, 0.f}}; float const dot{(target - origin).normalized().dot(this->get_global_basis().get_column(2))}; if(dot <= 0.2f && target.distance_to(origin) > 4.f) { this->can_see_player = false; } else { gd::PhysicsDirectSpaceState3D *space{this->get_world_3d()->get_direct_space_state()}; gd::Ref query{gd::PhysicsRayQueryParameters3D::create(origin, target)}; gd::Dictionary dict{space->intersect_ray(query)}; this->can_see_player = (dict.is_empty() || gd::Object::cast_to(dict["collider"]) == this->player); } } void Enemy::set_update_interval(float time) { this->update_interval = time; } float Enemy::get_update_interval() const { return this->update_interval; }