172 lines
6.2 KiB
Rust
172 lines
6.2 KiB
Rust
use std::collections::HashSet;
|
|
|
|
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<FighterPendingHits>,
|
|
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<Assets<FighterAttackScript>>,
|
|
game_settings: Res<GameSettings>,
|
|
fighter_manifests: Res<Assets<FighterManifest>>,
|
|
) -> Result {
|
|
let hits = std::mem::take(&mut pending_hits.0);
|
|
let mut hit_offset = 0;
|
|
|
|
let mut stalled_attackers = HashSet::<Entity>::new();
|
|
|
|
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)?;
|
|
dbg!(hitbox.damage.0);
|
|
let damage_received = stale_move_queue
|
|
.get_attack_damage_multiplier(hit.attack_kind)
|
|
* hitbox.damage.0;
|
|
dbg!(damage_received);
|
|
Some((
|
|
hit.attacker,
|
|
damage_received,
|
|
hitbox,
|
|
matches!(facing_dir, FighterFacingDirection::Left),
|
|
player.handle,
|
|
))
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let total_damage_received_this_frame = damage_data
|
|
.iter()
|
|
.map(|(_, damage, ..)| damage)
|
|
.sum::<FGi32>();
|
|
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
|
|
stalled_attackers.insert(attacker_entity);
|
|
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 {
|
|
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),
|
|
);
|
|
}
|
|
}
|