Attack system revamp #82
@@ -52,7 +52,8 @@ pub enum HitReactionKind {
|
|||||||
pub struct FighterHitReaction {
|
pub struct FighterHitReaction {
|
||||||
pub launch_velocity: FGi32,
|
pub launch_velocity: FGi32,
|
||||||
pub launch_angle: FGi32,
|
pub launch_angle: FGi32,
|
||||||
pub hitstop_frames: HitStop,
|
pub victim_hitstop_frames: HitStop,
|
||||||
|
pub attacker_hitstop_frames: HitStop,
|
||||||
pub hitstun_frames: u32,
|
pub hitstun_frames: u32,
|
||||||
pub kind: HitReactionKind,
|
pub kind: HitReactionKind,
|
||||||
pub damage: FighterDamage,
|
pub damage: FighterDamage,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ pub fn resolve_fighter_hits(
|
|||||||
&Player,
|
&Player,
|
||||||
&mut FighterHitstop,
|
&mut FighterHitstop,
|
||||||
&mut FighterHitstopShake,
|
&mut FighterHitstopShake,
|
||||||
|
&FighterTranslation,
|
||||||
)>,
|
)>,
|
||||||
mut receiver: Query<(
|
mut receiver: Query<(
|
||||||
&mut FighterDamage,
|
&mut FighterDamage,
|
||||||
@@ -33,6 +34,7 @@ pub fn resolve_fighter_hits(
|
|||||||
&FighterState,
|
&FighterState,
|
||||||
&FighterInvulnerabilityState,
|
&FighterInvulnerabilityState,
|
||||||
&FighterSurfaceState,
|
&FighterSurfaceState,
|
||||||
|
&FighterTranslation,
|
||||||
)>,
|
)>,
|
||||||
attack_scripts: Res<Assets<FighterAttackScript>>,
|
attack_scripts: Res<Assets<FighterAttackScript>>,
|
||||||
game_settings: Res<GameSettings>,
|
game_settings: Res<GameSettings>,
|
||||||
@@ -55,6 +57,7 @@ pub fn resolve_fighter_hits(
|
|||||||
receiver_state,
|
receiver_state,
|
||||||
invuln_state,
|
invuln_state,
|
||||||
receiver_surface_state,
|
receiver_surface_state,
|
||||||
|
receiver_translation,
|
||||||
)) = receiver.get_mut(defender)
|
)) = receiver.get_mut(defender)
|
||||||
else {
|
else {
|
||||||
hit_offset = next_defender;
|
hit_offset = next_defender;
|
||||||
@@ -64,7 +67,7 @@ pub fn resolve_fighter_hits(
|
|||||||
let damage_data = hits[hit_offset..next_defender]
|
let damage_data = hits[hit_offset..next_defender]
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|hit| {
|
.filter_map(|hit| {
|
||||||
let Ok((stale_move_queue, facing_dir, _, player, _, _)) =
|
let Ok((stale_move_queue, facing_dir, _, player, _, _, attacker_translation)) =
|
||||||
attacker.get(hit.attacker)
|
attacker.get(hit.attacker)
|
||||||
else {
|
else {
|
||||||
return None;
|
return None;
|
||||||
@@ -74,12 +77,13 @@ pub fn resolve_fighter_hits(
|
|||||||
let damage_received = stale_move_queue
|
let damage_received = stale_move_queue
|
||||||
.get_attack_damage_multiplier(hit.attack_kind)
|
.get_attack_damage_multiplier(hit.attack_kind)
|
||||||
* hitbox.damage.0;
|
* hitbox.damage.0;
|
||||||
|
let angle_flip = receiver_translation.0.x <= attacker_translation.0.x;
|
||||||
Some((
|
Some((
|
||||||
hit.attacker,
|
hit.attacker,
|
||||||
damage_received,
|
damage_received,
|
||||||
hitbox,
|
hitbox,
|
||||||
matches!(facing_dir, FighterFacingDirection::Left),
|
|
||||||
player.handle,
|
player.handle,
|
||||||
|
angle_flip,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -93,28 +97,35 @@ pub fn resolve_fighter_hits(
|
|||||||
.ok_or(FighterErrors::FighterManifestNotFound)?;
|
.ok_or(FighterErrors::FighterManifestNotFound)?;
|
||||||
let mut reactions = Vec::with_capacity(damage_data.len());
|
let mut reactions = Vec::with_capacity(damage_data.len());
|
||||||
|
|
||||||
for (attacker_entity, attack_damage, hitbox, facing_left, attacker_slot) in damage_data {
|
let crouch_canceling = if let FighterState::Crouch(crouch_state) = &receiver_state {
|
||||||
|
crouch_state.is_crouch_cancelling()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
for (attacker_entity, attack_damage, hitbox, attacker_slot, angle_flip) in damage_data {
|
||||||
let knockback = hitbox.calculate_knockback(
|
let knockback = hitbox.calculate_knockback(
|
||||||
&fighter_manifest.attributes,
|
&fighter_manifest.attributes,
|
||||||
*receiver_damage,
|
*receiver_damage,
|
||||||
FighterDamage(total_damage_received_this_frame),
|
FighterDamage(total_damage_received_this_frame),
|
||||||
false,
|
false,
|
||||||
false,
|
crouch_canceling,
|
||||||
);
|
);
|
||||||
let angle = hitbox.angle.get_angle(
|
let launch_angle = hitbox.angle.get_angle(
|
||||||
&knockback,
|
&knockback,
|
||||||
receiver_surface_state.is_grounded(),
|
receiver_surface_state.is_grounded(),
|
||||||
&game_settings,
|
&game_settings,
|
||||||
);
|
);
|
||||||
dbg!(knockback, angle);
|
|
||||||
let launch_angle = if facing_left {
|
let launch_angle = if angle_flip {
|
||||||
FGi32::lit("180.0") - angle
|
FGi32::lit("180.0") - launch_angle
|
||||||
} else {
|
} else {
|
||||||
angle
|
launch_angle
|
||||||
};
|
};
|
||||||
|
|
||||||
// Attacker hitstop and data
|
// Attacker hitstop and data
|
||||||
let hitstop = HitStop::calculate(FighterDamage(attack_damage), matches!(receiver_state, FighterState::Crouch(..)));
|
let victim_hitstop_frames = HitStop::calculate(FighterDamage(attack_damage), crouch_canceling);
|
||||||
|
let attacker_hitstop_frames = HitStop::calculate(FighterDamage(attack_damage), false);
|
||||||
reactions.push((
|
reactions.push((
|
||||||
attacker_entity,
|
attacker_entity,
|
||||||
attacker_slot,
|
attacker_slot,
|
||||||
@@ -122,7 +133,8 @@ pub fn resolve_fighter_hits(
|
|||||||
launch_velocity: knockback.get_launch_speed(&game_settings),
|
launch_velocity: knockback.get_launch_speed(&game_settings),
|
||||||
launch_angle,
|
launch_angle,
|
||||||
hitstun_frames: knockback.get_hitstun_frames(&game_settings),
|
hitstun_frames: knockback.get_hitstun_frames(&game_settings),
|
||||||
hitstop_frames: hitstop,
|
victim_hitstop_frames,
|
||||||
|
attacker_hitstop_frames,
|
||||||
kind: HitReactionKind::Flinch,
|
kind: HitReactionKind::Flinch,
|
||||||
damage: FighterDamage(attack_damage),
|
damage: FighterDamage(attack_damage),
|
||||||
},
|
},
|
||||||
@@ -137,20 +149,20 @@ pub fn resolve_fighter_hits(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (attacker_entity, _, reaction) in &reactions {
|
for (attacker_entity, _, reaction) in &reactions {
|
||||||
let Ok((.., _, mut hitstop, mut hitstop_shake)) = attacker.get_mut(*attacker_entity)
|
let Ok((.., _, mut hitstop, mut hitstop_shake, _)) = attacker.get_mut(*attacker_entity)
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if hitstop.remaining_frames < reaction.hitstop_frames.0 {
|
if hitstop.remaining_frames < reaction.attacker_hitstop_frames.0 {
|
||||||
let shake_kind = if receiver_state.is_visually_grounded() {
|
let shake_kind = if receiver_state.is_visually_grounded() {
|
||||||
FighterHitstopShakeKind::Horizontal
|
FighterHitstopShakeKind::Horizontal
|
||||||
} else {
|
} else {
|
||||||
FighterHitstopShakeKind::Vertical
|
FighterHitstopShakeKind::Vertical
|
||||||
};
|
};
|
||||||
hitstop.remaining_frames = reaction.hitstop_frames.0;
|
hitstop.remaining_frames = reaction.attacker_hitstop_frames.0;
|
||||||
let shake_frames = reaction
|
let shake_frames = reaction
|
||||||
.hitstop_frames
|
.attacker_hitstop_frames
|
||||||
.calculate_shake_frames(&game_settings);
|
.calculate_shake_frames(&game_settings);
|
||||||
hitstop_shake.total_frames = shake_frames;
|
hitstop_shake.total_frames = shake_frames;
|
||||||
hitstop_shake.remaining_frames = shake_frames;
|
hitstop_shake.remaining_frames = shake_frames;
|
||||||
|
|||||||
@@ -148,6 +148,15 @@ impl FighterStateImpl for CrouchState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CrouchState {
|
||||||
|
pub fn is_crouch_cancelling(&self) -> bool {
|
||||||
|
match self.sub_state {
|
||||||
|
CrouchSubState::Entering { .. } | CrouchSubState::Idle => true,
|
||||||
|
CrouchSubState::Exiting => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_input(state_context: &InterruptContext) -> Option<FighterState> {
|
pub fn check_input(state_context: &InterruptContext) -> Option<FighterState> {
|
||||||
let input_frame = state_context.input.get_last_frame();
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|
||||||
|
|||||||
@@ -494,9 +494,9 @@ pub fn state_interrupt_system(
|
|||||||
.expect("Fighter manifest should be valid");
|
.expect("Fighter manifest should be valid");
|
||||||
|
|
||||||
let new_state = if let Some(reaction) = pending_hit_reaction.take() {
|
let new_state = if let Some(reaction) = pending_hit_reaction.take() {
|
||||||
hitstop.remaining_frames = *reaction.hitstop_frames;
|
hitstop.remaining_frames = *reaction.victim_hitstop_frames;
|
||||||
let shake_frames = reaction
|
let shake_frames = reaction
|
||||||
.hitstop_frames
|
.victim_hitstop_frames
|
||||||
.calculate_shake_frames(&game_settings);
|
.calculate_shake_frames(&game_settings);
|
||||||
hitstop_shake.role = FighterHitstopShakeRole::Receiver;
|
hitstop_shake.role = FighterHitstopShakeRole::Receiver;
|
||||||
hitstop_shake.total_frames = shake_frames;
|
hitstop_shake.total_frames = shake_frames;
|
||||||
@@ -515,7 +515,7 @@ pub fn state_interrupt_system(
|
|||||||
|
|
||||||
if let hitstun::HitstopStateKind::Grounded = hitstop_kind {
|
if let hitstun::HitstopStateKind::Grounded = hitstop_kind {
|
||||||
let straight_up_angle = FGi32::lit("90.0");
|
let straight_up_angle = FGi32::lit("90.0");
|
||||||
if reaction.hitstop_frames
|
if reaction.victim_hitstop_frames
|
||||||
>= HitStop::new(game_settings.fighter_common.tumble_bounce_hitstop_threshold)
|
>= HitStop::new(game_settings.fighter_common.tumble_bounce_hitstop_threshold)
|
||||||
{
|
{
|
||||||
hitstop_kind = hitstun::HitstopStateKind::Airborne;
|
hitstop_kind = hitstun::HitstopStateKind::Airborne;
|
||||||
|
|||||||
@@ -260,9 +260,8 @@ pub fn compile_script(text: &str) -> Result<FighterAttackScript, String> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the original script syntax working. A hitbox without an
|
// Keep the original script syntax working. Melee's default collision
|
||||||
// explicit group behaves as its own group, which preserves the old
|
// group is shared by all hitbox slots in an attack.
|
||||||
// one-hit-per-target behavior.
|
|
||||||
{
|
{
|
||||||
let mc = mc.clone();
|
let mc = mc.clone();
|
||||||
engine.register_fn(
|
engine.register_fn(
|
||||||
@@ -280,7 +279,7 @@ pub fn compile_script(text: &str) -> Result<FighterAttackScript, String> {
|
|||||||
add_hitbox(
|
add_hitbox(
|
||||||
&mc,
|
&mc,
|
||||||
id,
|
id,
|
||||||
id,
|
0,
|
||||||
bone,
|
bone,
|
||||||
damage,
|
damage,
|
||||||
offset,
|
offset,
|
||||||
@@ -461,4 +460,19 @@ mod tests {
|
|||||||
assert_eq!(script.hitboxes[0].id, 7);
|
assert_eq!(script.hitboxes[0].id, 7);
|
||||||
assert_eq!(script.hitboxes[0].group, 3);
|
assert_eq!(script.hitboxes[0].group, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn legacy_hitboxes_share_the_default_collision_group() {
|
||||||
|
let script = compile_script(
|
||||||
|
r#"
|
||||||
|
frame(1);
|
||||||
|
hitbox(0, "hand_l", damage("1"), vec3("0", "0", "0"), fg32("1"), angle("0"), knockback_normal(), knockback("1"), fg32("1"));
|
||||||
|
hitbox(1, "hand_l", damage("1"), vec3("0", "0", "0"), fg32("1"), angle("0"), knockback_normal(), knockback("1"), fg32("1"));
|
||||||
|
frame(2);
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(script.hitboxes.iter().map(|hitbox| hitbox.group).collect::<Vec<_>>(), vec![0, 0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user