#include "player_states.h" #include "break_utopia/level_status.h" #include "break_utopia/player_camera.h" #include "core/object/object.h" #include "scene/animation/animation_tree.h" #include "scene/main/viewport.h" void PlayerState::_bind_methods() {} void PlayerState::ready() { this->anim = cast_to(get_node(NodePath("%AnimationTree"))); this->anim_fsm = this->anim->get("parameters/playback"); } void PlayerState::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; } switch (what) { default: return; case NOTIFICATION_READY: ready(); return; } } void PlayerState::apply_root_motion(double delta) { PlayerBody *body{ get_body() }; Basis basis{ body->get_global_basis() }; Vector3 root_motion{ this->anim->get_root_motion_position() }; Vector3 forward{ basis.get_column(2) }; Vector3 left{ basis.get_column(0) }; forward.y = left.y = 0; body->set_velocity((root_motion.x * left + root_motion.z * forward) / delta); } void PlayerState::turn_toward_input(float speed) { double angle{ get_body()->get_basis().get_column(2).signed_angle_to(get_body()->get_transformed_movement_input(), { 0.f, 1.f, 0.f }) }; double motion{ angle * speed }; if (Math::abs(motion) < Math::abs(angle)) { get_body()->rotate_y(motion); } else { get_body()->look_at(get_body()->get_global_position() - get_body()->get_transformed_movement_input()); } } void PlayerState::input_trigger_entry_actions(Ref const &what) { if (what->is_action_pressed("light_attack")) { switch_state("SwingWideRight"); } } void PlayerIdleState::_bind_methods() {} void PlayerIdleState::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; } switch (what) { default: return; case NOTIFICATION_PROCESS: get_body()->set_velocity(get_body()->get_velocity().move_toward(Vector3(), get_process_delta_time() * 100.f)); if (!get_body()->is_on_floor()) { switch_state(); } return; } } void PlayerIdleState::state_entered() { set_process_unhandled_input(true); set_process(true); if (!get_body()->get_transformed_movement_input().is_zero_approx()) { switch_state(); } } void PlayerIdleState::state_exited() { set_process_unhandled_input(false); set_process(false); } void PlayerIdleState::unhandled_input(Ref const &what) { if (what->is_action("move_left") || what->is_action("move_right") || what->is_action("move_forward") || what->is_action("move_backward")) { switch_state(); } else { input_trigger_entry_actions(what); } } void PlayerRunState::_bind_methods() { BIND_PROPERTY(Variant::FLOAT, rotation_speed); } void PlayerRunState::process(double delta) { apply_root_motion(delta); if (!get_body()->is_on_floor()) { switch_state(); } else if (get_body()->get_transformed_movement_input().is_zero_approx()) { switch_state(); } else { turn_toward_input(this->rotation_speed * delta); } } void PlayerRunState::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; } switch (what) { default: return; case NOTIFICATION_PROCESS: process(get_process_delta_time()); return; } } void PlayerRunState::unhandled_input(Ref const &what) { input_trigger_entry_actions(what); } void PlayerRunState::state_entered() { set_process(true); set_process_unhandled_input(true); get_anim_fsm()->travel("run"); } void PlayerRunState::state_exited() { set_process(false); set_process_unhandled_input(false); get_anim_fsm()->travel("idle"); } void PlayerFallState::_bind_methods() {} void PlayerFallState::process_physics(double delta) { Vector3 velocity{ get_body()->get_velocity() }; velocity.x *= 0.99; velocity.z *= 0.99; velocity += get_body()->get_gravity() * delta; get_body()->set_velocity(velocity); if (get_body()->is_on_floor()) { switch_state(); } } void PlayerFallState::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; } switch (what) { default: return; case NOTIFICATION_PHYSICS_PROCESS: process_physics(get_physics_process_delta_time()); return; } } void PlayerFallState::state_entered() { set_physics_process(true); get_anim_fsm()->travel("falling"); } void PlayerFallState::state_exited() { set_physics_process(false); get_anim_fsm()->travel("idle"); } void PlayerBasicAttackState::_bind_methods() { BIND_PROPERTY(Variant::STRING, animation_name); BIND_HPROPERTY(Variant::NODE_PATH, next_fast, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CharacterState"); BIND_HPROPERTY(Variant::NODE_PATH, next_slow, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CharacterState"); BIND_HPROPERTY(Variant::NODE_PATH, next_special_fast, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CharacterState"); BIND_HPROPERTY(Variant::NODE_PATH, next_special_slow, PROPERTY_HINT_NODE_PATH_VALID_TYPES, "CharacterState"); BIND_PROPERTY(Variant::FLOAT, trigger_slow_margin); BIND_PROPERTY(Variant::FLOAT, input_slow_margin); BIND_PROPERTY(Variant::FLOAT, trigger_fast_margin); BIND_HPROPERTY(Variant::OBJECT, hammer_model, PROPERTY_HINT_NODE_TYPE, "MeshInstance3D"); BIND_HPROPERTY(Variant::OBJECT, hammer_attack_outline, PROPERTY_HINT_RESOURCE_TYPE, "StandardMaterial3D"); } void PlayerBasicAttackState::process(double delta) { if (!get_body()->is_on_floor()) { get_body()->set_velocity(get_body()->get_velocity().clampf(-1.0, 1.0)); switch_state(); return; } apply_root_motion(delta); if (!this->animation_started) { if (get_anim_fsm()->get_current_node() == this->animation_name) { this->animation_started = true; this->anim_length = get_anim_fsm()->get_current_length(); if (this->hammer_model && this->hammer_attack_outline.is_valid()) { this->hammer_model->set_material_overlay(this->hammer_attack_outline); } } else { return; } } delta_since_start += delta; float const trigger_fast_progress{ this->anim_length - this->trigger_fast_margin }; float const trigger_slow_progress{ this->anim_length - this->trigger_slow_margin }; bool const slow_ready{ this->delta_since_start >= trigger_slow_progress }; bool const fast_ready{ this->delta_since_start >= trigger_fast_progress }; if (this->next_queued == AttackLightSlow && slow_ready && !this->next_slow.is_empty()) { switch_state(this->next_slow); } else if (this->next_queued == AttackSpecialSlow && slow_ready && !this->next_special_slow.is_empty()) { switch_state(this->next_special_slow); } else if (this->next_queued == AttackLightFast && fast_ready && !this->next_fast.is_empty()) { switch_state(this->next_fast); } else if (this->next_queued == AttackSpecialFast && fast_ready && !this->next_special_fast.is_empty()) { switch_state(this->next_special_fast); } else if (this->delta_since_start > trigger_fast_progress && get_anim_fsm()->get_current_node() != this->animation_name) { switch_state(); } if (this->hammer_model && this->hammer_model->get_material_overlay() == this->hammer_attack_outline && this->anim_length - this->input_slow_margin < this->delta_since_start) { this->hammer_model->set_material_overlay(nullptr); } } void PlayerBasicAttackState::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; } switch (what) { default: return; case NOTIFICATION_PROCESS: process(get_process_delta_time()); return; } } void PlayerBasicAttackState::unhandled_input(Ref const &what) { if (this->next_queued == None) { if (what->is_action_pressed("light_attack")) { float const wait_combo{ this->anim_length - this->input_slow_margin }; this->next_queued = wait_combo < this->delta_since_start ? AttackLightSlow : AttackLightFast; get_viewport()->set_input_as_handled(); } else if (what->is_action_pressed("special_attack")) { float const wait_combo{ this->anim_length - this->input_slow_margin }; this->next_queued = wait_combo < this->delta_since_start ? AttackSpecialSlow : AttackSpecialFast; get_viewport()->set_input_as_handled(); } } } void PlayerBasicAttackState::state_entered() { set_process(true); set_process_unhandled_input(true); this->next_queued = None; this->animation_started = false; this->delta_since_start = 0.0; get_anim_fsm()->start(this->animation_name); if (!get_body()->get_transformed_movement_input().is_zero_approx()) { turn_toward_input(0.25); } } void PlayerBasicAttackState::state_exited() { set_process(false); set_process_unhandled_input(false); if (this->hammer_model && this->hammer_model->get_material_overlay() == this->hammer_attack_outline) { this->hammer_model->set_material_overlay(nullptr); } } void PlayerSpecialAttackState::_bind_methods() { BIND_PROPERTY(Variant::FLOAT, meter_cost); } void PlayerSpecialAttackState::fail_activation() { switch_state(); } void PlayerSpecialAttackState::state_entered() { this->activated_succesful = LevelStatus::get_instance()->try_use_energy(this->meter_cost); if (this->activated_succesful) { super_type::state_entered(); } else { callable_mp(this, &self_type::fail_activation).call_deferred(); } } void PlayerSpecialAttackState::state_exited() { if (this->activated_succesful) { super_type::state_exited(); } } void PlayerHurtState::_bind_methods() { BIND_PROPERTY(Variant::STRING, animation_name); } void PlayerHurtState::process(double delta) { apply_root_motion(delta); if (!this->animation_started) { if (get_anim_fsm()->get_current_node() == this->animation_name) { this->animation_started = true; this->anim_length = get_anim_fsm()->get_current_length(); } else { return; } } this->delta_since_start += delta; if (this->delta_since_start > this->anim_length) { switch_state(); } } void PlayerHurtState::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; } switch (what) { default: return; case NOTIFICATION_PROCESS: process(get_process_delta_time()); break; } } void PlayerHurtState::state_entered() { set_process(true); get_anim_fsm()->travel(this->animation_name); this->animation_started = false; this->delta_since_start = 0.0; PlayerCamera::get_instance()->impact_effect(Color(1.0, 0.0, 0.0), 0.2, 1.0, 2.0); } void PlayerHurtState::state_exited() { set_process(false); }