use bevy::prelude::*; use bevy_ggrs::prelude::GgrsSchedule; use super::*; use crate::{ fighter::{ Fighter, FighterFacingDirection, FighterHitboxes, FighterHitstop, FighterHitstopShake, FighterHitstopShakeKind, FighterHitstopShakeRole, FighterPendingHitReaction, FighterStaleMoveQueue, 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, &FighterFacingDirection, &mut FighterHitboxes, &Player, &mut FighterHitstop, &mut FighterHitstopShake, )>, mut receiver: Query<(&mut FighterDamage, &Fighter, &mut FighterPendingHitReaction, &FighterState)>, 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)) = 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, facing_dir, _, player, _, _)) = 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; Some(( hit.attacker, damage_received, hitbox, matches!(facing_dir, FighterFacingDirection::Left), player.handle, )) }) .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()); for (attacker_entity, attack_damage, hitbox, facing_left, attacker_slot) in damage_data { let knockback = hitbox.calculate_knockback( &fighter_manifest.attributes, *receiver_damage, FighterDamage(total_damage_received_this_frame), false, false, ); let angle = match &hitbox.angle { AttackAngle::Normal(angle) => *angle, AttackAngle::Sakurai => FGi32::lit("32.0"), }; let launch_angle = if facing_left { FGi32::lit("180.0") - angle } else { angle }; // Attacker hitstop and data let hitstop = 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), hitstop_frames: hitstop, kind: HitReactionKind::Flinch, damage: FighterDamage(attack_damage) }, )); } 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.hitstop_frames.0 { let shake_kind = if receiver_state.is_visually_grounded() { FighterHitstopShakeKind::Horizontal } else { FighterHitstopShakeKind::Vertical }; hitstop.remaining_frames = reaction.hitstop_frames.0; let shake_frames = reaction.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; } } 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, _facing_direction, 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), ); } }