Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c849caf5b9 | ||
|
|
9f66d55dcc | ||
|
|
578648d2ec | ||
|
|
0d96a8aed8 | ||
|
|
cebf1148c2 | ||
|
|
088d594e97 | ||
|
|
868df95cd4 | ||
|
|
504f63be54 | ||
|
|
8c11a01446 | ||
|
|
30477254e5 | ||
|
|
4861374aef | ||
|
|
d3a688e768 | ||
|
|
376a40d38c | ||
|
|
b29085f8a5 | ||
|
|
b0b1420f48 |
@@ -0,0 +1,51 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
steps:
|
||||||
|
- name: Check out source
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
run: |
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
||||||
|
sh -s -- -y --profile minimal
|
||||||
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||||
|
"$HOME/.cargo/bin/rustup" toolchain install nightly-2026-09-14 --profile minimal
|
||||||
|
|
||||||
|
- name: Cache Rust dependencies and build artifacts
|
||||||
|
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||||
|
with:
|
||||||
|
shared-key: linux-nightly
|
||||||
|
cache-bin: false
|
||||||
|
|
||||||
|
- name: Install Linux dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install --no-install-recommends -y \
|
||||||
|
clang \
|
||||||
|
libasound2-dev \
|
||||||
|
libudev-dev \
|
||||||
|
libusb-1.0-0-dev \
|
||||||
|
libwayland-dev \
|
||||||
|
libx11-dev \
|
||||||
|
libxkbcommon-dev \
|
||||||
|
libxkbcommon-x11-0 \
|
||||||
|
mesa-vulkan-drivers \
|
||||||
|
mold \
|
||||||
|
pkg-config
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: RUSTFLAGS="-D warnings" cargo build --locked -j 4
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: cargo test --locked --lib -j 4
|
||||||
+10
-1
@@ -1,3 +1,12 @@
|
|||||||
/target
|
/target
|
||||||
/.jackdaw
|
/.jackdaw
|
||||||
imported_assets
|
imported_assets
|
||||||
|
|
||||||
|
# Local editor configuration
|
||||||
|
**/.vscode/
|
||||||
|
|
||||||
|
# Generated and backup assets
|
||||||
|
*.blend1
|
||||||
|
*_old.glb
|
||||||
|
/replay.json
|
||||||
|
*.replay.tmp
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
frame(3);
|
frame(4);
|
||||||
hitbox(0,
|
hitbox(0,
|
||||||
"Foot.L",
|
"hand_l",
|
||||||
damage("5.0"),
|
damage("5.0"),
|
||||||
vec3("0.0", "0.0", "0.0"),
|
vec3("0.0", "0.0", "0.0"),
|
||||||
fg32("20.0"),
|
fg32("5.0"),
|
||||||
angle_sakurai(),
|
angle_sakurai(),
|
||||||
knockback_normal(),
|
knockback_normal(),
|
||||||
knockback("10.0"),
|
knockback("10.0"),
|
||||||
fg32("1.0")
|
fg32("1.0")
|
||||||
);
|
);
|
||||||
frame(6);
|
frame(8);
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
frame(3);
|
||||||
|
hitbox(0,
|
||||||
|
"hand_l",
|
||||||
|
damage("5.0"),
|
||||||
|
vec3("0.0", "0.0", "0.0"),
|
||||||
|
fg32("5.0"),
|
||||||
|
angle(fg32("45.0")),
|
||||||
|
knockback_normal(),
|
||||||
|
knockback("18.0"),
|
||||||
|
fg32("140.0")
|
||||||
|
);
|
||||||
|
frame(8);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
frame(9);
|
||||||
|
hitbox(0,
|
||||||
|
"hand_l",
|
||||||
|
damage("5.0"),
|
||||||
|
vec3("1.0", "0.0", "0.0"),
|
||||||
|
fg32("7.0"),
|
||||||
|
angle_sakurai(),
|
||||||
|
knockback_normal(),
|
||||||
|
knockback("10.0"),
|
||||||
|
fg32("1.0")
|
||||||
|
);
|
||||||
|
|
||||||
|
frame(15);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
(
|
(
|
||||||
id: TestFighter,
|
id: TestFighter,
|
||||||
model_path: "fighters/test/test-fighter-2.glb",
|
model_path: "fighters/test/test_fighter.gltf",
|
||||||
model_scale: "10.0",
|
model_scale: "10.0",
|
||||||
baked_animation_path: "fighters/test/test.fighterbake",
|
baked_animation_path: "fighters/test/test.fighterbake",
|
||||||
icon: (
|
icon: (
|
||||||
@@ -15,10 +15,10 @@
|
|||||||
dash_duration: 24,
|
dash_duration: 24,
|
||||||
base_dash_accel: "0.1",
|
base_dash_accel: "0.1",
|
||||||
stick_dash_accel: "0.05",
|
stick_dash_accel: "0.05",
|
||||||
max_dash_vel: "2.3",
|
max_dash_vel: "2.0",
|
||||||
short_hop_vertical_velocity: "1.35",
|
short_hop_vertical_velocity: "1.45",
|
||||||
full_jump_vertical_velocity: "2.1",
|
full_jump_vertical_velocity: "2.0",
|
||||||
jump_horizontal_velocity: "0.7",
|
jump_horizontal_velocity: "0.6",
|
||||||
air_jump_horizontal_velocity: "0.96",
|
air_jump_horizontal_velocity: "0.96",
|
||||||
air_jump_multiplier: "1.0",
|
air_jump_multiplier: "1.0",
|
||||||
dash_initial_velocity: "1.4",
|
dash_initial_velocity: "1.4",
|
||||||
@@ -63,6 +63,10 @@
|
|||||||
DoubleJumpBack: "JumpB_Game",
|
DoubleJumpBack: "JumpB_Game",
|
||||||
Fall: "Fall_Loop_Game",
|
Fall: "Fall_Loop_Game",
|
||||||
Turn: "Turn_Game",
|
Turn: "Turn_Game",
|
||||||
|
|
||||||
|
FlinchGround: "Air_Hit_Game",
|
||||||
|
FlinchAir: "Air_Hit_Game",
|
||||||
|
|
||||||
AttackJab1: "Punch_Jab_Game",
|
AttackJab1: "Punch_Jab_Game",
|
||||||
AttackUpTilt: "Idle_Loop_Game",
|
AttackUpTilt: "Idle_Loop_Game",
|
||||||
AttackForwardTilt: "Idle_Loop_Game",
|
AttackForwardTilt: "Idle_Loop_Game",
|
||||||
@@ -70,7 +74,7 @@
|
|||||||
},
|
},
|
||||||
attack_scripts: {
|
attack_scripts: {
|
||||||
NeutralAir: "fighters/test/scripts/fair.attack.rhai",
|
NeutralAir: "fighters/test/scripts/fair.attack.rhai",
|
||||||
Jab: "fighters/test/scripts/fair.attack.rhai",
|
Jab: "fighters/test/scripts/jab.attack.rhai",
|
||||||
UpTilt: "fighters/test/scripts/uptilt.attack.rhai",
|
UpTilt: "fighters/test/scripts/uptilt.attack.rhai",
|
||||||
ForwardTilt: "fighters/test/scripts/uptilt.attack.rhai",
|
ForwardTilt: "fighters/test/scripts/uptilt.attack.rhai",
|
||||||
DownTilt: "fighters/test/scripts/uptilt.attack.rhai",
|
DownTilt: "fighters/test/scripts/uptilt.attack.rhai",
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -8,12 +8,16 @@
|
|||||||
smash_input_frame_threshold: 3,
|
smash_input_frame_threshold: 3,
|
||||||
input_buffer_size: 3,
|
input_buffer_size: 3,
|
||||||
turn_into_dashback_window: 2,
|
turn_into_dashback_window: 2,
|
||||||
|
|
||||||
),
|
),
|
||||||
fighter_common: (
|
fighter_common: (
|
||||||
walk_speed_ease: "0.5",
|
walk_speed_ease: "0.5",
|
||||||
ground_max_horizontal_velocity: "3.0",
|
ground_max_horizontal_velocity: "3.0",
|
||||||
ground_friction_over_walk_speed_multiplier: "2.0"
|
ground_friction_over_walk_speed_multiplier: "2.0",
|
||||||
|
knockback_stacking_window: 2,
|
||||||
|
knockback_decay_per_frame: "0.051",
|
||||||
|
knockback_launch_velocity_multiplier: "0.03",
|
||||||
|
hitstun_frames_per_unit_knockback: "0.4",
|
||||||
|
|
||||||
),
|
),
|
||||||
stage_manifest_paths: [
|
stage_manifest_paths: [
|
||||||
"stages/battlefield/test.stage.ron"
|
"stages/battlefield/test.stage.ron"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,84 +0,0 @@
|
|||||||
(
|
|
||||||
id: TestStage,
|
|
||||||
model_path: "stages/battlefield.glb#Scene0",
|
|
||||||
camera: (
|
|
||||||
left: -75.0,
|
|
||||||
right: 75.0,
|
|
||||||
bottom: -30.0,
|
|
||||||
top: 70.0,
|
|
||||||
origin: (0.0, 15.0),
|
|
||||||
vertical_fov_degrees: 30.0,
|
|
||||||
min_depth: 150.0,
|
|
||||||
max_depth: 400.0,
|
|
||||||
subject_scale: 1.0,
|
|
||||||
tracking_smoothness: 3.0,
|
|
||||||
vertical_tilt_degrees: 0.0,
|
|
||||||
horizontal_pan_coefficient: 0.0,
|
|
||||||
vertical_pan_coefficient: 0.0,
|
|
||||||
max_horizontal_pan_degrees: 17.5,
|
|
||||||
max_upward_pan_degrees: 5.0,
|
|
||||||
max_downward_pan_degrees: 7.0,
|
|
||||||
max_downward_expansion: 40.0,
|
|
||||||
),
|
|
||||||
polygons: [
|
|
||||||
(
|
|
||||||
poly_type: Platform,
|
|
||||||
lines: [
|
|
||||||
(
|
|
||||||
(x: "-59.5", y: "23.45"),
|
|
||||||
Floor
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "-28.0", y: "23.45"),
|
|
||||||
Floor
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
(
|
|
||||||
poly_type: Platform,
|
|
||||||
lines: [
|
|
||||||
(
|
|
||||||
(x: "28.0", y: "23.45"),
|
|
||||||
Floor
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "59.5", y: "23.45"),
|
|
||||||
Floor
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
|
||||||
(
|
|
||||||
poly_type: Closed,
|
|
||||||
lines: [
|
|
||||||
(
|
|
||||||
(x: "-56", y: "-200.0"),
|
|
||||||
Wall
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "-56.0", y: "-3.5"),
|
|
||||||
Floor,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "-39.2", y: "0.0"),
|
|
||||||
Floor,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "0.0", y: "0.0"),
|
|
||||||
Floor
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "39.2", y: "0.0"),
|
|
||||||
Floor
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "56.0", y: "-3.5"),
|
|
||||||
Wall
|
|
||||||
),
|
|
||||||
(
|
|
||||||
(x: "56", y: "-200.0"),
|
|
||||||
Wall
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 871 B |
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128"
|
||||||
|
height="128"
|
||||||
|
viewBox="0 0 33.866666 33.866666"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
|
||||||
|
sodipodi:docname="cursor.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#000000"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="5.6568542"
|
||||||
|
inkscape:cx="8.6620581"
|
||||||
|
inkscape:cy="67.086756"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1006"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient1"
|
||||||
|
inkscape:collect="always">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#949494;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop1" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#6c6c6c;stop-opacity:1;"
|
||||||
|
offset="0.77473962"
|
||||||
|
id="stop3" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#9f9f9f;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop2" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient1"
|
||||||
|
id="radialGradient2"
|
||||||
|
cx="16.933332"
|
||||||
|
cy="16.933332"
|
||||||
|
fx="16.933332"
|
||||||
|
fy="16.933332"
|
||||||
|
r="12.733072"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(1.2093771,0,0,1.2093771,-3.5454518,-3.5454518)" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Capa 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<circle
|
||||||
|
style="fill:url(#radialGradient2);stroke-width:0.319981"
|
||||||
|
id="path1"
|
||||||
|
cx="16.933332"
|
||||||
|
cy="16.933332"
|
||||||
|
r="15.399086" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly"
|
channel = "nightly-2026-09-14"
|
||||||
|
|||||||
+359
-116
@@ -3,11 +3,12 @@
|
|||||||
use std::{collections::HashMap, time::Duration};
|
use std::{collections::HashMap, time::Duration};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
|
input::InputSystems,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
settings::{ReflectSettingsGroup, SaveSettingsDeferred, SettingsGroup},
|
settings::{ReflectSettingsGroup, SaveSettingsDeferred, SettingsGroup},
|
||||||
};
|
};
|
||||||
use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui};
|
use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui};
|
||||||
use bevy_ggrs::RollbackFrameRate;
|
use bevy_ggrs::{RollbackFrameRate, RunGgrsSystems};
|
||||||
use enum_cycling::EnumCycle;
|
use enum_cycling::EnumCycle;
|
||||||
use enum_cycling_derive::EnumCycle;
|
use enum_cycling_derive::EnumCycle;
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ impl PresentationMotionMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(EnumCycle, Reflect)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumCycle, Reflect)]
|
||||||
pub enum GameSpeedDebug {
|
pub enum GameSpeedDebug {
|
||||||
Full,
|
Full,
|
||||||
Half,
|
Half,
|
||||||
@@ -114,6 +115,7 @@ enum DebugFlag {
|
|||||||
Hurtboxes,
|
Hurtboxes,
|
||||||
Camera,
|
Camera,
|
||||||
DrawThroughGeometry,
|
DrawThroughGeometry,
|
||||||
|
FrameStepping,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@@ -174,6 +176,10 @@ const WORLD_MENU: &[DebugMenuEntry] = &[
|
|||||||
DebugMenuEntry::GameSpeed {
|
DebugMenuEntry::GameSpeed {
|
||||||
label: "Game speed",
|
label: "Game speed",
|
||||||
},
|
},
|
||||||
|
DebugMenuEntry::Toggle {
|
||||||
|
label: "Frame stepping",
|
||||||
|
flag: DebugFlag::FrameStepping,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const DIAGNOSTICS_MENU: &[DebugMenuEntry] = &[DebugMenuEntry::Toggle {
|
const DIAGNOSTICS_MENU: &[DebugMenuEntry] = &[DebugMenuEntry::Toggle {
|
||||||
@@ -233,6 +239,8 @@ pub struct DebugSettings {
|
|||||||
selections: Vec<usize>,
|
selections: Vec<usize>,
|
||||||
#[reflect(ignore)]
|
#[reflect(ignore)]
|
||||||
pub player_rows: HashMap<usize, [FighterDebugRowKind; PLAYER_DEBUG_ROW_COUNT]>,
|
pub player_rows: HashMap<usize, [FighterDebugRowKind; PLAYER_DEBUG_ROW_COUNT]>,
|
||||||
|
#[reflect(ignore)]
|
||||||
|
pub frame_stepping: bool,
|
||||||
pub network: bool,
|
pub network: bool,
|
||||||
pub ecb: bool,
|
pub ecb: bool,
|
||||||
pub stage_collision: bool,
|
pub stage_collision: bool,
|
||||||
@@ -253,6 +261,7 @@ impl Default for DebugSettings {
|
|||||||
pages: vec![DebugMenuPage::Static(ROOT_MENU)],
|
pages: vec![DebugMenuPage::Static(ROOT_MENU)],
|
||||||
selections: vec![0],
|
selections: vec![0],
|
||||||
player_rows: HashMap::new(),
|
player_rows: HashMap::new(),
|
||||||
|
frame_stepping: false,
|
||||||
network: false,
|
network: false,
|
||||||
ecb: false,
|
ecb: false,
|
||||||
stage_collision: false,
|
stage_collision: false,
|
||||||
@@ -325,6 +334,7 @@ impl DebugSettings {
|
|||||||
DebugFlag::Hurtboxes => self.hurtboxes,
|
DebugFlag::Hurtboxes => self.hurtboxes,
|
||||||
DebugFlag::DrawThroughGeometry => self.draw_through_geometry,
|
DebugFlag::DrawThroughGeometry => self.draw_through_geometry,
|
||||||
DebugFlag::Camera => self.camera,
|
DebugFlag::Camera => self.camera,
|
||||||
|
DebugFlag::FrameStepping => self.frame_stepping,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,6 +350,7 @@ impl DebugSettings {
|
|||||||
self.draw_through_geometry = !self.draw_through_geometry
|
self.draw_through_geometry = !self.draw_through_geometry
|
||||||
}
|
}
|
||||||
DebugFlag::Camera => self.camera = !self.camera,
|
DebugFlag::Camera => self.camera = !self.camera,
|
||||||
|
DebugFlag::FrameStepping => self.frame_stepping = !self.frame_stepping,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,9 +367,25 @@ impl DebugSettings {
|
|||||||
|
|
||||||
pub struct DebugToolsPlugin;
|
pub struct DebugToolsPlugin;
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct FrameStepState {
|
||||||
|
requested: bool,
|
||||||
|
time_backup: Option<Time>,
|
||||||
|
last_step_time: Time
|
||||||
|
}
|
||||||
|
|
||||||
impl Plugin for DebugToolsPlugin {
|
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>()
|
||||||
|
.add_systems(
|
||||||
|
PreUpdate,
|
||||||
|
prepare_frame_step
|
||||||
|
.after(InputSystems)
|
||||||
|
.before(RunGgrsSystems)
|
||||||
|
.run_if(in_state(AppState::InMatch)),
|
||||||
|
)
|
||||||
|
.add_systems(PreUpdate, restore_frame_step_time.after(RunGgrsSystems))
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
(handle_palette_input, sync_gizmo_depth_bias)
|
(handle_palette_input, sync_gizmo_depth_bias)
|
||||||
@@ -387,6 +414,141 @@ fn page_len(page: DebugMenuPage, player_handles: &[usize]) -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn queue_settings_save(commands: &mut Commands) {
|
||||||
|
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame_step_duration(frame_rate: usize) -> Duration {
|
||||||
|
Duration::from_nanos(1_000_000_000u64 / frame_rate as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_frame_step(
|
||||||
|
settings: Res<DebugSettings>,
|
||||||
|
gamepads: Query<&Gamepad>,
|
||||||
|
key: Res<ButtonInput<KeyCode>>,
|
||||||
|
mut state: ResMut<FrameStepState>,
|
||||||
|
mut time: ResMut<Time>,
|
||||||
|
frame_rate: Res<RollbackFrameRate>,
|
||||||
|
) {
|
||||||
|
if !settings.frame_stepping {
|
||||||
|
state.requested = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pressed = gamepads
|
||||||
|
.iter()
|
||||||
|
.any(|gamepad| gamepad.pressed(GamepadButton::DPadDown));
|
||||||
|
|
||||||
|
let just_pressed = gamepads
|
||||||
|
.iter()
|
||||||
|
.any(|gamepad| gamepad.just_pressed(GamepadButton::DPadDown));
|
||||||
|
|
||||||
|
let just_pressed = key.just_pressed(KeyCode::F4) || just_pressed;
|
||||||
|
let pressed = key.pressed(KeyCode::F4) || pressed;
|
||||||
|
|
||||||
|
if just_pressed {
|
||||||
|
state.last_step_time = time.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let can_repress = pressed && (time.elapsed() - state.last_step_time.elapsed()).as_millis() > 500;
|
||||||
|
|
||||||
|
if !settings.menu_open && (just_pressed || can_repress)
|
||||||
|
{
|
||||||
|
state.requested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.time_backup = Some(*time);
|
||||||
|
let delta = if state.requested {
|
||||||
|
frame_step_duration(**frame_rate)
|
||||||
|
} else {
|
||||||
|
Duration::ZERO
|
||||||
|
};
|
||||||
|
time.advance_by(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore_frame_step_time(mut state: ResMut<FrameStepState>, mut time: ResMut<Time>) {
|
||||||
|
if let Some(backup) = state.time_backup.take() {
|
||||||
|
*time = backup;
|
||||||
|
state.requested = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate_selected(
|
||||||
|
settings: &mut DebugSettings,
|
||||||
|
page_len: usize,
|
||||||
|
player_handles: &[usize],
|
||||||
|
commands: &mut Commands,
|
||||||
|
) {
|
||||||
|
if page_len == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match settings.page() {
|
||||||
|
DebugMenuPage::Static(entries) => match entries[settings.selected()] {
|
||||||
|
DebugMenuEntry::StopRecording { .. } => {
|
||||||
|
commands.insert_resource(StopReplayRecording);
|
||||||
|
}
|
||||||
|
DebugMenuEntry::Toggle { flag, .. } => {
|
||||||
|
settings.toggle_flag(flag);
|
||||||
|
if !matches!(flag, DebugFlag::FrameStepping) {
|
||||||
|
queue_settings_save(commands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebugMenuEntry::Submenu { entries, .. } => {
|
||||||
|
settings.push_page(DebugMenuPage::Static(entries));
|
||||||
|
}
|
||||||
|
DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList),
|
||||||
|
DebugMenuEntry::MotionSampling { .. } => {
|
||||||
|
settings.cycle_motion_sampling();
|
||||||
|
queue_settings_save(commands);
|
||||||
|
}
|
||||||
|
DebugMenuEntry::GameSpeed { .. } => {
|
||||||
|
commands.insert_resource(RollbackFrameRate(settings.game_speed.frame_rate()));
|
||||||
|
queue_settings_save(commands);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DebugMenuPage::PlayerList => {
|
||||||
|
let handle = player_handles[settings.selected()];
|
||||||
|
settings.rows_mut(handle);
|
||||||
|
settings.push_page(DebugMenuPage::PlayerRows(handle));
|
||||||
|
}
|
||||||
|
DebugMenuPage::PlayerRows(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cycle_selected_value(settings: &mut DebugSettings, direction: i8, commands: &mut Commands) {
|
||||||
|
match settings.page() {
|
||||||
|
DebugMenuPage::Static(entries) => {
|
||||||
|
if let Some(entry) = entries.get(settings.selected()) {
|
||||||
|
match entry {
|
||||||
|
DebugMenuEntry::MotionSampling { .. } => {
|
||||||
|
settings.cycle_motion_sampling();
|
||||||
|
queue_settings_save(commands);
|
||||||
|
}
|
||||||
|
DebugMenuEntry::GameSpeed { .. } => {
|
||||||
|
settings.game_speed = if direction > 0 {
|
||||||
|
settings.game_speed.up()
|
||||||
|
} else {
|
||||||
|
settings.game_speed.down()
|
||||||
|
};
|
||||||
|
commands
|
||||||
|
.insert_resource(RollbackFrameRate(settings.game_speed.frame_rate()));
|
||||||
|
queue_settings_save(commands);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebugMenuPage::PlayerRows(handle) => {
|
||||||
|
let selected = settings.selected();
|
||||||
|
let rows = settings.rows_mut(handle);
|
||||||
|
rows[selected] = rows[selected].cycle(direction);
|
||||||
|
queue_settings_save(commands);
|
||||||
|
}
|
||||||
|
DebugMenuPage::PlayerList => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_palette_input(
|
fn handle_palette_input(
|
||||||
keyboard: Res<ButtonInput<KeyCode>>,
|
keyboard: Res<ButtonInput<KeyCode>>,
|
||||||
gamepads: Query<(Entity, &Gamepad)>,
|
gamepads: Query<(Entity, &Gamepad)>,
|
||||||
@@ -398,6 +560,15 @@ fn handle_palette_input(
|
|||||||
settings.toggle_flag(DebugFlag::DrawThroughGeometry);
|
settings.toggle_flag(DebugFlag::DrawThroughGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if keyboard.just_pressed(KeyCode::F3) {
|
||||||
|
settings.menu_open = !settings.menu_open;
|
||||||
|
settings.controller = None;
|
||||||
|
if settings.menu_open {
|
||||||
|
settings.reset_navigation();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if !settings.menu_open {
|
if !settings.menu_open {
|
||||||
for (entity, gamepad) in &gamepads {
|
for (entity, gamepad) in &gamepads {
|
||||||
if gamepad.just_pressed(GamepadButton::Start) && gamepad.pressed(GamepadButton::DPadUp)
|
if gamepad.just_pressed(GamepadButton::Start) && gamepad.pressed(GamepadButton::DPadUp)
|
||||||
@@ -411,14 +582,15 @@ fn handle_palette_input(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(controller) = settings.controller else {
|
let gamepad = if let Some(controller) = settings.controller {
|
||||||
settings.menu_open = false;
|
let Ok((_, gamepad)) = gamepads.get(controller) else {
|
||||||
return;
|
settings.menu_open = false;
|
||||||
};
|
settings.controller = None;
|
||||||
let Ok((_, gamepad)) = gamepads.get(controller) else {
|
return;
|
||||||
settings.menu_open = false;
|
};
|
||||||
settings.controller = None;
|
Some(gamepad)
|
||||||
return;
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
let player_handles = player_handles(&players);
|
let player_handles = player_handles(&players);
|
||||||
let page = settings.page();
|
let page = settings.page();
|
||||||
@@ -428,95 +600,51 @@ fn handle_palette_input(
|
|||||||
*settings.selected_mut() = 0;
|
*settings.selected_mut() = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if gamepad.just_pressed(GamepadButton::DPadUp) && page_len != 0 {
|
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadUp))
|
||||||
|
|| keyboard.just_pressed(KeyCode::ArrowUp)
|
||||||
|
|| keyboard.just_pressed(KeyCode::KeyW))
|
||||||
|
&& page_len != 0
|
||||||
|
{
|
||||||
let selected = settings.selected_mut();
|
let selected = settings.selected_mut();
|
||||||
*selected = selected.checked_sub(1).unwrap_or(page_len - 1);
|
*selected = selected.checked_sub(1).unwrap_or(page_len - 1);
|
||||||
}
|
}
|
||||||
if gamepad.just_pressed(GamepadButton::DPadDown) && page_len != 0 {
|
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadDown))
|
||||||
|
|| keyboard.just_pressed(KeyCode::ArrowDown)
|
||||||
|
|| keyboard.just_pressed(KeyCode::KeyS))
|
||||||
|
&& page_len != 0
|
||||||
|
{
|
||||||
let selected = settings.selected_mut();
|
let selected = settings.selected_mut();
|
||||||
*selected = (*selected + 1) % page_len;
|
*selected = (*selected + 1) % page_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
let direction = if gamepad.just_pressed(GamepadButton::DPadLeft) {
|
let direction = if gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadLeft))
|
||||||
|
|| keyboard.just_pressed(KeyCode::ArrowLeft)
|
||||||
|
|| keyboard.just_pressed(KeyCode::KeyA)
|
||||||
|
{
|
||||||
Some(-1)
|
Some(-1)
|
||||||
} else if gamepad.just_pressed(GamepadButton::DPadRight) {
|
} else if gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::DPadRight))
|
||||||
|
|| keyboard.just_pressed(KeyCode::ArrowRight)
|
||||||
|
|| keyboard.just_pressed(KeyCode::KeyD)
|
||||||
|
{
|
||||||
Some(1)
|
Some(1)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(direction) = direction {
|
if let Some(direction) = direction {
|
||||||
match page {
|
cycle_selected_value(&mut settings, direction, &mut commands);
|
||||||
DebugMenuPage::Static(entries) => {
|
|
||||||
if let Some(entry) = entries.get(settings.selected()) {
|
|
||||||
match entry {
|
|
||||||
DebugMenuEntry::StopRecording { .. } => {}
|
|
||||||
DebugMenuEntry::MotionSampling { .. } => settings.cycle_motion_sampling(),
|
|
||||||
DebugMenuEntry::GameSpeed { .. } => {
|
|
||||||
if direction > 0 {
|
|
||||||
settings.game_speed = settings.game_speed.up();
|
|
||||||
} else {
|
|
||||||
settings.game_speed = settings.game_speed.down();
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.insert_resource(RollbackFrameRate(
|
|
||||||
settings.game_speed.frame_rate(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DebugMenuPage::PlayerList => todo!(),
|
|
||||||
DebugMenuPage::PlayerRows(_) => {
|
|
||||||
let DebugMenuPage::PlayerRows(handle) = page else {
|
|
||||||
unreachable!("player row navigation must have a player row page")
|
|
||||||
};
|
|
||||||
let selected = settings.selected();
|
|
||||||
let rows = settings.rows_mut(handle);
|
|
||||||
rows[selected] = rows[selected].cycle(direction);
|
|
||||||
|
|
||||||
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::South))
|
||||||
if matches!(page, DebugMenuPage::PlayerRows(_)) {
|
|| keyboard.just_pressed(KeyCode::Enter)
|
||||||
let direction = if gamepad.just_pressed(GamepadButton::DPadLeft) {
|
|| keyboard.just_pressed(KeyCode::Space))
|
||||||
Some(-1)
|
&& page_len != 0
|
||||||
} else if gamepad.just_pressed(GamepadButton::DPadRight) {
|
{
|
||||||
Some(1)
|
activate_selected(&mut settings, page_len, &player_handles, &mut commands);
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
if let Some(_direction) = direction {}
|
|
||||||
}
|
}
|
||||||
if gamepad.just_pressed(GamepadButton::South) {
|
if (gamepad.is_some_and(|pad| pad.just_pressed(GamepadButton::West))
|
||||||
match page {
|
|| keyboard.just_pressed(KeyCode::Escape))
|
||||||
DebugMenuPage::Static(entries) if page_len != 0 => match entries[settings.selected()] {
|
&& !settings.pop_page()
|
||||||
DebugMenuEntry::StopRecording { .. } => {
|
{
|
||||||
commands.insert_resource(StopReplayRecording);
|
|
||||||
}
|
|
||||||
DebugMenuEntry::Toggle { flag, .. } => {
|
|
||||||
settings.toggle_flag(flag);
|
|
||||||
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
|
|
||||||
}
|
|
||||||
DebugMenuEntry::Submenu { entries, .. } => {
|
|
||||||
settings.push_page(DebugMenuPage::Static(entries));
|
|
||||||
}
|
|
||||||
DebugMenuEntry::PlayerSlots { .. } => settings.push_page(DebugMenuPage::PlayerList),
|
|
||||||
DebugMenuEntry::MotionSampling { .. } => settings.cycle_motion_sampling(),
|
|
||||||
DebugMenuEntry::GameSpeed { .. } => {}
|
|
||||||
},
|
|
||||||
DebugMenuPage::PlayerList if page_len != 0 => {
|
|
||||||
let handle = player_handles[settings.selected()];
|
|
||||||
settings.rows_mut(handle);
|
|
||||||
settings.push_page(DebugMenuPage::PlayerRows(handle));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if gamepad.just_pressed(GamepadButton::West) && !settings.pop_page() {
|
|
||||||
settings.menu_open = false;
|
settings.menu_open = false;
|
||||||
settings.controller = None;
|
settings.controller = None;
|
||||||
}
|
}
|
||||||
@@ -538,8 +666,9 @@ fn sync_gizmo_depth_bias(settings: Res<DebugSettings>, mut configs: ResMut<Gizmo
|
|||||||
|
|
||||||
fn draw_palette(
|
fn draw_palette(
|
||||||
mut contexts: EguiContexts,
|
mut contexts: EguiContexts,
|
||||||
settings: Res<DebugSettings>,
|
mut settings: ResMut<DebugSettings>,
|
||||||
players: Query<&Player>,
|
players: Query<&Player>,
|
||||||
|
mut commands: Commands,
|
||||||
) -> Result {
|
) -> Result {
|
||||||
if !settings.menu_open {
|
if !settings.menu_open {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -547,7 +676,7 @@ fn draw_palette(
|
|||||||
|
|
||||||
let player_handles = player_handles(&players);
|
let player_handles = player_handles(&players);
|
||||||
let page = settings.page();
|
let page = settings.page();
|
||||||
let selected = settings.selected();
|
let page_len = page_len(page, &player_handles);
|
||||||
egui::Window::new("Debug")
|
egui::Window::new("Debug")
|
||||||
.anchor(egui::Align2::RIGHT_TOP, egui::vec2(-12.0, 12.0))
|
.anchor(egui::Align2::RIGHT_TOP, egui::vec2(-12.0, 12.0))
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
@@ -556,38 +685,113 @@ fn draw_palette(
|
|||||||
match page {
|
match page {
|
||||||
DebugMenuPage::Static(entries) => {
|
DebugMenuPage::Static(entries) => {
|
||||||
for (index, entry) in entries.iter().enumerate() {
|
for (index, entry) in entries.iter().enumerate() {
|
||||||
let marker = if index == selected { ">" } else { " " };
|
ui.horizontal(|ui| {
|
||||||
match entry {
|
ui.label(if index == settings.selected() {
|
||||||
DebugMenuEntry::StopRecording { label } => {
|
">"
|
||||||
ui.label(format!("{marker} {label}"));
|
} else {
|
||||||
|
" "
|
||||||
|
});
|
||||||
|
match entry {
|
||||||
|
DebugMenuEntry::StopRecording { label } => {
|
||||||
|
if ui.button(*label).clicked() {
|
||||||
|
*settings.selected_mut() = index;
|
||||||
|
activate_selected(
|
||||||
|
&mut settings,
|
||||||
|
page_len,
|
||||||
|
&player_handles,
|
||||||
|
&mut commands,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebugMenuEntry::Toggle { label, flag } => {
|
||||||
|
let mut value = settings.flag(*flag);
|
||||||
|
if ui.checkbox(&mut value, *label).changed() {
|
||||||
|
settings.toggle_flag(*flag);
|
||||||
|
if !matches!(flag, DebugFlag::FrameStepping) {
|
||||||
|
queue_settings_save(&mut commands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebugMenuEntry::Submenu { label, .. }
|
||||||
|
| DebugMenuEntry::PlayerSlots { label } => {
|
||||||
|
if ui.button(format!("{label} >")).clicked() {
|
||||||
|
*settings.selected_mut() = index;
|
||||||
|
activate_selected(
|
||||||
|
&mut settings,
|
||||||
|
page_len,
|
||||||
|
&player_handles,
|
||||||
|
&mut commands,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebugMenuEntry::MotionSampling { label } => {
|
||||||
|
let old = settings.motion_sampling;
|
||||||
|
egui::ComboBox::from_id_salt(("debug-motion", index))
|
||||||
|
.width(180.0)
|
||||||
|
.selected_text(format!("{label}: {}", old.label()))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
ui.selectable_value(
|
||||||
|
&mut settings.motion_sampling,
|
||||||
|
PresentationMotionMode::Interpolation,
|
||||||
|
"Interpolation",
|
||||||
|
);
|
||||||
|
ui.selectable_value(
|
||||||
|
&mut settings.motion_sampling,
|
||||||
|
PresentationMotionMode::Extrapolation,
|
||||||
|
"Extrapolation",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if settings.motion_sampling != old {
|
||||||
|
queue_settings_save(&mut commands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebugMenuEntry::GameSpeed { label } => {
|
||||||
|
let old = settings.game_speed;
|
||||||
|
egui::ComboBox::from_id_salt(("debug-speed", index))
|
||||||
|
.width(180.0)
|
||||||
|
.selected_text(format!("{label}: {}", old.label()))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for speed in [
|
||||||
|
GameSpeedDebug::Full,
|
||||||
|
GameSpeedDebug::Half,
|
||||||
|
GameSpeedDebug::Quarter,
|
||||||
|
] {
|
||||||
|
ui.selectable_value(
|
||||||
|
&mut settings.game_speed,
|
||||||
|
speed,
|
||||||
|
speed.label(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if settings.game_speed != old {
|
||||||
|
commands.insert_resource(RollbackFrameRate(
|
||||||
|
settings.game_speed.frame_rate(),
|
||||||
|
));
|
||||||
|
queue_settings_save(&mut commands);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DebugMenuEntry::Toggle { label, flag } => {
|
});
|
||||||
let state = if settings.flag(*flag) { "on" } else { "off" };
|
|
||||||
ui.label(format!("{marker} {label}: {state}"));
|
|
||||||
}
|
|
||||||
DebugMenuEntry::Submenu { label, .. }
|
|
||||||
| DebugMenuEntry::PlayerSlots { label } => {
|
|
||||||
ui.label(format!("{marker} {label} >"));
|
|
||||||
}
|
|
||||||
DebugMenuEntry::MotionSampling { label } => {
|
|
||||||
ui.label(format!(
|
|
||||||
"{marker} {label}: {}",
|
|
||||||
settings.motion_sampling.label()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
DebugMenuEntry::GameSpeed { label } => {
|
|
||||||
ui.label(format!(
|
|
||||||
"{marker} {label}: {}",
|
|
||||||
settings.game_speed.label()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DebugMenuPage::PlayerList => {
|
DebugMenuPage::PlayerList => {
|
||||||
for (index, handle) in player_handles.iter().enumerate() {
|
for (index, handle) in player_handles.iter().enumerate() {
|
||||||
let marker = if index == selected { ">" } else { " " };
|
ui.horizontal(|ui| {
|
||||||
ui.label(format!("{marker} Player {} >", handle + 1));
|
ui.label(if index == settings.selected() {
|
||||||
|
">"
|
||||||
|
} else {
|
||||||
|
" "
|
||||||
|
});
|
||||||
|
if ui.button(format!("Player {} >", handle + 1)).clicked() {
|
||||||
|
*settings.selected_mut() = index;
|
||||||
|
activate_selected(
|
||||||
|
&mut settings,
|
||||||
|
page_len,
|
||||||
|
&player_handles,
|
||||||
|
&mut commands,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if player_handles.is_empty() {
|
if player_handles.is_empty() {
|
||||||
ui.label("No players in match");
|
ui.label("No players in match");
|
||||||
@@ -600,16 +804,30 @@ fn draw_palette(
|
|||||||
.unwrap_or([FighterDebugRowKind::None; PLAYER_DEBUG_ROW_COUNT]);
|
.unwrap_or([FighterDebugRowKind::None; PLAYER_DEBUG_ROW_COUNT]);
|
||||||
ui.label(format!("Player {}", handle + 1));
|
ui.label(format!("Player {}", handle + 1));
|
||||||
for (index, row) in rows.iter().enumerate() {
|
for (index, row) in rows.iter().enumerate() {
|
||||||
let marker = if index == selected { ">" } else { " " };
|
ui.horizontal(|ui| {
|
||||||
ui.label(format!("{marker} Row {}: {}", index + 1, row.label()));
|
ui.label(if index == settings.selected() {
|
||||||
|
">"
|
||||||
|
} else {
|
||||||
|
" "
|
||||||
|
});
|
||||||
|
egui::ComboBox::from_id_salt(("debug-row", handle, index))
|
||||||
|
.width(180.0)
|
||||||
|
.selected_text(format!("Row {}: {}", index + 1, row.label()))
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
let rows = settings.rows_mut(handle);
|
||||||
|
for kind in FighterDebugRowKind::ALL {
|
||||||
|
ui.selectable_value(&mut rows[index], kind, kind.label());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
let controls = if matches!(page, DebugMenuPage::PlayerRows(_)) {
|
let controls = if matches!(page, DebugMenuPage::PlayerRows(_)) {
|
||||||
"D-pad: select/change B: back"
|
"Arrows/WASD: select/change Escape/B: back"
|
||||||
} else {
|
} else {
|
||||||
"D-pad: select A: enter/toggle Left/right: change B: back"
|
"Arrows/WASD: select Enter/A: activate Escape/B: back"
|
||||||
};
|
};
|
||||||
ui.label(controls);
|
ui.label(controls);
|
||||||
});
|
});
|
||||||
@@ -699,4 +917,29 @@ mod tests {
|
|||||||
mode.cycle();
|
mode.cycle();
|
||||||
assert_eq!(mode, PresentationMotionMode::Interpolation);
|
assert_eq!(mode, PresentationMotionMode::Interpolation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn game_speed_has_expected_frame_rates() {
|
||||||
|
assert_eq!(GameSpeedDebug::Full.frame_rate(), 60);
|
||||||
|
assert_eq!(GameSpeedDebug::Half.frame_rate(), 30);
|
||||||
|
assert_eq!(GameSpeedDebug::Quarter.frame_rate(), 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn frame_stepping_is_disabled_by_default_and_uses_game_speed_timestep() {
|
||||||
|
let settings = DebugSettings::default();
|
||||||
|
|
||||||
|
assert!(!settings.frame_stepping);
|
||||||
|
assert_eq!(frame_step_duration(60), Duration::from_nanos(16_666_666));
|
||||||
|
assert_eq!(frame_step_duration(30), Duration::from_nanos(33_333_333));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_player_list_does_not_activate() {
|
||||||
|
let mut settings = DebugSettings::default();
|
||||||
|
settings.push_page(DebugMenuPage::PlayerList);
|
||||||
|
|
||||||
|
assert_eq!(page_len(settings.page(), &[]), 0);
|
||||||
|
assert_eq!(settings.selected(), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
io,
|
io,
|
||||||
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
@@ -517,6 +518,7 @@ struct FighterBakeDeclaration {
|
|||||||
#[derive(Asset, TypePath)]
|
#[derive(Asset, TypePath)]
|
||||||
struct FighterBakeSource {
|
struct FighterBakeSource {
|
||||||
model_bytes: Vec<u8>,
|
model_bytes: Vec<u8>,
|
||||||
|
buffers: Vec<Vec<u8>>,
|
||||||
animations: HashMap<AnimKind, String>,
|
animations: HashMap<AnimKind, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,8 +552,31 @@ impl AssetLoader for FighterBakeSourceLoader {
|
|||||||
.read_asset_bytes(&manifest.model_path)
|
.read_asset_bytes(&manifest.model_path)
|
||||||
.await
|
.await
|
||||||
.map_err(|error| io::Error::other(error.to_string()))?;
|
.map_err(|error| io::Error::other(error.to_string()))?;
|
||||||
|
|
||||||
|
let gltf = gltf::Gltf::from_slice(&model_bytes)
|
||||||
|
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
||||||
|
let mut buffers = Vec::with_capacity(gltf.buffers().len());
|
||||||
|
for buffer in gltf.buffers() {
|
||||||
|
let bytes = match buffer.source() {
|
||||||
|
gltf::buffer::Source::Uri(uri) => load_context
|
||||||
|
.read_asset_bytes(AssetPath::from_path_buf(
|
||||||
|
Path::new(&manifest.model_path)
|
||||||
|
.parent()
|
||||||
|
.unwrap_or_else(|| Path::new(""))
|
||||||
|
.join(uri),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.map_err(|error| io::Error::other(error.to_string()))?,
|
||||||
|
gltf::buffer::Source::Bin => gltf.blob.clone().ok_or_else(|| {
|
||||||
|
io::Error::new(io::ErrorKind::InvalidData, "missing glTF binary blob")
|
||||||
|
})?,
|
||||||
|
};
|
||||||
|
buffers.push(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(FighterBakeSource {
|
Ok(FighterBakeSource {
|
||||||
model_bytes,
|
model_bytes,
|
||||||
|
buffers,
|
||||||
animations: manifest.animations,
|
animations: manifest.animations,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -600,7 +625,7 @@ impl AssetTransformer for FighterBakeTransformer {
|
|||||||
source: TransformedAsset<Self::AssetInput>,
|
source: TransformedAsset<Self::AssetInput>,
|
||||||
_: &'a Self::Settings,
|
_: &'a Self::Settings,
|
||||||
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
|
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
|
||||||
let baked = bake_gltf(&source.model_bytes, &source.animations)?;
|
let baked = bake_gltf(&source.model_bytes, &source.buffers, &source.animations)?;
|
||||||
Ok(source.replace_asset(baked))
|
Ok(source.replace_asset(baked))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -656,10 +681,43 @@ impl Plugin for BakedAnimationPlugin {
|
|||||||
|
|
||||||
fn bake_gltf(
|
fn bake_gltf(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
|
buffer_bytes: &[Vec<u8>],
|
||||||
animations: &HashMap<AnimKind, String>,
|
animations: &HashMap<AnimKind, String>,
|
||||||
) -> io::Result<BakedFighterAnimations> {
|
) -> io::Result<BakedFighterAnimations> {
|
||||||
let (document, buffers, _) = gltf::import_slice(bytes)
|
let gltf = gltf::Gltf::from_slice(bytes)
|
||||||
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
||||||
|
let buffers = gltf
|
||||||
|
.buffers()
|
||||||
|
.zip(buffer_bytes)
|
||||||
|
.map(|(buffer, bytes)| {
|
||||||
|
if bytes.len() < buffer.length() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
format!(
|
||||||
|
"glTF buffer {} is too short: expected {}, got {}",
|
||||||
|
buffer.index(),
|
||||||
|
buffer.length(),
|
||||||
|
bytes.len()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(gltf::buffer::Data(bytes.clone()))
|
||||||
|
})
|
||||||
|
.collect::<io::Result<Vec<_>>>()?;
|
||||||
|
if buffers.len() != gltf.buffers().len() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
"glTF buffer data is incomplete",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
bake_gltf_document(gltf.document, buffers, animations)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bake_gltf_document(
|
||||||
|
document: gltf::Document,
|
||||||
|
buffers: Vec<gltf::buffer::Data>,
|
||||||
|
animations: &HashMap<AnimKind, String>,
|
||||||
|
) -> io::Result<BakedFighterAnimations> {
|
||||||
let nodes: Vec<_> = document.nodes().collect();
|
let nodes: Vec<_> = document.nodes().collect();
|
||||||
if nodes.is_empty() {
|
if nodes.is_empty() {
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
@@ -933,11 +991,12 @@ mod tests {
|
|||||||
fn configured_fighter_clips_bake_to_fixed_point_frames() {
|
fn configured_fighter_clips_bake_to_fixed_point_frames() {
|
||||||
let manifest: FighterManifest =
|
let manifest: FighterManifest =
|
||||||
ron::from_str(include_str!("../../assets/fighters/test/test.fighter.ron")).unwrap();
|
ron::from_str(include_str!("../../assets/fighters/test/test.fighter.ron")).unwrap();
|
||||||
let baked = bake_gltf(
|
let (document, buffers, _) = gltf::import(concat!(
|
||||||
include_bytes!("../../assets/fighters/test/test-fighter-2.glb"),
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
&manifest.animations,
|
"/assets/fighters/test/test_fighter.gltf"
|
||||||
)
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let baked = bake_gltf_document(document, buffers, &manifest.animations).unwrap();
|
||||||
|
|
||||||
assert_eq!(baked.frame_rate, BAKED_ANIMATION_FPS);
|
assert_eq!(baked.frame_rate, BAKED_ANIMATION_FPS);
|
||||||
assert_eq!(baked.frame_count(AnimKind::Wait), Some(121));
|
assert_eq!(baked.frame_count(AnimKind::Wait), Some(121));
|
||||||
|
|||||||
+1
-5
@@ -1,7 +1,3 @@
|
|||||||
//! Standalone binary for game-test: `cargo run` plays the game
|
|
||||||
//! without the editor. The editor's Play button runs the same
|
|
||||||
//! [`game_test::GamePlugin`] through its own game runner instead.
|
|
||||||
|
|
||||||
use bevy::asset::AssetMode::Processed;
|
use bevy::asset::AssetMode::Processed;
|
||||||
use bevy::camera_controller::free_camera::FreeCameraPlugin;
|
use bevy::camera_controller::free_camera::FreeCameraPlugin;
|
||||||
use bevy::image::{ImageAddressMode, ImagePlugin, ImageSamplerDescriptor};
|
use bevy::image::{ImageAddressMode, ImagePlugin, ImageSamplerDescriptor};
|
||||||
@@ -30,7 +26,7 @@ fn main() -> AppExit {
|
|||||||
App::new()
|
App::new()
|
||||||
.add_plugins(default_plugins)
|
.add_plugins(default_plugins)
|
||||||
.add_plugins(avian3d::prelude::PhysicsPlugins::default())
|
.add_plugins(avian3d::prelude::PhysicsPlugins::default())
|
||||||
.add_plugins(game_test::GamePlugin)
|
.add_plugins(shinespark::GamePlugin)
|
||||||
.add_plugins(FreeCameraPlugin)
|
.add_plugins(FreeCameraPlugin)
|
||||||
.add_plugins(EguiPlugin::default())
|
.add_plugins(EguiPlugin::default())
|
||||||
.add_plugins(WorldInspectorPlugin::default()) // adds default options and `InspectorEguiImpl`s
|
.add_plugins(WorldInspectorPlugin::default()) // adds default options and `InspectorEguiImpl`s
|
||||||
|
|||||||
Reference in New Issue
Block a user