#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)); } void Enemy::update() { if(this->current_action_fn != nullptr) this->current_action_fn = (ActionFn)(this->*current_action_fn)(); } void Enemy::chase_enter() { if(this->player != nullptr) this->agent->set_target_position(this->player->get_global_position()); } void Enemy::chase() { bool const at_end{!this->agent->is_navigation_finished()}; this->anim_tree->set_lock_running(at_end); this->anim_tree->set_aim_weapon(!at_end); if(at_end) { gd::Vector3 const global_pos{this->get_global_position()}; gd::Vector3 const target{global_pos * 2 - this->agent->get_next_path_position()}; this->look_at({target.x, global_pos.y, target.z}); } else if(this->get_global_position().distance_to(this->player->get_global_position()) >= this->agent->get_target_desired_distance()) this->chase_enter(); // repath } 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->anim_tree->set_aim_weapon(true); if(this->anim_tree->get_current_state().begins_with("Aim")) return (ActionFn)&Enemy::hit; else return(ActionFn)&Enemy::take_aim; } Enemy::ActionFn Enemy::miss() { if(this->can_see_player) { gd::Basis const basis{this->get_global_basis()}; this->look_at(this->get_global_position() * 2 - this->player->get_global_position() + basis.get_column(0) * 0.6f); this->anim_tree->set_aim_weapon(true); this->anim_tree->set_fire_weapon(); ++this->missed_shots; return (ActionFn)&Enemy::wait_end_of_shot; } else { this->chase(); } return (ActionFn)&Enemy::miss; } Enemy::ActionFn Enemy::hit() { if(this->can_see_player) { this->look_at(this->get_global_position() * 2 - this->player->get_global_position()); this->anim_tree->set_aim_weapon(true); this->anim_tree->set_fire_weapon(); this->missed_shots = 0; return (ActionFn)&Enemy::wait_end_of_shot; } else { this->chase(); } return (ActionFn)&Enemy::hit; } Enemy::ActionFn Enemy::wait_end_of_shot() { if(this->anim_tree->get_current_state().begins_with("Fire") || this->anim_tree->get_fire_weapon()) // last shot still going return (ActionFn)&Enemy::wait_end_of_shot; else return this->missed_shots >= SHOTS_BEFORE_HIT ? (ActionFn)&Enemy::hit : (ActionFn)&Enemy::miss; } void Enemy::_physics_process(double delta) { 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); } 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 origin{this->get_global_position() + gd::Vector3{0.f, 1.8f, 0.f}}; gd::PhysicsDirectSpaceState3D *space{this->get_world_3d()->get_direct_space_state()}; gd::Ref query{gd::PhysicsRayQueryParameters3D::create(origin, this->player->get_global_position() + gd::Vector3{0.f, 1.8f, 0.f})}; 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; }