Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
598313f69d | ||
|
|
099cd62e04 |
+5
-9
@@ -5,7 +5,7 @@ use bevy_ggrs::GgrsFrameTiming;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
debug_tools::DebugSettings,
|
debug_tools::DebugSettings,
|
||||||
fighter::{manifest::FighterManifest, motion::{sample_presentation_translation_with_snap, FighterPresentationSnap}},
|
fighter::{manifest::FighterManifest, motion::sample_presentation_translation},
|
||||||
stage::manifest::StageCameraProfile,
|
stage::manifest::StageCameraProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -313,7 +313,6 @@ fn update_match_camera(
|
|||||||
&crate::fighter::motion::FighterPreviousTranslation,
|
&crate::fighter::motion::FighterPreviousTranslation,
|
||||||
&crate::fighter::Fighter,
|
&crate::fighter::Fighter,
|
||||||
&crate::fighter::FighterFacingDirection,
|
&crate::fighter::FighterFacingDirection,
|
||||||
&FighterPresentationSnap,
|
|
||||||
&mut FighterCameraExtents,
|
&mut FighterCameraExtents,
|
||||||
),
|
),
|
||||||
With<crate::player::Player>,
|
With<crate::player::Player>,
|
||||||
@@ -330,17 +329,16 @@ fn update_match_camera(
|
|||||||
let subjects = fighters
|
let subjects = fighters
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(
|
.map(
|
||||||
|(translation, previous_translation, fighter, facing, presentation_snap, mut extents)| {
|
|(translation, previous_translation, fighter, facing, mut extents)| {
|
||||||
let manifest = manifests
|
let manifest = manifests
|
||||||
.get(&fighter.manifest)
|
.get(&fighter.manifest)
|
||||||
.expect("Fighter manifest should be valid");
|
.expect("Fighter manifest should be valid");
|
||||||
fighter_subject_bounds(
|
fighter_subject_bounds(
|
||||||
sample_presentation_translation_with_snap(
|
sample_presentation_translation(
|
||||||
*previous_translation,
|
*previous_translation,
|
||||||
*translation,
|
*translation,
|
||||||
timing.overstep_fraction(),
|
timing.overstep_fraction(),
|
||||||
debug_settings.motion_sampling,
|
debug_settings.motion_sampling,
|
||||||
*presentation_snap,
|
|
||||||
),
|
),
|
||||||
manifest.camera,
|
manifest.camera,
|
||||||
*facing,
|
*facing,
|
||||||
@@ -467,7 +465,6 @@ fn debug_draw_camera(
|
|||||||
&crate::fighter::motion::FighterPreviousTranslation,
|
&crate::fighter::motion::FighterPreviousTranslation,
|
||||||
&crate::fighter::Fighter,
|
&crate::fighter::Fighter,
|
||||||
&crate::fighter::FighterFacingDirection,
|
&crate::fighter::FighterFacingDirection,
|
||||||
&FighterPresentationSnap,
|
|
||||||
&FighterCameraExtents,
|
&FighterCameraExtents,
|
||||||
),
|
),
|
||||||
With<crate::player::Player>,
|
With<crate::player::Player>,
|
||||||
@@ -489,16 +486,15 @@ fn debug_draw_camera(
|
|||||||
Color::srgb(1.0, 0.5, 0.0),
|
Color::srgb(1.0, 0.5, 0.0),
|
||||||
);
|
);
|
||||||
draw_rectangle(&mut gizmos, camera.frame, Color::srgb(1.0, 1.0, 0.0));
|
draw_rectangle(&mut gizmos, camera.frame, Color::srgb(1.0, 1.0, 0.0));
|
||||||
for (translation, previous_translation, fighter, _facing, presentation_snap, extents) in &fighters {
|
for (translation, previous_translation, fighter, _facing, extents) in &fighters {
|
||||||
let manifest = manifests
|
let manifest = manifests
|
||||||
.get(&fighter.manifest)
|
.get(&fighter.manifest)
|
||||||
.expect("Fighter manifest should be valid");
|
.expect("Fighter manifest should be valid");
|
||||||
let center = sample_presentation_translation_with_snap(
|
let center = sample_presentation_translation(
|
||||||
*previous_translation,
|
*previous_translation,
|
||||||
*translation,
|
*translation,
|
||||||
timing.overstep_fraction(),
|
timing.overstep_fraction(),
|
||||||
debug_settings.motion_sampling,
|
debug_settings.motion_sampling,
|
||||||
*presentation_snap,
|
|
||||||
) + Vec2::Y * manifest.camera.vertical_origin_offset.to_num::<f32>();
|
) + Vec2::Y * manifest.camera.vertical_origin_offset.to_num::<f32>();
|
||||||
draw_rectangle(
|
draw_rectangle(
|
||||||
&mut gizmos,
|
&mut gizmos,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::fighter::ecb::FighterPreviousECB;
|
|||||||
use crate::fighter::{FighterECB, FighterPreviousTranslation, FighterTranslation};
|
use crate::fighter::{FighterECB, FighterPreviousTranslation, FighterTranslation};
|
||||||
use crate::math::segment::FGSegment2d;
|
use crate::math::segment::FGSegment2d;
|
||||||
use crate::math::vec::FGVec2;
|
use crate::math::vec::FGVec2;
|
||||||
use crate::stage::line::{StageCollision, StageLineID, StagePolyType};
|
use crate::stage::line::{StageCollision, StageLineID};
|
||||||
/*
|
/*
|
||||||
pub fn collide_fighter_with_scene(
|
pub fn collide_fighter_with_scene(
|
||||||
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
|
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
|
||||||
@@ -45,7 +45,6 @@ pub fn air_collide_with_stage(
|
|||||||
ecb: &FighterECB,
|
ecb: &FighterECB,
|
||||||
stage_collision: &StageCollision,
|
stage_collision: &StageCollision,
|
||||||
ignored_polys: &[usize],
|
ignored_polys: &[usize],
|
||||||
ignore_soft_platforms: bool,
|
|
||||||
) -> Option<AirCollisionWithStageResult> {
|
) -> Option<AirCollisionWithStageResult> {
|
||||||
let ray_segment = FGSegment2d::new(
|
let ray_segment = FGSegment2d::new(
|
||||||
prev_translation.0 + prev_ecb.get_bottom_point(),
|
prev_translation.0 + prev_ecb.get_bottom_point(),
|
||||||
@@ -56,10 +55,7 @@ pub fn air_collide_with_stage(
|
|||||||
.stage_polys
|
.stage_polys
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(poly_idx, poly)| {
|
.filter(|(poly_idx, _)| !ignored_polys.contains(poly_idx))
|
||||||
!ignored_polys.contains(poly_idx)
|
|
||||||
&& !(ignore_soft_platforms && matches!(poly.poly_type, StagePolyType::Platform))
|
|
||||||
})
|
|
||||||
.filter_map(|(poly_idx, poly)| {
|
.filter_map(|(poly_idx, poly)| {
|
||||||
poly.intersect_ray(ray_segment).map(|intersect_result| {
|
poly.intersect_ray(ray_segment).map(|intersect_result| {
|
||||||
(
|
(
|
||||||
@@ -99,18 +95,6 @@ mod tests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn solid_floor() -> StagePoly {
|
|
||||||
StagePoly::build(
|
|
||||||
StagePolyType::Closed,
|
|
||||||
&[
|
|
||||||
(FGVec2::lit("-2", "0"), StagePolyLineSegmentType::Floor),
|
|
||||||
(FGVec2::lit("2", "0"), StagePolyLineSegmentType::Floor),
|
|
||||||
(FGVec2::lit("2", "-2"), StagePolyLineSegmentType::Wall),
|
|
||||||
(FGVec2::lit("-2", "-2"), StagePolyLineSegmentType::Wall),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn airborne_collision_skips_ignored_polygons() {
|
fn airborne_collision_skips_ignored_polygons() {
|
||||||
let stage_collision = StageCollision {
|
let stage_collision = StageCollision {
|
||||||
@@ -138,78 +122,10 @@ mod tests {
|
|||||||
&ecb,
|
&ecb,
|
||||||
&stage_collision,
|
&stage_collision,
|
||||||
&[1],
|
&[1],
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.expect("the non-ignored platform should still be collidable");
|
.expect("the non-ignored platform should still be collidable");
|
||||||
|
|
||||||
assert_eq!(collision.line_id.polygon, 0);
|
assert_eq!(collision.line_id.polygon, 0);
|
||||||
assert_eq!(collision.hit_position, FGVec2::lit("0", "0"));
|
assert_eq!(collision.hit_position, FGVec2::lit("0", "0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn airborne_collision_skips_soft_platforms_while_passing_down() {
|
|
||||||
let stage_collision = StageCollision {
|
|
||||||
stage_polys: vec![horizontal_platform("0")],
|
|
||||||
};
|
|
||||||
let previous_translation = FighterPreviousTranslation(FGVec2::lit("0", "2"));
|
|
||||||
let translation = FighterTranslation(FGVec2::lit("0", "-1"));
|
|
||||||
let previous_ecb = FighterPreviousECB {
|
|
||||||
bottom: FGVec2::ZERO,
|
|
||||||
top: FGVec2::ZERO,
|
|
||||||
left: FGVec2::ZERO,
|
|
||||||
right: FGVec2::ZERO,
|
|
||||||
};
|
|
||||||
let ecb = FighterECB {
|
|
||||||
bottom: FGVec2::ZERO,
|
|
||||||
top: FGVec2::ZERO,
|
|
||||||
left: FGVec2::ZERO,
|
|
||||||
right: FGVec2::ZERO,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert!(air_collide_with_stage(
|
|
||||||
&previous_translation,
|
|
||||||
&translation,
|
|
||||||
&previous_ecb,
|
|
||||||
&ecb,
|
|
||||||
&stage_collision,
|
|
||||||
&[],
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn airborne_collision_still_hits_solid_floors_while_passing_down() {
|
|
||||||
let stage_collision = StageCollision {
|
|
||||||
stage_polys: vec![solid_floor()],
|
|
||||||
};
|
|
||||||
let previous_translation = FighterPreviousTranslation(FGVec2::lit("0", "2"));
|
|
||||||
let translation = FighterTranslation(FGVec2::lit("0", "-1"));
|
|
||||||
let previous_ecb = FighterPreviousECB {
|
|
||||||
bottom: FGVec2::ZERO,
|
|
||||||
top: FGVec2::ZERO,
|
|
||||||
left: FGVec2::ZERO,
|
|
||||||
right: FGVec2::ZERO,
|
|
||||||
};
|
|
||||||
let ecb = FighterECB {
|
|
||||||
bottom: FGVec2::ZERO,
|
|
||||||
top: FGVec2::ZERO,
|
|
||||||
left: FGVec2::ZERO,
|
|
||||||
right: FGVec2::ZERO,
|
|
||||||
};
|
|
||||||
|
|
||||||
let collision = air_collide_with_stage(
|
|
||||||
&previous_translation,
|
|
||||||
&translation,
|
|
||||||
&previous_ecb,
|
|
||||||
&ecb,
|
|
||||||
&stage_collision,
|
|
||||||
&[],
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.expect("solid floors remain collidable while passing down");
|
|
||||||
|
|
||||||
assert_eq!(collision.line_id.polygon, 0);
|
|
||||||
assert_eq!(collision.hit_position, FGVec2::lit("0", "0"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-9
@@ -29,8 +29,7 @@ pub use attributes::FighterAttributes;
|
|||||||
pub use ecb::FighterECB;
|
pub use ecb::FighterECB;
|
||||||
pub use manifest::FighterId;
|
pub use manifest::FighterId;
|
||||||
pub use motion::{
|
pub use motion::{
|
||||||
FighterPresentationSnap, FighterPreviousFacingDirection, FighterPreviousTranslation,
|
FighterPreviousFacingDirection, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||||
FighterTranslation, FighterVelocity,
|
|
||||||
};
|
};
|
||||||
pub use surface::FighterSurfaceState;
|
pub use surface::FighterSurfaceState;
|
||||||
|
|
||||||
@@ -148,10 +147,6 @@ impl Plugin for FighterPlugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_plugins(FighterAttackPlugin)
|
app.add_plugins(FighterAttackPlugin)
|
||||||
// Every state gets a chance to interrupt itself, in priority order.
|
// Every state gets a chance to interrupt itself, in priority order.
|
||||||
.add_systems(
|
|
||||||
GgrsSchedule,
|
|
||||||
motion::clear_fighter_presentation_snaps.in_set(GameplaySet::Input),
|
|
||||||
)
|
|
||||||
.add_systems(
|
.add_systems(
|
||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
(
|
(
|
||||||
@@ -207,8 +202,6 @@ impl Plugin for FighterPlugin {
|
|||||||
.checksum_component_with_hash::<FighterTranslation>()
|
.checksum_component_with_hash::<FighterTranslation>()
|
||||||
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
||||||
.checksum_component_with_hash::<FighterPreviousTranslation>()
|
.checksum_component_with_hash::<FighterPreviousTranslation>()
|
||||||
.rollback_component_with_copy::<FighterPresentationSnap>()
|
|
||||||
.checksum_component_with_hash::<FighterPresentationSnap>()
|
|
||||||
.rollback_component_with_copy::<FighterPreviousFacingDirection>()
|
.rollback_component_with_copy::<FighterPreviousFacingDirection>()
|
||||||
.checksum_component_with_hash::<FighterPreviousFacingDirection>()
|
.checksum_component_with_hash::<FighterPreviousFacingDirection>()
|
||||||
.rollback_component_with_copy::<animation::FighterAnimationFrame>()
|
.rollback_component_with_copy::<animation::FighterAnimationFrame>()
|
||||||
@@ -311,7 +304,6 @@ pub fn spawn_fighter(
|
|||||||
FighterVelocity::default(),
|
FighterVelocity::default(),
|
||||||
FighterTranslation(spawn_position),
|
FighterTranslation(spawn_position),
|
||||||
FighterPreviousTranslation(spawn_position),
|
FighterPreviousTranslation(spawn_position),
|
||||||
FighterPresentationSnap::default(),
|
|
||||||
),
|
),
|
||||||
visual_root,
|
visual_root,
|
||||||
animations,
|
animations,
|
||||||
|
|||||||
+2
-42
@@ -21,11 +21,6 @@ pub struct FighterVelocity(pub FGVec2);
|
|||||||
#[derive(Component, Deref, DerefMut, Default, Clone, Copy, Hash)]
|
#[derive(Component, Deref, DerefMut, Default, Clone, Copy, Hash)]
|
||||||
pub struct FighterPreviousTranslation(pub FGVec2);
|
pub struct FighterPreviousTranslation(pub FGVec2);
|
||||||
|
|
||||||
/// Marks a simulation frame whose position changed discontinuously for
|
|
||||||
/// presentation purposes (for example, a landing correction).
|
|
||||||
#[derive(Component, Default, Clone, Copy, Hash, Debug, PartialEq, Eq)]
|
|
||||||
pub struct FighterPresentationSnap(pub bool);
|
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Default, Clone, Copy, Hash)]
|
#[derive(Component, Deref, DerefMut, Default, Clone, Copy, Hash)]
|
||||||
#[require(FighterPreviousTranslation)]
|
#[require(FighterPreviousTranslation)]
|
||||||
pub struct FighterTranslation(pub FGVec2);
|
pub struct FighterTranslation(pub FGVec2);
|
||||||
@@ -57,31 +52,10 @@ pub fn sample_presentation_translation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_presentation_translation_with_snap(
|
|
||||||
previous: FighterPreviousTranslation,
|
|
||||||
current: FighterTranslation,
|
|
||||||
overstep_fraction: f32,
|
|
||||||
mode: PresentationMotionMode,
|
|
||||||
snapped: FighterPresentationSnap,
|
|
||||||
) -> Vec2 {
|
|
||||||
if snapped.0 {
|
|
||||||
Vec2::new(current.x.to_num(), current.y.to_num())
|
|
||||||
} else {
|
|
||||||
sample_presentation_translation(previous, current, overstep_fraction, mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_fighter_presentation_snaps(mut fighters: Query<&mut FighterPresentationSnap>) {
|
|
||||||
for mut snap in &mut fighters {
|
|
||||||
snap.0 = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_fighter_translation_to_visuals(
|
pub fn apply_fighter_translation_to_visuals(
|
||||||
query: Query<(
|
query: Query<(
|
||||||
&FighterTranslation,
|
&FighterTranslation,
|
||||||
&FighterPreviousTranslation,
|
&FighterPreviousTranslation,
|
||||||
&FighterPresentationSnap,
|
|
||||||
&FighterHitstopShake,
|
&FighterHitstopShake,
|
||||||
&mut Transform,
|
&mut Transform,
|
||||||
)>,
|
)>,
|
||||||
@@ -90,13 +64,12 @@ pub fn apply_fighter_translation_to_visuals(
|
|||||||
game_settings: Res<GameSettings>,
|
game_settings: Res<GameSettings>,
|
||||||
) {
|
) {
|
||||||
let overstep_fraction = timing.overstep_fraction();
|
let overstep_fraction = timing.overstep_fraction();
|
||||||
for (translation, previous_translation, presentation_snap, hitstop_shake, mut transform) in query {
|
for (translation, previous_translation, hitstop_shake, mut transform) in query {
|
||||||
let translation = sample_presentation_translation_with_snap(
|
let translation = sample_presentation_translation(
|
||||||
*previous_translation,
|
*previous_translation,
|
||||||
*translation,
|
*translation,
|
||||||
overstep_fraction,
|
overstep_fraction,
|
||||||
debug_settings.motion_sampling,
|
debug_settings.motion_sampling,
|
||||||
*presentation_snap,
|
|
||||||
);
|
);
|
||||||
transform.translation = Vec3::new(
|
transform.translation = Vec3::new(
|
||||||
translation.x,
|
translation.x,
|
||||||
@@ -196,19 +169,6 @@ mod tests {
|
|||||||
assert_eq!(position, Vec2::new(7.0, 14.0));
|
assert_eq!(position, Vec2::new(7.0, 14.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn presentation_translation_uses_current_sample_after_snap() {
|
|
||||||
let position = sample_presentation_translation_with_snap(
|
|
||||||
FighterPreviousTranslation(FGVec2::new(FGi32::lit("2"), FGi32::lit("4"))),
|
|
||||||
FighterTranslation(FGVec2::new(FGi32::lit("6"), FGi32::lit("12"))),
|
|
||||||
0.25,
|
|
||||||
PresentationMotionMode::Extrapolation,
|
|
||||||
FighterPresentationSnap(true),
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(position, Vec2::new(6.0, 12.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn snapshot_fighter_motion_copies_current_world_state() {
|
fn snapshot_fighter_motion_copies_current_world_state() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ pub fn collision_interrupt(
|
|||||||
ctx.ecb.left -= diff;
|
ctx.ecb.left -= diff;
|
||||||
ctx.ecb.right -= diff;
|
ctx.ecb.right -= diff;
|
||||||
ctx.translation.0 = res.hit_position;
|
ctx.translation.0 = res.hit_position;
|
||||||
ctx.presentation_snap.0 = true;
|
|
||||||
ctx.surface.land(res.line_id);
|
ctx.surface.land(res.line_id);
|
||||||
Some(FighterState::Land(LandingState::create(landing_type)))
|
Some(FighterState::Land(LandingState::create(landing_type)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,8 +120,8 @@ impl FighterStateImpl for CrouchState {
|
|||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut UpdateContext) {
|
||||||
if let CrouchSubState::Entering {
|
if let CrouchSubState::Entering {
|
||||||
pass_queued,
|
ref mut pass_queued,
|
||||||
} = &mut self.sub_state
|
} = self.sub_state
|
||||||
{
|
{
|
||||||
if state_context
|
if state_context
|
||||||
.input
|
.input
|
||||||
|
|||||||
@@ -248,7 +248,6 @@ mod tests {
|
|||||||
use crate::fighter::ecb::FighterPreviousECB;
|
use crate::fighter::ecb::FighterPreviousECB;
|
||||||
use crate::fighter::{FighterECB, FighterSurfaceState};
|
use crate::fighter::{FighterECB, FighterSurfaceState};
|
||||||
use crate::stage::line::StagePolyType;
|
use crate::stage::line::StagePolyType;
|
||||||
use crate::input::FighterInput;
|
|
||||||
|
|
||||||
fn two_segment_floor() -> StagePoly {
|
fn two_segment_floor() -> StagePoly {
|
||||||
StagePoly::build(
|
StagePoly::build(
|
||||||
@@ -376,19 +375,15 @@ mod tests {
|
|||||||
right: FGVec2::lit("0.5", "0.0"),
|
right: FGVec2::lit("0.5", "0.0"),
|
||||||
};
|
};
|
||||||
let previous_ecb = FighterPreviousECB::from(ecb);
|
let previous_ecb = FighterPreviousECB::from(ecb);
|
||||||
let mut presentation_snap = crate::fighter::FighterPresentationSnap::default();
|
|
||||||
let mut context = CollisionContext {
|
let mut context = CollisionContext {
|
||||||
translation: &mut translation,
|
translation: &mut translation,
|
||||||
prev_translation: &previous_translation,
|
prev_translation: &previous_translation,
|
||||||
input: &FighterInput::default(),
|
|
||||||
velocity: &mut velocity,
|
velocity: &mut velocity,
|
||||||
surface: &mut surface,
|
surface: &mut surface,
|
||||||
ecb: &mut ecb,
|
ecb: &mut ecb,
|
||||||
prev_ecb: &previous_ecb,
|
prev_ecb: &previous_ecb,
|
||||||
stage_collision: &stage_collision,
|
stage_collision: &stage_collision,
|
||||||
leave_ground_ecb_lock_duration: 10,
|
leave_ground_ecb_lock_duration: 10,
|
||||||
presentation_snap: &mut presentation_snap,
|
|
||||||
downward_stick_threshold: FGi32::ONE,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
@@ -431,19 +426,15 @@ mod tests {
|
|||||||
right: FGVec2::lit("0.5", "0.0"),
|
right: FGVec2::lit("0.5", "0.0"),
|
||||||
};
|
};
|
||||||
let previous_ecb = FighterPreviousECB::from(ecb);
|
let previous_ecb = FighterPreviousECB::from(ecb);
|
||||||
let mut presentation_snap = crate::fighter::FighterPresentationSnap::default();
|
|
||||||
let mut context = CollisionContext {
|
let mut context = CollisionContext {
|
||||||
translation: &mut translation,
|
translation: &mut translation,
|
||||||
prev_translation: &previous_translation,
|
prev_translation: &previous_translation,
|
||||||
input: &FighterInput::default(),
|
|
||||||
velocity: &mut velocity,
|
velocity: &mut velocity,
|
||||||
surface: &mut surface,
|
surface: &mut surface,
|
||||||
ecb: &mut ecb,
|
ecb: &mut ecb,
|
||||||
prev_ecb: &previous_ecb,
|
prev_ecb: &previous_ecb,
|
||||||
stage_collision: &stage_collision,
|
stage_collision: &stage_collision,
|
||||||
leave_ground_ecb_lock_duration: 10,
|
leave_ground_ecb_lock_duration: 10,
|
||||||
presentation_snap: &mut presentation_snap,
|
|
||||||
downward_stick_threshold: FGi32::ONE,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ use crate::fighter::{
|
|||||||
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
Fighter, FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
||||||
FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole,
|
FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole,
|
||||||
FighterPreviousTranslation, FighterSurfaceState, FighterTranslation, FighterVelocity,
|
FighterPreviousTranslation, FighterSurfaceState, FighterTranslation, FighterVelocity,
|
||||||
FighterPresentationSnap,
|
|
||||||
};
|
};
|
||||||
use crate::fighter::surface::EcbLockPolicy;
|
use crate::fighter::surface::EcbLockPolicy;
|
||||||
use crate::game_settings::GameSettings;
|
use crate::game_settings::GameSettings;
|
||||||
@@ -194,15 +193,12 @@ impl UpdateContext<'_> {
|
|||||||
pub struct CollisionContext<'a> {
|
pub struct CollisionContext<'a> {
|
||||||
pub translation: &'a mut FighterTranslation,
|
pub translation: &'a mut FighterTranslation,
|
||||||
pub prev_translation: &'a FighterPreviousTranslation,
|
pub prev_translation: &'a FighterPreviousTranslation,
|
||||||
pub input: &'a FighterInput,
|
|
||||||
pub velocity: &'a mut FighterVelocity,
|
pub velocity: &'a mut FighterVelocity,
|
||||||
pub surface: &'a mut FighterSurfaceState,
|
pub surface: &'a mut FighterSurfaceState,
|
||||||
pub ecb: &'a mut FighterECB,
|
pub ecb: &'a mut FighterECB,
|
||||||
pub prev_ecb: &'a FighterPreviousECB,
|
pub prev_ecb: &'a FighterPreviousECB,
|
||||||
pub stage_collision: &'a StageCollision,
|
pub stage_collision: &'a StageCollision,
|
||||||
pub leave_ground_ecb_lock_duration: u32,
|
pub leave_ground_ecb_lock_duration: u32,
|
||||||
pub presentation_snap: &'a mut FighterPresentationSnap,
|
|
||||||
pub downward_stick_threshold: FGi32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CollisionContext<'_> {
|
impl CollisionContext<'_> {
|
||||||
@@ -210,8 +206,6 @@ impl CollisionContext<'_> {
|
|||||||
&self,
|
&self,
|
||||||
) -> Option<crate::fighter::collision::AirCollisionWithStageResult> {
|
) -> Option<crate::fighter::collision::AirCollisionWithStageResult> {
|
||||||
let ignored_polys = self.surface.ignored_polygon_ids();
|
let ignored_polys = self.surface.ignored_polygon_ids();
|
||||||
let ignore_soft_platforms = self.input.get_last_frame().movement.y
|
|
||||||
<= -self.downward_stick_threshold;
|
|
||||||
crate::fighter::collision::air_collide_with_stage(
|
crate::fighter::collision::air_collide_with_stage(
|
||||||
self.prev_translation,
|
self.prev_translation,
|
||||||
self.translation,
|
self.translation,
|
||||||
@@ -219,7 +213,6 @@ impl CollisionContext<'_> {
|
|||||||
self.ecb,
|
self.ecb,
|
||||||
self.stage_collision,
|
self.stage_collision,
|
||||||
&ignored_polys,
|
&ignored_polys,
|
||||||
ignore_soft_platforms,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +239,6 @@ fighter_states! {
|
|||||||
pub struct FighterFrameQuery {
|
pub struct FighterFrameQuery {
|
||||||
pub translation: &'static mut FighterTranslation,
|
pub translation: &'static mut FighterTranslation,
|
||||||
pub prev_translation: &'static mut FighterPreviousTranslation,
|
pub prev_translation: &'static mut FighterPreviousTranslation,
|
||||||
pub presentation_snap: &'static mut FighterPresentationSnap,
|
|
||||||
pub input: &'static mut FighterInput,
|
pub input: &'static mut FighterInput,
|
||||||
pub hitboxes: &'static mut FighterHitboxes,
|
pub hitboxes: &'static mut FighterHitboxes,
|
||||||
pub velocity: &'static mut FighterVelocity,
|
pub velocity: &'static mut FighterVelocity,
|
||||||
@@ -357,15 +349,12 @@ impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
|||||||
CollisionContext {
|
CollisionContext {
|
||||||
translation: &mut self.translation,
|
translation: &mut self.translation,
|
||||||
prev_translation: &self.prev_translation,
|
prev_translation: &self.prev_translation,
|
||||||
presentation_snap: &mut self.presentation_snap,
|
|
||||||
input: &self.input,
|
|
||||||
velocity: &mut self.velocity,
|
velocity: &mut self.velocity,
|
||||||
surface: &mut self.surface,
|
surface: &mut self.surface,
|
||||||
ecb: &mut self.ecb,
|
ecb: &mut self.ecb,
|
||||||
prev_ecb: &self.prev_ecb,
|
prev_ecb: &self.prev_ecb,
|
||||||
stage_collision,
|
stage_collision,
|
||||||
leave_ground_ecb_lock_duration: game_settings.fighter_common.leave_ground_ecb_frame_lock_duration,
|
leave_ground_ecb_lock_duration: game_settings.fighter_common.leave_ground_ecb_frame_lock_duration
|
||||||
downward_stick_threshold: game_settings.input_common.run_stick_threshold,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -116,12 +116,13 @@ impl Plugin for FighterInputPlugin {
|
|||||||
binding: GamepadBinding::Button(GamepadButton::North),
|
binding: GamepadBinding::Button(GamepadButton::North),
|
||||||
action: InputMapAction::Jump,
|
action: InputMapAction::Jump,
|
||||||
},
|
},
|
||||||
|
// shield/airdodge -- both shoulder buttons, Melee-style
|
||||||
GamepadInputMapElement {
|
GamepadInputMapElement {
|
||||||
binding: GamepadBinding::Button(GamepadButton::LeftTrigger2),
|
binding: GamepadBinding::Button(GamepadButton::LeftTrigger),
|
||||||
action: InputMapAction::Shield,
|
action: InputMapAction::Shield,
|
||||||
},
|
},
|
||||||
GamepadInputMapElement {
|
GamepadInputMapElement {
|
||||||
binding: GamepadBinding::Button(GamepadButton::RightTrigger2),
|
binding: GamepadBinding::Button(GamepadButton::RightTrigger),
|
||||||
action: InputMapAction::Shield,
|
action: InputMapAction::Shield,
|
||||||
},
|
},
|
||||||
// attack
|
// attack
|
||||||
|
|||||||
+3
-59
@@ -1,14 +1,9 @@
|
|||||||
//! Rollback-simulated state for an active match.
|
//! Rollback-simulated state for an active match.
|
||||||
|
|
||||||
use bevy::input::InputSystems;
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_ggrs::{RunGgrsSystems, prelude::*};
|
use bevy_ggrs::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::match_loading::{MatchConfig, MatchRules};
|
||||||
AppState,
|
|
||||||
match_loading::{MatchConfig, MatchRules},
|
|
||||||
netcode::session::SessionConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Resource, Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Resource, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct MatchState {
|
pub struct MatchState {
|
||||||
@@ -24,11 +19,6 @@ pub struct PlayerMatchState {
|
|||||||
pub deaths: u32,
|
pub deaths: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource, Debug, Default, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub struct PauseState {
|
|
||||||
pub paused: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MatchState {
|
impl MatchState {
|
||||||
pub fn from_config(config: &MatchConfig) -> Self {
|
pub fn from_config(config: &MatchConfig) -> Self {
|
||||||
let stocks_remaining = match &config.rules {
|
let stocks_remaining = match &config.rules {
|
||||||
@@ -56,56 +46,10 @@ pub struct MatchRuntimePlugin;
|
|||||||
impl Plugin for MatchRuntimePlugin {
|
impl Plugin for MatchRuntimePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.rollback_resource_with_clone::<MatchState>()
|
app.rollback_resource_with_clone::<MatchState>()
|
||||||
.checksum_resource_with_hash::<MatchState>()
|
.checksum_resource_with_hash::<MatchState>();
|
||||||
.init_resource::<PauseState>()
|
|
||||||
.configure_sets(PreUpdate, RunGgrsSystems.run_if(ggrs_should_run))
|
|
||||||
.add_systems(OnEnter(AppState::InMatch), reset_pause)
|
|
||||||
.add_systems(OnExit(AppState::InMatch), reset_pause)
|
|
||||||
.add_systems(
|
|
||||||
PreUpdate,
|
|
||||||
toggle_pause
|
|
||||||
.after(InputSystems)
|
|
||||||
.before(RunGgrsSystems)
|
|
||||||
.run_if(in_state(AppState::InMatch)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ggrs_should_run(pause: Option<Res<PauseState>>) -> bool {
|
|
||||||
!pause.is_some_and(|pause| pause.paused)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_pause(
|
|
||||||
key: Res<ButtonInput<KeyCode>>,
|
|
||||||
gamepads: Query<&Gamepad>,
|
|
||||||
session: Res<SessionConfig>,
|
|
||||||
mut pause: ResMut<PauseState>,
|
|
||||||
mut virtual_time: ResMut<Time<Virtual>>,
|
|
||||||
) {
|
|
||||||
if session.is_multiplayer() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pressed = key.just_pressed(KeyCode::Escape)
|
|
||||||
|| gamepads
|
|
||||||
.iter()
|
|
||||||
.any(|gamepad| gamepad.just_pressed(GamepadButton::Start));
|
|
||||||
if !pressed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pause.paused = !pause.paused;
|
|
||||||
virtual_time.toggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_pause(
|
|
||||||
mut pause: ResMut<PauseState>,
|
|
||||||
mut virtual_time: ResMut<Time<Virtual>>,
|
|
||||||
) {
|
|
||||||
pause.paused = false;
|
|
||||||
virtual_time.unpause();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
+1
-1
@@ -226,7 +226,7 @@ fn gamepad_input(
|
|||||||
input.accept.accumulate(&status);
|
input.accept.accumulate(&status);
|
||||||
}
|
}
|
||||||
MenuInputMapAction::Start => {
|
MenuInputMapAction::Start => {
|
||||||
input.start.accumulate(&status);
|
input.accept.accumulate(&status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,15 +42,6 @@ pub enum SessionConfigError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SessionConfig {
|
impl SessionConfig {
|
||||||
/// Returns whether this match has more than one remote participant.
|
|
||||||
pub fn is_multiplayer(&self) -> bool {
|
|
||||||
self.players.len() > 1
|
|
||||||
|| self
|
|
||||||
.players
|
|
||||||
.iter()
|
|
||||||
.any(|player| matches!(player, PlayerConnection::Remote(_)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_args(args: &Args, player_count: usize) -> Result<Self, SessionConfigError> {
|
pub fn from_args(args: &Args, player_count: usize) -> Result<Self, SessionConfigError> {
|
||||||
if player_count == 0 {
|
if player_count == 0 {
|
||||||
return Err(SessionConfigError::NoPlayers);
|
return Err(SessionConfigError::NoPlayers);
|
||||||
@@ -205,13 +196,6 @@ mod tests {
|
|||||||
config.players,
|
config.players,
|
||||||
[PlayerConnection::Local, PlayerConnection::Local]
|
[PlayerConnection::Local, PlayerConnection::Local]
|
||||||
);
|
);
|
||||||
assert!(config.is_multiplayer());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn single_local_player_is_not_multiplayer() {
|
|
||||||
let config = SessionConfig::from_args(&args(&["game", "--players", "1"]), 1).unwrap();
|
|
||||||
assert!(!config.is_multiplayer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -223,7 +207,6 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(config.players[0], PlayerConnection::Local));
|
assert!(matches!(config.players[0], PlayerConnection::Local));
|
||||||
assert!(matches!(config.players[1], PlayerConnection::Remote(_)));
|
assert!(matches!(config.players[1], PlayerConnection::Remote(_)));
|
||||||
assert!(config.is_multiplayer());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+3
-54
@@ -3,7 +3,7 @@ use bevy::prelude::*;
|
|||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
match_loading::{MatchConfig, MatchRules},
|
match_loading::{MatchConfig, MatchRules},
|
||||||
match_runtime::{MatchState, PauseState},
|
match_runtime::MatchState,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod battle_ui;
|
pub mod battle_ui;
|
||||||
@@ -11,9 +11,6 @@ pub mod errors;
|
|||||||
pub mod fighter_info;
|
pub mod fighter_info;
|
||||||
pub mod stock_icon;
|
pub mod stock_icon;
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct PauseOverlay;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct InGameUIPlugin;
|
pub struct InGameUIPlugin;
|
||||||
|
|
||||||
@@ -33,56 +30,8 @@ impl Plugin for InGameUIPlugin {
|
|||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
.add_systems(Update, sync_pause_overlay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync_pause_overlay(
|
|
||||||
pause: Res<PauseState>,
|
|
||||||
overlay: Query<Entity, With<PauseOverlay>>,
|
|
||||||
mut commands: Commands,
|
|
||||||
) {
|
|
||||||
if !pause.is_changed() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if pause.paused {
|
|
||||||
if overlay.is_empty() {
|
|
||||||
commands.spawn((
|
|
||||||
PauseOverlay,
|
|
||||||
Node {
|
|
||||||
position_type: PositionType::Absolute,
|
|
||||||
left: px(0),
|
|
||||||
right: px(0),
|
|
||||||
top: px(0),
|
|
||||||
bottom: px(0),
|
|
||||||
align_items: AlignItems::Center,
|
|
||||||
justify_content: JustifyContent::Center,
|
|
||||||
flex_direction: FlexDirection::Column,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.7)),
|
|
||||||
ZIndex(100),
|
|
||||||
children![
|
|
||||||
(
|
|
||||||
Text::new("Paused"),
|
|
||||||
TextFont::from_font_size(crate::menus::style::H1_SIZE as f32),
|
|
||||||
TextColor(Color::WHITE),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Text::new("Press Esc or Start to resume"),
|
|
||||||
TextFont::from_font_size(crate::menus::style::TEXT_SIZE as f32),
|
|
||||||
TextColor(Color::WHITE),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for entity in &overlay {
|
|
||||||
commands.entity(entity).despawn();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user