#include "enemy_wretched.h" #include "scene/animation/animation_player.h" #include "wave_survival/macros.h" #include "wave_survival/npc_unit.h" #include "wave_survival/patrol_path.h" #include "wave_survival/player_body.h" #include "wave_survival/state_machine.h" void EnemyWretched::_bind_methods() { BIND_PROPERTY(Variant::FLOAT, chase_speed); } void EnemyWretched::on_child_entered(Node *node) { if (StateMachine * fsm{ cast_to(node) }) { this->fsm = fsm; } if (node->has_node(NodePath("AnimationPlayer"))) { this->anim = cast_to(node->get_node(NodePath("AnimationPlayer"))); } } void EnemyWretched::ready() { fsm->add_state(memnew(WretchedPatrolState)); fsm->add_state(memnew(WretchedChaseState)); fsm->add_state(memnew(WretchedAttackState)); } void EnemyWretched::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; } switch (what) { default: return; case NOTIFICATION_ENTER_TREE: connect("child_entered_tree", callable_mp(this, &self_type::on_child_entered)); return; case NOTIFICATION_READY: ready(); return; } } void EnemyWretched::set_chase_speed(float speed) { this->chase_speed = speed; } float EnemyWretched::get_chase_speed() const { return this->chase_speed; } AnimationPlayer *EnemyWretched::get_anim() const { return this->anim; } void WretchedState::set_target(Node *node) { this->target = cast_to(node); } EnemyWretched *WretchedState::get_target() const { return this->target; } NpcUnit *WretchedState::get_unit() const { return this->target->get_unit(); } NavigationAgent3D *WretchedState::get_nav() const { return this->target->get_nav(); } AnimationPlayer *WretchedState::get_anim() const { return this->target->get_anim(); } void WretchedPatrolState::set_patrol_target(Vector3 path_point) { get_nav()->set_target_position(path_point + get_target()->get_unit_offset_3d()); } void WretchedPatrolState::on_velocity_calculated(Vector3 direction) { get_target()->set_movement_direction(Vector2{ direction.x, direction.z } / get_target()->get_movement_speed()); } void WretchedPatrolState::enter_state() { this->path = get_target()->get_unit()->get_patrol_path(); float const max_speed{ get_unit()->get_patrol_speed() }; get_target()->set_movement_speed(max_speed); get_nav()->set_max_speed(max_speed); if (this->path) { Vector3 const nav_target{ this->path->get_closest_point(get_target()->get_global_position(), &this->path_point) }; set_patrol_target(nav_target); } get_nav()->connect("velocity_computed", this->mp_on_velocity_calculated); } void WretchedPatrolState::process(double delta) { if (this->path) { if (get_nav()->is_navigation_finished()) { this->path_point += 1; set_patrol_target(this->path->point_at(this->path_point)); } Vector3 const direction{ get_target()->get_global_position().direction_to(get_nav()->get_next_path_position()).normalized() }; if (get_nav()->get_avoidance_enabled()) { get_nav()->set_velocity(direction); } else { on_velocity_calculated(direction); } } } void WretchedPatrolState::exit_state() { get_nav()->disconnect("velocity_computed", this->mp_on_velocity_calculated); } String WretchedPatrolState::get_next_state() const { if (get_target()->get_unit()->is_aware_of_player()) { return WretchedChaseState::get_class_static(); } return get_class(); } void WretchedChaseState::enter_state() { get_target()->set_movement_speed(get_target()->get_chase_speed()); get_nav()->set_max_speed(get_target()->get_chase_speed()); get_nav()->set_target_position(PlayerBody::get_singleton()->get_global_position()); get_anim()->play("ready"); // TODO: replace this with "run" } void WretchedChaseState::process(double delta) { // TODO: optimize this with some checks to avoid re-pathing every frame get_nav()->set_target_position(PlayerBody::get_singleton()->get_global_position()); Vector3 const direction{ get_target()->get_global_position().direction_to(get_nav()->get_next_path_position()) }; get_target()->set_movement_direction(Vector2{ direction.x, direction.z }.normalized()); } String WretchedChaseState::get_next_state() const { if (get_target()->get_global_position().distance_to(PlayerBody::get_singleton()->get_global_position()) < 2.f) { return WretchedAttackState::get_class_static(); } return get_class(); } void WretchedAttackState::enter_state() { get_anim()->play("attack"); } String WretchedAttackState::get_next_state() const { if (get_anim()->get_current_animation().is_empty()) { return WretchedChaseState::get_class_static(); } return get_class(); }