use bevy::prelude::*; use bevy_ggrs::prelude::GgrsSchedule; use super::*; use crate::{ fighter::{ Fighter, FighterHitboxes, FighterHitstop, FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole, FighterPendingHitReaction, FighterStaleMoveQueue, FighterSurfaceState, errors::FighterErrors, manifest::FighterManifest, state::FighterState, }, game_settings::GameSettings, math::int::FGi32, player::Player, schedule::GameplaySet, scripting::FighterAttackScript, }; pub fn resolve_fighter_hits( mut pending_hits: ResMut, mut attacker: Query<( &mut FighterStaleMoveQueue, &mut FighterHitboxes, &Player, &mut FighterHitstop, &mut FighterHitstopShake, &FighterTranslation, )>, mut receiver: Query<( &mut FighterDamage, &Fighter, &mut FighterPendingHitReaction, &FighterState, &FighterInvulnerabilityState, &FighterSurfaceState, &FighterTranslation, )>, attack_scripts: Res>, game_settings: Res, fighter_manifests: Res>, ) -> Result { let hits = std::mem::take(&mut pending_hits.0); let mut hit_offset = 0; while hit_offset < hits.len() { let defender = hits[hit_offset].defender; let next_defender = hits[hit_offset..] .iter() .position(|hit| hit.defender != defender) .map_or(hits.len(), |offset| hit_offset + offset); let Ok(( mut receiver_damage, fighter, mut pending_reaction, receiver_state, invuln_state, receiver_surface_state, receiver_translation, )) = receiver.get_mut(defender) else { hit_offset = next_defender; continue; }; let damage_data = hits[hit_offset..next_defender] .iter() .filter_map(|hit| { let Ok((stale_move_queue, _, player, _, _, attacker_translation)) = attacker.get(hit.attacker) else { return None; }; let attack_script = attack_scripts.get(&hit.attack_script)?; let hitbox = attack_script.hitboxes.get(hit.hitbox_index)?; let damage_received = stale_move_queue .get_attack_damage_multiplier(hit.attack_kind) * hitbox.damage.0; let angle_flip = receiver_translation.0.x <= attacker_translation.0.x; Some(( hit.attacker, damage_received, hitbox, player.handle, angle_flip, )) }) .collect::>(); let total_damage_received_this_frame = damage_data .iter() .map(|(_, damage, ..)| damage) .sum::(); let fighter_manifest = fighter_manifests .get(&fighter.manifest) .ok_or(FighterErrors::FighterManifestNotFound)?; let mut reactions = Vec::with_capacity(damage_data.len()); 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( &fighter_manifest.attributes, *receiver_damage, FighterDamage(total_damage_received_this_frame), false, crouch_canceling, ); let launch_angle = hitbox.angle.get_angle( &knockback, receiver_surface_state.is_grounded(), &game_settings, ); let launch_angle = if angle_flip { FGi32::lit("180.0") - launch_angle } else { launch_angle }; // Attacker hitstop and data let victim_hitstop_frames = HitStop::calculate(FighterDamage(attack_damage), crouch_canceling); let attacker_hitstop_frames = HitStop::calculate(FighterDamage(attack_damage), false); reactions.push(( attacker_entity, attacker_slot, FighterHitReaction { launch_velocity: knockback.get_launch_speed(&game_settings), launch_angle, hitstun_frames: knockback.get_hitstun_frames(&game_settings), victim_hitstop_frames, attacker_hitstop_frames, kind: HitReactionKind::Flinch, damage: FighterDamage(attack_damage), }, )); } match invuln_state { FighterInvulnerabilityState::Vulnerable => { receiver_damage.0 += total_damage_received_this_frame } _ => (), } for (attacker_entity, _, reaction) in &reactions { let Ok((.., _, mut hitstop, mut hitstop_shake, _)) = attacker.get_mut(*attacker_entity) else { continue; }; if hitstop.remaining_frames < reaction.attacker_hitstop_frames.0 { let shake_kind = if receiver_state.is_visually_grounded() { FighterHitstopShakeKind::Horizontal } else { FighterHitstopShakeKind::Vertical }; hitstop.remaining_frames = reaction.attacker_hitstop_frames.0; let shake_frames = reaction .attacker_hitstop_frames .calculate_shake_frames(&game_settings); hitstop_shake.total_frames = shake_frames; hitstop_shake.remaining_frames = shake_frames; hitstop_shake.kind = shake_kind; hitstop_shake.role = FighterHitstopShakeRole::Attacker; } } match invuln_state { FighterInvulnerabilityState::Vulnerable => { pending_reaction.0 = reactions .into_iter() .min_by(|(_, port_a, _), (_, port_b, _)| port_a.cmp(port_b)) .map(|(_, _, reaction)| reaction); } _ => (), } hit_offset = next_defender; } for hit in hits { let Ok((mut stale_move_queue, mut hitboxes, ..)) = attacker.get_mut(hit.attacker) else { continue; }; if let Some(ref mut hitbox) = hitboxes.0 { if !hitbox.stalled { hitbox.stalled = true; stale_move_queue.push(hitbox.kind); } } } Ok(()) } pub struct FighterReactionPlugin; impl Plugin for FighterReactionPlugin { fn build(&self, app: &mut App) { app.add_systems( GgrsSchedule, resolve_fighter_hits .in_set(GameplaySet::Attacks) .after(super::intersect_attacks_with_hurtboxes), ); } }