341 lines
10 KiB
C++
341 lines
10 KiB
C++
#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<AnimationTree>(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<InputEvent> 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<PlayerFallState>();
|
|
}
|
|
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<PlayerRunState>();
|
|
}
|
|
}
|
|
|
|
void PlayerIdleState::state_exited() {
|
|
set_process_unhandled_input(false);
|
|
set_process(false);
|
|
}
|
|
|
|
void PlayerIdleState::unhandled_input(Ref<InputEvent> 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<PlayerRunState>();
|
|
} 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<PlayerFallState>();
|
|
} else if (get_body()->get_transformed_movement_input().is_zero_approx()) {
|
|
switch_state<PlayerIdleState>();
|
|
} 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<InputEvent> 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<PlayerIdleState>();
|
|
}
|
|
}
|
|
|
|
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<PlayerFallState>();
|
|
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<PlayerIdleState>();
|
|
}
|
|
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<InputEvent> 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<PlayerIdleState>();
|
|
}
|
|
|
|
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<PlayerIdleState>();
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|