197 lines
6.9 KiB
Rust
197 lines
6.9 KiB
Rust
use bevy::prelude::*;
|
|
|
|
use crate::{
|
|
camera::MatchCamera,
|
|
fighter::{
|
|
FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop, FighterHitstopShake,
|
|
FighterPreviousFacingDirection, FighterPreviousTranslation, FighterSurfaceState,
|
|
FighterTranslation, FighterVelocity,
|
|
animation::FighterAnimationFrame,
|
|
attack::{
|
|
FighterDamage, FighterKnockbackVelocity, FighterPendingHitReaction, FighterPendingHits,
|
|
FighterSolvedHurtboxes, FighterStaleMoveQueue,
|
|
},
|
|
baked_animation::FighterBoneMatrices,
|
|
state::FighterState,
|
|
},
|
|
input::FighterInput,
|
|
player::Player,
|
|
stage::line::StageCollision,
|
|
};
|
|
|
|
#[derive(Resource, Default)]
|
|
pub(super) struct DebugSaveState {
|
|
checkpoint: Option<Checkpoint>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct Checkpoint {
|
|
fighters: Vec<FighterCheckpoint>,
|
|
stage_collision: StageCollision,
|
|
pending_hits: FighterPendingHits,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct FighterCheckpoint {
|
|
entity: Entity,
|
|
handle: usize,
|
|
ecb: FighterECB,
|
|
previous_ecb: crate::fighter::ecb::FighterPreviousECB,
|
|
velocity: FighterVelocity,
|
|
translation: FighterTranslation,
|
|
previous_translation: FighterPreviousTranslation,
|
|
facing: FighterFacingDirection,
|
|
previous_facing: FighterPreviousFacingDirection,
|
|
animation: FighterAnimationFrame,
|
|
state: FighterState,
|
|
input: FighterInput,
|
|
damage: FighterDamage,
|
|
pending_hit_reaction: FighterPendingHitReaction,
|
|
hitstop: FighterHitstop,
|
|
hitstop_shake: FighterHitstopShake,
|
|
knockback_velocity: FighterKnockbackVelocity,
|
|
stale_moves: FighterStaleMoveQueue,
|
|
hitboxes: FighterHitboxes,
|
|
solved_hurtboxes: FighterSolvedHurtboxes,
|
|
bone_matrices: FighterBoneMatrices,
|
|
surface: FighterSurfaceState,
|
|
}
|
|
|
|
pub(super) fn save_or_restore(world: &mut World) {
|
|
if world.resource::<super::DebugSettings>().menu_open {
|
|
return;
|
|
}
|
|
|
|
let left = world
|
|
.query::<&Gamepad>()
|
|
.iter(world)
|
|
.any(|gamepad| gamepad.just_pressed(GamepadButton::DPadLeft));
|
|
let right = world
|
|
.query::<&Gamepad>()
|
|
.iter(world)
|
|
.any(|gamepad| gamepad.just_pressed(GamepadButton::DPadRight));
|
|
|
|
if left == right {
|
|
return;
|
|
}
|
|
|
|
let Some(roster) = world
|
|
.get_resource::<crate::fighter::FighterRoster>()
|
|
.map(|roster| roster.0.clone())
|
|
else {
|
|
return;
|
|
};
|
|
if roster.len() != 1 {
|
|
warn!("Save states are only available in single-player matches");
|
|
return;
|
|
}
|
|
|
|
if left {
|
|
let Some(stage_collision) = world.get_resource::<StageCollision>().cloned() else {
|
|
return;
|
|
};
|
|
let mut checkpoint_fighters = Vec::with_capacity(roster.len());
|
|
for entity in roster.iter().copied() {
|
|
let Some(checkpoint) = capture_fighter(world, entity) else {
|
|
warn!("Could not save the current fighter state");
|
|
return;
|
|
};
|
|
checkpoint_fighters.push(checkpoint);
|
|
}
|
|
let pending_hits = world.resource::<FighterPendingHits>().clone();
|
|
world.resource_mut::<DebugSaveState>().checkpoint = Some(Checkpoint {
|
|
fighters: checkpoint_fighters,
|
|
stage_collision,
|
|
pending_hits,
|
|
});
|
|
info!("Saved debug game state");
|
|
return;
|
|
}
|
|
|
|
let Some(checkpoint) = world.resource::<DebugSaveState>().checkpoint.clone() else {
|
|
warn!("No debug game state has been saved");
|
|
return;
|
|
};
|
|
if checkpoint.fighters.len() != roster.len()
|
|
|| checkpoint
|
|
.fighters
|
|
.iter()
|
|
.zip(roster.iter())
|
|
.any(|(saved, current)| saved.entity != *current)
|
|
{
|
|
warn!("Saved debug game state belongs to another match");
|
|
return;
|
|
}
|
|
|
|
for saved in &checkpoint.fighters {
|
|
let Some(player) = world.entity(saved.entity).get::<Player>() else {
|
|
warn!("Could not restore the saved fighter state");
|
|
return;
|
|
};
|
|
if player.handle != saved.handle {
|
|
warn!("Saved debug game state belongs to another match");
|
|
return;
|
|
}
|
|
|
|
let mut entity = world.entity_mut(saved.entity);
|
|
entity.insert(saved.ecb);
|
|
entity.insert(saved.previous_ecb);
|
|
entity.insert(saved.velocity);
|
|
entity.insert(saved.translation);
|
|
entity.insert(saved.previous_translation);
|
|
entity.insert(saved.facing);
|
|
entity.insert(saved.previous_facing);
|
|
entity.insert(saved.animation);
|
|
entity.insert(saved.state.clone());
|
|
entity.insert(saved.input);
|
|
entity.insert(saved.damage);
|
|
entity.insert(saved.pending_hit_reaction.clone());
|
|
entity.insert(saved.hitstop);
|
|
entity.insert(saved.hitstop_shake);
|
|
entity.insert(saved.knockback_velocity);
|
|
entity.insert(saved.stale_moves.clone());
|
|
entity.insert(saved.hitboxes.clone());
|
|
entity.insert(saved.solved_hurtboxes.clone());
|
|
entity.insert(saved.bone_matrices.clone());
|
|
entity.insert(saved.surface);
|
|
}
|
|
world.insert_resource(checkpoint.pending_hits.clone());
|
|
world.insert_resource(checkpoint.stage_collision);
|
|
for mut match_camera in world.query::<&mut MatchCamera>().iter_mut(world) {
|
|
match_camera.needs_snap = true;
|
|
}
|
|
info!("Restored debug game state");
|
|
}
|
|
|
|
fn capture_fighter(world: &World, entity: Entity) -> Option<FighterCheckpoint> {
|
|
let fighter = world.entity(entity);
|
|
Some(FighterCheckpoint {
|
|
entity,
|
|
handle: fighter.get::<Player>()?.handle,
|
|
ecb: *fighter.get::<FighterECB>()?,
|
|
previous_ecb: *fighter.get::<crate::fighter::ecb::FighterPreviousECB>()?,
|
|
velocity: *fighter.get::<FighterVelocity>()?,
|
|
translation: *fighter.get::<FighterTranslation>()?,
|
|
previous_translation: *fighter.get::<FighterPreviousTranslation>()?,
|
|
facing: *fighter.get::<FighterFacingDirection>()?,
|
|
previous_facing: *fighter.get::<FighterPreviousFacingDirection>()?,
|
|
animation: *fighter.get::<FighterAnimationFrame>()?,
|
|
state: fighter.get::<FighterState>()?.clone(),
|
|
input: *fighter.get::<FighterInput>()?,
|
|
damage: *fighter.get::<FighterDamage>()?,
|
|
pending_hit_reaction: fighter.get::<FighterPendingHitReaction>()?.clone(),
|
|
hitstop: *fighter.get::<FighterHitstop>()?,
|
|
hitstop_shake: *fighter.get::<FighterHitstopShake>()?,
|
|
knockback_velocity: *fighter.get::<FighterKnockbackVelocity>()?,
|
|
stale_moves: fighter.get::<FighterStaleMoveQueue>()?.clone(),
|
|
hitboxes: fighter.get::<FighterHitboxes>()?.clone(),
|
|
solved_hurtboxes: fighter.get::<FighterSolvedHurtboxes>()?.clone(),
|
|
bone_matrices: fighter.get::<FighterBoneMatrices>()?.clone(),
|
|
surface: *fighter.get::<FighterSurfaceState>()?,
|
|
})
|
|
}
|
|
|
|
pub(super) fn clear(mut save_state: ResMut<DebugSaveState>) {
|
|
save_state.checkpoint = None;
|
|
}
|