diff --git a/crates/menu/src/lib.rs b/crates/menu/src/lib.rs index 0de69792..9c2e54ef 100644 --- a/crates/menu/src/lib.rs +++ b/crates/menu/src/lib.rs @@ -1,22 +1,17 @@ use aftergame::AfterGamePlugin; use bevy::{app::PluginGroupBuilder, prelude::*}; -use create::CreateGamePlugin; use de_core::{gresult::GameResult, nested_state, state::AppState}; -use gamelisting::GameListingPlugin; use mainmenu::MainMenuPlugin; use mapselection::MapSelectionPlugin; -use menu::MenuPlugin; -use signin::SignInPlugin; +use menu::{MenuPlugin, ScreenStatePlugin}; +use multiplayer::MultiplayerPlugin; use singleplayer::SinglePlayerPlugin; mod aftergame; -mod create; -mod gamelisting; mod mainmenu; mod mapselection; mod menu; -mod requests; -mod signin; +mod multiplayer; mod singleplayer; pub struct MenuPluginGroup; @@ -26,27 +21,23 @@ impl PluginGroup for MenuPluginGroup { PluginGroupBuilder::start::() .add(MenuStatePlugin) .add(MenuPlugin) + .add(ScreenStatePlugin::::default()) .add(MainMenuPlugin) .add(MapSelectionPlugin) - .add(SignInPlugin) - .add(GameListingPlugin) .add(SinglePlayerPlugin) - .add(CreateGamePlugin) + .add(MultiplayerPlugin) .add(AfterGamePlugin) } } nested_state!( AppState::InMenu -> MenuState, - doc = "Top-level menu state. Each variant corresponds to a single menu screen.", + doc = "Top-level menu state. Each variant corresponds to menu section or a single menu screen.", enter = menu_entered_system, variants = { MainMenu, SinglePlayerGame, - SignIn, - GameListing, - GameCreation, - MultiPlayerGame, + Multiplayer, AfterGame, } ); diff --git a/crates/menu/src/mainmenu.rs b/crates/menu/src/mainmenu.rs index 42c5c3b6..c89dd299 100644 --- a/crates/menu/src/mainmenu.rs +++ b/crates/menu/src/mainmenu.rs @@ -44,7 +44,7 @@ fn setup(mut commands: GuiCommands, menu: Res) { button( &mut commands, column_node, - ButtonAction::SwithState(MenuState::SignIn), + ButtonAction::SwithState(MenuState::Multiplayer), "Multiplayer", ); button(&mut commands, column_node, ButtonAction::Quit, "Quit Game"); diff --git a/crates/menu/src/menu.rs b/crates/menu/src/menu.rs index 9bebbf5b..4933cc35 100644 --- a/crates/menu/src/menu.rs +++ b/crates/menu/src/menu.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use bevy::prelude::*; use de_core::state::AppState; use de_gui::{ButtonCommands, GuiCommands, OuterStyle}; @@ -10,7 +12,6 @@ impl Plugin for MenuPlugin { fn build(&self, app: &mut App) { app.add_systems(OnEnter(AppState::InMenu), setup) .add_systems(OnExit(AppState::InMenu), cleanup) - .add_systems(PreUpdate, clean_up_root.run_if(resource_exists::())) .add_systems( Update, ( @@ -23,6 +24,22 @@ impl Plugin for MenuPlugin { } } +/// This plugin handles state transitions leading to new screen (i.e. screen +/// cleaning). +#[derive(Default)] +pub(crate) struct ScreenStatePlugin { + _marker: PhantomData, +} + +impl Plugin for ScreenStatePlugin { + fn build(&self, app: &mut App) { + app.add_systems( + PreUpdate, + clean_up_root::.run_if(resource_exists::()), + ); + } +} + #[derive(Resource)] pub(crate) struct Menu { root_node: Entity, @@ -51,7 +68,7 @@ enum ButtonAction { Close, } -fn clean_up_root(mut commands: Commands, state: Res>, menu: Res) { +fn clean_up_root(mut commands: Commands, state: Res>, menu: Res) { if state.0.is_none() { return; }; diff --git a/crates/menu/src/create.rs b/crates/menu/src/multiplayer/create.rs similarity index 94% rename from crates/menu/src/create.rs rename to crates/menu/src/multiplayer/create.rs index 96b175bc..7a08a2c9 100644 --- a/crates/menu/src/create.rs +++ b/crates/menu/src/multiplayer/create.rs @@ -9,21 +9,21 @@ use de_lobby_client::CreateGameRequest; use de_lobby_model::{GameConfig, GameMap, GameSetup, Validatable}; use de_map::hash::MapHash; +use super::MultiplayerState; use crate::{ mapselection::{MapSelectedEvent, SelectMapEvent}, menu::Menu, - requests::{Receiver, RequestsPlugin, Sender}, - MenuState, + multiplayer::requests::{Receiver, RequestsPlugin, Sender}, }; -pub(crate) struct CreateGamePlugin; +pub(super) struct CreateGamePlugin; impl Plugin for CreateGamePlugin { fn build(&self, app: &mut App) { app.add_plugins(RequestsPlugin::::new()) .add_event::() - .add_systems(OnEnter(MenuState::GameCreation), setup) - .add_systems(OnExit(MenuState::GameCreation), cleanup) + .add_systems(OnEnter(MultiplayerState::GameCreation), setup) + .add_systems(OnExit(MultiplayerState::GameCreation), cleanup) .add_systems( Update, ( @@ -35,13 +35,13 @@ impl Plugin for CreateGamePlugin { .after(CreateSet::MapSelected), response_system, ) - .run_if(in_state(MenuState::GameCreation)), + .run_if(in_state(MultiplayerState::GameCreation)), ); } } #[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, SystemSet)] -pub(crate) enum CreateSet { +enum CreateSet { Buttons, MapSelected, } @@ -271,13 +271,13 @@ fn create_game_system( } fn response_system( - mut next_state: ResMut>, + mut next_state: ResMut>, mut receiver: Receiver, mut toasts: EventWriter, ) { if let Some(result) = receiver.receive() { match result { - Ok(_) => next_state.set(MenuState::MultiPlayerGame), + Ok(_) => next_state.set(MultiplayerState::GameJoined), Err(error) => toasts.send(ToastEvent::new(error)), } } diff --git a/crates/menu/src/gamelisting.rs b/crates/menu/src/multiplayer/gamelisting.rs similarity index 92% rename from crates/menu/src/gamelisting.rs rename to crates/menu/src/multiplayer/gamelisting.rs index 29878ce1..bd8cb8d3 100644 --- a/crates/menu/src/gamelisting.rs +++ b/crates/menu/src/multiplayer/gamelisting.rs @@ -5,20 +5,21 @@ use de_gui::{ButtonCommands, GuiCommands, LabelCommands, OuterStyle, ToastEvent} use de_lobby_client::{ListGamesRequest, RequestEvent, ResponseEvent}; use de_lobby_model::GamePartial; -use crate::{menu::Menu, MenuState}; +use super::MultiplayerState; +use crate::menu::Menu; const REFRESH_INTERVAL: Duration = Duration::from_secs(10); -pub(crate) struct GameListingPlugin; +pub(super) struct GameListingPlugin; impl Plugin for GameListingPlugin { fn build(&self, app: &mut App) { - app.add_systems(OnEnter(MenuState::GameListing), setup) - .add_systems(OnExit(MenuState::GameListing), cleanup) + app.add_systems(OnEnter(MultiplayerState::GameListing), setup) + .add_systems(OnExit(MultiplayerState::GameListing), cleanup) .add_systems( Update, (refresh_system, list_games_system, button_system) - .run_if(in_state(MenuState::GameListing)), + .run_if(in_state(MultiplayerState::GameListing)), ); } } @@ -183,14 +184,14 @@ fn list_games_system( } fn button_system( - mut next_state: ResMut>, + mut next_state: ResMut>, interactions: Query<(&Interaction, &ButtonAction), Changed>, mut toasts: EventWriter, ) { for (&interaction, action) in interactions.iter() { if let Interaction::Pressed = interaction { match action { - ButtonAction::Create => next_state.set(MenuState::GameCreation), + ButtonAction::Create => next_state.set(MultiplayerState::GameCreation), ButtonAction::Join => { toasts.send(ToastEvent::new("Not yet implemented (issue #301).")) } diff --git a/crates/menu/src/multiplayer/mod.rs b/crates/menu/src/multiplayer/mod.rs new file mode 100644 index 00000000..4e6702c2 --- /dev/null +++ b/crates/menu/src/multiplayer/mod.rs @@ -0,0 +1,40 @@ +use bevy::prelude::*; +use de_core::nested_state; + +use self::{create::CreateGamePlugin, gamelisting::GameListingPlugin, signin::SignInPlugin}; +use crate::{menu::ScreenStatePlugin, MenuState}; + +mod create; +mod gamelisting; +mod requests; +mod signin; + +pub(super) struct MultiplayerPlugin; + +impl Plugin for MultiplayerPlugin { + fn build(&self, app: &mut App) { + app.add_plugins(( + MultiplayerStatePlugin, + ScreenStatePlugin::::default(), + SignInPlugin, + GameListingPlugin, + CreateGamePlugin, + )); + } +} + +nested_state!( + MenuState::Multiplayer -> MultiplayerState, + doc = "Each state corresponds to an individual multiplayer related menu screen.", + enter = multiplayer_entered_system, + variants = { + SignIn, + GameListing, + GameCreation, + GameJoined, + } +); + +fn multiplayer_entered_system(mut next_state: ResMut>) { + next_state.set(MultiplayerState::SignIn); +} diff --git a/crates/menu/src/requests.rs b/crates/menu/src/multiplayer/requests.rs similarity index 83% rename from crates/menu/src/requests.rs rename to crates/menu/src/multiplayer/requests.rs index 9aa8f489..b434fcf2 100644 --- a/crates/menu/src/requests.rs +++ b/crates/menu/src/multiplayer/requests.rs @@ -1,10 +1,11 @@ use std::marker::PhantomData; use bevy::{ecs::system::SystemParam, prelude::*}; -use de_core::state::AppState; use de_lobby_client::{LobbyRequest, RequestEvent, ResponseEvent, Result}; -pub(crate) struct RequestsPlugin +use crate::MenuState; + +pub(super) struct RequestsPlugin where T: LobbyRequest, { @@ -15,7 +16,7 @@ impl RequestsPlugin where T: LobbyRequest, { - pub(crate) fn new() -> Self { + pub(super) fn new() -> Self { Self { _marker: PhantomData, } @@ -27,13 +28,13 @@ where T: LobbyRequest, { fn build(&self, app: &mut App) { - app.add_systems(OnEnter(AppState::InMenu), setup::) - .add_systems(OnExit(AppState::InMenu), cleanup::); + app.add_systems(OnEnter(MenuState::Multiplayer), setup::) + .add_systems(OnExit(MenuState::Multiplayer), cleanup::); } } #[derive(SystemParam)] -pub(crate) struct Sender<'w, T> +pub(super) struct Sender<'w, T> where T: LobbyRequest, { @@ -45,14 +46,14 @@ impl<'w, T> Sender<'w, T> where T: LobbyRequest, { - pub(crate) fn send(&mut self, request: T) { + pub(super) fn send(&mut self, request: T) { self.requests .send(RequestEvent::new(self.counter.increment(), request)); } } #[derive(SystemParam)] -pub(crate) struct Receiver<'w, 's, T> +pub(super) struct Receiver<'w, 's, T> where T: LobbyRequest, { @@ -67,7 +68,7 @@ where /// Returns the response result corresponding the ID of the last request. /// Responses to earlier requests or requests not made via Sender are /// ignored. - pub(crate) fn receive(&mut self) -> Option<&Result> { + pub(super) fn receive(&mut self) -> Option<&Result> { self.responses .iter() .filter_map(|e| { @@ -82,7 +83,7 @@ where } #[derive(Resource)] -pub(crate) struct Counter +pub(super) struct Counter where T: LobbyRequest, { diff --git a/crates/menu/src/signin.rs b/crates/menu/src/multiplayer/signin.rs similarity index 93% rename from crates/menu/src/signin.rs rename to crates/menu/src/multiplayer/signin.rs index 58e9cd60..94db0d5c 100644 --- a/crates/menu/src/signin.rs +++ b/crates/menu/src/multiplayer/signin.rs @@ -6,13 +6,13 @@ use de_gui::{ use de_lobby_client::{Authentication, LobbyRequest, SignInRequest, SignUpRequest}; use de_lobby_model::{User, UserWithPassword, UsernameAndPassword}; -use crate::{ - menu::Menu, +use super::{ requests::{Receiver, RequestsPlugin, Sender}, - MenuState, + MultiplayerState, }; +use crate::menu::Menu; -pub(crate) struct SignInPlugin; +pub(super) struct SignInPlugin; impl Plugin for SignInPlugin { fn build(&self, app: &mut App) { @@ -20,8 +20,8 @@ impl Plugin for SignInPlugin { RequestsPlugin::::new(), RequestsPlugin::::new(), )) - .add_systems(OnEnter(MenuState::SignIn), setup) - .add_systems(OnExit(MenuState::SignIn), cleanup) + .add_systems(OnEnter(MultiplayerState::SignIn), setup) + .add_systems(OnExit(MultiplayerState::SignIn), cleanup) .add_systems( Update, ( @@ -30,7 +30,7 @@ impl Plugin for SignInPlugin { response_system::, auth_system, ) - .run_if(in_state(MenuState::SignIn)), + .run_if(in_state(MultiplayerState::SignIn)), ); } } @@ -204,8 +204,8 @@ where } } -fn auth_system(mut next_state: ResMut>, auth: Res) { +fn auth_system(mut next_state: ResMut>, auth: Res) { if auth.is_authenticated() { - next_state.set(MenuState::GameListing); + next_state.set(MultiplayerState::GameListing); } }