diff --git a/src/fighter/animation.rs b/src/fighter/animation.rs index 6596531..bf9713a 100644 --- a/src/fighter/animation.rs +++ b/src/fighter/animation.rs @@ -3,7 +3,11 @@ use std::time::Duration; use bevy::prelude::*; use crate::{ - fighter::{FighterECB, FighterFacingDirection, FighterTranslation, FighterVisual, visual::FighterAnimations}, player::Player, + fighter::{ + FighterECB, FighterFacingDirection, FighterTranslation, FighterVisual, + visual::FighterAnimations, + }, + player::Player, }; #[derive(Component)] @@ -26,7 +30,11 @@ pub struct FighterAnimationFrame { impl FighterAnimationFrame { pub const fn new(kind: AnimKind, repeat: bool) -> Self { - Self { kind, frame: 0, repeat } + Self { + kind, + frame: 0, + repeat, + } } pub fn reset(&mut self, kind: AnimKind, repeat: bool) { @@ -78,16 +86,28 @@ pub fn setup_fighter_animation_player( } } -pub fn apply_animation(children: Query<(&mut Transform, &FighterFacingDirection, &FighterAnimationFrame), With>) { +pub fn apply_animation( + children: Query< + ( + &mut Transform, + &FighterFacingDirection, + &FighterAnimationFrame, + ), + With, + >, +) { for (mut trf, facing_direction, frame) in children { // Melee model scale is in decimeters trf.rotation = Quat::IDENTITY; if let AnimKind::Turn = frame.kind { - trf.rotate_local_y(std::f32::consts::PI * -0.5 * facing_direction.to_sign().to_num::()); + trf.rotate_local_y( + std::f32::consts::PI * -0.5 * facing_direction.to_sign().to_num::(), + ); } else { - trf.rotate_local_y(std::f32::consts::PI * 0.5 * facing_direction.to_sign().to_num::()); + trf.rotate_local_y( + std::f32::consts::PI * 0.5 * facing_direction.to_sign().to_num::(), + ); } - } } diff --git a/src/fighter/attributes.rs b/src/fighter/attributes.rs index 3cf6f64..9ba5684 100644 --- a/src/fighter/attributes.rs +++ b/src/fighter/attributes.rs @@ -40,7 +40,7 @@ pub struct FighterAttributes { pub air_dodge_duration: u32, pub air_dodge_velocity: FGi32, - pub air_dodge_decay: FGi32 + pub air_dodge_decay: FGi32, } impl FighterAttributes { diff --git a/src/fighter/debug.rs b/src/fighter/debug.rs index ad0d7b0..c2cdda8 100644 --- a/src/fighter/debug.rs +++ b/src/fighter/debug.rs @@ -10,7 +10,10 @@ use crate::fighter::ecb::FighterPreviousECB; use crate::fighter::manifest::{FighterManifest, FighterManifestRegistry}; use crate::fighter::state::StateNameDebug; use crate::fighter::visual::FighterAnimations; -use crate::fighter::{Fighter, FighterECB, FighterFacingDirection, FighterPreviousTranslation, FighterTranslation, FighterVelocity}; +use crate::fighter::{ + Fighter, FighterECB, FighterFacingDirection, FighterPreviousTranslation, FighterTranslation, + FighterVelocity, +}; use crate::input::FighterInput; use crate::match_loading::MatchPlayer; @@ -45,26 +48,27 @@ pub fn fighter_debug( ui.label(format!("{:?}", vel)); ui.label(format!("{:?}", input.get_last_frame())); }); - } Ok(()) } pub fn animation_debug( - query: Query<( - &GlobalTransform, - &FighterAnimationFrame, - &FighterAnimations, - )>, + query: Query<(&GlobalTransform, &FighterAnimationFrame, &FighterAnimations)>, baked_anims: Res>, mut gizmos: Gizmos, ) { for (global_transform, frame, anims) in query { - let baked: &BakedFighterAnimations = baked_anims.get(&anims.baked).expect("Baked anims should be in"); + let baked: &BakedFighterAnimations = baked_anims + .get(&anims.baked) + .expect("Baked anims should be in"); for bone in &baked.bone_names { let out = anims.sample_bone(&baked_anims, *frame, bone); if let Some(out) = out { - let pos = global_transform.transform_point(Vec3::new(out.cols[3][0].to_num(), out.cols[3][1].to_num(), out.cols[3][2].to_num())); + let pos = global_transform.transform_point(Vec3::new( + out.cols[3][0].to_num(), + out.cols[3][1].to_num(), + out.cols[3][2].to_num(), + )); gizmos.cross(pos, 0.5, bevy::color::palettes::css::RED); } } diff --git a/src/fighter/ecb.rs b/src/fighter/ecb.rs index edba154..48193b6 100644 --- a/src/fighter/ecb.rs +++ b/src/fighter/ecb.rs @@ -36,10 +36,22 @@ pub fn snapshot_fighter_ecb(fighters: Query<(&FighterECB, &mut FighterPreviousEC impl FighterECB { pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] { - let rel_up = trf.transform_point(Vec3::new(0.0, (self.vertical_half + self.vertical_half).to_num(), 0.0)); + let rel_up = trf.transform_point(Vec3::new( + 0.0, + (self.vertical_half + self.vertical_half).to_num(), + 0.0, + )); let rel_down = trf.transform_point(Vec3::new(0.0, 0.0, 0.0)); - let rel_left = trf.transform_point(Vec3::new((-self.horizontal_half).to_num(), self.vertical_half.to_num(), 0.0)); - let rel_right = trf.transform_point(Vec3::new(self.horizontal_half.to_num(), self.vertical_half.to_num(), 0.0)); + let rel_left = trf.transform_point(Vec3::new( + (-self.horizontal_half).to_num(), + self.vertical_half.to_num(), + 0.0, + )); + let rel_right = trf.transform_point(Vec3::new( + self.horizontal_half.to_num(), + self.vertical_half.to_num(), + 0.0, + )); [rel_up, rel_right, rel_down, rel_left] } @@ -50,10 +62,22 @@ impl FighterECB { impl FighterPreviousECB { pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] { - let rel_up = trf.transform_point(Vec3::new(0.0, (self.vertical_half + self.vertical_half).to_num(), 0.0)); + let rel_up = trf.transform_point(Vec3::new( + 0.0, + (self.vertical_half + self.vertical_half).to_num(), + 0.0, + )); let rel_down = trf.transform_point(Vec3::new(0.0, 0.0, 0.0)); - let rel_left = trf.transform_point(Vec3::new((-self.horizontal_half).to_num(), self.vertical_half.to_num(), 0.0)); - let rel_right = trf.transform_point(Vec3::new(self.horizontal_half.to_num(), self.vertical_half.to_num(), 0.0)); + let rel_left = trf.transform_point(Vec3::new( + (-self.horizontal_half).to_num(), + self.vertical_half.to_num(), + 0.0, + )); + let rel_right = trf.transform_point(Vec3::new( + self.horizontal_half.to_num(), + self.vertical_half.to_num(), + 0.0, + )); [rel_up, rel_right, rel_down, rel_left] } diff --git a/src/fighter/mod.rs b/src/fighter/mod.rs index 38a2ed9..8539c56 100644 --- a/src/fighter/mod.rs +++ b/src/fighter/mod.rs @@ -7,8 +7,8 @@ use bevy_ggrs::prelude::*; use serde::{Deserialize, Serialize}; pub mod animation; -pub mod baked_animation; pub mod attributes; +pub mod baked_animation; pub mod collision; pub mod debug; pub mod ecb; @@ -28,8 +28,16 @@ pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity use crate::{ fighter::{ - animation::animation_init, manifest::FighterManifest, state::{FighterState, fall::FallState}, visual::FighterAnimations, - }, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, stage::line::StageCollision, + animation::animation_init, + manifest::FighterManifest, + state::{FighterState, fall::FallState}, + visual::FighterAnimations, + }, + input::FighterInput, + math::{int::FGi32, vec::FGVec2}, + player::Player, + schedule::GameplaySet, + stage::line::StageCollision, }; use state::state_interrupt_system; @@ -91,7 +99,7 @@ pub struct FighterVisual; #[derive(Component)] pub struct Fighter { id: FighterId, - pub manifest: Handle + pub manifest: Handle, } impl Plugin for FighterPlugin { @@ -174,7 +182,7 @@ pub fn spawn_fighter( }, Fighter { id: manifest.1.id, - manifest: manifest.0 + manifest: manifest.0, }, FighterFacingDirection::Right, FighterECB { @@ -189,5 +197,6 @@ pub fn spawn_fighter( animation::FighterAnimationFrame::new(animation::AnimKind::Wait, true), FighterVisual, FighterInput::default(), + crate::camera::FighterCameraExtents::default(), )); } diff --git a/src/fighter/state/turn.rs b/src/fighter/state/turn.rs index 492f9a5..bc4a83d 100644 --- a/src/fighter/state/turn.rs +++ b/src/fighter/state/turn.rs @@ -4,7 +4,7 @@ use crate::fighter::collision; use crate::fighter::state::fall::FallState; use crate::fighter::state::ground::{self, GroundedMotionResult, GroundedStateCommon}; use crate::fighter::state::land::LandingState; -use crate::fighter::state::{FighterState, air, dash, jump, wait, walk}; +use crate::fighter::state::{air, dash, jump, wait, walk, FighterState}; #[derive(Debug, Clone, Hash)] pub struct TurnState { @@ -80,9 +80,13 @@ pub fn check_input( grounded_common: &GroundedStateCommon, ) -> Option { let input_frame = state_context.input.get_last_frame(); - + let smash_input_frame_threshold = state_context + .game_settings + .input_common + .smash_input_frame_threshold; if !input_frame.movement.x.is_zero() && input_frame.movement.x.signum() != state_context.facing_direction.to_sign() + && state_context.input.get_frames_in_smash_move_deadzone() > smash_input_frame_threshold { return Some(FighterState::Turn(TurnState { grounded_common: grounded_common.clone(), diff --git a/src/fighter/state/wait.rs b/src/fighter/state/wait.rs index 09387fb..c0c9cec 100644 --- a/src/fighter/state/wait.rs +++ b/src/fighter/state/wait.rs @@ -18,20 +18,18 @@ pub struct WaitState { impl FighterStateImpl for WaitState { const NAME: &'static str = "Wait"; fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option { - if let Some(state) = ground::grounded_movement_standstill_common_interrupts( + if let Some(state) = ground::grounded_movement_common_interrupts(state_context, &self.grounded_common) { + Some(state) + } else if let Some(state) = ground::grounded_movement_standstill_common_interrupts( state_context, - &self.grounded_common, - ) { - // Prevent transitioning into myself + &self.grounded_common) + { + // Don't transition into yourself if let FighterState::Wait(_) = state { None } else { Some(state) } - } else if let Some(state) = - ground::grounded_movement_common_interrupts(state_context, &self.grounded_common) - { - Some(state) } else { None } diff --git a/src/fighter/state/walk.rs b/src/fighter/state/walk.rs index e8fe20a..970859d 100644 --- a/src/fighter/state/walk.rs +++ b/src/fighter/state/walk.rs @@ -6,6 +6,7 @@ use super::{FighterStateContext, FighterStateImpl, dash, ground, wait}; use crate::fighter::animation::AnimKind; use crate::fighter::state::FighterState; use crate::fighter::state::fall::FallState; +use crate::fighter::state::turn; use crate::fighter::state::ground::{GroundedMotionResult, GroundedStateCommon}; #[derive(Debug, Clone, Hash)] @@ -16,7 +17,10 @@ pub struct WalkState { impl FighterStateImpl for WalkState { const NAME: &'static str = "Walk"; fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option { - if let Some(state) = + if let Some(state) = turn::check_input(state_context, &self.grounded_common) { + return Some(state) + } + else if let Some(state) = ground::grounded_movement_common_interrupts(state_context, &self.grounded_common) { return Some(state); diff --git a/src/input/buffer.rs b/src/input/buffer.rs index 925bb8f..cfe91e8 100644 --- a/src/input/buffer.rs +++ b/src/input/buffer.rs @@ -94,6 +94,10 @@ impl FighterInput { pub fn reduce_command_lifetime(&mut self, command: FighterCommands) { self.command_lifetimes[command] = self.command_lifetimes[command].saturating_sub(1); } + + pub fn get_frames_in_smash_move_deadzone(&self) -> u8 { + self.frames_in_smash_move_deadzone + } } /// Turns this frame's rolled-back inputs into per-fighter [`FighterInput`] @@ -124,7 +128,7 @@ pub fn postprocess_input( if prev_x_abs < reset_deadzone || input.prev_frame.movement.x.signum() != input.current_frame.movement.x.signum() { - input.frames_in_smash_move_deadzone = 0; + input.frames_in_smash_move_deadzone = 1; } else { input.frames_in_smash_move_deadzone = input.frames_in_smash_move_deadzone.saturating_add(1); } diff --git a/src/stage/manifest.rs b/src/stage/manifest.rs index 269e25d..99d4908 100644 --- a/src/stage/manifest.rs +++ b/src/stage/manifest.rs @@ -2,37 +2,62 @@ use std::collections::HashMap; use bevy_asset_loader::asset_collection::AssetCollection; use serde::{Deserialize, Serialize}; -use strum_macros::{EnumIter}; use strum::IntoEnumIterator; +use strum_macros::EnumIter; use crate::{ - AppState, game_settings::GameSettings, math::{int::FGi32, vec::FGVec2}, stage::{StagePoly, line::{StageCollision, StagePolyLineSegmentType, StagePolyType}}, + AppState, + game_settings::GameSettings, + math::{int::FGi32, vec::FGVec2}, + stage::{ + StagePoly, + line::{StageCollision, StagePolyLineSegmentType, StagePolyType}, + }, }; use bevy::prelude::*; /// Per-stage presentation tuning for the standard match camera. /// -/// Values use gameplay world units on the XY plane +/// Spatial values use gameplay world units on the XY plane. Angle values use +/// degrees #[derive(Component, Serialize, Deserialize, Clone, Reflect)] #[reflect(opaque)] pub struct StageCameraProfile { + /// World-space left edge of the camera range. pub left: f32, + /// World-space right edge of the camera range. pub right: f32, + /// World-space bottom edge of the camera range. pub bottom: f32, + /// World-space top edge of the camera range. pub top: f32, + /// Neutral focus point used for empty framing and dynamic pan calculations. pub origin: Vec2, + /// Vertical perspective field of view for the standard match camera. pub vertical_fov_degrees: f32, + /// Nearest allowed positive Z distance from the gameplay plane. pub min_depth: f32, + /// Farthest allowed positive Z distance from the gameplay plane. pub max_depth: f32, + /// Multiplies fighter camera extents after the player-count scale. pub subject_scale: f32, + /// Multiplies the facing-forward horizontal fighter camera extent. + pub fighter_forward_extent_scale: f32, + /// Multiplies the standard camera's interest and eye follow speeds. pub tracking_smoothness: f32, - pub vertical_tilt_degrees: f32, - pub horizontal_pan_coefficient: f32, - pub vertical_pan_coefficient: f32, + /// Constant vertical pitch added after dynamic vertical pan is clamped. + pub vertical_pan_degrees: f32, + /// Dynamic horizontal pan in degrees for every world unit from 0,0 + pub horizontal_pan_degrees_per_unit: f32, + /// Dynamic vertical pan in degrees for every world unit from the vertical reference line + pub vertical_pan_degrees_per_unit: f32, + /// Symmetric limit on dynamic horizontal pan, before any other adjustment. pub max_horizontal_pan_degrees: f32, + /// Maximum upward dynamic vertical pan, before constant pitch is added. pub max_upward_pan_degrees: f32, + /// Magnitude of the maximum downward dynamic vertical pan, before constant + /// pitch is added. pub max_downward_pan_degrees: f32, - pub max_downward_expansion: f32, } #[derive(Reflect, Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Hash, Debug, EnumIter)] @@ -101,7 +126,6 @@ fn fail(next_state: &mut NextState, message: &str) { next_state.set(AppState::CommonAssetLoadFailed); } - pub fn prepare_stage_manifests( mut commands: Commands, handles: Res, @@ -154,13 +178,13 @@ pub fn prepare_stage_manifests( impl StageManifest { pub fn to_collision(&self) -> StageCollision { - let stage_polys: Vec = self.polygons.iter().map(|def| { - StagePoly::build(def.poly_type, &def.lines) - }).collect(); - - StageCollision { - stage_polys - } + let stage_polys: Vec = self + .polygons + .iter() + .map(|def| StagePoly::build(def.poly_type, &def.lines)) + .collect(); + + StageCollision { stage_polys } } } @@ -175,24 +199,33 @@ impl StageCameraProfile { && self.min_depth.is_finite() && self.max_depth.is_finite() && self.subject_scale.is_finite() + && self.fighter_forward_extent_scale.is_finite() && self.tracking_smoothness.is_finite() - && self.vertical_tilt_degrees.is_finite() - && self.horizontal_pan_coefficient.is_finite() - && self.vertical_pan_coefficient.is_finite() + && self.vertical_pan_degrees.is_finite() + && self.horizontal_pan_degrees_per_unit.is_finite() + && self.vertical_pan_degrees_per_unit.is_finite() && self.max_horizontal_pan_degrees.is_finite() && self.max_upward_pan_degrees.is_finite() && self.max_downward_pan_degrees.is_finite() - && self.max_downward_expansion.is_finite() && self.left < self.right && self.bottom < self.top + && (self.left..=self.right).contains(&self.origin.x) + && (self.bottom..=self.top).contains(&self.origin.y) && (0.0..180.0).contains(&self.vertical_fov_degrees) && self.min_depth > 0.0 && self.min_depth <= self.max_depth && self.subject_scale > 0.0 + && self.fighter_forward_extent_scale > 0.0 && self.tracking_smoothness >= 0.0 && self.max_horizontal_pan_degrees >= 0.0 && self.max_upward_pan_degrees >= 0.0 && self.max_downward_pan_degrees >= 0.0 - && self.max_downward_expansion >= 0.0 + && self.vertical_fov_degrees * 0.5 + self.max_horizontal_pan_degrees < 90.0 + && self.vertical_fov_degrees * 0.5 + + self.vertical_pan_degrees.abs() + + self + .max_upward_pan_degrees + .max(self.max_downward_pan_degrees) + < 90.0 } }