Files
shinespark/src/menus/match_config.rs
T
EIREXE 6f1d8a7706
CI / build-and-test (push) Successful in 2m2s
CI / production-build (push) Successful in 12m18s
Add the ability to respawn and improve controller port mapping (#72)
Reviewed-on: #72
Co-authored-by: Álex Román Núñez <eirexe@eirteam.moe>
2026-09-27 11:54:08 +00:00

133 lines
3.7 KiB
Rust

use bevy::prelude::*;
use thiserror::Error;
use crate::{
fighter::FighterId,
match_loading::{MatchConfig, MatchPlayer, MatchRules},
stage::manifest::StageId,
};
pub struct MenuMatchPlayerSlot {
pub slot: usize,
pub fighter: Option<FighterId>,
}
#[derive(Resource, Default)]
pub struct MenuMatchConfig {
pub stage: Option<StageId>,
pub players: Vec<MenuMatchPlayerSlot>,
}
#[derive(Error, Debug, Reflect)]
pub enum MenuMatchConfigErrors {
#[error("Fighter slot {0} was invalid!")]
PlayerSlotInvalid(usize),
#[error("Invalid configuration: Slot {0} doesn't have a fighter selected")]
FighterNotSelectedForSlot(usize),
#[error("Invalid configuration: No stage selected")]
StageNotSelected,
}
impl MenuMatchConfig {
pub fn set_player_fighter(
&mut self,
player_slot: usize,
fighter_id: Option<FighterId>,
) -> Result<(), MenuMatchConfigErrors> {
let slot = self
.players
.get_mut(player_slot)
.ok_or(MenuMatchConfigErrors::PlayerSlotInvalid(player_slot))?;
slot.fighter = fighter_id;
Ok(())
}
pub fn get_player_fighter(
&self,
player_slot: usize,
) -> Result<Option<FighterId>, MenuMatchConfigErrors> {
let slot = self
.players
.get(player_slot)
.ok_or(MenuMatchConfigErrors::PlayerSlotInvalid(player_slot))?;
Ok(slot.fighter)
}
pub fn all_players_ready(&self) -> bool {
let has_unselected_fighter = self
.players
.iter()
.any(|player_slot| player_slot.fighter.is_none());
!has_unselected_fighter
}
pub fn to_match_config(&self) -> Result<MatchConfig, MenuMatchConfigErrors> {
let mut match_players = vec![];
let stage = self.stage.ok_or(MenuMatchConfigErrors::StageNotSelected)?;
let mut players = self.players.iter().collect::<Vec<_>>();
players.sort_unstable_by_key(|player| player.slot);
for (handle, player) in players.into_iter().enumerate() {
let fighter_id =
player
.fighter
.ok_or(MenuMatchConfigErrors::FighterNotSelectedForSlot(
player.slot,
))?;
match_players.push(MatchPlayer {
fighter: fighter_id,
handle,
controller_port: player.slot,
});
}
Ok(MatchConfig {
players: match_players,
stage,
rules: MatchRules::default(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn menu_config_uses_default_match_rules() {
let menu = MenuMatchConfig {
stage: Some(StageId::TestStage),
players: vec![MenuMatchPlayerSlot {
slot: 0,
fighter: Some(FighterId::TestFighter),
}],
};
assert_eq!(menu.to_match_config().unwrap().rules, MatchRules::default());
}
#[test]
fn sparse_controller_ports_get_dense_ggrs_handles() {
let menu = MenuMatchConfig {
stage: Some(StageId::TestStage),
players: vec![
MenuMatchPlayerSlot {
slot: 3,
fighter: Some(FighterId::TestFighter),
},
MenuMatchPlayerSlot {
slot: 0,
fighter: Some(FighterId::TestFighter),
},
],
};
let config = menu.to_match_config().unwrap();
assert_eq!(config.players[0].handle, 0);
assert_eq!(config.players[0].controller_port, 0);
assert_eq!(config.players[1].handle, 1);
assert_eq!(config.players[1].controller_port, 3);
}
}