Skip to content

Commit

Permalink
hitreg de-jank attempt 1
Browse files Browse the repository at this point in the history
  • Loading branch information
haihala committed Dec 29, 2023
1 parent be65bf4 commit 5de2cf4
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 69 deletions.
2 changes: 1 addition & 1 deletion client/characters/src/actions/movement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub struct Movement {
pub duration: usize,
}
impl Movement {
pub(crate) fn impulse(amount: Vec2) -> Self {
pub fn impulse(amount: Vec2) -> Self {
Self {
amount,
duration: 1,
Expand Down
130 changes: 64 additions & 66 deletions client/lib/src/damage/hitreg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use bevy::{ecs::query::WorldQuery, prelude::*};
use strum::IntoEnumIterator;

use characters::{
ActionEvent, Attack, AttackHeight, BlockType, Hitbox, Hurtbox, ResourceType, WAGResources,
ActionEvent, Attack, AttackHeight, BlockType, Hitbox, Hurtbox, Movement, ResourceType,
WAGResources,
};
use input_parsing::InputParser;
use player_state::PlayerState;
Expand All @@ -20,22 +21,23 @@ use crate::{
use super::{hitboxes::ProjectileMarker, Combo, Defense, HitTracker, HitboxSpawner};

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(super) enum HitType {
pub(super) enum ContactType {
Strike,
Block,
Parry,
Throw,
Tech,
Stunlock,
}

#[derive(Debug, PartialEq, Clone)]
pub(super) struct Hit {
pub(super) struct Contact {
attacker: Entity,
defender: Entity,
hitbox: Entity,
overlap: Area,
hit_type: HitType,
attack: Attack,
is_opener: bool,
contact_type: ContactType,
}

#[derive(WorldQuery)]
Expand Down Expand Up @@ -133,7 +135,7 @@ pub(super) fn detect_hits(
players: Res<Players>,
hurtboxes: Query<(&Hurtbox, &Owner)>,
defenders: Query<(&Transform, &PlayerState, &InputParser)>,
) -> Vec<Hit> {
) -> Vec<Contact> {
hitboxes
.iter_mut()
.filter_map(
Expand Down Expand Up @@ -172,80 +174,67 @@ pub(super) fn detect_hits(
notifications.add(defending_player, "Intangible".to_owned());
}
return None;
} else if hit_tracker.hit_intangible {
// Just a nice notification for now.
notifications.add(attacking_player, "Meaty!".to_owned());
}

if hit_tracker.hits >= 1 {
hit_tracker.register_hit(clock.frame)
} else {
return None;
}

let (hit_type, notification) = if !state.can_block() {
(
match attack.to_hit.block_type {
BlockType::Strike(_) => HitType::Strike,
BlockType::Grab => HitType::Throw,
},
if state.action_in_progress() {
"Busy"
} else if !state.is_grounded() {
"Airborne"
} else {
"Can't avoid"
}
.into(),
)
} else if state.has_flag(StatusFlag::Parry)
&& attack.to_hit.block_type != BlockType::Grab
{
(HitType::Parry, "Parry!".into())
} else {
match attack.to_hit.block_type {
BlockType::Strike(height) => {
handle_blocking(height, parser.get_relative_stick_position())
}
BlockType::Grab => {
if teched(parser) {
notifications.add(defending_player, "Teched".into());
// Connection confirmed

return None;
}
let (avoid_notification, contact_type) = match attack.to_hit.block_type {
BlockType::Strike(height) => {
let parrying = state.has_flag(StatusFlag::Parry) && state.is_grounded();
let (blocked, reason) =
handle_blocking(height, parser.get_relative_stick_position());

(HitType::Throw, "Grappled".into())
if parrying {
(Some("Parry!".into()), ContactType::Parry)
} else if blocked && state.can_block() {
(Some(reason), ContactType::Block)
} else {
(None, ContactType::Strike)
}
}
BlockType::Grab => {
if combo.is_some() {
(Some("Can't grab from stun".into()), ContactType::Stunlock)
} else if yomi_teched(parser)
&& (state.can_block() && !state.action_in_progress())
{
(Some("Teched".into()), ContactType::Tech)
} else {
(None, ContactType::Throw)
}
}
};

// TODO: This could be moved into hit processing, as it's not really relevant to hit recognition
let is_opener = combo.is_none() && hit_type == HitType::Strike;
if combo.is_none() {
notifications.add(
defending_player,
format!(
"{} - {}",
if is_opener { "Opener!" } else { "Avoid" },
notification,
),
);
if let Some(reason) = avoid_notification {
notifications.add(defending_player, format!("Avoid - {}", reason,));
}

if hit_tracker.hit_intangible {
// Just a nice notification for now.
notifications.add(attacking_player, "Meaty!".to_owned());
}

Some(Hit {
Some(Contact {
defender,
attacker,
hitbox: hitbox_entity,
overlap,
hit_type,
is_opener,
attack: attack.to_owned(),
contact_type,
})
},
)
.collect()
}

pub(super) fn apply_hits(
In(hits): In<Vec<Hit>>,
pub(super) fn apply_connections(
In(hits): In<Vec<Contact>>,
mut commands: Commands,
mut notifications: ResMut<Notifications>,
combo: Option<Res<Combo>>,
Expand All @@ -256,7 +245,10 @@ pub(super) fn apply_hits(
) {
if hits.len() == 2 {
// TODO: Handle strike and throw clash
if hits.iter().all(|hit| hit.hit_type == HitType::Throw) {
if hits
.iter()
.all(|hit| hit.contact_type == ContactType::Throw)
{
// Two grabs can't hit on the same frame
for mut player in &mut players {
player
Expand All @@ -277,8 +269,8 @@ pub(super) fn apply_hits(
commands.insert_resource(Combo);
}

let (mut attacker_actions, mut defender_actions, sound, particle) = match hit.hit_type {
HitType::Strike | HitType::Throw => {
let (mut attacker_actions, mut defender_actions, sound, particle) = match hit.contact_type {
ContactType::Strike | ContactType::Throw => {
// Handle blocking and state transitions here
attacker.state.register_hit();
defender.defense.reset();
Expand All @@ -289,7 +281,7 @@ pub(super) fn apply_hits(
VisualEffect::Hit,
)
}
HitType::Block => {
ContactType::Block => {
attacker.state.register_hit(); // TODO: Specify it was blocked
defender.defense.bump_streak(clock.frame);
defender
Expand All @@ -315,7 +307,7 @@ pub(super) fn apply_hits(
VisualEffect::Block,
)
}
HitType::Parry => (
ContactType::Parry => (
vec![],
vec![
ActionEvent::ModifyResource(ResourceType::Meter, GI_PARRY_METER_GAIN),
Expand All @@ -324,9 +316,15 @@ pub(super) fn apply_hits(
SoundEffect::Clash,
VisualEffect::Clash,
),
ContactType::Tech | ContactType::Stunlock => (
vec![],
vec![Movement::impulse(Vec2::X * -8.0).into()],
SoundEffect::Clash,
VisualEffect::Clash,
),
};

if hit.is_opener {
if combo.is_none() {
sounds.play(SoundEffect::Whoosh); // TODO change sound effect
attacker_actions = handle_opener(attacker_actions, attacker.stats);
attacker_actions.push(ActionEvent::ModifyResource(
Expand All @@ -350,24 +348,24 @@ pub(super) fn apply_hits(
}
}

fn handle_blocking(height: AttackHeight, stick: StickPosition) -> (HitType, String) {
fn handle_blocking(height: AttackHeight, stick: StickPosition) -> (bool, String) {
let blocking_high = stick == StickPosition::W;
let blocking_low = stick == StickPosition::SW;

if !(blocking_high || blocking_low) {
(HitType::Strike, "Not blocking".into())
(false, "Not blocking".into())
} else if match height {
AttackHeight::Low => blocking_low,
AttackHeight::Mid => blocking_low || blocking_high,
AttackHeight::High => blocking_high,
} {
(HitType::Block, "Blocked!".into())
(true, "Blocked!".into())
} else {
(HitType::Strike, format!("Hit {:?}", height))
(false, format!("Hit {:?}", height))
}
}

fn teched(parser: &InputParser) -> bool {
fn yomi_teched(parser: &InputParser) -> bool {
parser.head_is_clear()
}

Expand Down
2 changes: 1 addition & 1 deletion client/lib/src/damage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl Plugin for DamagePlugin {
(
hitboxes::spawn_new_hitboxes,
hitreg::clash_parry,
hitreg::detect_hits.pipe(hitreg::apply_hits),
hitreg::detect_hits.pipe(hitreg::apply_connections),
hitboxes::handle_despawn_flags,
hitboxes::despawn_marked,
hitreg::stun_actions,
Expand Down
2 changes: 1 addition & 1 deletion client/lib/src/player/move_activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ pub(super) fn move_activator(
continue;
};

if state.unlock_frame().is_some() || state.free_since.is_none() {
if state.unlock_frame().is_some() {
continue;
}

Expand Down

0 comments on commit 5de2cf4

Please sign in to comment.