#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; this->drone_sound = this->get_node("%DroneSound"); if(this->has_node("%DebugLabel")) { this->debug_label = this->get_node("%DebugLabel"); } this->agent->connect("velocity_computed", callable_mp(this, &Enemy::_on_velocity_calculated)); } void Enemy::_process(double delta) { this->set_current_state_name(this->current_state_name); float const angle_left{gd::Math::wrapf(this->target_rotation - this->get_rotation().y, -M_2_PIf, M_2_PIf)}; float const step(gd::Math::sign(angle_left) * delta * (this->anim_tree->get_current_state().begins_with("Run") ? this->TURN_SPEED : this->AIM_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::_on_velocity_calculated(gd::Vector3 velocity) { if(this->current_action_fn == (ActionFn)&Enemy::chase_player && !this->agent->is_navigation_finished()) this->target_rotation = gd::Vector3{0.f, 0.f, 1.f}.signed_angle_to(velocity, {0.f, 1.f, 0.f}); } void Enemy::update() { if(this->can_see_player) { this->last_known_player_position = this->player->get_global_position(); this->last_known_player_rotation = -this->player->get_global_rotation().y; this->shots_fired = 0; } if(this->current_action_fn != nullptr) this->current_action_fn = (ActionFn)(this->*current_action_fn)(); } Enemy::ActionFn Enemy::wait_line_of_sight() { this->set_current_state_name("Guard"); if(this->get_global_position().distance_squared_to(this->player->get_global_position()) < this->STAB_RANGE * this->STAB_RANGE) return (ActionFn)&Enemy::stab; else if(this->can_see_player) return (ActionFn)&Enemy::take_aim; else return (ActionFn)&Enemy::wait_line_of_sight; } Enemy::ActionFn Enemy::take_aim() { this->set_current_state_name("Aim"); this->target_rotation = gd::Vector3{0.f, 0.f, 1.f}.signed_angle_to(this->last_known_player_position - this->aim_offset_position(), {0.f, 1.f, 0.f}); 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->set_current_state_name("Shoot (fire)"); ++this->shots_fired; this->target_rotation = gd::Vector3{0.f, 0.f, 1.f}.signed_angle_to(this->last_known_player_position - this->aim_offset_position(), {0.f, 1.f, 0.f}); this->anim_tree->set_fire_weapon(); return (ActionFn)&Enemy::wait_end_of_shot; } Enemy::ActionFn Enemy::wait_end_of_shot() { this->set_current_state_name("Shoot (wait)"); this->target_rotation = gd::Vector3{0.f, 0.f, 1.f}.signed_angle_to(this->last_known_player_position - this->aim_offset_position(), {0.f, 1.f, 0.f}); if(this->at_target_angle && !this->anim_tree->get_current_state().begins_with("Fire") && !this->anim_tree->get_fire_weapon()) return this->shots_fired < this->SHOTS_BEFORE_MOVE ? (ActionFn)&Enemy::fire : (ActionFn)&Enemy::chase_enter; return (ActionFn)&Enemy::wait_end_of_shot; } Enemy::ActionFn Enemy::stab() { this->set_current_state_name("Stab (start)"); this->anim_tree->set_aim_weapon(false); if(this->anim_tree->get_current_state().begins_with("Aim")) return (ActionFn)&Enemy::stab; this->anim_tree->set_stab(); float const target_diff{this->get_global_basis().get_column(2).signed_angle_to(this->last_known_player_position - this->aim_offset_position(), {0.f, 1.f, 0.f})}; this->target_rotation = this->get_global_rotation().y + target_diff; return (ActionFn)&Enemy::wait_end_of_stab; } Enemy::ActionFn Enemy::wait_end_of_stab() { this->set_current_state_name("Stab (wait)"); float const target_diff{this->get_global_basis().get_column(2).signed_angle_to(this->last_known_player_position - this->aim_offset_position(), {0.f, 1.f, 0.f})}; this->target_rotation = this->get_global_rotation().y + target_diff; if(this->at_target_angle && !this->anim_tree->get_current_state().begins_with("Stab") && !this->anim_tree->get_fire_weapon()) return this->is_in_stab_range() ? (ActionFn)&Enemy::chase_enter : (ActionFn)&Enemy::stab; return (ActionFn)&Enemy::wait_end_of_stab; } Enemy::ActionFn Enemy::chase_enter() { this->set_current_state_name("Chase (plot)"); this->anim_tree->set_aim_weapon(false); this->agent->set_target_position(this->last_known_player_position); this->agent->set_avoidance_priority(this->MOVING_NAV_PRIORITY); return (ActionFn)&Enemy::chase_player; } Enemy::ActionFn Enemy::chase_player() { this->set_current_state_name("Chase (run)"); if(this->can_see_player && this->is_in_stab_range()) { this->anim_tree->set_lock_running(false); return (ActionFn)&Enemy::stab; } else if(this->agent->is_navigation_finished()) { this->target_rotation = this->last_known_player_rotation; this->anim_tree->set_lock_running(false); return (ActionFn)&Enemy::stop_running; } else { this->anim_tree->set_lock_running(true); return (ActionFn)&Enemy::chase_player; } } Enemy::ActionFn Enemy::stop_running() { this->set_current_state_name("Chase (stop)"); this->agent->set_target_position(this->get_global_position()); this->agent->set_avoidance_priority(this->STATIONARY_NAV_PRIORITY); return this->anim_tree->get_current_state().begins_with("Run") ? (ActionFn)&Enemy::stop_running : (ActionFn)&Enemy::wait_line_of_sight; } void Enemy::_physics_process(double delta) { gd::Basis const basis{this->get_global_basis()}; gd::Vector3 const motion{this->anim_tree->get_root_motion_position()}; this->set_velocity(gd::Vector3{ basis.get_column(0) * motion.x + basis.get_column(1) * motion.y + basis.get_column(2) * motion.z } / delta); this->move_and_slide(); this->update_can_see_player(); } 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); this->drone_sound->stop(); this->current_action_fn = nullptr; this->set_current_state_name("None"); } 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(this->current_action_fn != (ActionFn)&Enemy::chase_player && 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; } bool Enemy::is_in_stab_range() const { return this->player->get_global_position().distance_squared_to(this->get_global_position()) < this->STAB_RANGE*this->STAB_RANGE; } void Enemy::set_current_state_name(gd::String name) { this->debug_label->set_text(gd::vformat("Action: %s\nState: %s", name, this->anim_tree->get_current_state())); this->current_state_name = name; } gd::Vector3 Enemy::aim_offset_position() const { gd::Basis const basis{this->get_global_basis()}; return this->get_global_position() + basis.get_column(0) * this->AIM_OFFSET; }