2024-12-06 16:12:06 +00:00
|
|
|
#include "enemy.hpp"
|
2024-12-09 22:55:11 +00:00
|
|
|
#include "utils/godot_macros.hpp"
|
|
|
|
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
|
|
|
|
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
|
|
|
|
#include <godot_cpp/classes/timer.hpp>
|
|
|
|
#include <godot_cpp/classes/world3d.hpp>
|
|
|
|
#include <godot_cpp/variant/utility_functions.hpp>
|
2024-12-06 16:12:06 +00:00
|
|
|
|
2024-12-09 22:55:11 +00:00
|
|
|
void Enemy::_bind_methods() {
|
|
|
|
#define CLASSNAME Enemy
|
|
|
|
GDFUNCTION_ARGS(notice_player, "player");
|
|
|
|
GDPROPERTY(update_interval, gd::Variant::FLOAT);
|
|
|
|
}
|
2024-12-06 16:12:06 +00:00
|
|
|
|
|
|
|
void Enemy::_ready() {
|
|
|
|
this->anim_tree = this->get_node<PlayerAnimTree>("CharacterModel/AnimationTree");
|
2024-12-09 22:55:11 +00:00
|
|
|
this->agent = this->get_node<gd::NavigationAgent3D>("%NavigationAgent3D");
|
|
|
|
gd::Timer *timer{memnew(gd::Timer)};
|
|
|
|
this->add_child(timer);
|
|
|
|
timer->start(this->update_interval);
|
|
|
|
timer->connect("timeout", callable_mp(this, &Enemy::update));
|
2024-12-11 17:39:41 +00:00
|
|
|
this->target_rotation = this->get_rotation().y;
|
2024-12-12 19:46:21 +00:00
|
|
|
this->drone_sound = this->get_node<gd::AudioStreamPlayer3D>("%DroneSound");
|
2024-12-19 22:09:58 +00:00
|
|
|
if(this->has_node("%DebugLabel")) {
|
|
|
|
this->debug_label = this->get_node<gd::Label3D>("%DebugLabel");
|
|
|
|
}
|
2024-12-11 17:39:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Enemy::_process(double delta) {
|
2024-12-19 22:09:58 +00:00
|
|
|
this->set_current_state_name(this->current_state_name);
|
2024-12-11 17:39:41 +00:00
|
|
|
float const angle_left{this->target_rotation - this->get_rotation().y};
|
2024-12-19 22:09:58 +00:00
|
|
|
float const step(gd::Math::sign(angle_left) * delta * (this->anim_tree->get_current_state().begins_with("Run") ? this->TURN_SPEED : this->AIM_SPEED));
|
2024-12-11 17:39:41 +00:00
|
|
|
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;
|
|
|
|
}
|
2024-12-19 22:09:58 +00:00
|
|
|
if(this->current_action_fn == (ActionFn)&Enemy::chase_player)
|
|
|
|
this->target_rotation = gd::Vector3{0.f, 0.f, 1.f}.signed_angle_to(this->agent->get_next_path_position() - this->get_global_position(), {0.f, 1.f, 0.f});
|
2024-12-09 22:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Enemy::update() {
|
2024-12-19 22:09:58 +00:00
|
|
|
if(this->can_see_player) {
|
2024-12-11 17:39:41 +00:00
|
|
|
this->last_known_player_position = this->player->get_global_position();
|
2024-12-19 22:09:58 +00:00
|
|
|
this->last_known_player_rotation = -this->player->get_global_rotation().y;
|
|
|
|
this->shots_fired = 0;
|
|
|
|
}
|
2024-12-09 22:55:11 +00:00
|
|
|
if(this->current_action_fn != nullptr)
|
|
|
|
this->current_action_fn = (ActionFn)(this->*current_action_fn)();
|
|
|
|
}
|
|
|
|
|
2024-12-10 19:05:13 +00:00
|
|
|
Enemy::ActionFn Enemy::wait_line_of_sight() {
|
2024-12-19 22:09:58 +00:00
|
|
|
this->set_current_state_name("Guard");
|
2024-12-10 19:05:13 +00:00
|
|
|
if(this->can_see_player)
|
|
|
|
return (ActionFn)&Enemy::take_aim;
|
|
|
|
else
|
|
|
|
return (ActionFn)&Enemy::wait_line_of_sight;
|
|
|
|
}
|
|
|
|
|
|
|
|
Enemy::ActionFn Enemy::take_aim() {
|
2024-12-19 22:09:58 +00:00
|
|
|
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});
|
2024-12-09 22:55:11 +00:00
|
|
|
this->anim_tree->set_aim_weapon(true);
|
2024-12-19 22:09:58 +00:00
|
|
|
|
2024-12-11 17:39:41 +00:00
|
|
|
if(this->anim_tree->get_current_state().begins_with("Aim") && this->at_target_angle)
|
|
|
|
return (ActionFn)&Enemy::fire;
|
2024-12-10 19:05:13 +00:00
|
|
|
else
|
|
|
|
return(ActionFn)&Enemy::take_aim;
|
2024-12-09 22:55:11 +00:00
|
|
|
}
|
|
|
|
|
2024-12-11 17:39:41 +00:00
|
|
|
Enemy::ActionFn Enemy::fire() {
|
2024-12-19 22:09:58 +00:00
|
|
|
this->set_current_state_name("Shoot (fire)");
|
|
|
|
++this->shots_fired;
|
2024-12-11 17:39:41 +00:00
|
|
|
this->anim_tree->set_fire_weapon();
|
|
|
|
return (ActionFn)&Enemy::wait_end_of_shot;
|
2024-12-09 22:55:11 +00:00
|
|
|
}
|
|
|
|
|
2024-12-10 19:05:13 +00:00
|
|
|
Enemy::ActionFn Enemy::wait_end_of_shot() {
|
2024-12-19 22:09:58 +00:00
|
|
|
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();
|
2024-12-17 13:13:15 +00:00
|
|
|
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})};
|
2024-12-11 17:39:41 +00:00
|
|
|
this->target_rotation = this->get_global_rotation().y + target_diff;
|
2024-12-19 22:09:58 +00:00
|
|
|
return (ActionFn)&Enemy::wait_end_of_stab;
|
|
|
|
}
|
2024-12-11 17:39:41 +00:00
|
|
|
|
2024-12-19 22:09:58 +00:00
|
|
|
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;
|
2024-12-10 19:05:13 +00:00
|
|
|
}
|
|
|
|
|
2024-12-19 22:09:58 +00:00
|
|
|
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);
|
|
|
|
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->is_on_wall()) {
|
|
|
|
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());
|
|
|
|
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) {
|
2024-12-09 22:55:11 +00:00
|
|
|
gd::Basis const basis{this->get_global_basis()};
|
|
|
|
gd::Vector3 const motion{this->anim_tree->get_root_motion_position()};
|
2024-12-19 22:09:58 +00:00
|
|
|
this->set_velocity(gd::Vector3{
|
2024-12-09 22:55:11 +00:00
|
|
|
basis.get_column(0) * motion.x +
|
|
|
|
basis.get_column(1) * motion.y +
|
|
|
|
basis.get_column(2) * motion.z
|
2024-12-19 22:09:58 +00:00
|
|
|
} / delta);
|
2024-12-09 22:55:11 +00:00
|
|
|
this->move_and_slide();
|
2024-12-19 22:09:58 +00:00
|
|
|
|
|
|
|
this->update_can_see_player();
|
2024-12-06 16:12:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Enemy::damage() {
|
|
|
|
this->anim_tree->death_animation();
|
|
|
|
this->set_collision_mask(0x0);
|
|
|
|
this->set_collision_layer(0x0);
|
2024-12-11 21:41:29 +00:00
|
|
|
this->set_process(false);
|
|
|
|
this->set_physics_process(false);
|
2024-12-12 19:46:21 +00:00
|
|
|
this->drone_sound->stop();
|
2024-12-19 22:09:58 +00:00
|
|
|
this->current_action_fn = nullptr;
|
|
|
|
this->set_current_state_name("None");
|
2024-12-06 16:12:06 +00:00
|
|
|
}
|
2024-12-09 22:55:11 +00:00
|
|
|
|
|
|
|
void Enemy::notice_player(Player *player) {
|
|
|
|
this->player = player;
|
2024-12-10 19:05:13 +00:00
|
|
|
this->current_action_fn = (ActionFn)&Enemy::wait_line_of_sight;
|
2024-12-09 22:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Enemy::update_can_see_player() {
|
|
|
|
if(this->player == nullptr)
|
|
|
|
return;
|
2024-12-11 17:39:41 +00:00
|
|
|
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<gd::PhysicsRayQueryParameters3D> query{gd::PhysicsRayQueryParameters3D::create(origin, target)};
|
|
|
|
gd::Dictionary dict{space->intersect_ray(query)};
|
|
|
|
this->can_see_player = (dict.is_empty() || gd::Object::cast_to<Node>(dict["collider"]) == this->player);
|
|
|
|
}
|
2024-12-09 22:55:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Enemy::set_update_interval(float time) {
|
|
|
|
this->update_interval = time;
|
|
|
|
}
|
|
|
|
|
|
|
|
float Enemy::get_update_interval() const {
|
|
|
|
return this->update_interval;
|
|
|
|
}
|
2024-12-17 13:13:15 +00:00
|
|
|
|
2024-12-19 22:09:58 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-12-17 13:13:15 +00:00
|
|
|
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;
|
|
|
|
}
|