diff --git a/client/characters/src/actions/action.rs b/client/characters/src/actions/action.rs index 5f72e1e3..1613d377 100644 --- a/client/characters/src/actions/action.rs +++ b/client/characters/src/actions/action.rs @@ -2,7 +2,8 @@ use bevy::prelude::*; use wag_core::Animation; use crate::{ - ActionBlock, ActionRequirement, Attack, CancelCategory, CancelRule, ContinuationRequirement, + ActionBlock, ActionEvent, ActionRequirement, AnimationRequest, Attack, CancelCategory, + CancelRule, ContinuationRequirement, FlashRequest, ResourceType, }; #[derive(Clone)] @@ -66,6 +67,41 @@ impl Action { ) } + pub fn throw_target( + animation: impl Into, + duration: usize, + damage: i32, + launch_impulse: Vec2, + ) -> Self { + Action::new( + None, + CancelCategory::Uncancellable, + vec![ActionBlock { + events: vec![ + ActionEvent::Animation(AnimationRequest { + animation: animation.into(), + invert: true, + ..default() + }), + ActionEvent::ModifyResource(ResourceType::Health, -damage), + if launch_impulse == Vec2::ZERO { + ActionEvent::Noop + } else { + ActionEvent::Launch { + impulse: launch_impulse, + } + }, + ActionEvent::Flash(FlashRequest::hit_flash()), + ActionEvent::Hitstop, + ActionEvent::Lock(duration), + ], + exit_requirement: ContinuationRequirement::Time(duration), + ..default() + }], + vec![], + ) + } + pub fn ground_normal( input: &'static str, animation: impl Into, diff --git a/client/characters/src/actions/action_block.rs b/client/characters/src/actions/action_block.rs index bf6c2a66..bbb89b1e 100644 --- a/client/characters/src/actions/action_block.rs +++ b/client/characters/src/actions/action_block.rs @@ -1,5 +1,4 @@ -use crate::{ActionEvent, ActionRequirement, CancelRule, FlashRequest, ResourceType, Situation}; -use bevy::prelude::*; +use crate::{ActionEvent, ActionRequirement, CancelRule, Situation}; #[derive(Clone, Debug, PartialEq)] pub struct ActionBlock { @@ -26,21 +25,6 @@ impl ActionBlock { self.clone() } } - - pub fn throw_target(damage: i32, launch_impulse: Vec2) -> Self { - Self { - events: vec![ - ActionEvent::ModifyResource(ResourceType::Health, -damage), - ActionEvent::Launch { - // This may be broken, but it may have been fixed while - // fixing flipping launch velocities - impulse: launch_impulse, - }, - ActionEvent::Flash(FlashRequest::hit_flash()), - ], - ..default() - } - } } #[derive(Clone, Debug, Default, PartialEq)] diff --git a/client/characters/src/actions/action_event.rs b/client/characters/src/actions/action_event.rs index 9e8f5028..449ef402 100644 --- a/client/characters/src/actions/action_event.rs +++ b/client/characters/src/actions/action_event.rs @@ -33,6 +33,7 @@ pub enum ActionEvent { CameraTilt(Vec2), CameraShake, // TODO: Add strength Flash(FlashRequest), + Lock(usize), Noop, // makes writing macros easier } impl ActionEvent { diff --git a/client/characters/src/characters/mizku.rs b/client/characters/src/characters/mizku.rs index bdc805c4..7c86339e 100644 --- a/client/characters/src/characters/mizku.rs +++ b/client/characters/src/characters/mizku.rs @@ -10,7 +10,7 @@ use wag_core::{ }; use crate::{ - actions::{ActionRequirement, AnimationRequest, Projectile}, + actions::{ActionRequirement, Projectile}, resources::{RenderInstructions, ResourceType}, Action, ActionBlock, ActionEvent::*, @@ -493,23 +493,7 @@ fn normals() -> impl Iterator { ), ( MizkuActionId::StandThrowTarget, - Action::grounded( - None, - CancelCategory::Uncancellable, - vec![ - ActionBlock { - events: vec![AnimationRequest { - animation: MizkuAnimation::StandThrowTarget.into(), - invert: true, - ..default() - } - .into()], - exit_requirement: ContinuationRequirement::Time(20), - ..default() - }, - ActionBlock::throw_target(10, Vec2::new(2.0, 3.0)), - ], - ), + Action::throw_target(MizkuAnimation::StandThrowTarget, 20, 10, Vec2::ZERO), ), ( MizkuActionId::CrouchThrow, @@ -547,22 +531,11 @@ fn normals() -> impl Iterator { ), ( MizkuActionId::CrouchThrowTarget, - Action::grounded( - None, - CancelCategory::Uncancellable, - vec![ - ActionBlock { - events: vec![AnimationRequest { - animation: MizkuAnimation::CrouchThrowTarget.into(), - invert: true, - ..default() - } - .into()], - exit_requirement: ContinuationRequirement::Time(20), - ..default() - }, - ActionBlock::throw_target(10, Vec2::new(-2.0, 3.0)), - ], + Action::throw_target( + MizkuAnimation::CrouchThrowTarget, + 21, + 10, + Vec2::new(-5.0, 2.0), ), ), ( @@ -601,23 +574,7 @@ fn normals() -> impl Iterator { ), ( MizkuActionId::AirThrowTarget, - Action::airborne( - None, - CancelCategory::Uncancellable, - vec![ - ActionBlock::throw_target(10, Vec2::new(-2.0, 2.0)), - ActionBlock { - events: vec![AnimationRequest { - animation: MizkuAnimation::AirThrowTarget.into(), - invert: true, - ..default() - } - .into()], - exit_requirement: ContinuationRequirement::Time(60), - ..default() - }, - ], - ), + Action::throw_target(MizkuAnimation::AirThrowTarget, 3, 10, Vec2::new(-2.0, 2.0)), ), ] .into_iter() diff --git a/client/input_parsing/src/input_stream/parrot_stream.rs b/client/input_parsing/src/input_stream/parrot_stream.rs index 8f7f33cb..1f0f1a0f 100644 --- a/client/input_parsing/src/input_stream/parrot_stream.rs +++ b/client/input_parsing/src/input_stream/parrot_stream.rs @@ -28,15 +28,15 @@ impl ParrotStream { pub fn cycle(&mut self) { self.mode = match self.mode { ParrotMode::Listening => { - dbg!("Starting playback."); + println!("Starting playback."); ParrotMode::Repeating } ParrotMode::Repeating => { - dbg!("Entered direct control mode."); + println!("Entered direct control mode."); ParrotMode::Noop } ParrotMode::Noop => { - dbg!("Starting recording."); + println!("Starting recording."); self.buffer = vec![]; self.buffer_index = 0; ParrotMode::Listening diff --git a/client/lib/src/assets/models.rs b/client/lib/src/assets/models.rs index 13764c85..a8c4d540 100644 --- a/client/lib/src/assets/models.rs +++ b/client/lib/src/assets/models.rs @@ -45,7 +45,7 @@ pub fn prep_player_gltf( for (entity, parent, name, instance, update_material) in &unloaded_instances { if scene_manager.instance_is_ready(**instance) { cmds.entity(entity).remove::(); - dbg!("Scene is ready"); + println!("Scene is ready"); assign_joints( name.cloned().unwrap_or_default().as_str(), entity, @@ -54,7 +54,7 @@ pub fn prep_player_gltf( &children, &names, ); - dbg!("Joints are ready"); + println!("Joints are ready"); } // Iterate over all entities in scene (once it's loaded) diff --git a/client/lib/src/damage/hitboxes.rs b/client/lib/src/damage/hitboxes.rs index f5cbfc11..49862169 100644 --- a/client/lib/src/damage/hitboxes.rs +++ b/client/lib/src/damage/hitboxes.rs @@ -108,7 +108,7 @@ impl HitboxSpawner { } } -pub(super) fn spawn_new( +pub(super) fn spawn_new_hitboxes( mut commands: Commands, clock: Res, models: Res, diff --git a/client/lib/src/damage/hitreg.rs b/client/lib/src/damage/hitreg.rs index d4061686..098c5b71 100644 --- a/client/lib/src/damage/hitreg.rs +++ b/client/lib/src/damage/hitreg.rs @@ -412,13 +412,13 @@ pub(super) fn snap_and_switch( }); if actions.contains(&ActionEvent::SnapToOpponent) { - let switch = actions.contains(&ActionEvent::SideSwitch) as i32 as f32; + let switch = actions.contains(&ActionEvent::SideSwitch); let raw_diff = self_tf.translation.x - other_tf.translation.x; // This ought to be positive when attacker is on the left let width_between = (self_pushbox.width() + other_pushbox.width()) / 2.0; let new_position = other_tf.translation - + Vec3::X * raw_diff.signum() * width_between * (1.0 - (2.0 * switch)); + + Vec3::X * raw_diff.signum() * width_between * (if switch { -1.0 } else { 1.0 }); self_tf.translation = new_position; self_velocity.sync_with(&other_velocity); @@ -431,6 +431,10 @@ pub(super) fn stun_actions( clock: Res, ) { for (mut state, mut velocity, facing) in &mut query { + if state.unlock_frame().is_some() { + continue; + } + for action in state.drain_matching_actions(|action| { if matches!( *action, diff --git a/client/lib/src/damage/mod.rs b/client/lib/src/damage/mod.rs index 7d9f7d2f..a474432f 100644 --- a/client/lib/src/damage/mod.rs +++ b/client/lib/src/damage/mod.rs @@ -21,7 +21,7 @@ impl Plugin for DamagePlugin { app.add_systems( Update, ( - hitboxes::spawn_new, + hitboxes::spawn_new_hitboxes, hitreg::clash_parry, hitreg::detect_hits.pipe(hitreg::apply_hits), hitreg::stun_actions, diff --git a/client/lib/src/dev/mod.rs b/client/lib/src/dev/mod.rs index 02d45a78..cfe7b721 100644 --- a/client/lib/src/dev/mod.rs +++ b/client/lib/src/dev/mod.rs @@ -62,7 +62,7 @@ fn setup(mut config: ResMut) { fn shader_test_system(keys: Res>, mut players: Query<&mut PlayerState>) { if keys.just_pressed(KeyCode::S) { - dbg!("Playing shader flash"); + println!("Playing shader flash"); for mut player in &mut players { player.add_actions(vec![ActionEvent::Flash(FlashRequest { color: GI_PARRY_FLASH_COLOR, @@ -75,7 +75,7 @@ fn shader_test_system(keys: Res>, mut players: Query<&mut PlayerS fn audio_test_system(keys: Res>, mut sounds: ResMut) { if keys.just_pressed(KeyCode::A) { - dbg!("Playing whoosh audio"); + println!("Playing whoosh audio"); sounds.play(SoundEffect::Whoosh); } } @@ -83,6 +83,7 @@ fn audio_test_system(keys: Res>, mut sounds: ResMut) { fn fullscreen_toggle(keys: Res>, mut windows: Query<&mut Window>) { if keys.just_pressed(KeyCode::F) { let mut win = windows.get_single_mut().unwrap(); + println!("Fullscreen toggle"); win.mode = match win.mode { WindowMode::Windowed => WindowMode::BorderlessFullscreen, @@ -94,6 +95,7 @@ fn fullscreen_toggle(keys: Res>, mut windows: Query<&mut Window>) fn pause_toggle(keys: Res>, mut time: ResMut>) { if keys.just_pressed(KeyCode::P) { + println!("Pause toggle"); let new_speed = 1.0 - time.relative_speed(); time.set_relative_speed(new_speed); } diff --git a/client/lib/src/physics/mod.rs b/client/lib/src/physics/mod.rs index 84ac00bd..66e56e9b 100644 --- a/client/lib/src/physics/mod.rs +++ b/client/lib/src/physics/mod.rs @@ -75,6 +75,10 @@ fn player_gravity( )>, ) { for (mut velocity, mut state, mut spawner, tf, stats) in &mut players { + if state.unlock_frame().is_some() { + continue; + } + let is_airborne = tf.translation.y > GROUND_PLANE_HEIGHT; if is_airborne { @@ -96,6 +100,10 @@ fn player_input( mut query: Query<(&mut PlayerState, &mut PlayerVelocity, &Stats, &Facing)>, ) { for (mut state, mut velocity, status_effects, facing) in &mut query { + if state.unlock_frame().is_some() { + continue; + } + for _ in state.drain_matching_actions(|action| { if ActionEvent::ClearMovement == *action { Some(()) @@ -140,12 +148,19 @@ struct PlayerMovingQuery<'a> { fn move_players(mut query: Query) { for mut p in &mut query { + if p.state.unlock_frame().is_some() { + continue; + } p.tf.translation += p.velocity.get_shift().extend(0.0); } } fn push_players(mut query: Query, players: Res) { if let Ok([p1, p2]) = query.get_many_mut([players.one, players.two]) { + if p1.state.unlock_frame().is_some() || p2.state.unlock_frame().is_some() { + return; + } + if let Some(overlap) = p1 .push_box .with_offset(p1.tf.translation.truncate()) @@ -195,6 +210,10 @@ fn clamp_players( let right_border = camera_x + VIEWPORT_HALFWIDTH - CAMERA_EDGE_COLLISION_PADDING; if let Ok([mut p1, mut p2]) = queries.p0().get_many_mut([players.one, players.two]) { + if p1.state.unlock_frame().is_some() || p2.state.unlock_frame().is_some() { + return; + } + // Either neither or both should be pushing assert!(p1.velocity.pushing == p2.velocity.pushing); let pushing = p1.velocity.pushing || p2.velocity.pushing; diff --git a/client/lib/src/player/locks.rs b/client/lib/src/player/locks.rs new file mode 100644 index 00000000..212fa0cd --- /dev/null +++ b/client/lib/src/player/locks.rs @@ -0,0 +1,38 @@ +use bevy::prelude::*; +use characters::ActionEvent; +use player_state::PlayerState; +use wag_core::{Clock, Joint, Joints}; + +pub fn handle_locks( + mut players: Query<(&mut PlayerState, Entity, &Joints)>, + clock: Res, + mut tfs: Query<&mut Transform>, +) { + for (mut state, player_entity, joints) in &mut players { + if let Some(unlock_frame) = state.unlock_frame() { + if unlock_frame <= clock.frame { + // Move the player by abdomen joint transform to snap the character where the model is + let model = joints.nodes.get(&Joint::Abdomen).unwrap(); + let [model_tf, mut player_tf] = tfs.get_many_mut([*model, player_entity]).unwrap(); + player_tf.translation -= Vec3 { + // This is a hack, as there is no good bone to base the offset on + x: model_tf.translation.x, + y: model_tf.translation.y - 0.95, + z: 0.0, + }; + + state.unlock(player_tf.translation.y > 0.1); + } + } + + for duration in state.drain_matching_actions(|action| { + if let ActionEvent::Lock(frames) = action { + Some(frames.to_owned()) + } else { + None + } + }) { + state.lock(duration + clock.frame); + } + } +} diff --git a/client/lib/src/player/mod.rs b/client/lib/src/player/mod.rs index b167db5e..9463beaf 100644 --- a/client/lib/src/player/mod.rs +++ b/client/lib/src/player/mod.rs @@ -2,12 +2,12 @@ mod asset_updater; mod charge_accumulator; mod condition_management; mod dynamic_colliders; +mod locks; mod move_activation; mod move_advancement; mod movement; mod player_flash; mod recovery; -mod root_mover; mod size_adjustment; use characters::{dummy, mizku, Character, Inventory, WAGResources}; @@ -33,8 +33,6 @@ pub use move_activation::MoveBuffer; pub use player_flash::{ExtendedFlashMaterial, FlashMaterial}; -use root_mover::RootMover; - const PLAYER_SPAWN_DISTANCE: f32 = 2.5; // Distance from x=0(middle) const PLAYER_SPAWN_HEIGHT: f32 = GROUND_PLANE_HEIGHT + 0.001; @@ -76,6 +74,7 @@ impl Plugin for PlayerPlugin { move_activation::special_cancel, move_activation::move_activator, move_advancement::move_advancement, + locks::handle_locks, // This being the first system after hit move advancement is important recovery::stun_recovery, recovery::ground_recovery, movement::movement, @@ -84,7 +83,6 @@ impl Plugin for PlayerPlugin { condition_management::manage_conditions, asset_updater::update_animation, asset_updater::update_audio, - root_mover::update_root_transform, ) .chain() .in_set(WAGStage::PlayerUpdates), @@ -168,18 +166,15 @@ fn spawn_player( player, )) .with_children(move |parent| { - parent.spawn(( - HookedSceneBundle { - scene: SceneBundle { - scene: models[&character.model].clone(), - ..default() - }, - hook: SceneHook::new(move |_, cmds| { - cmds.insert((PlayerModelHook(colors.clone()), NoFrustumCulling)); - }), + parent.spawn((HookedSceneBundle { + scene: SceneBundle { + scene: models[&character.model].clone(), + ..default() }, - RootMover, - )); + hook: SceneHook::new(move |_, cmds| { + cmds.insert((PlayerModelHook(colors.clone()), NoFrustumCulling)); + }), + },)); }) .id() } @@ -201,7 +196,7 @@ fn setup_combat( mut clock: ResMut, bevy_time: Res