175 lines
5.7 KiB
Rust
175 lines
5.7 KiB
Rust
use bevy::{asset::Assets, log::error};
|
|
|
|
use crate::{
|
|
fighter::{
|
|
attack::{AttackKind, FighterAttackScriptAssets, update_active_attack},
|
|
state::{
|
|
CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl,
|
|
InterruptContext, UpdateContext, ground, wait::WaitState,
|
|
},
|
|
},
|
|
scripting::FighterAttackScript,
|
|
};
|
|
|
|
#[derive(Clone, Debug, Hash)]
|
|
pub struct GroundAttackState {
|
|
pub attack_kind: AttackKind,
|
|
pub use_root_motion: bool,
|
|
}
|
|
|
|
impl FighterStateImpl for GroundAttackState {
|
|
const NAME: &'static str = "GroundAttack";
|
|
|
|
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
|
// Allow interrupting dash attack only into an up/down smash
|
|
if let AttackKind::DashAttack = self.attack_kind {
|
|
if state_context.get_current_animation_frame() < state_context.fighter_attributes.dash_attack_cancel_window {
|
|
if let Some(state) = check_interrupt(state_context) {
|
|
if let FighterState::GroundAttack(state) = state {
|
|
if matches!(state.attack_kind, AttackKind::UpSmash) || matches!(state.attack_kind, AttackKind::DownSmash) {
|
|
return Some(FighterState::GroundAttack(state));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if state_context.is_current_animation_finished() {
|
|
Some(FighterState::Wait(WaitState))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn update(&mut self, state_context: &mut UpdateContext) {
|
|
let animation_frame = state_context.get_current_animation_frame();
|
|
let script = state_context
|
|
.attack_scripts
|
|
.get(&state_context.attack_script_assets.scripts[&self.attack_kind]);
|
|
if let Some(script) = script {
|
|
update_active_attack(
|
|
script,
|
|
state_context
|
|
.hitboxes
|
|
.0
|
|
.as_mut()
|
|
.expect("ground attack must have active hitboxes"),
|
|
animation_frame,
|
|
);
|
|
|
|
// root motion
|
|
if script.use_root_motion {
|
|
state_context.velocity.0.x = state_context.animation_root_motion_delta.x;
|
|
ground::apply_grounded_motion(
|
|
state_context.velocity,
|
|
state_context.translation,
|
|
);
|
|
} else {
|
|
ground::update_friction_motion(state_context);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
|
state_context
|
|
.input
|
|
.consume_command(crate::input::FighterCommands::Attack);
|
|
state_context.play_animation(self.attack_kind.get_animation(), false);
|
|
let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone();
|
|
state_context.hitboxes.0 = Some(crate::fighter::FighterActiveAttack {
|
|
kind: self.attack_kind,
|
|
script: script.clone(),
|
|
active_hitboxes: Vec::new(),
|
|
solved_hitboxes: Vec::new(),
|
|
hit_groups: Vec::new(),
|
|
stalled: false,
|
|
});
|
|
}
|
|
|
|
fn on_exit(&mut self, state_context: &mut ExitContext) {
|
|
state_context.hitboxes.0 = None;
|
|
}
|
|
|
|
fn check_collision_interrupt(
|
|
&mut self,
|
|
state_context: &mut CollisionContext,
|
|
) -> Option<FighterState> {
|
|
ground::collision_interrupt(state_context, !self.use_root_motion)
|
|
}
|
|
|
|
fn is_visually_grounded<'a>(&'a self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
impl GroundAttackState {
|
|
fn create(
|
|
attack_kind: AttackKind,
|
|
attack_script_assets: &FighterAttackScriptAssets,
|
|
attack_scripts: &Assets<FighterAttackScript>,
|
|
) -> Result<Self, AirAttackStateCreateError> {
|
|
let asset_script = attack_script_assets.scripts.get(&attack_kind).ok_or(
|
|
AirAttackStateCreateError::AttackScriptAssetNotFound {
|
|
attack: attack_kind,
|
|
},
|
|
)?;
|
|
|
|
let script = attack_scripts.get(asset_script).ok_or(
|
|
AirAttackStateCreateError::AttackScriptNotFound {
|
|
attack: attack_kind,
|
|
},
|
|
)?;
|
|
|
|
Ok(Self {
|
|
attack_kind,
|
|
use_root_motion: script.use_root_motion
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
pub enum AirAttackStateCreateError {
|
|
#[error("Attack script: {attack:?} not found")]
|
|
AttackScriptNotFound { attack: AttackKind },
|
|
#[error("Attack script asset: {attack:?} not found")]
|
|
AttackScriptAssetNotFound { attack: AttackKind },
|
|
}
|
|
|
|
pub fn check_interrupt_shared(state_context: &InterruptContext, attack_kind: AttackKind) -> Option<FighterState> {
|
|
let state = GroundAttackState::create(attack_kind, state_context.attack_script_assets, state_context.attack_scripts);
|
|
|
|
match state {
|
|
Ok(state) => Some(FighterState::GroundAttack(state)),
|
|
Err(create_error) => {
|
|
error!("{}", create_error);
|
|
None
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn check_interrupt(state_context: &InterruptContext) -> Option<FighterState> {
|
|
if state_context
|
|
.input
|
|
.has_command(crate::input::FighterCommands::Attack)
|
|
{
|
|
let attack_kind = state_context
|
|
.input
|
|
.get_attack_command_type()
|
|
.to_attack_kind_grounded();
|
|
check_interrupt_shared(state_context, attack_kind)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn check_interrupt_dash_attack(state_context: &InterruptContext) -> Option<FighterState> {
|
|
if state_context
|
|
.input
|
|
.has_command(crate::input::FighterCommands::Attack)
|
|
{
|
|
check_interrupt_shared(state_context, AttackKind::DashAttack)
|
|
} else {
|
|
None
|
|
}
|
|
}
|