Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e7529a3ac | ||
|
|
b8b65e60e9 | ||
|
|
43a04e9491 |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# for Linux
|
# for Linux
|
||||||
[target.x86_64-unknown-linux-gnu]
|
[target.x86_64-unknown-linux-gnu]
|
||||||
linker = "clang"
|
linker = "clang"
|
||||||
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold", "-Zshare-generics=y", ]
|
rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold", "-Zshare-generics=y", "-D", "warnings", ]
|
||||||
|
|
||||||
[unstable]
|
[unstable]
|
||||||
codegen-backend = true
|
codegen-backend = true
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ jobs:
|
|||||||
- name: Cache Rust dependencies and build artifacts
|
- name: Cache Rust dependencies and build artifacts
|
||||||
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
with:
|
with:
|
||||||
|
prefix-key: v1-rust
|
||||||
shared-key: linux-nightly
|
shared-key: linux-nightly
|
||||||
cache-bin: false
|
cache-bin: false
|
||||||
|
|
||||||
@@ -45,7 +46,7 @@ jobs:
|
|||||||
pkg-config
|
pkg-config
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: RUSTFLAGS="-D warnings" cargo build --locked -j 4
|
run: cargo build --locked -j 4
|
||||||
|
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: cargo test --locked --lib -j 4
|
run: cargo test --locked --lib -j 4
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ use enum_cycling_derive::EnumCycle;
|
|||||||
|
|
||||||
use crate::{AppState, player::Player, replay::StopReplayRecording};
|
use crate::{AppState, player::Player, replay::StopReplayRecording};
|
||||||
|
|
||||||
|
mod save_state;
|
||||||
|
|
||||||
pub const PLAYER_DEBUG_ROW_COUNT: usize = 6;
|
pub const PLAYER_DEBUG_ROW_COUNT: usize = 6;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
|
||||||
@@ -378,6 +380,9 @@ impl Plugin for DebugToolsPlugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_resource::<DebugSettings>()
|
app.init_resource::<DebugSettings>()
|
||||||
.init_resource::<FrameStepState>()
|
.init_resource::<FrameStepState>()
|
||||||
|
.init_resource::<save_state::DebugSaveState>()
|
||||||
|
.add_systems(OnEnter(AppState::InMatch), save_state::clear)
|
||||||
|
.add_systems(OnExit(AppState::InMatch), save_state::clear)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PreUpdate,
|
PreUpdate,
|
||||||
prepare_frame_step
|
prepare_frame_step
|
||||||
@@ -385,6 +390,13 @@ impl Plugin for DebugToolsPlugin {
|
|||||||
.before(RunGgrsSystems)
|
.before(RunGgrsSystems)
|
||||||
.run_if(in_state(AppState::InMatch)),
|
.run_if(in_state(AppState::InMatch)),
|
||||||
)
|
)
|
||||||
|
.add_systems(
|
||||||
|
PreUpdate,
|
||||||
|
save_state::save_or_restore
|
||||||
|
.after(InputSystems)
|
||||||
|
.before(RunGgrsSystems)
|
||||||
|
.run_if(in_state(AppState::InMatch)),
|
||||||
|
)
|
||||||
.add_systems(PreUpdate, restore_frame_step_time.after(RunGgrsSystems))
|
.add_systems(PreUpdate, restore_frame_step_time.after(RunGgrsSystems))
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
camera::MatchCamera,
|
||||||
|
fighter::{
|
||||||
|
FighterECB, FighterFacingDirection, FighterHitboxes, FighterHitstop,
|
||||||
|
FighterPreviousFacingDirection, FighterPreviousTranslation, FighterTranslation,
|
||||||
|
FighterVelocity, Grounded,
|
||||||
|
animation::FighterAnimationFrame,
|
||||||
|
attack::{
|
||||||
|
FighterDamage, FighterKnockbackVelocity, FighterPendingHitReaction,
|
||||||
|
FighterReceivedAttacks, 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
knockback_velocity: FighterKnockbackVelocity,
|
||||||
|
stale_moves: FighterStaleMoveQueue,
|
||||||
|
hitboxes: FighterHitboxes,
|
||||||
|
received_attacks: FighterReceivedAttacks,
|
||||||
|
solved_hurtboxes: FighterSolvedHurtboxes,
|
||||||
|
bone_matrices: FighterBoneMatrices,
|
||||||
|
grounded: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
world.resource_mut::<DebugSaveState>().checkpoint = Some(Checkpoint {
|
||||||
|
fighters: checkpoint_fighters,
|
||||||
|
stage_collision,
|
||||||
|
});
|
||||||
|
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.knockback_velocity);
|
||||||
|
entity.insert(saved.stale_moves.clone());
|
||||||
|
entity.insert(saved.hitboxes.clone());
|
||||||
|
entity.insert(saved.received_attacks.clone());
|
||||||
|
entity.insert(saved.solved_hurtboxes.clone());
|
||||||
|
entity.insert(saved.bone_matrices.clone());
|
||||||
|
if saved.grounded {
|
||||||
|
entity.insert(Grounded);
|
||||||
|
} else {
|
||||||
|
entity.remove::<Grounded>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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>()?,
|
||||||
|
knockback_velocity: *fighter.get::<FighterKnockbackVelocity>()?,
|
||||||
|
stale_moves: fighter.get::<FighterStaleMoveQueue>()?.clone(),
|
||||||
|
hitboxes: fighter.get::<FighterHitboxes>()?.clone(),
|
||||||
|
received_attacks: fighter.get::<FighterReceivedAttacks>()?.clone(),
|
||||||
|
solved_hurtboxes: fighter.get::<FighterSolvedHurtboxes>()?.clone(),
|
||||||
|
bone_matrices: fighter.get::<FighterBoneMatrices>()?.clone(),
|
||||||
|
grounded: fighter.contains::<Grounded>(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn clear(mut save_state: ResMut<DebugSaveState>) {
|
||||||
|
save_state.checkpoint = None;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user