Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77cd5033c3 |
@@ -8,8 +8,8 @@ use crate::{
|
|||||||
FighterVelocity, Grounded,
|
FighterVelocity, Grounded,
|
||||||
animation::FighterAnimationFrame,
|
animation::FighterAnimationFrame,
|
||||||
attack::{
|
attack::{
|
||||||
FighterDamage, FighterKnockbackVelocity, FighterPendingHitReaction, FighterPendingHits,
|
FighterDamage, FighterKnockbackVelocity, FighterPendingHitReaction,
|
||||||
FighterSolvedHurtboxes, FighterStaleMoveQueue,
|
FighterReceivedAttacks, FighterSolvedHurtboxes, FighterStaleMoveQueue,
|
||||||
},
|
},
|
||||||
baked_animation::FighterBoneMatrices,
|
baked_animation::FighterBoneMatrices,
|
||||||
state::FighterState,
|
state::FighterState,
|
||||||
@@ -28,7 +28,6 @@ pub(super) struct DebugSaveState {
|
|||||||
struct Checkpoint {
|
struct Checkpoint {
|
||||||
fighters: Vec<FighterCheckpoint>,
|
fighters: Vec<FighterCheckpoint>,
|
||||||
stage_collision: StageCollision,
|
stage_collision: StageCollision,
|
||||||
pending_hits: FighterPendingHits,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -51,6 +50,7 @@ struct FighterCheckpoint {
|
|||||||
knockback_velocity: FighterKnockbackVelocity,
|
knockback_velocity: FighterKnockbackVelocity,
|
||||||
stale_moves: FighterStaleMoveQueue,
|
stale_moves: FighterStaleMoveQueue,
|
||||||
hitboxes: FighterHitboxes,
|
hitboxes: FighterHitboxes,
|
||||||
|
received_attacks: FighterReceivedAttacks,
|
||||||
solved_hurtboxes: FighterSolvedHurtboxes,
|
solved_hurtboxes: FighterSolvedHurtboxes,
|
||||||
bone_matrices: FighterBoneMatrices,
|
bone_matrices: FighterBoneMatrices,
|
||||||
grounded: bool,
|
grounded: bool,
|
||||||
@@ -97,11 +97,9 @@ pub(super) fn save_or_restore(world: &mut World) {
|
|||||||
};
|
};
|
||||||
checkpoint_fighters.push(checkpoint);
|
checkpoint_fighters.push(checkpoint);
|
||||||
}
|
}
|
||||||
let pending_hits = world.resource::<FighterPendingHits>().clone();
|
|
||||||
world.resource_mut::<DebugSaveState>().checkpoint = Some(Checkpoint {
|
world.resource_mut::<DebugSaveState>().checkpoint = Some(Checkpoint {
|
||||||
fighters: checkpoint_fighters,
|
fighters: checkpoint_fighters,
|
||||||
stage_collision,
|
stage_collision,
|
||||||
pending_hits,
|
|
||||||
});
|
});
|
||||||
info!("Saved debug game state");
|
info!("Saved debug game state");
|
||||||
return;
|
return;
|
||||||
@@ -149,6 +147,7 @@ pub(super) fn save_or_restore(world: &mut World) {
|
|||||||
entity.insert(saved.knockback_velocity);
|
entity.insert(saved.knockback_velocity);
|
||||||
entity.insert(saved.stale_moves.clone());
|
entity.insert(saved.stale_moves.clone());
|
||||||
entity.insert(saved.hitboxes.clone());
|
entity.insert(saved.hitboxes.clone());
|
||||||
|
entity.insert(saved.received_attacks.clone());
|
||||||
entity.insert(saved.solved_hurtboxes.clone());
|
entity.insert(saved.solved_hurtboxes.clone());
|
||||||
entity.insert(saved.bone_matrices.clone());
|
entity.insert(saved.bone_matrices.clone());
|
||||||
if saved.grounded {
|
if saved.grounded {
|
||||||
@@ -157,7 +156,6 @@ pub(super) fn save_or_restore(world: &mut World) {
|
|||||||
entity.remove::<Grounded>();
|
entity.remove::<Grounded>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
world.insert_resource(checkpoint.pending_hits.clone());
|
|
||||||
world.insert_resource(checkpoint.stage_collision);
|
world.insert_resource(checkpoint.stage_collision);
|
||||||
for mut match_camera in world.query::<&mut MatchCamera>().iter_mut(world) {
|
for mut match_camera in world.query::<&mut MatchCamera>().iter_mut(world) {
|
||||||
match_camera.needs_snap = true;
|
match_camera.needs_snap = true;
|
||||||
@@ -186,6 +184,7 @@ fn capture_fighter(world: &World, entity: Entity) -> Option<FighterCheckpoint> {
|
|||||||
knockback_velocity: *fighter.get::<FighterKnockbackVelocity>()?,
|
knockback_velocity: *fighter.get::<FighterKnockbackVelocity>()?,
|
||||||
stale_moves: fighter.get::<FighterStaleMoveQueue>()?.clone(),
|
stale_moves: fighter.get::<FighterStaleMoveQueue>()?.clone(),
|
||||||
hitboxes: fighter.get::<FighterHitboxes>()?.clone(),
|
hitboxes: fighter.get::<FighterHitboxes>()?.clone(),
|
||||||
|
received_attacks: fighter.get::<FighterReceivedAttacks>()?.clone(),
|
||||||
solved_hurtboxes: fighter.get::<FighterSolvedHurtboxes>()?.clone(),
|
solved_hurtboxes: fighter.get::<FighterSolvedHurtboxes>()?.clone(),
|
||||||
bone_matrices: fighter.get::<FighterBoneMatrices>()?.clone(),
|
bone_matrices: fighter.get::<FighterBoneMatrices>()?.clone(),
|
||||||
grounded: fighter.contains::<Grounded>(),
|
grounded: fighter.contains::<Grounded>(),
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
mod reaction;
|
|
||||||
|
|
||||||
use bevy::ecs::entity::{EntityMapper, MapEntities};
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_ggrs::{Rollback, RollbackApp, prelude::GgrsSchedule};
|
use bevy_ggrs::{Rollback, prelude::GgrsSchedule};
|
||||||
|
use fixed_math::SinCos;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
Fighter, FighterAttributes, FighterFacingDirection, FighterHitboxes,
|
Fighter, FighterAttributes, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
||||||
FighterPreviousFacingDirection, FighterPreviousTranslation, FighterRoster,
|
FighterPreviousFacingDirection, FighterPreviousTranslation, FighterRoster,
|
||||||
FighterTranslation,
|
FighterTranslation,
|
||||||
animation::{AnimKind, FighterAnimationFrame},
|
animation::{AnimKind, FighterAnimationFrame},
|
||||||
baked_animation::{FighterBoneIndexMap, FighterBoneMatrices, FixedMat4},
|
baked_animation::{FighterBoneIndexMap, FighterBoneMatrices, FixedMat4},
|
||||||
|
errors::FighterErrors,
|
||||||
hurtbox::{FixedAffineCapsule, FixedCapsule},
|
hurtbox::{FixedAffineCapsule, FixedCapsule},
|
||||||
manifest::FighterManifest,
|
manifest::FighterManifest,
|
||||||
},
|
},
|
||||||
@@ -433,26 +432,15 @@ pub struct FighterAttackScriptAssets {
|
|||||||
#[derive(Component, Clone, Debug, Default, Hash)]
|
#[derive(Component, Clone, Debug, Default, Hash)]
|
||||||
pub struct FighterSolvedHurtboxes(pub Vec<FighterSolvedHurtbox>);
|
pub struct FighterSolvedHurtboxes(pub Vec<FighterSolvedHurtbox>);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash)]
|
#[derive(Component, Clone, Debug, Default, Hash, DerefMut, Deref)]
|
||||||
pub struct FighterHit {
|
pub struct FighterReceivedAttacks(
|
||||||
pub attacker: Entity,
|
pub Vec<(
|
||||||
pub defender: Entity,
|
/* attacker */ Entity,
|
||||||
pub attack_script: Handle<FighterAttackScript>,
|
Handle<FighterAttackScript>,
|
||||||
pub attack_kind: AttackKind,
|
AttackKind,
|
||||||
pub hitbox_index: usize,
|
/* hitbox_idx */ usize,
|
||||||
}
|
)>,
|
||||||
|
);
|
||||||
#[derive(Resource, Clone, Debug, Default, Hash)]
|
|
||||||
pub struct FighterPendingHits(pub Vec<FighterHit>);
|
|
||||||
|
|
||||||
impl MapEntities for FighterPendingHits {
|
|
||||||
fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
|
|
||||||
for hit in &mut self.0 {
|
|
||||||
hit.attacker = entity_mapper.get_mapped(hit.attacker);
|
|
||||||
hit.defender = entity_mapper.get_mapped(hit.defender);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn solve_hurtboxes(
|
pub fn solve_hurtboxes(
|
||||||
mut query: Query<(
|
mut query: Query<(
|
||||||
@@ -510,14 +498,19 @@ pub fn intersect_attacks_with_hurtboxes(
|
|||||||
&Player,
|
&Player,
|
||||||
&FighterStaleMoveQueue,
|
&FighterStaleMoveQueue,
|
||||||
)>,
|
)>,
|
||||||
query_attacked: Query<(Entity, &FighterSolvedHurtboxes)>,
|
mut query_attacked: Query<(
|
||||||
mut pending_hits: ResMut<FighterPendingHits>,
|
Entity,
|
||||||
|
&FighterSolvedHurtboxes,
|
||||||
|
&Player,
|
||||||
|
&Fighter,
|
||||||
|
&mut FighterReceivedAttacks,
|
||||||
|
)>,
|
||||||
attack_scripts: Res<Assets<FighterAttackScript>>,
|
attack_scripts: Res<Assets<FighterAttackScript>>,
|
||||||
) {
|
) {
|
||||||
pending_hits.0.clear();
|
|
||||||
|
|
||||||
for &entity_receiving in &roster.0 {
|
for &entity_receiving in &roster.0 {
|
||||||
let Ok((_, hurtboxes)) = query_attacked.get(entity_receiving) else {
|
let Ok((_, hurtboxes, _, _receiving_fighter, mut received_attacks)) =
|
||||||
|
query_attacked.get_mut(entity_receiving)
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -558,13 +551,12 @@ pub fn intersect_attacks_with_hurtboxes(
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
pending_hits.0.push(FighterHit {
|
received_attacks.push((
|
||||||
attacker: entity_hurting,
|
entity_hurting,
|
||||||
defender: entity_receiving,
|
active_attack.script.clone(),
|
||||||
attack_script: active_attack.script.clone(),
|
active_attack.kind,
|
||||||
attack_kind: active_attack.kind,
|
hitting_hitbox_idx,
|
||||||
hitbox_index: hitting_hitbox_idx,
|
));
|
||||||
});
|
|
||||||
info!("Hit by hitbox {}!!!!", hitting_hitbox_idx);
|
info!("Hit by hitbox {}!!!!", hitting_hitbox_idx);
|
||||||
|
|
||||||
active_attack.hit_fighters.push(entity_receiving);
|
active_attack.hit_fighters.push(entity_receiving);
|
||||||
@@ -572,21 +564,140 @@ pub fn intersect_attacks_with_hurtboxes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn calculate_received_attacks_trajectories(
|
||||||
|
mut attacker: Query<(
|
||||||
|
&mut FighterStaleMoveQueue,
|
||||||
|
&FighterFacingDirection,
|
||||||
|
&FighterHitboxes,
|
||||||
|
&Player,
|
||||||
|
&mut FighterHitstop,
|
||||||
|
)>,
|
||||||
|
receiver: Query<(
|
||||||
|
&mut FighterReceivedAttacks,
|
||||||
|
&mut FighterDamage,
|
||||||
|
&Fighter,
|
||||||
|
&mut FighterPendingHitReaction,
|
||||||
|
)>,
|
||||||
|
attack_scripts: Res<Assets<FighterAttackScript>>,
|
||||||
|
game_settings: Res<GameSettings>,
|
||||||
|
fighter_manifests: Res<Assets<FighterManifest>>,
|
||||||
|
) -> Result {
|
||||||
|
for (mut received_attacks, mut receiver_damage, fighter, mut pending_reactions) in receiver {
|
||||||
|
if received_attacks.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we got attacked, time to calculate the total damage
|
||||||
|
|
||||||
|
let damage_data = received_attacks
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(from_entity, attack_script, attack_kind, hitbox_idx)| {
|
||||||
|
let Ok((stale_move_queue, facing_dir, _hitboxes, player, _attacker_hitstop)) =
|
||||||
|
attacker.get(*from_entity)
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(attack_script) = attack_scripts.get(attack_script) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let hitbox = &attack_script.hitboxes[*hitbox_idx];
|
||||||
|
|
||||||
|
let damage_received =
|
||||||
|
stale_move_queue.get_attack_damage_multiplier(*attack_kind) * hitbox.damage.0;
|
||||||
|
|
||||||
|
Some((
|
||||||
|
from_entity,
|
||||||
|
damage_received,
|
||||||
|
hitbox,
|
||||||
|
attack_script,
|
||||||
|
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![];
|
||||||
|
|
||||||
|
for (attacker_entity, attack_damage, hitbox, _attack_script, 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
|
||||||
|
};
|
||||||
|
let (sin, cos) = launch_angle.sin_cos();
|
||||||
|
let launch_direction = FGVec2::new(cos, sin);
|
||||||
|
let hitstop = HitStop::calculate(FighterDamage(attack_damage), false);
|
||||||
|
reactions.push((
|
||||||
|
*attacker_entity,
|
||||||
|
attacker_slot,
|
||||||
|
FighterHitReaction {
|
||||||
|
launch_velocity: launch_direction * knockback.get_launch_speed(&game_settings),
|
||||||
|
hitstun_frames: knockback.get_hitstun_frames(&game_settings),
|
||||||
|
hitstop_frames: hitstop,
|
||||||
|
kind: HitReactionKind::Flinch,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver_damage.0 += total_damage_received_this_frame;
|
||||||
|
|
||||||
|
for (attacker_entity, _, reaction) in reactions.iter() {
|
||||||
|
let Ok((.., mut hitstop)) = attacker.get_mut(*attacker_entity) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
hitstop.remaining_frames = reaction.hitstop_frames.max(hitstop.remaining_frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick attack by port priority
|
||||||
|
pending_reactions.0 = reactions
|
||||||
|
.into_iter()
|
||||||
|
.min_by(|(_, port_a, _), (_, port_b, _)| port_a.cmp(port_b))
|
||||||
|
.map(|(_, _, reaction)| reaction);
|
||||||
|
|
||||||
|
received_attacks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FighterAttackPlugin;
|
pub struct FighterAttackPlugin;
|
||||||
|
|
||||||
impl Plugin for FighterAttackPlugin {
|
impl Plugin for FighterAttackPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_resource::<FighterPendingHits>()
|
|
||||||
.add_plugins(reaction::FighterReactionPlugin)
|
|
||||||
.rollback_resource_with_clone::<FighterPendingHits>()
|
|
||||||
.update_resource_with_map_entities::<FighterPendingHits>()
|
|
||||||
.checksum_resource_with_hash::<FighterPendingHits>();
|
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
GgrsSchedule,
|
GgrsSchedule,
|
||||||
(
|
(
|
||||||
solve_hurtboxes,
|
solve_hurtboxes,
|
||||||
solve_hitboxes,
|
solve_hitboxes,
|
||||||
intersect_attacks_with_hurtboxes,
|
intersect_attacks_with_hurtboxes,
|
||||||
|
calculate_received_attacks_trajectories,
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.after(crate::fighter::baked_animation::update_fighter_bone_matrices)
|
.after(crate::fighter::baked_animation::update_fighter_bone_matrices)
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
use bevy_ggrs::prelude::GgrsSchedule;
|
|
||||||
use fixed_math::SinCos;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::{
|
|
||||||
fighter::{
|
|
||||||
Fighter, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
|
||||||
FighterPendingHitReaction, FighterStaleMoveQueue, errors::FighterErrors,
|
|
||||||
manifest::FighterManifest,
|
|
||||||
},
|
|
||||||
game_settings::GameSettings,
|
|
||||||
math::{int::FGi32, vec::FGVec2},
|
|
||||||
player::Player,
|
|
||||||
schedule::GameplaySet,
|
|
||||||
scripting::FighterAttackScript,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn resolve_fighter_hits(
|
|
||||||
mut pending_hits: ResMut<FighterPendingHits>,
|
|
||||||
mut attacker: Query<(
|
|
||||||
&FighterStaleMoveQueue,
|
|
||||||
&FighterFacingDirection,
|
|
||||||
&FighterHitboxes,
|
|
||||||
&Player,
|
|
||||||
&mut FighterHitstop,
|
|
||||||
)>,
|
|
||||||
mut receiver: Query<(&mut FighterDamage, &Fighter, &mut FighterPendingHitReaction)>,
|
|
||||||
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;
|
|
||||||
|
|
||||||
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.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, _hitboxes, 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::<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
|
|
||||||
};
|
|
||||||
let (sin, cos) = launch_angle.sin_cos();
|
|
||||||
let launch_direction = FGVec2::new(cos, sin);
|
|
||||||
let hitstop = HitStop::calculate(FighterDamage(attack_damage), false);
|
|
||||||
reactions.push((
|
|
||||||
attacker_entity,
|
|
||||||
attacker_slot,
|
|
||||||
FighterHitReaction {
|
|
||||||
launch_velocity: launch_direction * knockback.get_launch_speed(&game_settings),
|
|
||||||
hitstun_frames: knockback.get_hitstun_frames(&game_settings),
|
|
||||||
hitstop_frames: hitstop,
|
|
||||||
kind: HitReactionKind::Flinch,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
receiver_damage.0 += total_damage_received_this_frame;
|
|
||||||
|
|
||||||
for (attacker_entity, _, reaction) in &reactions {
|
|
||||||
let Ok((.., mut hitstop)) = attacker.get_mut(*attacker_entity) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
hitstop.remaining_frames = reaction.hitstop_frames.max(hitstop.remaining_frames);
|
|
||||||
}
|
|
||||||
|
|
||||||
pending_reaction.0 = reactions
|
|
||||||
.into_iter()
|
|
||||||
.min_by(|(_, port_a, _), (_, port_b, _)| port_a.cmp(port_b))
|
|
||||||
.map(|(_, _, reaction)| reaction);
|
|
||||||
|
|
||||||
hit_offset = next_defender;
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
//! Resolving an integrated fighter position against the stage.
|
//! Resolving an integrated fighter position against the stage.
|
||||||
|
|
||||||
use crate::fighter::ecb::FighterPreviousECB;
|
use crate::fighter::state::FighterStateContext;
|
||||||
use crate::fighter::{FighterECB, FighterPreviousTranslation, FighterTranslation};
|
|
||||||
use crate::math::segment::FGSegment2d;
|
use crate::math::segment::FGSegment2d;
|
||||||
use crate::math::vec::FGVec2;
|
use crate::math::vec::FGVec2;
|
||||||
use crate::stage::line::{StageCollision, StageLineID};
|
use crate::stage::line::StageLineID;
|
||||||
/*
|
/*
|
||||||
pub fn collide_fighter_with_scene(
|
pub fn collide_fighter_with_scene(
|
||||||
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
|
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
|
||||||
@@ -39,20 +38,16 @@ pub struct AirCollisionWithStageResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn air_collide_with_stage(
|
pub fn air_collide_with_stage(
|
||||||
prev_translation: &FighterPreviousTranslation,
|
state_context: &FighterStateContext,
|
||||||
translation: &FighterTranslation,
|
|
||||||
prev_ecb: &FighterPreviousECB,
|
|
||||||
ecb: &FighterECB,
|
|
||||||
stage_collision: &StageCollision,
|
|
||||||
) -> Option<AirCollisionWithStageResult> {
|
) -> Option<AirCollisionWithStageResult> {
|
||||||
let ray_segment = FGSegment2d::new(
|
let ray_segment = FGSegment2d::new(
|
||||||
prev_translation.0 + prev_ecb.get_bottom_point(),
|
state_context.prev_translation.0 + state_context.prev_ecb.get_bottom_point(),
|
||||||
translation.0 + ecb.get_bottom_point(),
|
state_context.translation.0 + state_context.ecb.get_bottom_point(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut intersect_results = vec![];
|
let mut intersect_results = vec![];
|
||||||
|
|
||||||
for (poly_idx, poly) in stage_collision.stage_polys.iter().enumerate() {
|
for (poly_idx, poly) in state_context.stage_collision.stage_polys.iter().enumerate() {
|
||||||
if let Some(intersect_result) = poly.intersect_ray(ray_segment) {
|
if let Some(intersect_result) = poly.intersect_ray(ray_segment) {
|
||||||
intersect_results.push((
|
intersect_results.push((
|
||||||
AirCollisionWithStageResult {
|
AirCollisionWithStageResult {
|
||||||
|
|||||||
+8
-13
@@ -33,24 +33,17 @@ pub use motion::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState, fighter::{
|
||||||
fighter::{
|
|
||||||
attack::{
|
attack::{
|
||||||
AttackKind, FighterAttackPlugin, FighterAttackScriptAssets, FighterDamage,
|
AttackKind, FighterAttackPlugin, FighterAttackScriptAssets, FighterDamage,
|
||||||
FighterKnockbackVelocity, FighterPendingHitReaction, FighterSolvedHurtboxes,
|
FighterKnockbackVelocity, FighterPendingHitReaction, FighterReceivedAttacks,
|
||||||
FighterStaleMoveQueue,
|
FighterSolvedHurtboxes, FighterStaleMoveQueue,
|
||||||
},
|
},
|
||||||
baked_animation::{FighterBoneIndexMap, FighterBoneMatrices},
|
baked_animation::{FighterBoneIndexMap, FighterBoneMatrices},
|
||||||
manifest::FighterManifest,
|
manifest::FighterManifest,
|
||||||
state::{FighterState, fall::FallState},
|
state::{FighterState, fall::FallState},
|
||||||
visual::FighterAnimations,
|
visual::FighterAnimations,
|
||||||
},
|
}, input::FighterInput, math::{int::FGi32, vec::FGVec2}, player::Player, schedule::GameplaySet, scripting::FighterAttackScript, stage::line::StageCollision,
|
||||||
input::FighterInput,
|
|
||||||
math::{int::FGi32, vec::FGVec2},
|
|
||||||
player::Player,
|
|
||||||
schedule::GameplaySet,
|
|
||||||
scripting::FighterAttackScript,
|
|
||||||
stage::line::StageCollision,
|
|
||||||
};
|
};
|
||||||
use state::state_interrupt_system;
|
use state::state_interrupt_system;
|
||||||
|
|
||||||
@@ -116,6 +109,7 @@ pub struct FighterVisual;
|
|||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
#[require(
|
#[require(
|
||||||
|
FighterReceivedAttacks,
|
||||||
FighterStaleMoveQueue,
|
FighterStaleMoveQueue,
|
||||||
FighterDamage,
|
FighterDamage,
|
||||||
FighterPendingHitReaction,
|
FighterPendingHitReaction,
|
||||||
@@ -198,6 +192,8 @@ impl Plugin for FighterPlugin {
|
|||||||
.checksum_component_with_hash::<FighterHitstop>()
|
.checksum_component_with_hash::<FighterHitstop>()
|
||||||
.rollback_component_with_copy::<FighterKnockbackVelocity>()
|
.rollback_component_with_copy::<FighterKnockbackVelocity>()
|
||||||
.checksum_component_with_hash::<FighterKnockbackVelocity>()
|
.checksum_component_with_hash::<FighterKnockbackVelocity>()
|
||||||
|
.rollback_component_with_clone::<FighterReceivedAttacks>()
|
||||||
|
.checksum_component_with_hash::<FighterReceivedAttacks>()
|
||||||
.rollback_resource_with_reflect::<StageCollision>()
|
.rollback_resource_with_reflect::<StageCollision>()
|
||||||
// Debug views.
|
// Debug views.
|
||||||
.add_systems(
|
.add_systems(
|
||||||
@@ -220,8 +216,7 @@ impl Plugin for FighterPlugin {
|
|||||||
.after(debug::attack_debug_gather),
|
.after(debug::attack_debug_gather),
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.run_if(crate::debug_tools::attack_hitboxes_enabled)
|
.run_if(crate::debug_tools::attack_hitboxes_enabled).run_if(in_state(AppState::InMatch)),
|
||||||
.run_if(in_state(AppState::InMatch)),
|
|
||||||
)
|
)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
FixedPostUpdate,
|
FixedPostUpdate,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
animation::AnimKind,
|
animation::AnimKind,
|
||||||
|
collision,
|
||||||
state::{
|
state::{
|
||||||
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext,
|
FighterState, FighterStateContext, FighterStateImpl, air,
|
||||||
UpdateContext, air,
|
|
||||||
fall::FallState,
|
fall::FallState,
|
||||||
ground::GroundedStateCommon,
|
ground::GroundedStateCommon,
|
||||||
land::{LandingState, LandingType},
|
land::{LandingState, LandingType},
|
||||||
@@ -21,7 +21,7 @@ pub struct AirDodgeState {
|
|||||||
impl FighterStateImpl for AirDodgeState {
|
impl FighterStateImpl for AirDodgeState {
|
||||||
const NAME: &'static str = "AirDodge";
|
const NAME: &'static str = "AirDodge";
|
||||||
|
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
if state_context.is_current_animation_finished() {
|
if state_context.is_current_animation_finished() {
|
||||||
Some(FighterState::Fall(FallState))
|
Some(FighterState::Fall(FallState))
|
||||||
} else {
|
} else {
|
||||||
@@ -29,8 +29,8 @@ impl FighterStateImpl for AirDodgeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||||
state_context.velocity.0 *= state_context.fighter_attributes.air_dodge_decay;
|
state_context.velocity.0 *= state_context.fighter_manifest.attributes.air_dodge_decay;
|
||||||
|
|
||||||
air::apply_air_motion(
|
air::apply_air_motion(
|
||||||
state_context.velocity,
|
state_context.velocity,
|
||||||
@@ -41,9 +41,9 @@ impl FighterStateImpl for AirDodgeState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let Some(res) = state_context.air_collide_with_stage() {
|
if let Some(res) = collision::air_collide_with_stage(state_context) {
|
||||||
// Adjust translation
|
// Adjust translation
|
||||||
state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point();
|
state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point();
|
||||||
let grounded_common = GroundedStateCommon {
|
let grounded_common = GroundedStateCommon {
|
||||||
@@ -58,15 +58,15 @@ impl FighterStateImpl for AirDodgeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||||
state_context.play_animation(AnimKind::AirDodge, false);
|
state_context.play_animation(AnimKind::AirDodge, false);
|
||||||
|
|
||||||
state_context.velocity.0 =
|
state_context.velocity.0 =
|
||||||
self.direction * state_context.fighter_attributes.air_dodge_velocity;
|
self.direction * state_context.fighter_manifest.attributes.air_dodge_velocity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(state_context: &InterruptContext) -> Option<FighterState> {
|
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterState> {
|
||||||
state_context
|
state_context
|
||||||
.input
|
.input
|
||||||
.has_command(input::FighterCommands::Shield)
|
.has_command(input::FighterCommands::Shield)
|
||||||
|
|||||||
+22
-16
@@ -1,9 +1,6 @@
|
|||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
|
|
||||||
use super::{
|
use super::{FighterStateContext, FighterStateImpl, ground, run, wait, walk};
|
||||||
CollisionContext, EnterContext, FighterStateImpl, InterruptContext, UpdateContext, ground, run,
|
|
||||||
wait, walk,
|
|
||||||
};
|
|
||||||
use crate::fighter::FighterFacingDirection;
|
use crate::fighter::FighterFacingDirection;
|
||||||
use crate::fighter::animation::AnimKind;
|
use crate::fighter::animation::AnimKind;
|
||||||
use crate::fighter::state::FighterState;
|
use crate::fighter::state::FighterState;
|
||||||
@@ -34,9 +31,9 @@ impl DashState {
|
|||||||
|
|
||||||
impl FighterStateImpl for DashState {
|
impl FighterStateImpl for DashState {
|
||||||
const NAME: &'static str = "Dash";
|
const NAME: &'static str = "Dash";
|
||||||
fn check_interrupt(&self, ctx: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, ctx: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
let ground = &self.ground_common;
|
let ground = &self.ground_common;
|
||||||
let attributes = ctx.fighter_attributes;
|
let attributes = ctx.fighter_manifest.attributes;
|
||||||
|
|
||||||
// A reverse dash can interrupt before IASA
|
// A reverse dash can interrupt before IASA
|
||||||
if let Some(direction) =
|
if let Some(direction) =
|
||||||
@@ -65,10 +62,13 @@ impl FighterStateImpl for DashState {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||||
*state_context.facing_direction = self.direction;
|
*state_context.facing_direction = self.direction;
|
||||||
state_context.velocity.x =
|
state_context.velocity.x = state_context
|
||||||
state_context.fighter_attributes.dash_initial_velocity * self.direction.to_sign();
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
|
.dash_initial_velocity
|
||||||
|
* self.direction.to_sign();
|
||||||
|
|
||||||
state_context.play_animation(AnimKind::Dash, false);
|
state_context.play_animation(AnimKind::Dash, false);
|
||||||
|
|
||||||
@@ -86,20 +86,24 @@ impl FighterStateImpl for DashState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||||
if state_context.get_current_animation_frame()
|
if state_context.get_current_animation_frame()
|
||||||
< state_context.fighter_attributes.dash_acceleration_duration
|
< state_context
|
||||||
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
|
.dash_acceleration_duration
|
||||||
{}
|
{}
|
||||||
|
|
||||||
let last_frame = state_context.input.get_last_frame();
|
let last_frame = state_context.input.get_last_frame();
|
||||||
let (accel, target_vel) = state_context
|
let (accel, target_vel) = state_context
|
||||||
.fighter_attributes
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
.get_accel_and_target_dashrun(&last_frame, self.direction.to_sign());
|
.get_accel_and_target_dashrun(&last_frame, self.direction.to_sign());
|
||||||
let accel = ground::compute_ground_accel(
|
let accel = ground::compute_ground_accel(
|
||||||
accel,
|
accel,
|
||||||
target_vel,
|
target_vel,
|
||||||
state_context.velocity.x,
|
state_context.velocity.x,
|
||||||
state_context.fighter_attributes,
|
&state_context.fighter_manifest.attributes,
|
||||||
state_context.game_settings,
|
state_context.game_settings,
|
||||||
);
|
);
|
||||||
state_context.velocity.x += accel;
|
state_context.velocity.x += accel;
|
||||||
@@ -113,7 +117,7 @@ impl FighterStateImpl for DashState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let GroundedMotionResult::InAir =
|
if let GroundedMotionResult::InAir =
|
||||||
ground::collide_with_stage_grounded(state_context, &mut self.ground_common, true)
|
ground::collide_with_stage_grounded(state_context, &mut self.ground_common, true)
|
||||||
@@ -125,7 +129,9 @@ impl FighterStateImpl for DashState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_smash_input_with_dir(state_context: &InterruptContext) -> Option<FighterFacingDirection> {
|
fn check_smash_input_with_dir(
|
||||||
|
state_context: &FighterStateContext,
|
||||||
|
) -> Option<FighterFacingDirection> {
|
||||||
if state_context.input.has_command(SmashMoveLeft) {
|
if state_context.input.has_command(SmashMoveLeft) {
|
||||||
return Some(FighterFacingDirection::Left);
|
return Some(FighterFacingDirection::Left);
|
||||||
} else if state_context.input.has_command(SmashMoveRight) {
|
} else if state_context.input.has_command(SmashMoveRight) {
|
||||||
@@ -136,7 +142,7 @@ fn check_smash_input_with_dir(state_context: &InterruptContext) -> Option<Fighte
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(
|
pub fn check_input(
|
||||||
state_context: &InterruptContext,
|
state_context: &FighterStateContext,
|
||||||
ground_common: &GroundedStateCommon,
|
ground_common: &GroundedStateCommon,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
check_smash_input_with_dir(state_context)
|
check_smash_input_with_dir(state_context)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::{CollisionContext, EnterContext, FighterStateImpl, InterruptContext, UpdateContext};
|
use super::{FighterStateContext, FighterStateImpl};
|
||||||
use crate::fighter::animation::AnimKind;
|
use crate::fighter::animation::AnimKind;
|
||||||
|
use crate::fighter::collision;
|
||||||
use crate::fighter::state::ground::GroundedStateCommon;
|
use crate::fighter::state::ground::GroundedStateCommon;
|
||||||
use crate::fighter::state::land::{LandingState, LandingType};
|
use crate::fighter::state::land::{LandingState, LandingType};
|
||||||
use crate::fighter::state::{FighterState, air, jump};
|
use crate::fighter::state::{FighterState, air, jump};
|
||||||
@@ -9,19 +10,22 @@ pub struct FallState;
|
|||||||
|
|
||||||
impl FighterStateImpl for FallState {
|
impl FighterStateImpl for FallState {
|
||||||
const NAME: &'static str = "Fall";
|
const NAME: &'static str = "Fall";
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
jump::check_input_double_jump(state_context)
|
jump::check_input_double_jump(state_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
state_context.play_animation(AnimKind::Fall, true);
|
state_context.play_animation(AnimKind::Fall, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
air::integrate_gravity(state_context.velocity, state_context.fighter_attributes);
|
air::integrate_gravity(
|
||||||
|
state_context.velocity,
|
||||||
|
&state_context.fighter_manifest.attributes,
|
||||||
|
);
|
||||||
air::apply_air_drift(
|
air::apply_air_drift(
|
||||||
state_context.velocity,
|
state_context.velocity,
|
||||||
state_context.fighter_attributes,
|
&state_context.fighter_manifest.attributes,
|
||||||
state_context.input,
|
state_context.input,
|
||||||
);
|
);
|
||||||
air::apply_air_motion(
|
air::apply_air_motion(
|
||||||
@@ -33,9 +37,9 @@ impl FighterStateImpl for FallState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let Some(res) = state_context.air_collide_with_stage() {
|
if let Some(res) = collision::air_collide_with_stage(state_context) {
|
||||||
// Adjust translation
|
// Adjust translation
|
||||||
state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point();
|
state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point();
|
||||||
let grounded_common = GroundedStateCommon {
|
let grounded_common = GroundedStateCommon {
|
||||||
@@ -51,7 +55,7 @@ impl FighterStateImpl for FallState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(state_context: &InterruptContext) -> Option<FighterState> {
|
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterState> {
|
||||||
let input_frame = state_context.input.get_last_frame();
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|
||||||
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
|
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! bit-for-bit deterministic for rollback.
|
//! bit-for-bit deterministic for rollback.
|
||||||
|
|
||||||
use crate::fighter::state::{
|
use crate::fighter::state::{
|
||||||
CollisionContext, FighterState, InterruptContext, dash, ground_attack, turn, wait, walk,
|
FighterState, FighterStateContext, dash, ground_attack, turn, wait, walk,
|
||||||
};
|
};
|
||||||
use crate::fighter::{
|
use crate::fighter::{
|
||||||
FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
FighterAttributes, FighterPreviousTranslation, FighterTranslation, FighterVelocity,
|
||||||
@@ -164,13 +164,13 @@ pub fn apply_grounded_motion(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn collide_with_stage_grounded(
|
pub fn collide_with_stage_grounded(
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
ground_common: &mut GroundedStateCommon,
|
ground_common: &mut GroundedStateCommon,
|
||||||
can_walk_off: bool,
|
can_walk_off: bool,
|
||||||
) -> GroundedMotionResult {
|
) -> GroundedMotionResult {
|
||||||
let translation = &mut state_context.translation;
|
let translation = &mut state_context.translation;
|
||||||
let ecb = state_context.ecb;
|
let ecb = &state_context.ecb;
|
||||||
let stage_collision = state_context.stage_collision;
|
let stage_collision = &state_context.stage_collision;
|
||||||
|
|
||||||
let stage_poly = stage_collision
|
let stage_poly = stage_collision
|
||||||
.stage_polys
|
.stage_polys
|
||||||
@@ -322,14 +322,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn grounded_movement_common_interrupts(
|
pub fn grounded_movement_common_interrupts(
|
||||||
state_context: &InterruptContext,
|
state_context: &FighterStateContext,
|
||||||
ground_common: &GroundedStateCommon,
|
ground_common: &GroundedStateCommon,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
super::jump::check_input(state_context, ground_common)
|
super::jump::check_input(state_context, ground_common)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grounded_movement_standstill_common_interrupts(
|
pub fn grounded_movement_standstill_common_interrupts(
|
||||||
state_context: &InterruptContext,
|
state_context: &FighterStateContext,
|
||||||
ground_common: &GroundedStateCommon,
|
ground_common: &GroundedStateCommon,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let Some(state) = ground_attack::check_interrupt(state_context, &ground_common) {
|
if let Some(state) = ground_attack::check_interrupt(state_context, &ground_common) {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
use crate::fighter::{
|
use crate::fighter::{
|
||||||
attack::{AttackKind, update_active_hitbox_list},
|
attack::{AttackKind, update_active_hitbox_list},
|
||||||
state::{
|
state::{FighterState, FighterStateImpl, ground::GroundedStateCommon, wait::WaitState},
|
||||||
CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl,
|
|
||||||
InterruptContext, UpdateContext, ground::GroundedStateCommon, wait::WaitState,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash)]
|
#[derive(Clone, Debug, Hash)]
|
||||||
@@ -15,7 +12,10 @@ pub struct GroundAttackState {
|
|||||||
impl FighterStateImpl for GroundAttackState {
|
impl FighterStateImpl for GroundAttackState {
|
||||||
const NAME: &'static str = "GroundAttack";
|
const NAME: &'static str = "GroundAttack";
|
||||||
|
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(
|
||||||
|
&self,
|
||||||
|
state_context: &mut super::FighterStateContext,
|
||||||
|
) -> Option<FighterState> {
|
||||||
if state_context.is_current_animation_finished() {
|
if state_context.is_current_animation_finished() {
|
||||||
Some(FighterState::Wait(WaitState {
|
Some(FighterState::Wait(WaitState {
|
||||||
grounded_common: self.grounded_common.clone(),
|
grounded_common: self.grounded_common.clone(),
|
||||||
@@ -25,7 +25,7 @@ impl FighterStateImpl for GroundAttackState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
let animation_frame = state_context.get_current_animation_frame();
|
let animation_frame = state_context.get_current_animation_frame();
|
||||||
let script = state_context
|
let script = state_context
|
||||||
.attack_scripts
|
.attack_scripts
|
||||||
@@ -44,7 +44,7 @@ impl FighterStateImpl for GroundAttackState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
state_context.play_animation(self.attack_kind.get_animation(), false);
|
state_context.play_animation(self.attack_kind.get_animation(), false);
|
||||||
let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone();
|
let script = state_context.attack_script_assets.scripts[&self.attack_kind].clone();
|
||||||
state_context.stale_move_queue.push(self.attack_kind);
|
state_context.stale_move_queue.push(self.attack_kind);
|
||||||
@@ -57,20 +57,20 @@ impl FighterStateImpl for GroundAttackState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_exit(&mut self, state_context: &mut ExitContext) {
|
fn on_exit(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
state_context.hitboxes.0 = None;
|
state_context.hitboxes.0 = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state_context: &mut CollisionContext,
|
_state_context: &mut super::FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_interrupt(
|
pub fn check_interrupt(
|
||||||
state_context: &InterruptContext,
|
state_context: &super::FighterStateContext,
|
||||||
grounded_common: &GroundedStateCommon,
|
grounded_common: &GroundedStateCommon,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if state_context
|
if state_context
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use crate::{
|
|||||||
fighter::{
|
fighter::{
|
||||||
animation::AnimKind,
|
animation::AnimKind,
|
||||||
attack::FighterHitReaction,
|
attack::FighterHitReaction,
|
||||||
|
collision,
|
||||||
state::{
|
state::{
|
||||||
CollisionContext, EnterContext, ExitContext, FighterState, FighterStateImpl,
|
FighterState, FighterStateContext, FighterStateImpl, air,
|
||||||
InterruptContext, UpdateContext, air,
|
|
||||||
fall::FallState,
|
fall::FallState,
|
||||||
ground::{GroundedMotionResult, GroundedStateCommon, collide_with_stage_grounded},
|
ground::{GroundedMotionResult, GroundedStateCommon, collide_with_stage_grounded},
|
||||||
land::{LandingState, LandingType},
|
land::{LandingState, LandingType},
|
||||||
@@ -43,7 +43,7 @@ impl FighterStateImpl for HitstunState {
|
|||||||
format!("Hitstun ({hitstop_kind}, {hit_reaction_type})")
|
format!("Hitstun ({hitstop_kind}, {hit_reaction_type})")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
if state_context.get_current_animation_frame() > self.hit_reaction.hitstun_frames {
|
if state_context.get_current_animation_frame() > self.hit_reaction.hitstun_frames {
|
||||||
match &self.hitstop_kind {
|
match &self.hitstop_kind {
|
||||||
HitstopStateKind::Grounded(grounded_state_common) => {
|
HitstopStateKind::Grounded(grounded_state_common) => {
|
||||||
@@ -58,8 +58,8 @@ impl FighterStateImpl for HitstunState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, ctx: &mut UpdateContext) {
|
fn update(&mut self, ctx: &mut FighterStateContext) {
|
||||||
air::integrate_gravity(ctx.velocity, ctx.fighter_attributes);
|
air::integrate_gravity(ctx.velocity, &ctx.fighter_manifest.attributes);
|
||||||
|
|
||||||
let speed = ctx.knockback_velocity.length();
|
let speed = ctx.knockback_velocity.length();
|
||||||
let next_speed = speed
|
let next_speed = speed
|
||||||
@@ -71,7 +71,7 @@ impl FighterStateImpl for HitstunState {
|
|||||||
ctx.translation.0 += ctx.velocity.0 + ctx.knockback_velocity.0;
|
ctx.translation.0 += ctx.velocity.0 + ctx.knockback_velocity.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||||
let animation = match self.hitstop_kind {
|
let animation = match self.hitstop_kind {
|
||||||
HitstopStateKind::Grounded(_) => AnimKind::FlinchGround,
|
HitstopStateKind::Grounded(_) => AnimKind::FlinchGround,
|
||||||
HitstopStateKind::Airborne => AnimKind::FlinchAir,
|
HitstopStateKind::Airborne => AnimKind::FlinchAir,
|
||||||
@@ -80,11 +80,11 @@ impl FighterStateImpl for HitstunState {
|
|||||||
state_context.knockback_velocity.0 = self.hit_reaction.launch_velocity;
|
state_context.knockback_velocity.0 = self.hit_reaction.launch_velocity;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_exit(&mut self, _state_context: &mut ExitContext) {}
|
fn on_exit(&mut self, _state_context: &mut FighterStateContext) {}
|
||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
match &mut self.hitstop_kind {
|
match &mut self.hitstop_kind {
|
||||||
HitstopStateKind::Grounded(grounded_state_common) => {
|
HitstopStateKind::Grounded(grounded_state_common) => {
|
||||||
@@ -97,7 +97,7 @@ impl FighterStateImpl for HitstunState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
HitstopStateKind::Airborne => {
|
HitstopStateKind::Airborne => {
|
||||||
if let Some(res) = state_context.air_collide_with_stage() {
|
if let Some(res) = collision::air_collide_with_stage(state_context) {
|
||||||
// Adjust translation
|
// Adjust translation
|
||||||
state_context.translation.0 =
|
state_context.translation.0 =
|
||||||
res.hit_position - state_context.ecb.get_bottom_point();
|
res.hit_position - state_context.ecb.get_bottom_point();
|
||||||
|
|||||||
+59
-33
@@ -3,9 +3,9 @@ use bevy::prelude::*;
|
|||||||
use crate::{
|
use crate::{
|
||||||
fighter::{
|
fighter::{
|
||||||
animation::AnimKind,
|
animation::AnimKind,
|
||||||
|
collision,
|
||||||
state::{
|
state::{
|
||||||
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext,
|
FighterState, FighterStateContext, FighterStateImpl, air,
|
||||||
UpdateContext, air,
|
|
||||||
air_dodge::{self, AirDodgeState},
|
air_dodge::{self, AirDodgeState},
|
||||||
fall::FallState,
|
fall::FallState,
|
||||||
ground::{self, GroundedMotionResult, GroundedStateCommon},
|
ground::{self, GroundedMotionResult, GroundedStateCommon},
|
||||||
@@ -45,7 +45,7 @@ impl JumpSquatState {
|
|||||||
impl FighterStateImpl for JumpSquatState {
|
impl FighterStateImpl for JumpSquatState {
|
||||||
const NAME: &'static str = "JumpSquat";
|
const NAME: &'static str = "JumpSquat";
|
||||||
|
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
// Special handling for wavedash from jump squat
|
// Special handling for wavedash from jump squat
|
||||||
if state_context.input.has_command(FighterCommands::Shield) {
|
if state_context.input.has_command(FighterCommands::Shield) {
|
||||||
// If aiming sideways and in the deadzone, do a perfect wavedash
|
// If aiming sideways and in the deadzone, do a perfect wavedash
|
||||||
@@ -69,7 +69,9 @@ impl FighterStateImpl for JumpSquatState {
|
|||||||
Some(FighterState::AirDodge(AirDodgeState {
|
Some(FighterState::AirDodge(AirDodgeState {
|
||||||
direction: wavedash_dir.normalize_or_zero(),
|
direction: wavedash_dir.normalize_or_zero(),
|
||||||
}))
|
}))
|
||||||
} else if self.duration_counter >= state_context.fighter_attributes.jumpsquat_duration {
|
} else if self.duration_counter
|
||||||
|
>= state_context.fighter_manifest.attributes.jumpsquat_duration
|
||||||
|
{
|
||||||
info!("Jump {}", state_context.input.get_last_frame().jump);
|
info!("Jump {}", state_context.input.get_last_frame().jump);
|
||||||
let jump_type = if state_context.input.get_last_frame().jump {
|
let jump_type = if state_context.input.get_last_frame().jump {
|
||||||
JumpType::LongJump
|
JumpType::LongJump
|
||||||
@@ -82,19 +84,20 @@ impl FighterStateImpl for JumpSquatState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||||
self.duration_counter += 1;
|
self.duration_counter += 1;
|
||||||
|
|
||||||
let friction =
|
let friction = if state_context.velocity.x.abs()
|
||||||
if state_context.velocity.x.abs() > state_context.fighter_attributes.max_walk_vel {
|
> state_context.fighter_manifest.attributes.max_walk_vel
|
||||||
state_context.fighter_attributes.ground_friction
|
{
|
||||||
* state_context
|
state_context.fighter_manifest.attributes.ground_friction
|
||||||
.game_settings
|
* state_context
|
||||||
.fighter_common
|
.game_settings
|
||||||
.ground_friction_over_walk_speed_multiplier
|
.fighter_common
|
||||||
} else {
|
.ground_friction_over_walk_speed_multiplier
|
||||||
state_context.fighter_attributes.ground_friction
|
} else {
|
||||||
};
|
state_context.fighter_manifest.attributes.ground_friction
|
||||||
|
};
|
||||||
|
|
||||||
let ground_velocity = state_context.velocity.x;
|
let ground_velocity = state_context.velocity.x;
|
||||||
|
|
||||||
@@ -109,7 +112,7 @@ impl FighterStateImpl for JumpSquatState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let GroundedMotionResult::InAir =
|
if let GroundedMotionResult::InAir =
|
||||||
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
||||||
@@ -120,7 +123,7 @@ impl FighterStateImpl for JumpSquatState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||||
state_context.play_animation(AnimKind::JumpSquat, false);
|
state_context.play_animation(AnimKind::JumpSquat, false);
|
||||||
state_context.input.clear_command(FighterCommands::Jump);
|
state_context.input.clear_command(FighterCommands::Jump);
|
||||||
}
|
}
|
||||||
@@ -128,7 +131,7 @@ impl FighterStateImpl for JumpSquatState {
|
|||||||
|
|
||||||
impl FighterStateImpl for JumpState {
|
impl FighterStateImpl for JumpState {
|
||||||
const NAME: &'static str = "Jump";
|
const NAME: &'static str = "Jump";
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
if state_context.input.has_command(FighterCommands::Jump) {
|
if state_context.input.has_command(FighterCommands::Jump) {
|
||||||
Some(FighterState::Jump(JumpState {
|
Some(FighterState::Jump(JumpState {
|
||||||
jump_type: JumpType::DoubleJump,
|
jump_type: JumpType::DoubleJump,
|
||||||
@@ -138,11 +141,14 @@ impl FighterStateImpl for JumpState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||||
air::integrate_gravity(state_context.velocity, state_context.fighter_attributes);
|
air::integrate_gravity(
|
||||||
|
state_context.velocity,
|
||||||
|
&state_context.fighter_manifest.attributes,
|
||||||
|
);
|
||||||
air::apply_air_drift(
|
air::apply_air_drift(
|
||||||
state_context.velocity,
|
state_context.velocity,
|
||||||
state_context.fighter_attributes,
|
&state_context.fighter_manifest.attributes,
|
||||||
state_context.input,
|
state_context.input,
|
||||||
);
|
);
|
||||||
air::apply_air_motion(
|
air::apply_air_motion(
|
||||||
@@ -152,7 +158,7 @@ impl FighterStateImpl for JumpState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||||
if let JumpType::DoubleJump = self.jump_type {
|
if let JumpType::DoubleJump = self.jump_type {
|
||||||
state_context.input.clear_command(FighterCommands::Jump);
|
state_context.input.clear_command(FighterCommands::Jump);
|
||||||
}
|
}
|
||||||
@@ -179,22 +185,42 @@ impl FighterStateImpl for JumpState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let vertical_vel = match self.jump_type {
|
let vertical_vel = match self.jump_type {
|
||||||
JumpType::ShortHop => state_context.fighter_attributes.short_hop_vertical_velocity,
|
JumpType::ShortHop => {
|
||||||
JumpType::LongJump => state_context.fighter_attributes.full_jump_vertical_velocity,
|
state_context
|
||||||
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
|
.short_hop_vertical_velocity
|
||||||
|
}
|
||||||
|
JumpType::LongJump => {
|
||||||
|
state_context
|
||||||
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
|
.full_jump_vertical_velocity
|
||||||
|
}
|
||||||
JumpType::DoubleJump => {
|
JumpType::DoubleJump => {
|
||||||
state_context.fighter_attributes.full_jump_vertical_velocity
|
state_context
|
||||||
* state_context.fighter_attributes.air_jump_multiplier
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
|
.full_jump_vertical_velocity
|
||||||
|
* state_context
|
||||||
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
|
.air_jump_multiplier
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.jump_type {
|
match self.jump_type {
|
||||||
JumpType::ShortHop | JumpType::LongJump => {
|
JumpType::ShortHop | JumpType::LongJump => {
|
||||||
state_context.velocity.x +=
|
state_context.velocity.x += state_context
|
||||||
state_context.fighter_attributes.jump_horizontal_velocity * movement_stick_x;
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
|
.jump_horizontal_velocity
|
||||||
|
* movement_stick_x;
|
||||||
}
|
}
|
||||||
JumpType::DoubleJump => {
|
JumpType::DoubleJump => {
|
||||||
state_context.velocity.x = state_context
|
state_context.velocity.x = state_context
|
||||||
.fighter_attributes
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
.air_jump_horizontal_velocity
|
.air_jump_horizontal_velocity
|
||||||
* movement_stick_x;
|
* movement_stick_x;
|
||||||
}
|
}
|
||||||
@@ -205,9 +231,9 @@ impl FighterStateImpl for JumpState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let Some(res) = state_context.air_collide_with_stage() {
|
if let Some(res) = collision::air_collide_with_stage(state_context) {
|
||||||
// Adjust translation
|
// Adjust translation
|
||||||
state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point();
|
state_context.translation.0 = res.hit_position - state_context.ecb.get_bottom_point();
|
||||||
let grounded_common = GroundedStateCommon {
|
let grounded_common = GroundedStateCommon {
|
||||||
@@ -224,7 +250,7 @@ impl FighterStateImpl for JumpState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(
|
pub fn check_input(
|
||||||
state_context: &InterruptContext,
|
state_context: &FighterStateContext,
|
||||||
ground_common: &GroundedStateCommon,
|
ground_common: &GroundedStateCommon,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
state_context
|
state_context
|
||||||
@@ -233,7 +259,7 @@ pub fn check_input(
|
|||||||
.then(|| FighterState::JumpSquat(JumpSquatState::create(ground_common.clone())))
|
.then(|| FighterState::JumpSquat(JumpSquatState::create(ground_common.clone())))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input_double_jump(state_context: &InterruptContext) -> Option<FighterState> {
|
pub fn check_input_double_jump(state_context: &FighterStateContext) -> Option<FighterState> {
|
||||||
state_context
|
state_context
|
||||||
.input
|
.input
|
||||||
.has_command(FighterCommands::Jump)
|
.has_command(FighterCommands::Jump)
|
||||||
|
|||||||
+17
-17
@@ -1,8 +1,7 @@
|
|||||||
use crate::fighter::{
|
use crate::fighter::{
|
||||||
animation::AnimKind,
|
animation::AnimKind,
|
||||||
state::{
|
state::{
|
||||||
CollisionContext, EnterContext, FighterState, FighterStateImpl, InterruptContext,
|
FighterState, FighterStateContext, FighterStateImpl, dash,
|
||||||
UpdateContext, dash,
|
|
||||||
fall::FallState,
|
fall::FallState,
|
||||||
ground::{
|
ground::{
|
||||||
self, GroundedMotionResult, GroundedStateCommon,
|
self, GroundedMotionResult, GroundedStateCommon,
|
||||||
@@ -36,11 +35,11 @@ impl LandingState {
|
|||||||
impl FighterStateImpl for LandingState {
|
impl FighterStateImpl for LandingState {
|
||||||
const NAME: &'static str = "Landing";
|
const NAME: &'static str = "Landing";
|
||||||
|
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
if state_context.is_current_animation_finished() {
|
if state_context.is_current_animation_finished() {
|
||||||
grounded_movement_standstill_common_interrupts(state_context, &self.grounded_common)
|
grounded_movement_standstill_common_interrupts(state_context, &self.grounded_common)
|
||||||
} else if state_context.get_current_animation_frame()
|
} else if state_context.get_current_animation_frame()
|
||||||
>= state_context.fighter_attributes.landing_iasa
|
>= state_context.fighter_manifest.attributes.landing_iasa
|
||||||
&& let LandingType::Normal = self.landing_type
|
&& let LandingType::Normal = self.landing_type
|
||||||
{
|
{
|
||||||
dash::check_input(state_context, &self.grounded_common).or_else(|| {
|
dash::check_input(state_context, &self.grounded_common).or_else(|| {
|
||||||
@@ -52,17 +51,18 @@ impl FighterStateImpl for LandingState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||||
let friction =
|
let friction = if state_context.velocity.x.abs()
|
||||||
if state_context.velocity.x.abs() > state_context.fighter_attributes.max_walk_vel {
|
> state_context.fighter_manifest.attributes.max_walk_vel
|
||||||
state_context.fighter_attributes.ground_friction
|
{
|
||||||
* state_context
|
state_context.fighter_manifest.attributes.ground_friction
|
||||||
.game_settings
|
* state_context
|
||||||
.fighter_common
|
.game_settings
|
||||||
.ground_friction_over_walk_speed_multiplier
|
.fighter_common
|
||||||
} else {
|
.ground_friction_over_walk_speed_multiplier
|
||||||
state_context.fighter_attributes.ground_friction
|
} else {
|
||||||
};
|
state_context.fighter_manifest.attributes.ground_friction
|
||||||
|
};
|
||||||
|
|
||||||
let ground_velocity = state_context.velocity.x;
|
let ground_velocity = state_context.velocity.x;
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ impl FighterStateImpl for LandingState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let GroundedMotionResult::InAir =
|
if let GroundedMotionResult::InAir =
|
||||||
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, true)
|
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, true)
|
||||||
@@ -88,7 +88,7 @@ impl FighterStateImpl for LandingState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||||
match self.landing_type {
|
match self.landing_type {
|
||||||
LandingType::Normal => {
|
LandingType::Normal => {
|
||||||
state_context.play_animation(AnimKind::Landing, false);
|
state_context.play_animation(AnimKind::Landing, false);
|
||||||
|
|||||||
+95
-196
@@ -10,7 +10,6 @@ use crate::fighter::attack::{
|
|||||||
FighterAttackScriptAssets, FighterKnockbackVelocity, FighterPendingHitReaction,
|
FighterAttackScriptAssets, FighterKnockbackVelocity, FighterPendingHitReaction,
|
||||||
FighterStaleMoveQueue,
|
FighterStaleMoveQueue,
|
||||||
};
|
};
|
||||||
use crate::fighter::attributes::FighterAttributes;
|
|
||||||
use crate::fighter::baked_animation::BakedFighterAnimations;
|
use crate::fighter::baked_animation::BakedFighterAnimations;
|
||||||
use crate::fighter::ecb::FighterPreviousECB;
|
use crate::fighter::ecb::FighterPreviousECB;
|
||||||
use crate::fighter::manifest::FighterManifest;
|
use crate::fighter::manifest::FighterManifest;
|
||||||
@@ -48,22 +47,22 @@ macro_rules! fighter_states {
|
|||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
match self { $(Self::$variant(_) => <$ty>::NAME),* }
|
match self { $(Self::$variant(_) => <$ty>::NAME),* }
|
||||||
}
|
}
|
||||||
pub fn check_interrupt(&self, ctx: &mut InterruptContext) -> Option<FighterState> {
|
pub fn check_interrupt(&self, ctx: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
match self { $(Self::$variant(s) => s.check_interrupt(ctx)),* }
|
match self { $(Self::$variant(s) => s.check_interrupt(ctx)),* }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, ctx: &mut UpdateContext) {
|
pub fn update(&mut self, ctx: &mut FighterStateContext) {
|
||||||
match self { $(Self::$variant(s) => s.update(ctx)),* }
|
match self { $(Self::$variant(s) => s.update(ctx)),* }
|
||||||
}
|
}
|
||||||
pub fn on_enter(&mut self, ctx: &mut EnterContext) {
|
pub fn on_enter(&mut self, ctx: &mut FighterStateContext) {
|
||||||
match self { $(Self::$variant(s) => s.on_enter(ctx)),* }
|
match self { $(Self::$variant(s) => s.on_enter(ctx)),* }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_exit(&mut self, ctx: &mut ExitContext) {
|
pub fn on_exit(&mut self, ctx: &mut FighterStateContext) {
|
||||||
match self { $(Self::$variant(s) => s.on_exit(ctx)),* }
|
match self { $(Self::$variant(s) => s.on_exit(ctx)),* }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_collision_interrupt(&mut self, ctx: &mut CollisionContext) -> Option<FighterState> {
|
pub fn check_collision_interrupt(&mut self, ctx: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
match self { $(Self::$variant(s) => s.check_collision_interrupt(ctx)),* }
|
match self { $(Self::$variant(s) => s.check_collision_interrupt(ctx)),* }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,108 +75,62 @@ macro_rules! fighter_states {
|
|||||||
|
|
||||||
pub trait FighterStateImpl: Sized {
|
pub trait FighterStateImpl: Sized {
|
||||||
const NAME: &'static str;
|
const NAME: &'static str;
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState>;
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState>;
|
||||||
|
|
||||||
fn on_enter(&mut self, _state_context: &mut EnterContext) {}
|
fn on_enter(&mut self, _state_context: &mut FighterStateContext) {}
|
||||||
fn on_exit(&mut self, _state_context: &mut ExitContext) {}
|
fn on_exit(&mut self, _state_context: &mut FighterStateContext) {}
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
Self::NAME.to_owned()
|
Self::NAME.to_owned()
|
||||||
}
|
}
|
||||||
fn update(&mut self, _state_context: &mut UpdateContext);
|
fn update(&mut self, _state_context: &mut FighterStateContext);
|
||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
_state_context: &mut CollisionContext,
|
_state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AnimationStatus {
|
pub struct FighterStateContext<'a> {
|
||||||
pub frame: u32,
|
pub translation: &'a mut FighterTranslation,
|
||||||
pub finished: bool,
|
pub prev_translation: &'a mut FighterPreviousTranslation,
|
||||||
}
|
|
||||||
|
|
||||||
pub struct InterruptContext<'a> {
|
|
||||||
pub input: &'a FighterInput,
|
|
||||||
pub facing_direction: &'a mut FighterFacingDirection,
|
|
||||||
pub game_settings: &'a GameSettings,
|
|
||||||
pub fighter_attributes: &'a FighterAttributes,
|
|
||||||
pub animation: AnimationStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterruptContext<'_> {
|
|
||||||
pub fn get_current_animation_frame(&self) -> u32 {
|
|
||||||
self.animation.frame
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_current_animation_finished(&self) -> bool {
|
|
||||||
self.animation.finished
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EnterContext<'a> {
|
|
||||||
pub input: &'a mut FighterInput,
|
pub input: &'a mut FighterInput,
|
||||||
pub hitboxes: &'a mut FighterHitboxes,
|
pub hitboxes: &'a mut FighterHitboxes,
|
||||||
pub velocity: &'a mut FighterVelocity,
|
pub velocity: &'a mut FighterVelocity,
|
||||||
pub knockback_velocity: &'a mut FighterKnockbackVelocity,
|
pub knockback_velocity: &'a mut FighterKnockbackVelocity,
|
||||||
pub stale_move_queue: &'a mut FighterStaleMoveQueue,
|
pub stale_move_queue: &'a mut FighterStaleMoveQueue,
|
||||||
|
pub ecb: &'a mut FighterECB,
|
||||||
|
pub prev_ecb: &'a mut FighterPreviousECB,
|
||||||
pub facing_direction: &'a mut FighterFacingDirection,
|
pub facing_direction: &'a mut FighterFacingDirection,
|
||||||
|
pub game_settings: &'a GameSettings,
|
||||||
|
pub stage_collision: &'a StageCollision,
|
||||||
|
pub animations: &'a FighterAnimations,
|
||||||
pub animation_frame: &'a mut FighterAnimationFrame,
|
pub animation_frame: &'a mut FighterAnimationFrame,
|
||||||
pub fighter_attributes: &'a FighterAttributes,
|
pub baked_animations: &'a Assets<BakedFighterAnimations>,
|
||||||
|
pub attack_scripts: &'a Assets<FighterAttackScript>,
|
||||||
|
pub fighter_manifest: &'a FighterManifest,
|
||||||
pub attack_script_assets: &'a FighterAttackScriptAssets,
|
pub attack_script_assets: &'a FighterAttackScriptAssets,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EnterContext<'_> {
|
impl FighterStateContext<'_> {
|
||||||
pub fn play_animation(&mut self, kind: AnimKind, repeat: bool) {
|
pub fn play_animation(&mut self, kind: AnimKind, repeat: bool) {
|
||||||
self.animation_frame.reset(kind, repeat);
|
self.animation_frame.reset(kind, repeat);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ExitContext<'a> {
|
|
||||||
pub hitboxes: &'a mut FighterHitboxes,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UpdateContext<'a> {
|
|
||||||
pub translation: &'a mut FighterTranslation,
|
|
||||||
pub prev_translation: &'a mut FighterPreviousTranslation,
|
|
||||||
pub input: &'a FighterInput,
|
|
||||||
pub hitboxes: &'a mut FighterHitboxes,
|
|
||||||
pub velocity: &'a mut FighterVelocity,
|
|
||||||
pub knockback_velocity: &'a mut FighterKnockbackVelocity,
|
|
||||||
pub game_settings: &'a GameSettings,
|
|
||||||
pub animation_frame: u32,
|
|
||||||
pub fighter_attributes: &'a FighterAttributes,
|
|
||||||
pub attack_scripts: &'a Assets<FighterAttackScript>,
|
|
||||||
pub attack_script_assets: &'a FighterAttackScriptAssets,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UpdateContext<'_> {
|
|
||||||
pub fn get_current_animation_frame(&self) -> u32 {
|
pub fn get_current_animation_frame(&self) -> u32 {
|
||||||
self.animation_frame
|
self.animation_frame.frame
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CollisionContext<'a> {
|
pub fn is_current_animation_finished(&self) -> bool {
|
||||||
pub translation: &'a mut FighterTranslation,
|
let anims_data = self
|
||||||
pub prev_translation: &'a FighterPreviousTranslation,
|
.baked_animations
|
||||||
pub velocity: &'a mut FighterVelocity,
|
.get(&self.animations.baked)
|
||||||
pub ecb: &'a FighterECB,
|
.expect("Animation should exist");
|
||||||
pub prev_ecb: &'a FighterPreviousECB,
|
anims_data
|
||||||
pub stage_collision: &'a StageCollision,
|
.frame_count(self.animation_frame.kind)
|
||||||
}
|
.expect("Current animation should have baked frames")
|
||||||
|
<= self.animation_frame.frame
|
||||||
impl CollisionContext<'_> {
|
|
||||||
pub fn air_collide_with_stage(
|
|
||||||
&self,
|
|
||||||
) -> Option<crate::fighter::collision::AirCollisionWithStageResult> {
|
|
||||||
crate::fighter::collision::air_collide_with_stage(
|
|
||||||
self.prev_translation,
|
|
||||||
self.translation,
|
|
||||||
self.prev_ecb,
|
|
||||||
self.ecb,
|
|
||||||
self.stage_collision,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,104 +159,43 @@ pub struct FighterFrameQuery {
|
|||||||
pub velocity: &'static mut FighterVelocity,
|
pub velocity: &'static mut FighterVelocity,
|
||||||
pub knockback_velocity: &'static mut FighterKnockbackVelocity,
|
pub knockback_velocity: &'static mut FighterKnockbackVelocity,
|
||||||
pub stale_move_queue: &'static mut FighterStaleMoveQueue,
|
pub stale_move_queue: &'static mut FighterStaleMoveQueue,
|
||||||
pub ecb: &'static FighterECB,
|
pub ecb: &'static mut FighterECB,
|
||||||
pub prev_ecb: &'static FighterPreviousECB,
|
pub prev_ecb: &'static mut FighterPreviousECB,
|
||||||
pub facing_direction: &'static mut FighterFacingDirection,
|
pub facing_direction: &'static mut FighterFacingDirection,
|
||||||
pub animation_frame: &'static mut FighterAnimationFrame,
|
pub animation_frame: &'static mut FighterAnimationFrame,
|
||||||
pub fighter: &'static Fighter,
|
pub fighter: &'static Fighter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
impl<'w, 's> FighterFrameQueryItem<'w, 's> {
|
||||||
fn animation_status(
|
fn context<'a>(
|
||||||
&self,
|
|
||||||
animations: &FighterAnimations,
|
|
||||||
baked_animations: &Assets<BakedFighterAnimations>,
|
|
||||||
) -> AnimationStatus {
|
|
||||||
let baked = baked_animations
|
|
||||||
.get(&animations.baked)
|
|
||||||
.expect("Animation should exist");
|
|
||||||
let frame = self.animation_frame.frame;
|
|
||||||
let finished = baked
|
|
||||||
.frame_count(self.animation_frame.kind)
|
|
||||||
.expect("Current animation should have baked frames")
|
|
||||||
<= frame;
|
|
||||||
AnimationStatus { frame, finished }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn interrupt_context<'a>(
|
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
game_settings: &'a GameSettings,
|
game_settings: &'a GameSettings,
|
||||||
|
stage_collision: &'a StageCollision,
|
||||||
animations: &'a FighterAnimations,
|
animations: &'a FighterAnimations,
|
||||||
baked_animations: &'a Assets<BakedFighterAnimations>,
|
baked_animations: &'a Assets<BakedFighterAnimations>,
|
||||||
attributes: &'a FighterAttributes,
|
attack_scripts: &'a Assets<FighterAttackScript>,
|
||||||
) -> InterruptContext<'a> {
|
|
||||||
let animation = self.animation_status(animations, baked_animations);
|
|
||||||
InterruptContext {
|
|
||||||
input: &self.input,
|
|
||||||
facing_direction: &mut self.facing_direction,
|
|
||||||
game_settings,
|
|
||||||
fighter_attributes: attributes,
|
|
||||||
animation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enter_context<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
attributes: &'a FighterAttributes,
|
|
||||||
attack_script_assets: &'a FighterAttackScriptAssets,
|
attack_script_assets: &'a FighterAttackScriptAssets,
|
||||||
) -> EnterContext<'a> {
|
manifest: &'a FighterManifest,
|
||||||
EnterContext {
|
) -> FighterStateContext<'a> {
|
||||||
|
FighterStateContext {
|
||||||
|
translation: &mut self.translation,
|
||||||
|
prev_translation: &mut self.prev_translation,
|
||||||
input: &mut self.input,
|
input: &mut self.input,
|
||||||
hitboxes: &mut self.hitboxes,
|
hitboxes: &mut self.hitboxes,
|
||||||
velocity: &mut self.velocity,
|
velocity: &mut self.velocity,
|
||||||
knockback_velocity: &mut self.knockback_velocity,
|
knockback_velocity: &mut self.knockback_velocity,
|
||||||
stale_move_queue: &mut self.stale_move_queue,
|
stale_move_queue: &mut self.stale_move_queue,
|
||||||
|
ecb: &mut self.ecb,
|
||||||
|
prev_ecb: &mut self.prev_ecb,
|
||||||
facing_direction: &mut self.facing_direction,
|
facing_direction: &mut self.facing_direction,
|
||||||
animation_frame: &mut self.animation_frame,
|
|
||||||
fighter_attributes: attributes,
|
|
||||||
attack_script_assets,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exit_context(&mut self) -> ExitContext<'_> {
|
|
||||||
ExitContext {
|
|
||||||
hitboxes: &mut self.hitboxes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_context<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
game_settings: &'a GameSettings,
|
|
||||||
attributes: &'a FighterAttributes,
|
|
||||||
attack_scripts: &'a Assets<FighterAttackScript>,
|
|
||||||
attack_script_assets: &'a FighterAttackScriptAssets,
|
|
||||||
) -> UpdateContext<'a> {
|
|
||||||
UpdateContext {
|
|
||||||
translation: &mut self.translation,
|
|
||||||
prev_translation: &mut self.prev_translation,
|
|
||||||
input: &self.input,
|
|
||||||
hitboxes: &mut self.hitboxes,
|
|
||||||
velocity: &mut self.velocity,
|
|
||||||
knockback_velocity: &mut self.knockback_velocity,
|
|
||||||
game_settings,
|
game_settings,
|
||||||
animation_frame: self.animation_frame.frame,
|
stage_collision,
|
||||||
fighter_attributes: attributes,
|
animations,
|
||||||
|
animation_frame: &mut self.animation_frame,
|
||||||
|
baked_animations,
|
||||||
attack_scripts,
|
attack_scripts,
|
||||||
attack_script_assets,
|
attack_script_assets,
|
||||||
}
|
fighter_manifest: manifest,
|
||||||
}
|
|
||||||
|
|
||||||
fn collision_context<'a>(
|
|
||||||
&'a mut self,
|
|
||||||
stage_collision: &'a StageCollision,
|
|
||||||
) -> CollisionContext<'a> {
|
|
||||||
CollisionContext {
|
|
||||||
translation: &mut self.translation,
|
|
||||||
prev_translation: &self.prev_translation,
|
|
||||||
velocity: &mut self.velocity,
|
|
||||||
ecb: &self.ecb,
|
|
||||||
prev_ecb: &self.prev_ecb,
|
|
||||||
stage_collision,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,7 +214,9 @@ pub struct StatefulFighterQuery {
|
|||||||
|
|
||||||
pub fn state_update_system(
|
pub fn state_update_system(
|
||||||
mut query: Query<StatefulFighterQuery>,
|
mut query: Query<StatefulFighterQuery>,
|
||||||
|
stage_collision: Res<StageCollision>,
|
||||||
game_settings: Res<GameSettings>,
|
game_settings: Res<GameSettings>,
|
||||||
|
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
||||||
attack_scripts: Res<Assets<FighterAttackScript>>,
|
attack_scripts: Res<Assets<FighterAttackScript>>,
|
||||||
manifests: Res<Assets<FighterManifest>>,
|
manifests: Res<Assets<FighterManifest>>,
|
||||||
) {
|
) {
|
||||||
@@ -330,6 +224,7 @@ pub fn state_update_system(
|
|||||||
let StatefulFighterQueryItem {
|
let StatefulFighterQueryItem {
|
||||||
mut state,
|
mut state,
|
||||||
mut frame,
|
mut frame,
|
||||||
|
animations,
|
||||||
hitstop,
|
hitstop,
|
||||||
..
|
..
|
||||||
} = fighter;
|
} = fighter;
|
||||||
@@ -338,14 +233,16 @@ pub fn state_update_system(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let manifest = manifests
|
let mut update_ctx = frame.context(
|
||||||
.get(&fighter.fighter.manifest)
|
|
||||||
.expect("Fighter manifest should be valid");
|
|
||||||
let mut update_ctx = frame.update_context(
|
|
||||||
&game_settings,
|
&game_settings,
|
||||||
&manifest.attributes,
|
&stage_collision,
|
||||||
|
animations,
|
||||||
|
&baked_animations,
|
||||||
&attack_scripts,
|
&attack_scripts,
|
||||||
&fighter.attack_script_assets,
|
&fighter.attack_script_assets,
|
||||||
|
&manifests
|
||||||
|
.get(&(fighter.fighter.manifest))
|
||||||
|
.expect("Fighter manifest should be valid"),
|
||||||
);
|
);
|
||||||
state.update(&mut update_ctx);
|
state.update(&mut update_ctx);
|
||||||
}
|
}
|
||||||
@@ -354,24 +251,24 @@ pub fn state_update_system(
|
|||||||
fn transition_state(
|
fn transition_state(
|
||||||
current: &mut FighterState,
|
current: &mut FighterState,
|
||||||
mut next: FighterState,
|
mut next: FighterState,
|
||||||
frame: &mut FighterFrameQueryItem,
|
ctx: &mut FighterStateContext,
|
||||||
attributes: &FighterAttributes,
|
|
||||||
attack_script_assets: &FighterAttackScriptAssets,
|
|
||||||
) {
|
) {
|
||||||
let old_name = current.name();
|
let old_name = current.name();
|
||||||
let new_name = next.name();
|
let new_name = next.name();
|
||||||
|
|
||||||
current.on_exit(&mut frame.exit_context());
|
current.on_exit(ctx);
|
||||||
next.on_enter(&mut frame.enter_context(attributes, attack_script_assets));
|
next.on_enter(ctx);
|
||||||
*current = next;
|
*current = next;
|
||||||
info!("{} -> {}", old_name, new_name);
|
info!("{} -> {}", old_name, new_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state_interrupt_system(
|
pub fn state_interrupt_system(
|
||||||
mut query: Query<StatefulFighterQuery>,
|
mut query: Query<StatefulFighterQuery>,
|
||||||
|
stage_collision: Res<StageCollision>,
|
||||||
game_settings: Res<GameSettings>,
|
game_settings: Res<GameSettings>,
|
||||||
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
||||||
fighter_manifests: Res<Assets<FighterManifest>>,
|
fighter_manifests: Res<Assets<FighterManifest>>,
|
||||||
|
attack_scripts: Res<Assets<FighterAttackScript>>,
|
||||||
) {
|
) {
|
||||||
for fighter in &mut query {
|
for fighter in &mut query {
|
||||||
let StatefulFighterQueryItem {
|
let StatefulFighterQueryItem {
|
||||||
@@ -383,9 +280,17 @@ pub fn state_interrupt_system(
|
|||||||
mut pending_hit_reaction,
|
mut pending_hit_reaction,
|
||||||
mut hitstop,
|
mut hitstop,
|
||||||
} = fighter;
|
} = fighter;
|
||||||
let manifest = fighter_manifests
|
let mut state_context = frame.context(
|
||||||
.get(&fighter.manifest)
|
&game_settings,
|
||||||
.expect("Fighter manifest should be valid");
|
&stage_collision,
|
||||||
|
animations,
|
||||||
|
&baked_animations,
|
||||||
|
&attack_scripts,
|
||||||
|
attack_script_assets,
|
||||||
|
fighter_manifests
|
||||||
|
.get(&fighter.manifest)
|
||||||
|
.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.hitstop_frames;
|
||||||
@@ -417,22 +322,11 @@ pub fn state_interrupt_system(
|
|||||||
} else if hitstop.remaining_frames > 0 {
|
} else if hitstop.remaining_frames > 0 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
state.check_interrupt(&mut frame.interrupt_context(
|
state.check_interrupt(&mut state_context)
|
||||||
&game_settings,
|
|
||||||
animations,
|
|
||||||
&baked_animations,
|
|
||||||
&manifest.attributes,
|
|
||||||
))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(new_state) = new_state {
|
if let Some(new_state) = new_state {
|
||||||
transition_state(
|
transition_state(&mut state, new_state, &mut state_context);
|
||||||
&mut state,
|
|
||||||
new_state,
|
|
||||||
&mut frame,
|
|
||||||
&manifest.attributes,
|
|
||||||
attack_script_assets,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -440,34 +334,39 @@ pub fn state_interrupt_system(
|
|||||||
pub fn state_collision_interrupt_system(
|
pub fn state_collision_interrupt_system(
|
||||||
mut query: Query<StatefulFighterQuery>,
|
mut query: Query<StatefulFighterQuery>,
|
||||||
stage_collision: Res<StageCollision>,
|
stage_collision: Res<StageCollision>,
|
||||||
|
game_settings: Res<GameSettings>,
|
||||||
|
baked_animations: Res<Assets<BakedFighterAnimations>>,
|
||||||
fighter_manifests: Res<Assets<FighterManifest>>,
|
fighter_manifests: Res<Assets<FighterManifest>>,
|
||||||
|
attack_scripts: Res<Assets<FighterAttackScript>>,
|
||||||
) {
|
) {
|
||||||
for fighter in &mut query {
|
for fighter in &mut query {
|
||||||
let StatefulFighterQueryItem {
|
let StatefulFighterQueryItem {
|
||||||
mut state,
|
mut state,
|
||||||
mut frame,
|
mut frame,
|
||||||
|
animations,
|
||||||
fighter,
|
fighter,
|
||||||
attack_script_assets,
|
attack_script_assets,
|
||||||
hitstop,
|
hitstop,
|
||||||
..
|
..
|
||||||
} = fighter;
|
} = fighter;
|
||||||
|
let mut state_context = frame.context(
|
||||||
|
&game_settings,
|
||||||
|
&stage_collision,
|
||||||
|
animations,
|
||||||
|
&baked_animations,
|
||||||
|
&attack_scripts,
|
||||||
|
&attack_script_assets,
|
||||||
|
fighter_manifests
|
||||||
|
.get(&fighter.manifest)
|
||||||
|
.expect("Fighter manifest should be valid"),
|
||||||
|
);
|
||||||
|
|
||||||
if hitstop.remaining_frames > 0 {
|
if hitstop.remaining_frames > 0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let manifest = fighter_manifests
|
if let Some(new_state) = state.check_collision_interrupt(&mut state_context) {
|
||||||
.get(&fighter.manifest)
|
transition_state(&mut state, new_state, &mut state_context);
|
||||||
.expect("Fighter manifest should be valid");
|
|
||||||
let new_state =
|
|
||||||
state.check_collision_interrupt(&mut frame.collision_context(&stage_collision));
|
|
||||||
if let Some(new_state) = new_state {
|
|
||||||
transition_state(
|
|
||||||
&mut state,
|
|
||||||
new_state,
|
|
||||||
&mut frame,
|
|
||||||
&manifest.attributes,
|
|
||||||
&attack_script_assets,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use super::{
|
use super::{FighterStateContext, FighterStateImpl, ground, wait, walk};
|
||||||
CollisionContext, EnterContext, FighterStateImpl, InterruptContext, UpdateContext, ground,
|
|
||||||
wait, walk,
|
|
||||||
};
|
|
||||||
use crate::fighter::animation::AnimKind;
|
use crate::fighter::animation::AnimKind;
|
||||||
use crate::fighter::state::FighterState;
|
use crate::fighter::state::FighterState;
|
||||||
use crate::fighter::state::fall::FallState;
|
use crate::fighter::state::fall::FallState;
|
||||||
@@ -17,7 +14,7 @@ pub struct RunState {
|
|||||||
|
|
||||||
impl FighterStateImpl for RunState {
|
impl FighterStateImpl for RunState {
|
||||||
const NAME: &'static str = "Run";
|
const NAME: &'static str = "Run";
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
if let Some(state) =
|
if let Some(state) =
|
||||||
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
||||||
{
|
{
|
||||||
@@ -35,20 +32,21 @@ impl FighterStateImpl for RunState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
state_context.play_animation(AnimKind::Run, true);
|
state_context.play_animation(AnimKind::Run, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
let last_frame = state_context.input.get_last_frame();
|
let last_frame = state_context.input.get_last_frame();
|
||||||
let (accel, target_vel) = state_context
|
let (accel, target_vel) = state_context
|
||||||
.fighter_attributes
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
.get_accel_and_target_dashrun(&last_frame, FGi32::ONE);
|
.get_accel_and_target_dashrun(&last_frame, FGi32::ONE);
|
||||||
let accel = ground::compute_ground_accel(
|
let accel = ground::compute_ground_accel(
|
||||||
accel,
|
accel,
|
||||||
target_vel,
|
target_vel,
|
||||||
state_context.velocity.x,
|
state_context.velocity.x,
|
||||||
state_context.fighter_attributes,
|
&state_context.fighter_manifest.attributes,
|
||||||
state_context.game_settings,
|
state_context.game_settings,
|
||||||
);
|
);
|
||||||
state_context.velocity.x += accel;
|
state_context.velocity.x += accel;
|
||||||
@@ -62,7 +60,7 @@ impl FighterStateImpl for RunState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let GroundedMotionResult::InAir =
|
if let GroundedMotionResult::InAir =
|
||||||
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
||||||
@@ -75,7 +73,7 @@ impl FighterStateImpl for RunState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(
|
pub fn check_input(
|
||||||
state_context: &InterruptContext,
|
state_context: &FighterStateContext,
|
||||||
ground_state_common: &GroundedStateCommon,
|
ground_state_common: &GroundedStateCommon,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
let input_frame = state_context.input.get_last_frame();
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|||||||
+17
-16
@@ -1,4 +1,4 @@
|
|||||||
use super::{CollisionContext, EnterContext, FighterStateImpl, InterruptContext, UpdateContext};
|
use super::{FighterStateContext, FighterStateImpl};
|
||||||
use crate::fighter::animation::AnimKind;
|
use crate::fighter::animation::AnimKind;
|
||||||
use crate::fighter::state::fall::FallState;
|
use crate::fighter::state::fall::FallState;
|
||||||
use crate::fighter::state::ground::{self, GroundedMotionResult, GroundedStateCommon};
|
use crate::fighter::state::ground::{self, GroundedMotionResult, GroundedStateCommon};
|
||||||
@@ -12,7 +12,7 @@ pub struct TurnState {
|
|||||||
|
|
||||||
impl FighterStateImpl for TurnState {
|
impl FighterStateImpl for TurnState {
|
||||||
const NAME: &'static str = "Turn";
|
const NAME: &'static str = "Turn";
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
// State chain that results in the turn completing
|
// State chain that results in the turn completing
|
||||||
if state_context.get_current_animation_frame()
|
if state_context.get_current_animation_frame()
|
||||||
<= state_context
|
<= state_context
|
||||||
@@ -42,22 +42,23 @@ impl FighterStateImpl for TurnState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
state_context.play_animation(AnimKind::Turn, false);
|
state_context.play_animation(AnimKind::Turn, false);
|
||||||
*state_context.facing_direction = state_context.facing_direction.reverse();
|
*state_context.facing_direction = state_context.facing_direction.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
let friction =
|
let friction = if state_context.velocity.x.abs()
|
||||||
if state_context.velocity.x.abs() > state_context.fighter_attributes.max_walk_vel {
|
> state_context.fighter_manifest.attributes.max_walk_vel
|
||||||
state_context.fighter_attributes.ground_friction
|
{
|
||||||
* state_context
|
state_context.fighter_manifest.attributes.ground_friction
|
||||||
.game_settings
|
* state_context
|
||||||
.fighter_common
|
.game_settings
|
||||||
.ground_friction_over_walk_speed_multiplier
|
.fighter_common
|
||||||
} else {
|
.ground_friction_over_walk_speed_multiplier
|
||||||
state_context.fighter_attributes.ground_friction
|
} else {
|
||||||
};
|
state_context.fighter_manifest.attributes.ground_friction
|
||||||
|
};
|
||||||
|
|
||||||
let ground_velocity = state_context.velocity.x;
|
let ground_velocity = state_context.velocity.x;
|
||||||
|
|
||||||
@@ -72,7 +73,7 @@ impl FighterStateImpl for TurnState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let GroundedMotionResult::InAir =
|
if let GroundedMotionResult::InAir =
|
||||||
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
||||||
@@ -85,7 +86,7 @@ impl FighterStateImpl for TurnState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(
|
pub fn check_input(
|
||||||
state_context: &InterruptContext,
|
state_context: &FighterStateContext,
|
||||||
grounded_common: &GroundedStateCommon,
|
grounded_common: &GroundedStateCommon,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
let input_frame = state_context.input.get_last_frame();
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|||||||
+17
-18
@@ -1,6 +1,4 @@
|
|||||||
use super::{
|
use super::{FighterStateContext, FighterStateImpl, ground};
|
||||||
CollisionContext, EnterContext, FighterStateImpl, InterruptContext, UpdateContext, ground,
|
|
||||||
};
|
|
||||||
use crate::fighter::animation::AnimKind;
|
use crate::fighter::animation::AnimKind;
|
||||||
use crate::fighter::state::FighterState;
|
use crate::fighter::state::FighterState;
|
||||||
use crate::fighter::state::fall::FallState;
|
use crate::fighter::state::fall::FallState;
|
||||||
@@ -13,7 +11,7 @@ pub struct WaitState {
|
|||||||
|
|
||||||
impl FighterStateImpl for WaitState {
|
impl FighterStateImpl for WaitState {
|
||||||
const NAME: &'static str = "Wait";
|
const NAME: &'static str = "Wait";
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
if let Some(state) =
|
if let Some(state) =
|
||||||
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
||||||
{
|
{
|
||||||
@@ -33,21 +31,22 @@ impl FighterStateImpl for WaitState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
state_context.play_animation(AnimKind::Wait, true);
|
state_context.play_animation(AnimKind::Wait, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut super::FighterStateContext) {
|
||||||
let friction =
|
let friction = if state_context.velocity.x.abs()
|
||||||
if state_context.velocity.x.abs() > state_context.fighter_attributes.max_walk_vel {
|
> state_context.fighter_manifest.attributes.max_walk_vel
|
||||||
state_context.fighter_attributes.ground_friction
|
{
|
||||||
* state_context
|
state_context.fighter_manifest.attributes.ground_friction
|
||||||
.game_settings
|
* state_context
|
||||||
.fighter_common
|
.game_settings
|
||||||
.ground_friction_over_walk_speed_multiplier
|
.fighter_common
|
||||||
} else {
|
.ground_friction_over_walk_speed_multiplier
|
||||||
state_context.fighter_attributes.ground_friction
|
} else {
|
||||||
};
|
state_context.fighter_manifest.attributes.ground_friction
|
||||||
|
};
|
||||||
|
|
||||||
let ground_velocity = state_context.velocity.x;
|
let ground_velocity = state_context.velocity.x;
|
||||||
|
|
||||||
@@ -62,7 +61,7 @@ impl FighterStateImpl for WaitState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let GroundedMotionResult::InAir =
|
if let GroundedMotionResult::InAir =
|
||||||
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
||||||
@@ -75,7 +74,7 @@ impl FighterStateImpl for WaitState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(
|
pub fn check_input(
|
||||||
state_context: &InterruptContext,
|
state_context: &FighterStateContext,
|
||||||
ground_state_common: &GroundedStateCommon,
|
ground_state_common: &GroundedStateCommon,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
let input_frame = state_context.input.get_last_frame();
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
use super::{
|
use super::{FighterStateContext, FighterStateImpl, dash, ground, wait};
|
||||||
CollisionContext, EnterContext, FighterStateImpl, InterruptContext, UpdateContext, dash,
|
|
||||||
ground, wait,
|
|
||||||
};
|
|
||||||
use crate::fighter::animation::AnimKind;
|
use crate::fighter::animation::AnimKind;
|
||||||
use crate::fighter::state::FighterState;
|
use crate::fighter::state::FighterState;
|
||||||
use crate::fighter::state::fall::FallState;
|
use crate::fighter::state::fall::FallState;
|
||||||
@@ -14,7 +11,7 @@ pub struct WalkState {
|
|||||||
|
|
||||||
impl FighterStateImpl for WalkState {
|
impl FighterStateImpl for WalkState {
|
||||||
const NAME: &'static str = "Walk";
|
const NAME: &'static str = "Walk";
|
||||||
fn check_interrupt(&self, state_context: &mut InterruptContext) -> Option<FighterState> {
|
fn check_interrupt(&self, state_context: &mut FighterStateContext) -> Option<FighterState> {
|
||||||
if let Some(state) =
|
if let Some(state) =
|
||||||
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
ground::grounded_movement_common_interrupts(state_context, &self.grounded_common)
|
||||||
{
|
{
|
||||||
@@ -24,21 +21,22 @@ impl FighterStateImpl for WalkState {
|
|||||||
.or_else(|| dash::check_input(state_context, &self.grounded_common))
|
.or_else(|| dash::check_input(state_context, &self.grounded_common))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_enter(&mut self, state_context: &mut EnterContext) {
|
fn on_enter(&mut self, state_context: &mut FighterStateContext) {
|
||||||
state_context.play_animation(AnimKind::Walk, true);
|
state_context.play_animation(AnimKind::Walk, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, state_context: &mut UpdateContext) {
|
fn update(&mut self, state_context: &mut FighterStateContext) {
|
||||||
let last_frame = state_context.input.get_last_frame();
|
let last_frame = state_context.input.get_last_frame();
|
||||||
let (accel, target_vel) = state_context
|
let (accel, target_vel) = state_context
|
||||||
.fighter_attributes
|
.fighter_manifest
|
||||||
|
.attributes
|
||||||
.get_accel_and_target_walk(&last_frame);
|
.get_accel_and_target_walk(&last_frame);
|
||||||
|
|
||||||
let accel = ground::compute_ground_accel(
|
let accel = ground::compute_ground_accel(
|
||||||
accel,
|
accel,
|
||||||
target_vel,
|
target_vel,
|
||||||
state_context.velocity.x,
|
state_context.velocity.x,
|
||||||
state_context.fighter_attributes,
|
&state_context.fighter_manifest.attributes,
|
||||||
&state_context.game_settings,
|
&state_context.game_settings,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -53,7 +51,7 @@ impl FighterStateImpl for WalkState {
|
|||||||
|
|
||||||
fn check_collision_interrupt(
|
fn check_collision_interrupt(
|
||||||
&mut self,
|
&mut self,
|
||||||
state_context: &mut CollisionContext,
|
state_context: &mut FighterStateContext,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
if let GroundedMotionResult::InAir =
|
if let GroundedMotionResult::InAir =
|
||||||
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
ground::collide_with_stage_grounded(state_context, &mut self.grounded_common, false)
|
||||||
@@ -66,7 +64,7 @@ impl FighterStateImpl for WalkState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_input(
|
pub fn check_input(
|
||||||
state_context: &InterruptContext,
|
state_context: &FighterStateContext,
|
||||||
ground_state_common: &GroundedStateCommon,
|
ground_state_common: &GroundedStateCommon,
|
||||||
) -> Option<FighterState> {
|
) -> Option<FighterState> {
|
||||||
let input_frame = state_context.input.get_last_frame();
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|||||||
Reference in New Issue
Block a user