From ccf2306d0e600635e8f856efe997df42cca0f4f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Rom=C3=A1n=20N=C3=BA=C3=B1ez?= Date: Sun, 27 Sep 2026 12:31:57 +0200 Subject: [PATCH] Refactor some things and make it safer --- src/debug_tools/save_state.rs | 6 ++++- src/input/mod.rs | 27 +++++++++++++-------- src/match_loading.rs | 44 +++++++++++++++++++++-------------- src/stage/manifest.rs | 4 ++++ 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/src/debug_tools/save_state.rs b/src/debug_tools/save_state.rs index 57f25fb..27b1d1d 100644 --- a/src/debug_tools/save_state.rs +++ b/src/debug_tools/save_state.rs @@ -6,7 +6,7 @@ use crate::{ FighterDamage, FighterKnockbackVelocity, FighterPendingHitReaction, FighterPendingHits, FighterSolvedHurtboxes, FighterStaleMoveQueue, }, baked_animation::FighterBoneMatrices, state::FighterState, - }, input::FighterInput, player::Player, stage::line::StageCollision, + }, input::FighterInput, match_runtime::MatchState, player::Player, stage::line::StageCollision, }; #[derive(Resource, Default)] @@ -19,6 +19,7 @@ struct Checkpoint { fighters: Vec, stage_collision: StageCollision, pending_hits: FighterPendingHits, + match_state: MatchState, } #[derive(Clone)] @@ -90,10 +91,12 @@ pub(super) fn save_or_restore(world: &mut World) { checkpoint_fighters.push(checkpoint); } let pending_hits = world.resource::().clone(); + let match_state = world.resource::().clone(); world.resource_mut::().checkpoint = Some(Checkpoint { fighters: checkpoint_fighters, stage_collision, pending_hits, + match_state, }); info!("Saved debug game state"); return; @@ -149,6 +152,7 @@ pub(super) fn save_or_restore(world: &mut World) { } world.insert_resource(checkpoint.pending_hits.clone()); world.insert_resource(checkpoint.stage_collision); + world.insert_resource(checkpoint.match_state); for mut match_camera in world.query::<&mut MatchCamera>().iter_mut(world) { match_camera.needs_snap = true; } diff --git a/src/input/mod.rs b/src/input/mod.rs index b9b5a80..8cf93bd 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -28,7 +28,7 @@ pub enum LocalInputSource { Gamepad(Entity), } -/// Maps an input source to a player index +/// Persistent mapping from physical/menu controller slots to input sources. #[derive(Resource, Clone, Debug, Default, Deref, DerefMut)] pub struct LocalInputAssignments(pub Vec<(usize, LocalInputSource)>); @@ -50,6 +50,16 @@ impl LocalInputAssignments { } } +/// Match-local mapping from dense GGRS handles to physical input sources. +#[derive(Resource, Clone, Debug, Default, Deref, DerefMut)] +pub struct MatchInputAssignments(pub Vec>); + +impl MatchInputAssignments { + pub fn get_for_handle(&self, handle: usize) -> Option { + self.0.get(handle).copied().flatten() + } +} + use crate::game_settings::GameSettings; use crate::math::int::FGi32; use crate::netcode::GGRSCfg; @@ -244,20 +254,17 @@ fn read_local_inputs( input_map: Res, gc_ports: Res, gamepads: Query<&Gamepad>, - assignments: Res, + assignments: Res, local_players: Res, game_settings: Res, ) { let mut local_inputs = HashMap::new(); for handle in &local_players.0 { - let assigned = assignments - .0 - .iter() - .find(|(player_handle, _)| player_handle == handle); + let assigned = assignments.get_for_handle(*handle); let gamepad = match assigned { - Some((_, LocalInputSource::Gamepad(entity))) => gamepads.get(*entity).ok(), - Some((_, LocalInputSource::Keyboard)) => None, + Some(LocalInputSource::Gamepad(entity)) => gamepads.get(entity).ok(), + Some(LocalInputSource::Keyboard) => None, None => gc_ports .entities .get(*handle) @@ -267,8 +274,8 @@ fn read_local_inputs( }; let mut input_frame = match assigned { - Some((_, LocalInputSource::Keyboard)) => keyboard::sample_keyboard(&key, &input_map), - Some((_, LocalInputSource::Gamepad(_))) => gamepad + Some(LocalInputSource::Keyboard) => keyboard::sample_keyboard(&key, &input_map), + Some(LocalInputSource::Gamepad(_)) => gamepad .map(|pad| gamepad::sample_gamepad(pad, &input_map)) .unwrap_or_default(), None => FighterInputFrame::default(), diff --git a/src/match_loading.rs b/src/match_loading.rs index 0d976e3..767a881 100644 --- a/src/match_loading.rs +++ b/src/match_loading.rs @@ -19,7 +19,7 @@ use crate::{ spawn_fighter, visual::FighterAnimations, }, - input::{LocalInputAssignments, LocalInputSource}, + input::{LocalInputAssignments, LocalInputSource, MatchInputAssignments}, match_runtime::MatchState, netcode::{ GGRSCfg, @@ -36,7 +36,7 @@ use crate::{ pub struct MatchPlayer { /// Dense index used by GGRS and rollback-simulated player state. pub handle: usize, - /// Physical controller port selected for a single-player match. + /// Physical controller port selected in the menu. pub controller_port: usize, pub fighter: FighterId, } @@ -282,7 +282,7 @@ pub fn initiate_default_match( pub fn prepare_match( mut commands: Commands, config: Res, - mut input_assignments: ResMut, + input_assignments: Res, assets: Res, gltfs: Res>, baked_animations: Res>, @@ -505,7 +505,10 @@ pub fn prepare_match( } }; - remap_local_input_assignments(&mut input_assignments, &config.players); + commands.insert_resource(create_match_input_assignments( + &input_assignments, + &config.players, + )); let root = commands.spawn((Name::new("Match"), MatchRoot)).id(); @@ -566,18 +569,17 @@ fn player_count_for_handles(players: &[MatchPlayer]) -> usize { players.len() } -fn remap_local_input_assignments( - assignments: &mut LocalInputAssignments, +fn create_match_input_assignments( + assignments: &LocalInputAssignments, players: &[MatchPlayer], -) { - for (assigned_port, _) in assignments.iter_mut() { - if let Some(player) = players - .iter() - .find(|player| player.controller_port == *assigned_port) - { - *assigned_port = player.handle; - } +) -> MatchInputAssignments { + let mut match_assignments = vec![None; players.len()]; + for player in players { + match_assignments[player.handle] = assignments + .get_from_slot(player.controller_port) + .copied(); } + MatchInputAssignments(match_assignments) } pub fn cleanup_match( @@ -598,6 +600,7 @@ pub fn cleanup_match( commands.remove_resource::(); commands.remove_resource::(); commands.remove_resource::(); + commands.remove_resource::(); commands.remove_resource::(); } @@ -701,7 +704,7 @@ mod tests { } #[test] - fn input_assignments_are_remapped_from_ports_to_ggrs_handles() { + fn match_input_assignments_map_ports_to_ggrs_handles_without_mutating_ports() { let players = [ MatchPlayer { handle: 0, @@ -714,15 +717,20 @@ mod tests { fighter: FighterId::TestFighter, }, ]; - let mut assignments = LocalInputAssignments(vec![ + let assignments = LocalInputAssignments(vec![ (0, LocalInputSource::Keyboard), (3, LocalInputSource::Gamepad(Entity::PLACEHOLDER)), ]); - remap_local_input_assignments(&mut assignments, &players); + let match_assignments = create_match_input_assignments(&assignments, &players); + assert_eq!(match_assignments.get_for_handle(0), Some(LocalInputSource::Keyboard)); + assert_eq!( + match_assignments.get_for_handle(1), + Some(LocalInputSource::Gamepad(Entity::PLACEHOLDER)) + ); assert_eq!(assignments[0].0, 0); - assert_eq!(assignments[1].0, 1); + assert_eq!(assignments[1].0, 3); } #[test] diff --git a/src/stage/manifest.rs b/src/stage/manifest.rs index 831f491..b765ede 100644 --- a/src/stage/manifest.rs +++ b/src/stage/manifest.rs @@ -232,6 +232,10 @@ impl StageManifest { return Err("no spawn points are configured".to_owned()); } + if self.respawn_points.is_empty() { + return Err("no respawn points are configured".to_owned()); + } + if self.neutral_spawn_point_sets.is_empty() { return Err("no neutral spawn layouts are configured".to_owned()); }