Compare commits

..
2 Commits
Author SHA1 Message Date
EIREXE 7271d02e29 Fix warnings and increase recursion
CI / production-build (pull_request) Successful in 11m5s
CI / build-and-test (pull_request) Successful in 1m58s
2026-09-26 19:37:18 +02:00
EIREXE 866a9c7500 Implement filled gizmos 2026-09-26 19:32:09 +02:00
7 changed files with 33 additions and 206 deletions
-13
View File
@@ -109,7 +109,6 @@ impl FighterDebugRowKind {
enum DebugFlag {
Network,
Ecb,
BlastZones,
StageCollision,
AnimationBones,
AttackHitboxes,
@@ -151,10 +150,6 @@ const FIGHTER_MENU: &[DebugMenuEntry] = &[
label: "ECB",
flag: DebugFlag::Ecb,
},
DebugMenuEntry::Toggle {
label: "Blast Zones",
flag: DebugFlag::BlastZones,
},
DebugMenuEntry::Toggle {
label: "Animation bones",
flag: DebugFlag::AnimationBones,
@@ -248,7 +243,6 @@ pub struct DebugSettings {
pub frame_stepping: bool,
pub network: bool,
pub ecb: bool,
pub blast_zones: bool,
pub stage_collision: bool,
pub animation_bones: bool,
pub attack_hitboxes: bool,
@@ -270,7 +264,6 @@ impl Default for DebugSettings {
frame_stepping: false,
network: false,
ecb: false,
blast_zones: false,
stage_collision: false,
animation_bones: false,
attack_hitboxes: false,
@@ -335,7 +328,6 @@ impl DebugSettings {
match flag {
DebugFlag::Network => self.network,
DebugFlag::Ecb => self.ecb,
DebugFlag::BlastZones => self.blast_zones,
DebugFlag::StageCollision => self.stage_collision,
DebugFlag::AnimationBones => self.animation_bones,
DebugFlag::AttackHitboxes => self.attack_hitboxes,
@@ -350,7 +342,6 @@ impl DebugSettings {
match flag {
DebugFlag::Network => self.network = !self.network,
DebugFlag::Ecb => self.ecb = !self.ecb,
DebugFlag::BlastZones => self.blast_zones = !self.blast_zones,
DebugFlag::StageCollision => self.stage_collision = !self.stage_collision,
DebugFlag::AnimationBones => self.animation_bones = !self.animation_bones,
DebugFlag::AttackHitboxes => self.attack_hitboxes = !self.attack_hitboxes,
@@ -893,10 +884,6 @@ pub fn stage_collision_enabled(settings: Res<DebugSettings>) -> bool {
settings.stage_collision
}
pub fn blast_zones_enabled(settings: Res<DebugSettings>) -> bool {
settings.blast_zones
}
pub fn animation_bones_enabled(settings: Res<DebugSettings>) -> bool {
settings.animation_bones
}
+17 -53
View File
@@ -21,6 +21,7 @@ use crate::{
},
input::{LocalInputAssignments, LocalInputSource},
match_runtime::MatchState,
math::{int::FGi32, vec::FGVec2},
netcode::{
GGRSCfg,
session::{ConnectionStatus, SessionConfig, create_session},
@@ -276,7 +277,7 @@ pub fn prepare_match(
if !valid_player_handles(&config.players) {
fail_match(
&mut next_state,
"player handles must be unique and non-empty",
"player handles must be unique and cover 0..player_count",
);
return;
}
@@ -286,20 +287,6 @@ pub fn prepare_match(
return;
}
let stage_handle = stage_registry
.get(config.stage)
.expect("Initialization check should ensure all stages exist");
let stage_manifest = stage_manifests
.get(&stage_handle)
.expect("Initialization check should ensure stage manifests are kept alive");
let spawn_positions = match stage_manifest.neutral_spawn_positions(config.players.len()) {
Ok(positions) => positions,
Err(error) => {
fail_match(&mut next_state, &error);
return;
}
};
if assets.fighters.len() != config.players.len() {
fail_match(
&mut next_state,
@@ -462,8 +449,7 @@ pub fn prepare_match(
));
}
let session_player_count = player_count_for_handles(&config.players);
let session_config = match SessionConfig::from_args(&args, session_player_count) {
let session_config = match SessionConfig::from_args(&args, config.players.len()) {
Ok(config) => config,
Err(error) => {
fail_match(
@@ -473,7 +459,7 @@ pub fn prepare_match(
return;
}
};
let session = match create_session(&args, session_player_count) {
let session = match create_session(&args, config.players.len()) {
Ok(session) => session,
Err(error) => {
fail_match(
@@ -486,6 +472,12 @@ pub fn prepare_match(
let root = commands.spawn((Name::new("Match"), MatchRoot)).id();
let stage_handle = stage_registry
.get(config.stage)
.expect("Initialization check should ensure all stages exist");
let stage_manifest = stage_manifests
.get(&stage_handle)
.expect("Initialization check should ensure stage manifests are kept alive");
stage::spawn_stage(
&mut commands,
root,
@@ -495,15 +487,14 @@ pub fn prepare_match(
);
let mut fighter_roster = Vec::with_capacity(config.players.len());
for (spawn_index, (player, (scene, animations, attack_scripts, manifest_handle, model_scale, ecb_bone_indices))) in
for (index, (player, (scene, animations, attack_scripts, manifest_handle, model_scale, ecb_bone_indices))) in
config.players.iter().zip(prepared).enumerate()
{
let spawn_point = spawn_positions[spawn_index];
let spawn_x = (index as i32 * 2 + 1 - config.players.len() as i32) * 5;
fighter_roster.push(spawn_fighter(
&mut commands,
player.handle,
spawn_point,
FGVec2::new(FGi32::from_num(spawn_x), FGi32::lit("12.5")),
manifest_handle.clone(),
model_scale,
animations,
@@ -527,22 +518,12 @@ fn fail_match(next_state: &mut NextState<AppState>, message: &str) {
}
fn valid_player_handles(players: &[MatchPlayer]) -> bool {
if players.is_empty() {
return false;
}
let mut handles = players.iter().map(|player| player.handle).collect::<Vec<_>>();
handles.sort_unstable();
handles.windows(2).all(|handles| handles[0] != handles[1])
&& handles.last().is_none_or(|handle| *handle != usize::MAX)
}
fn player_count_for_handles(players: &[MatchPlayer]) -> usize {
players
let mut handles = players
.iter()
.map(|player| player.handle)
.max()
.map_or(0, |handle| handle + 1)
.collect::<Vec<_>>();
handles.sort_unstable();
handles == (0..players.len()).collect::<Vec<_>>()
}
pub fn cleanup_match(
@@ -609,23 +590,6 @@ mod tests {
assert!(!valid_player_handles(&players));
}
#[test]
fn player_handles_may_have_gaps() {
let players = [
MatchPlayer {
handle: 0,
fighter: FighterId::TestFighter,
},
MatchPlayer {
handle: 2,
fighter: FighterId::TestFighter,
},
];
assert!(valid_player_handles(&players));
assert_eq!(player_count_for_handles(&players), 3);
}
#[test]
fn match_rules_reject_zero_limits() {
assert!(
+1 -6
View File
@@ -40,12 +40,7 @@ impl MatchState {
Self {
elapsed_frames: 0,
players: (0..config
.players
.iter()
.map(|player| player.handle)
.max()
.map_or(0, |handle| handle + 1))
players: (0..config.players.len())
.map(|_| PlayerMatchState {
stocks_remaining,
score: 0,
+3 -14
View File
@@ -140,7 +140,7 @@ fn validate(replay: &Replay) -> Result<(), String> {
));
}
if replay.players_are_invalid() {
return Err("replay player handles must be unique".into());
return Err("replay player handles must cover 0..player_count".into());
}
if replay.frames.is_empty() {
return Err("replay must contain at least one frame".into());
@@ -169,16 +169,7 @@ fn validate(replay: &Replay) -> Result<(), String> {
if replay
.frames
.iter()
.any(|frame| {
frame.inputs.len()
!= replay
.match_config
.players
.iter()
.map(|player| player.handle)
.max()
.map_or(0, |handle| handle + 1)
})
.any(|frame| frame.inputs.len() != replay.match_config.players.len())
{
return Err("replay frame input count does not match player count".into());
}
@@ -194,9 +185,7 @@ impl Replay {
.map(|player| player.handle)
.collect();
handles.sort_unstable();
handles.is_empty()
|| handles.windows(2).any(|handles| handles[0] == handles[1])
|| handles.last() == Some(&usize::MAX)
handles != (0..handles.len()).collect::<Vec<_>>()
}
fn frame(&self, frame: i32) -> Option<&ReplayFrame> {
+1 -29
View File
@@ -1,12 +1,6 @@
use bevy::{color::palettes::css::RED, prelude::*};
use crate::{
match_loading::MatchConfig,
stage::{
line::StageCollision,
manifest::{StageManifest, StageManifestRegistry},
},
};
use crate::stage::line::StageCollision;
pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res<StageCollision>) {
for poly in stage_collision.stage_polys.iter() {
@@ -22,25 +16,3 @@ pub fn debug_draw_scene(mut gizmos: Gizmos, stage_collision: Res<StageCollision>
}
}
}
pub fn debug_draw_blast_zones(
mut gizmos: Gizmos,
stage_registry: Res<StageManifestRegistry>,
stage_manifests: Res<Assets<StageManifest>>,
match_config: Res<MatchConfig>,
) {
let Some(stage_manifest_handle) = stage_registry.get(match_config.stage) else {
return;
};
let Some(stage_manifest) = stage_manifests.get(&stage_manifest_handle) else {
return;
};
let top_left = stage_manifest.blast_zone_top_left.to_vec2();
let bottom_right = stage_manifest.blast_zone_bottom_right.to_vec2();
let center = (top_left + bottom_right) / 2.0;
let size = (bottom_right - top_left).abs();
gizmos.rect(Vec3::new(center.x, center.y, 0.0), size, bevy::color::palettes::css::BLUE);
}
+7 -82
View File
@@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use bevy_asset_loader::asset_collection::AssetCollection;
use ron_asset_manager::prelude::RonAsset;
@@ -7,10 +7,14 @@ use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use crate::{
AppState, game_settings::GameSettings, math::{vec::FGVec2}, stage::{
AppState,
game_settings::GameSettings,
math::vec::FGVec2,
stage::{
StagePoly,
line::{StageCollision, StagePolyLineSegmentType, StagePolyType},
}, texture_reference::TextureReference,
},
texture_reference::TextureReference,
};
use bevy::prelude::*;
@@ -83,15 +87,6 @@ pub struct StageManifest {
pub icon: TextureReference,
pub camera: StageCameraProfile,
pub polygons: Vec<StagePolyDefinition>,
#[reflect(ignore)]
pub blast_zone_top_left: FGVec2,
#[reflect(ignore)]
pub blast_zone_bottom_right: FGVec2,
#[reflect(ignore)]
pub spawn_points: Vec<FGVec2>,
pub neutral_spawn_point_sets: Vec<Vec<usize>>
}
#[derive(Resource)]
@@ -167,13 +162,6 @@ pub fn prepare_stage_manifests(
);
return;
}
if let Err(error) = manifest.validate_spawn_points() {
fail(
&mut next_state,
&format!("{:?} has invalid spawn points: {error}", manifest.id),
);
return;
}
if registry.insert(manifest.id, handle.clone()).is_some() {
fail(
&mut next_state,
@@ -198,69 +186,6 @@ pub fn prepare_stage_manifests(
}
impl StageManifest {
pub fn neutral_spawn_positions(&self, player_count: usize) -> Result<Vec<FGVec2>, String> {
if player_count == 0 {
return Err("a match must contain at least one player".to_owned());
}
let layout = self
.neutral_spawn_point_sets
.get(player_count - 1)
.ok_or_else(|| format!("no spawn layout for {player_count} players"))?;
if layout.len() != player_count {
return Err(format!(
"the {player_count}-player layout contains {} positions",
layout.len()
));
}
layout
.iter()
.map(|&index| {
self.spawn_points.get(index).copied().ok_or_else(|| {
format!("the {player_count}-player layout references spawn point {index}")
})
})
.collect()
}
fn validate_spawn_points(&self) -> Result<(), String> {
if self.spawn_points.is_empty() {
return Err("no spawn points are configured".to_owned());
}
if self.neutral_spawn_point_sets.is_empty() {
return Err("no neutral spawn layouts are configured".to_owned());
}
for (layout_index, layout) in self.neutral_spawn_point_sets.iter().enumerate() {
let player_count = layout_index + 1;
if layout.len() != player_count {
return Err(format!(
"the {player_count}-player layout contains {} positions",
layout.len()
));
}
let mut unique_indices = HashSet::with_capacity(layout.len());
for &spawn_point_index in layout {
if spawn_point_index >= self.spawn_points.len() {
return Err(format!(
"the {player_count}-player layout references spawn point {spawn_point_index}"
));
}
if !unique_indices.insert(spawn_point_index) {
return Err(format!(
"the {player_count}-player layout uses spawn point {spawn_point_index} more than once"
));
}
}
}
Ok(())
}
pub fn to_collision(&self) -> StageCollision {
let stage_polys: Vec<StagePoly> = self
.polygons
+3 -8
View File
@@ -10,7 +10,7 @@ pub mod scene;
pub use line::{StagePlaneIntersectResult, StagePoly};
use crate::{match_loading::MatchConfig, stage::{line::StageCollision, manifest::StageCameraProfile}};
use crate::stage::{line::StageCollision, manifest::StageCameraProfile};
/// Owns the stage geometry: which scene gets spawned and how it is drawn.
#[derive(Default)]
@@ -20,14 +20,9 @@ impl Plugin for StagePlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(
(debug::debug_draw_scene
debug::debug_draw_scene
.run_if(resource_exists::<line::StageCollision>)
.run_if(crate::debug_tools::stage_collision_enabled)),
(debug::debug_draw_blast_zones
.run_if(resource_exists::<MatchConfig>)
.run_if(crate::debug_tools::blast_zones_enabled),),
),
.run_if(crate::debug_tools::stage_collision_enabled),
);
}
}