initial work on hitbox and hurtbox collisions
This commit is contained in:
@@ -83,4 +83,6 @@ pub enum AnimKind {
|
|||||||
// Attacks
|
// Attacks
|
||||||
AttackJab1,
|
AttackJab1,
|
||||||
AttackUpTilt,
|
AttackUpTilt,
|
||||||
|
AttackForwardTilt,
|
||||||
|
AttackDownTilt,
|
||||||
}
|
}
|
||||||
|
|||||||
+116
-13
@@ -6,18 +6,11 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
Fighter, FighterAttributes, FighterFacingDirection, FighterTranslation,
|
Fighter, FighterAttributes, FighterFacingDirection, FighterHitboxes, FighterTranslation, animation::{AnimKind, FighterAnimationFrame}, baked_animation::{FighterBoneMatrices, FixedMat4}, hurtbox::{FixedAffineCapsule, FixedCapsule}, manifest::FighterManifest,
|
||||||
animation::AnimKind,
|
}, math::{
|
||||||
baked_animation::{FighterBoneMatrices, FixedMat4},
|
|
||||||
hurtbox::FixedAffineCapsule,
|
|
||||||
manifest::FighterManifest,
|
|
||||||
},
|
|
||||||
math::{
|
|
||||||
int::{FGi32, FGi32Ext},
|
int::{FGi32, FGi32Ext},
|
||||||
vec3::FGVec3,
|
vec3::FGVec3,
|
||||||
},
|
}, schedule::GameplaySet, scripting::FighterAttackScript,
|
||||||
schedule::GameplaySet,
|
|
||||||
scripting::FighterAttackScript,
|
|
||||||
};
|
};
|
||||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
pub struct FighterDamage(FGi32);
|
pub struct FighterDamage(FGi32);
|
||||||
@@ -77,17 +70,35 @@ impl AttackAngle {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Reflect, PartialEq, Eq, Hash, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Reflect, PartialEq, Eq, Hash, Clone, Debug)]
|
||||||
pub enum AttackKind {
|
pub enum AttackKind {
|
||||||
NAir,
|
NeutralAir,
|
||||||
|
ForwardAir,
|
||||||
|
BackAir,
|
||||||
|
DownAir,
|
||||||
|
UpAir,
|
||||||
Jab,
|
Jab,
|
||||||
UpTilt,
|
UpTilt,
|
||||||
|
DownTilt,
|
||||||
|
ForwardTilt,
|
||||||
|
UpSmash,
|
||||||
|
DownSmash,
|
||||||
|
ForwardSmash,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AttackKind {
|
impl AttackKind {
|
||||||
pub fn get_animation(&self) -> AnimKind {
|
pub fn get_animation(&self) -> AnimKind {
|
||||||
match self {
|
match self {
|
||||||
AttackKind::NAir => AnimKind::AttackJab1,
|
AttackKind::NeutralAir => AnimKind::AttackJab1,
|
||||||
AttackKind::Jab => AnimKind::AttackJab1,
|
AttackKind::Jab => AnimKind::AttackJab1,
|
||||||
AttackKind::UpTilt => AnimKind::AttackUpTilt,
|
AttackKind::UpTilt => AnimKind::AttackUpTilt,
|
||||||
|
AttackKind::ForwardAir => AnimKind::AttackUpTilt,
|
||||||
|
AttackKind::BackAir => AnimKind::AttackUpTilt,
|
||||||
|
AttackKind::DownAir => AnimKind::AttackUpTilt,
|
||||||
|
AttackKind::UpAir => AnimKind::AttackUpTilt,
|
||||||
|
AttackKind::DownTilt => AnimKind::AttackDownTilt,
|
||||||
|
AttackKind::ForwardTilt => AnimKind::AttackForwardTilt,
|
||||||
|
AttackKind::UpSmash => AnimKind::AttackUpTilt,
|
||||||
|
AttackKind::DownSmash => AnimKind::AttackUpTilt,
|
||||||
|
AttackKind::ForwardSmash => AnimKind::AttackUpTilt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,6 +192,69 @@ pub struct FighterSolvedHurtbox {
|
|||||||
pub capsule: FixedAffineCapsule,
|
pub capsule: FixedAffineCapsule,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Hash)]
|
||||||
|
pub struct FighterSolvedHitbox {
|
||||||
|
pub hitbox_idx: usize,
|
||||||
|
pub capsule: FixedCapsule,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Debug, Default, Hash)]
|
||||||
|
pub struct FighterSolvedHitboxes(pub Vec<FighterSolvedHitbox>);
|
||||||
|
|
||||||
|
pub fn solve_hitboxes(
|
||||||
|
mut query: Query<(
|
||||||
|
&FighterBoneMatrices,
|
||||||
|
&FighterTranslation,
|
||||||
|
&FighterFacingDirection,
|
||||||
|
&mut FighterHitboxes,
|
||||||
|
&FighterAnimationFrame
|
||||||
|
)>,
|
||||||
|
attack_scripts: Res<Assets<FighterAttackScript>>,
|
||||||
|
) {
|
||||||
|
for (matrices, translation, facing_direction, mut hitboxes, animation_frame) in &mut query {
|
||||||
|
hitboxes.active_hitboxes_solved.clear();
|
||||||
|
if let Some(ref attack_script) = hitboxes.attack_script {
|
||||||
|
let attack_script = attack_scripts.get(attack_script).expect("Attack script should be valid");
|
||||||
|
let fighter_trf = translation.get_3d_transform(facing_direction);
|
||||||
|
let active_hitboxes = hitboxes.active_hitboxes.clone();
|
||||||
|
for hitbox_idx in active_hitboxes {
|
||||||
|
let hitbox = attack_script.hitboxes.get(hitbox_idx).expect("Hitbox IDX should be valid");
|
||||||
|
let matrices = matrices.get(&hitbox.bone);
|
||||||
|
|
||||||
|
if let None = matrices {
|
||||||
|
warn!("Bone {} not found!", hitbox.bone);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (prev_bone_matrix, curr_bone_matrix) = matrices.unwrap();
|
||||||
|
|
||||||
|
let curr_hbox_matrix = fighter_trf.mul(curr_bone_matrix);
|
||||||
|
let curr_hbox_pos = curr_hbox_matrix.transform_point(hitbox.offset);
|
||||||
|
|
||||||
|
if hitbox.start_frame == animation_frame.frame {
|
||||||
|
// Sphere only
|
||||||
|
hitboxes.active_hitboxes_solved.push(
|
||||||
|
FixedCapsule {
|
||||||
|
start: curr_hbox_pos,
|
||||||
|
end: curr_hbox_pos,
|
||||||
|
radius: hitbox.radius
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let prev_hbox_matrix = fighter_trf.mul(prev_bone_matrix);
|
||||||
|
let prev_hbox_pos = prev_hbox_matrix.transform_point(hitbox.offset);
|
||||||
|
hitboxes.active_hitboxes_solved.push(
|
||||||
|
FixedCapsule {
|
||||||
|
start: prev_hbox_pos,
|
||||||
|
end: curr_hbox_pos,
|
||||||
|
radius: hitbox.radius
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct FighterAttackScriptAssets {
|
pub struct FighterAttackScriptAssets {
|
||||||
pub scripts: HashMap<AttackKind, Handle<FighterAttackScript>>,
|
pub scripts: HashMap<AttackKind, Handle<FighterAttackScript>>,
|
||||||
@@ -229,13 +303,42 @@ pub fn solve_hurtboxes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn intersect_attacks_with_hurtboxes(query: Query<(Entity, &FighterSolvedHurtboxes, &FighterHitboxes)>, attack_scripts: Res<Assets<FighterAttackScript>>) {
|
||||||
|
/*for (entity_hurting, _, hitboxes) in query {
|
||||||
|
if hitboxes.attack_script.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref attack_script) = hitboxes.attack_script {
|
||||||
|
let attack_script = attack_scripts.get(attack_script);
|
||||||
|
|
||||||
|
if attack_script.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let attack_script = attack_script.unwrap();
|
||||||
|
|
||||||
|
let capsules: Vec<(&AttackHitbox, FixedCapsule)> = hitboxes.active_hitboxes.iter().map(|idx| {
|
||||||
|
(attack_script.hitboxes.get(*idx).unwrap(), )
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
for (entity_to_hurt, hurtboxes, _) in query {
|
||||||
|
// Self collisions not allowed
|
||||||
|
if entity_to_hurt == entity_hurting {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FighterAttackPlugin;
|
pub struct FighterAttackPlugin;
|
||||||
|
|
||||||
impl Plugin for FighterAttackPlugin {
|
impl Plugin for FighterAttackPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
solve_hurtboxes
|
(solve_hurtboxes, solve_hitboxes).chain()
|
||||||
.after(crate::fighter::baked_animation::update_fighter_bone_matrices)
|
.after(crate::fighter::baked_animation::update_fighter_bone_matrices)
|
||||||
.in_set(GameplaySet::Animation),
|
.in_set(GameplaySet::Animation),
|
||||||
);
|
);
|
||||||
|
|||||||
+17
-72
@@ -19,6 +19,7 @@ use crate::fighter::{
|
|||||||
FighterTranslation, FighterVelocity,
|
FighterTranslation, FighterVelocity,
|
||||||
};
|
};
|
||||||
use crate::input::FighterInput;
|
use crate::input::FighterInput;
|
||||||
|
use crate::math::int::FGi32;
|
||||||
use crate::math::vec3::FGVec3;
|
use crate::math::vec3::FGVec3;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
use crate::scripting::FighterAttackScript;
|
use crate::scripting::FighterAttackScript;
|
||||||
@@ -250,8 +251,7 @@ fn capsule_wireframe() -> &'static CapsuleWireframe {
|
|||||||
let mut lower_previous = lower_ring[index];
|
let mut lower_previous = lower_ring[index];
|
||||||
let mut upper_previous = upper_ring[index];
|
let mut upper_previous = upper_ring[index];
|
||||||
for step in 1..HEMISPHERE_STEPS {
|
for step in 1..HEMISPHERE_STEPS {
|
||||||
let latitude = step as f32 * std::f32::consts::FRAC_PI_2
|
let latitude = step as f32 * std::f32::consts::FRAC_PI_2 / HEMISPHERE_STEPS as f32;
|
||||||
/ HEMISPHERE_STEPS as f32;
|
|
||||||
let (sin_latitude, cos_latitude) = latitude.sin_cos();
|
let (sin_latitude, cos_latitude) = latitude.sin_cos();
|
||||||
|
|
||||||
let lower = vertices.len();
|
let lower = vertices.len();
|
||||||
@@ -324,86 +324,31 @@ pub fn attack_debug(
|
|||||||
) {
|
) {
|
||||||
for (hitboxes, matrices, translation, facing_direction, frame) in query {
|
for (hitboxes, matrices, translation, facing_direction, frame) in query {
|
||||||
if let Some(script) = &hitboxes.attack_script {
|
if let Some(script) = &hitboxes.attack_script {
|
||||||
let script = attack_scripts
|
for solved_hitbox in hitboxes.active_hitboxes_solved.iter() {
|
||||||
.get(script)
|
let start = solved_hitbox.start.to_vec3();
|
||||||
.expect("Script ref should be valid");
|
let end = solved_hitbox.end.to_vec3();
|
||||||
let player_trf = translation.get_3d_transform(facing_direction);
|
let center = (solved_hitbox.start + solved_hitbox.end) * FGi32::lit("0.5");
|
||||||
|
let length = (start - end).length();
|
||||||
|
|
||||||
for i in hitboxes.active_hitboxes.iter() {
|
|
||||||
let offset = script.hitboxes[*i].offset;
|
|
||||||
let radius = script.hitboxes[*i].radius;
|
|
||||||
let bone = &script.hitboxes[*i].bone;
|
|
||||||
|
|
||||||
let trfs = matrices.get(bone);
|
|
||||||
|
|
||||||
if let Some((prev_trf, curr_trf)) = trfs {
|
|
||||||
let curr_trf = player_trf.mul(curr_trf);
|
|
||||||
let curr_pos = curr_trf.transform_point(offset);
|
|
||||||
|
|
||||||
let curr_pos_draw = Vec3::new(
|
|
||||||
curr_pos.x.to_num(),
|
|
||||||
curr_pos.y.to_num(),
|
|
||||||
curr_pos.z.to_num(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if script.hitboxes[*i].start_frame == frame.frame {
|
|
||||||
gizmos.sphere(
|
|
||||||
curr_pos_draw,
|
|
||||||
radius.to_num(),
|
|
||||||
bevy::color::palettes::css::RED,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let prev_trf = player_trf.mul(prev_trf);
|
|
||||||
|
|
||||||
let prev_pos = prev_trf.transform_point(offset);
|
|
||||||
|
|
||||||
let prev_pos_draw = Vec3::new(
|
|
||||||
prev_pos.x.to_num(),
|
|
||||||
prev_pos.y.to_num(),
|
|
||||||
prev_pos.z.to_num(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let dist = Dir3::new_and_length(curr_pos_draw - prev_pos_draw);
|
|
||||||
|
|
||||||
if let Ok((aim_dir, dist)) = dist {
|
|
||||||
let attack_capsule = Capsule3d {
|
let attack_capsule = Capsule3d {
|
||||||
half_length: dist * 0.5,
|
half_length: length * 0.5,
|
||||||
radius: radius.to_num(),
|
radius: solved_hitbox.radius.to_num(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let aim_dir = (end - start).normalize_or(Vec3::Y);
|
||||||
|
|
||||||
let mut capsule_draw_trf = Transform::IDENTITY;
|
let mut capsule_draw_trf = Transform::IDENTITY;
|
||||||
capsule_draw_trf.align(
|
capsule_draw_trf.translation = center.to_vec3();
|
||||||
Dir3::Y,
|
capsule_draw_trf.align(Dir3::Y, aim_dir, Dir3::X, aim_dir.any_orthonormal_vector());
|
||||||
aim_dir,
|
|
||||||
Dir3::X,
|
|
||||||
aim_dir.any_orthonormal_vector(),
|
|
||||||
);
|
|
||||||
capsule_draw_trf.translation = (prev_pos_draw + curr_pos_draw) * 0.5;
|
|
||||||
gizmos.primitive_3d(
|
gizmos.primitive_3d(
|
||||||
&attack_capsule,
|
&attack_capsule,
|
||||||
capsule_draw_trf.to_isometry(),
|
capsule_draw_trf.to_isometry(),
|
||||||
bevy::color::palettes::css::RED,
|
bevy::color::palettes::css::RED,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
gizmos.cross(
|
gizmos.cross(start, 1.0, bevy::color::palettes::css::GREEN);
|
||||||
curr_trf
|
gizmos.cross(end, 1.0, bevy::color::palettes::css::GREEN);
|
||||||
.transform_point(FGVec3::lit("0.0", "0.0", "0.0"))
|
|
||||||
.to_vec3(),
|
|
||||||
1.0,
|
|
||||||
bevy::color::palettes::css::BLUE,
|
|
||||||
);
|
|
||||||
gizmos.cross(
|
|
||||||
curr_trf.to_mat4().transform_point(Vec3::ZERO),
|
|
||||||
1.0,
|
|
||||||
bevy::color::palettes::css::HOT_PINK,
|
|
||||||
);
|
|
||||||
|
|
||||||
gizmos.axes(curr_trf.to_mat4(), 1.0);
|
|
||||||
} else {
|
|
||||||
warn!("Bone not found required by attack script: {}", bone);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub struct FixedAffineCapsule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An ordinary world-space capsule, used for swept attack volumes.
|
/// An ordinary world-space capsule, used for swept attack volumes.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
|
||||||
pub struct FixedCapsule {
|
pub struct FixedCapsule {
|
||||||
pub start: FGVec3,
|
pub start: FGVec3,
|
||||||
pub end: FGVec3,
|
pub end: FGVec3,
|
||||||
|
|||||||
+5
-11
@@ -30,17 +30,8 @@ pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
attack::{FighterAttackPlugin, FighterAttackScriptAssets, FighterSolvedHurtboxes},
|
attack::{FighterAttackPlugin, FighterAttackScriptAssets, FighterSolvedHurtboxes}, baked_animation::FighterBoneMatrices, hurtbox::FixedCapsule, manifest::FighterManifest, state::{FighterState, fall::FallState}, visual::FighterAnimations,
|
||||||
manifest::FighterManifest,
|
}, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, scripting::FighterAttackScript, stage::line::StageCollision,
|
||||||
state::{FighterState, fall::FallState},
|
|
||||||
visual::FighterAnimations,
|
|
||||||
},
|
|
||||||
input::FighterInput,
|
|
||||||
math::{int::FGi32, vec::FGVec2},
|
|
||||||
player::Player,
|
|
||||||
schedule::GameplaySet,
|
|
||||||
scripting::FighterAttackScript,
|
|
||||||
stage::line::StageCollision,
|
|
||||||
};
|
};
|
||||||
use state::state_interrupt_system;
|
use state::state_interrupt_system;
|
||||||
|
|
||||||
@@ -182,6 +173,7 @@ impl Plugin for FighterPlugin {
|
|||||||
)
|
)
|
||||||
.rollback_component_with_clone::<FighterState>()
|
.rollback_component_with_clone::<FighterState>()
|
||||||
.rollback_component_with_clone::<FighterHitboxes>()
|
.rollback_component_with_clone::<FighterHitboxes>()
|
||||||
|
.rollback_component_with_clone::<FighterBoneMatrices>()
|
||||||
.checksum_component_with_hash::<FighterHitboxes>()
|
.checksum_component_with_hash::<FighterHitboxes>()
|
||||||
.rollback_component_with_clone::<FighterSolvedHurtboxes>()
|
.rollback_component_with_clone::<FighterSolvedHurtboxes>()
|
||||||
.checksum_component_with_hash::<FighterSolvedHurtboxes>()
|
.checksum_component_with_hash::<FighterSolvedHurtboxes>()
|
||||||
@@ -195,6 +187,7 @@ impl Plugin for FighterPlugin {
|
|||||||
pub struct FighterHitboxes {
|
pub struct FighterHitboxes {
|
||||||
pub attack_script: Option<Handle<FighterAttackScript>>,
|
pub attack_script: Option<Handle<FighterAttackScript>>,
|
||||||
pub active_hitboxes: Vec<usize>,
|
pub active_hitboxes: Vec<usize>,
|
||||||
|
pub active_hitboxes_solved: Vec<FixedCapsule>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FighterHitboxes {
|
impl FighterHitboxes {
|
||||||
@@ -228,6 +221,7 @@ pub fn spawn_fighter(
|
|||||||
FighterHitboxes {
|
FighterHitboxes {
|
||||||
attack_script: None,
|
attack_script: None,
|
||||||
active_hitboxes: vec![],
|
active_hitboxes: vec![],
|
||||||
|
active_hitboxes_solved: vec![]
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -71,12 +71,7 @@ pub fn check_interrupt(
|
|||||||
.input
|
.input
|
||||||
.has_command(crate::input::FighterCommands::Attack)
|
.has_command(crate::input::FighterCommands::Attack)
|
||||||
{
|
{
|
||||||
let attack_kind = if state_context.input.get_last_frame().movement.y > FGi32::ZERO {
|
let attack_kind = state_context.input.get_attack_command_type().to_attack_kind_grounded();
|
||||||
AttackKind::UpTilt
|
|
||||||
} else {
|
|
||||||
AttackKind::Jab
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(FighterState::GroundAttack(GroundAttackState {
|
Some(FighterState::GroundAttack(GroundAttackState {
|
||||||
attack_kind,
|
attack_kind,
|
||||||
grounded_common: grounded_common.clone(),
|
grounded_common: grounded_common.clone(),
|
||||||
|
|||||||
+124
-8
@@ -9,18 +9,50 @@ use bevy_ggrs::{PlayerInputs, Rollback};
|
|||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
|
use crate::fighter::attack::AttackKind;
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
use crate::input::FighterInputFrame;
|
use crate::input::FighterInputFrame;
|
||||||
|
use crate::math::int::FGi32;
|
||||||
use crate::netcode::GGRSCfg;
|
use crate::netcode::GGRSCfg;
|
||||||
use crate::player::Player;
|
use crate::player::Player;
|
||||||
|
|
||||||
#[derive(Clone, Copy, EnumIter, Debug)]
|
#[derive(Clone, Copy, EnumIter, Debug)]
|
||||||
pub enum FighterCommands {
|
pub enum FighterCommands {
|
||||||
SmashMoveLeft,
|
|
||||||
SmashMoveRight,
|
|
||||||
Jump,
|
Jump,
|
||||||
Shield,
|
Shield,
|
||||||
Attack,
|
Attack,
|
||||||
|
SmashMoveLeft,
|
||||||
|
SmashMoveRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Hash, Copy)]
|
||||||
|
pub enum FighterAttackCommandType {
|
||||||
|
#[default]
|
||||||
|
Neutral,
|
||||||
|
SmashAttackUp,
|
||||||
|
SmashAttackDown,
|
||||||
|
SmashAttackLeft,
|
||||||
|
SmashAttackRight,
|
||||||
|
TiltAttackUp,
|
||||||
|
TiltAttackDown,
|
||||||
|
TiltAttackLeft,
|
||||||
|
TiltAttackRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FighterAttackCommandType {
|
||||||
|
pub fn to_attack_kind_grounded(&self) -> AttackKind {
|
||||||
|
match self {
|
||||||
|
FighterAttackCommandType::Neutral => AttackKind::Jab,
|
||||||
|
FighterAttackCommandType::SmashAttackUp => AttackKind::UpSmash,
|
||||||
|
FighterAttackCommandType::SmashAttackDown => AttackKind::DownSmash,
|
||||||
|
FighterAttackCommandType::SmashAttackLeft => AttackKind::ForwardSmash,
|
||||||
|
FighterAttackCommandType::SmashAttackRight => AttackKind::ForwardSmash,
|
||||||
|
FighterAttackCommandType::TiltAttackUp => AttackKind::UpTilt,
|
||||||
|
FighterAttackCommandType::TiltAttackDown => AttackKind::DownTilt,
|
||||||
|
FighterAttackCommandType::TiltAttackLeft => AttackKind::ForwardTilt,
|
||||||
|
FighterAttackCommandType::TiltAttackRight => AttackKind::ForwardTilt,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Frames of life remaining for each buffered command. Indexed by
|
/// Frames of life remaining for each buffered command. Indexed by
|
||||||
@@ -66,7 +98,9 @@ pub struct FighterInput {
|
|||||||
command_lifetimes: FighterCommandLifetimes,
|
command_lifetimes: FighterCommandLifetimes,
|
||||||
prev_frame: FighterInputFrame,
|
prev_frame: FighterInputFrame,
|
||||||
current_frame: FighterInputFrame,
|
current_frame: FighterInputFrame,
|
||||||
frames_in_smash_move_deadzone: u8,
|
frames_in_x_flick_deadzone: u8,
|
||||||
|
frames_in_y_flick_deadzone: u8,
|
||||||
|
attack_command: FighterAttackCommandType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FighterInput {
|
impl FighterInput {
|
||||||
@@ -100,7 +134,15 @@ impl FighterInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_frames_in_smash_move_deadzone(&self) -> u8 {
|
pub fn get_frames_in_smash_move_deadzone(&self) -> u8 {
|
||||||
self.frames_in_smash_move_deadzone
|
self.frames_in_x_flick_deadzone
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_attack_command_type(&mut self, command: FighterAttackCommandType) {
|
||||||
|
self.attack_command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_attack_command_type(&self) -> FighterAttackCommandType {
|
||||||
|
self.attack_command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,21 +162,35 @@ pub fn postprocess_input(
|
|||||||
|
|
||||||
// Directional smash detection
|
// Directional smash detection
|
||||||
let x_abs = input.current_frame.movement.x.abs();
|
let x_abs = input.current_frame.movement.x.abs();
|
||||||
|
let y_abs = input.current_frame.movement.y.abs();
|
||||||
|
|
||||||
let reset_deadzone = game_settings.input_common.smash_input_reset_axis_threshold;
|
let reset_deadzone = game_settings.input_common.smash_input_reset_axis_threshold;
|
||||||
let axis_threshold = game_settings.input_common.smash_input_axis_threshold;
|
let axis_threshold = game_settings.input_common.smash_input_axis_threshold;
|
||||||
let frame_threshold = game_settings.input_common.smash_input_frame_threshold;
|
let frame_threshold = game_settings.input_common.smash_input_frame_threshold;
|
||||||
|
|
||||||
if x_abs < reset_deadzone {
|
if x_abs < reset_deadzone {
|
||||||
input.frames_in_smash_move_deadzone = 0xFE;
|
input.frames_in_x_flick_deadzone = 0xFE;
|
||||||
} else {
|
} else {
|
||||||
let prev_x_abs = input.prev_frame.movement.x.abs();
|
let prev_x_abs = input.prev_frame.movement.x.abs();
|
||||||
if prev_x_abs < reset_deadzone
|
if prev_x_abs < reset_deadzone
|
||||||
|| input.prev_frame.movement.x.signum() != input.current_frame.movement.x.signum()
|
|| input.prev_frame.movement.x.signum() != input.current_frame.movement.x.signum()
|
||||||
{
|
{
|
||||||
input.frames_in_smash_move_deadzone = 1;
|
input.frames_in_x_flick_deadzone = 1;
|
||||||
} else {
|
} else {
|
||||||
input.frames_in_smash_move_deadzone = input.frames_in_smash_move_deadzone.saturating_add(1);
|
input.frames_in_x_flick_deadzone = input.frames_in_x_flick_deadzone.saturating_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if y_abs < reset_deadzone {
|
||||||
|
input.frames_in_y_flick_deadzone = 0xFE;
|
||||||
|
} else {
|
||||||
|
let prev_y_abs = input.prev_frame.movement.y.abs();
|
||||||
|
if prev_y_abs < reset_deadzone
|
||||||
|
|| input.prev_frame.movement.y.signum() != input.current_frame.movement.y.signum()
|
||||||
|
{
|
||||||
|
input.frames_in_y_flick_deadzone = 1;
|
||||||
|
} else {
|
||||||
|
input.frames_in_y_flick_deadzone = input.frames_in_y_flick_deadzone.saturating_add(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +215,67 @@ pub fn postprocess_input(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if x_abs >= axis_threshold && input.frames_in_smash_move_deadzone <= frame_threshold {
|
if x_abs >= axis_threshold && input.frames_in_x_flick_deadzone <= frame_threshold {
|
||||||
|
input.clear_command(FighterCommands::SmashMoveLeft);
|
||||||
|
input.clear_command(FighterCommands::SmashMoveRight);
|
||||||
|
|
||||||
|
let command_to_set = if input.current_frame.movement.x > 0.0 {
|
||||||
|
FighterCommands::SmashMoveRight
|
||||||
|
} else {
|
||||||
|
FighterCommands::SmashMoveLeft
|
||||||
|
};
|
||||||
|
|
||||||
|
input.set_lifetime(command_to_set, game_settings.input_common.input_buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut y_flick_detected = y_abs >= axis_threshold && input.frames_in_y_flick_deadzone <= frame_threshold;
|
||||||
|
let mut x_flick_detected = x_abs >= axis_threshold && input.frames_in_x_flick_deadzone <= frame_threshold;
|
||||||
|
|
||||||
|
// if both flicks are detected, only one wins
|
||||||
|
|
||||||
|
if x_flick_detected && y_flick_detected {
|
||||||
|
x_flick_detected = x_abs > y_abs;
|
||||||
|
y_flick_detected = !x_flick_detected;
|
||||||
|
}
|
||||||
|
|
||||||
|
let attack_detected = input.current_frame.attack && !input.prev_frame.attack;
|
||||||
|
|
||||||
|
// Tilt/smash up/down
|
||||||
|
if attack_detected {
|
||||||
|
let attack_type = if y_flick_detected {
|
||||||
|
if input.current_frame.movement.y > 0.0 {
|
||||||
|
FighterAttackCommandType::SmashAttackUp
|
||||||
|
} else {
|
||||||
|
FighterAttackCommandType::SmashAttackDown
|
||||||
|
}
|
||||||
|
} else if x_flick_detected {
|
||||||
|
if input.current_frame.movement.x > 0.0 {
|
||||||
|
FighterAttackCommandType::SmashAttackRight
|
||||||
|
} else {
|
||||||
|
FighterAttackCommandType::SmashAttackLeft
|
||||||
|
}
|
||||||
|
} else if x_abs > y_abs {
|
||||||
|
if input.current_frame.movement.x.is_positive() {
|
||||||
|
FighterAttackCommandType::TiltAttackRight
|
||||||
|
} else {
|
||||||
|
FighterAttackCommandType::TiltAttackLeft
|
||||||
|
}
|
||||||
|
} else if y_abs > x_abs {
|
||||||
|
if input.current_frame.movement.y.is_positive() {
|
||||||
|
FighterAttackCommandType::TiltAttackUp
|
||||||
|
} else {
|
||||||
|
FighterAttackCommandType::TiltAttackDown
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FighterAttackCommandType::Neutral
|
||||||
|
};
|
||||||
|
|
||||||
|
input.set_lifetime(FighterCommands::Attack, game_settings.input_common.input_buffer_size);
|
||||||
|
input.set_attack_command_type(attack_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if x_flick_detected {
|
||||||
input.clear_command(FighterCommands::SmashMoveLeft);
|
input.clear_command(FighterCommands::SmashMoveLeft);
|
||||||
input.clear_command(FighterCommands::SmashMoveRight);
|
input.clear_command(FighterCommands::SmashMoveRight);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user