Files
shinespark/src/fighter/state/jump.rs
T
EIREXE 9dfae1de77
CI / build-and-test (push) Successful in 1m44s
CI / production-build (push) Successful in 12m17s
Implement land from below, fix attacks not lifting fighters off the ground (#96)
Reviewed-on: #96
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
2026-10-14 18:33:17 +00:00

232 lines
8.0 KiB
Rust

use crate::{
fighter::{
animation::AnimKind, state::{
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext, UpdateContext, air::{self, AirborneStateCommon}, ground::{self}, ground_attack,
},
}, input::{DigitalAction, FighterCommands},
};
#[derive(Debug, Hash, Clone, Copy)]
pub enum JumpType {
ShortHop,
LongJump,
DoubleJump,
}
#[derive(Debug, Clone, Hash)]
pub enum JumpStateInitiator {
Tap,
Button,
}
#[derive(Debug, Clone, Hash)]
pub struct JumpState {
pub jump_type: JumpType,
pub airborne_state_common: AirborneStateCommon,
pub initiator: JumpStateInitiator,
}
#[derive(Debug, Clone, Hash)]
pub struct JumpSquatState {
pub duration_counter: u32,
pub initiator: JumpStateInitiator,
}
impl JumpSquatState {
pub fn create(initiator: JumpStateInitiator) -> Self {
Self {
duration_counter: 0,
initiator,
}
}
}
impl FighterStateImpl for JumpSquatState {
const NAME: &'static str = "JumpSquat";
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
if let Some(state) = ground_attack::check_interrupt(state_context) {
return Some(state);
}
// Special handling for wavedash from jump squat
if state_context.input.has_command(FighterCommands::Shield) {
Some(ground::perform_wavedash(state_context.input, state_context.game_settings))
} else if self.duration_counter >= state_context.fighter_attributes.jumpsquat_duration {
let tap_jump_held = matches!(self.initiator, JumpStateInitiator::Tap)
&& state_context.input.get_last_frame().movement.y
> state_context
.game_settings
.input_common
.smash_input_axis_threshold;
let jump_type = if tap_jump_held
|| state_context
.input
.get_last_frame()
.is_held(DigitalAction::Jump)
{
JumpType::LongJump
} else {
JumpType::ShortHop
};
Some(FighterState::Jump(JumpState {
jump_type,
airborne_state_common: AirborneStateCommon::default(),
initiator: self.initiator.clone(),
}))
} else {
None
}
}
fn update(&mut self, state_context: &mut UpdateContext) {
self.duration_counter += 1;
ground::update_friction_motion(state_context);
}
fn check_collision_interrupt(
&mut self,
state_context: &mut CollisionContext,
) -> Option<FighterState> {
ground::collision_interrupt(state_context, false)
}
fn on_enter(&mut self, state_context: &mut EnterContext) {
state_context.play_animation(AnimKind::JumpSquat, false);
state_context.input.enable_hold_all(10);
state_context.input.clear_command(FighterCommands::Jump);
state_context.input.clear_command(FighterCommands::TapJump);
}
fn on_exit(&mut self, state_context: &mut crate::fighter::state::ExitContext) {
state_context.input.disable_hold_all();
// We clear tap jump here, it may have weird interactions with air dodging up
state_context.input.clear_command(FighterCommands::TapJump);
}
fn is_visually_grounded<'a>(&'a self) -> bool {
true
}
}
impl FighterStateImpl for JumpState {
const NAME: &'static str = "Jump";
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
air::air_interrupts_common(state_context, &self.airborne_state_common)
}
fn update(&mut self, state_context: &mut UpdateContext) {
air::update_standard_motion(state_context, &mut self.airborne_state_common);
}
fn on_enter(&mut self, state_context: &mut EnterContext) {
if let JumpType::DoubleJump = self.jump_type {
state_context.input.clear_command(FighterCommands::Jump);
}
let movement_stick_x = state_context.input.get_last_frame().movement.x;
match self.jump_type {
JumpType::ShortHop | JumpType::LongJump => {
if movement_stick_x.is_zero()
|| movement_stick_x.signum() == state_context.facing_direction.to_sign()
{
state_context.play_animation(AnimKind::JumpForward, false);
} else {
state_context.play_animation(AnimKind::JumpBack, false);
}
}
JumpType::DoubleJump => {
if movement_stick_x.is_zero()
|| movement_stick_x.signum() == state_context.facing_direction.to_sign()
{
state_context.play_animation(AnimKind::DoubleJumpForward, false);
} else {
state_context.play_animation(AnimKind::DoubleJumpBack, false);
}
}
}
let vertical_vel = match self.jump_type {
JumpType::ShortHop => state_context.fighter_attributes.short_hop_vertical_velocity,
JumpType::LongJump => state_context.fighter_attributes.full_jump_vertical_velocity,
JumpType::DoubleJump => {
state_context.fighter_attributes.full_jump_vertical_velocity
* state_context.fighter_attributes.air_jump_multiplier
}
};
match self.jump_type {
JumpType::ShortHop | JumpType::LongJump => {
state_context.velocity.x +=
state_context.fighter_attributes.jump_horizontal_velocity * movement_stick_x;
}
JumpType::DoubleJump => {
state_context.velocity.x = state_context
.fighter_attributes
.air_jump_horizontal_velocity
* movement_stick_x;
}
};
state_context.velocity.y = vertical_vel;
}
fn check_collision_interrupt(
&mut self,
state_context: &mut CollisionContext,
) -> Option<FighterState> {
air::collision_interrupt(
state_context,
crate::fighter::state::land::LandingType::Normal,
crate::fighter::collision::AirCollisionOptions {
can_pass_through_soft_platforms: true,
..Default::default()
},
)
}
fn is_visually_grounded<'a>(&'a self) -> bool {
false
}
fn surface_policy(&self) -> super::SurfacePolicy {
use crate::fighter::surface::EcbLockPolicy;
match self.jump_type {
JumpType::DoubleJump => super::SurfacePolicy::Airborne(EcbLockPolicy::Restart),
JumpType::ShortHop | JumpType::LongJump => {
super::SurfacePolicy::Airborne(EcbLockPolicy::StartOnDetach)
}
}
}
}
pub fn check_input(state_context: &InterruptContext) -> Option<FighterState> {
if state_context.input.has_command(FighterCommands::Jump) {
Some(FighterState::JumpSquat(JumpSquatState::create(
JumpStateInitiator::Button,
)))
} else if state_context.input.has_command(FighterCommands::TapJump) {
Some(FighterState::JumpSquat(JumpSquatState::create(
JumpStateInitiator::Tap,
)))
} else {
None
}
}
pub fn check_input_double_jump(state_context: &InterruptContext) -> Option<FighterState> {
if state_context.input.has_command(FighterCommands::Jump) {
Some(FighterState::Jump(JumpState {
jump_type: JumpType::DoubleJump,
airborne_state_common: AirborneStateCommon::default(),
initiator: JumpStateInitiator::Button,
}))
} else if state_context.input.has_command(FighterCommands::TapJump) {
Some(FighterState::Jump(JumpState {
jump_type: JumpType::DoubleJump,
airborne_state_common: AirborneStateCommon::default(),
initiator: JumpStateInitiator::Tap,
}))
} else {
None
}
}