From 8ab3485c944b03eb9df93b7a1b1cc038ebef0b10 Mon Sep 17 00:00:00 2001 From: Martin Indra Date: Thu, 3 Aug 2023 21:43:14 +0200 Subject: [PATCH] WIP --- crates/lobby_client/src/endpoints.rs | 24 +++- crates/lobby_client/src/lib.rs | 1 + crates/menu/src/multiplayer/gamelisting.rs | 12 +- crates/menu/src/multiplayer/joining.rs | 140 +++++++++++++++++++++ crates/menu/src/multiplayer/mod.rs | 5 +- crates/multiplayer/src/game.rs | 7 ++ crates/multiplayer/src/lib.rs | 2 +- 7 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 crates/menu/src/multiplayer/joining.rs diff --git a/crates/lobby_client/src/endpoints.rs b/crates/lobby_client/src/endpoints.rs index b9d0b630..8cdedeb8 100644 --- a/crates/lobby_client/src/endpoints.rs +++ b/crates/lobby_client/src/endpoints.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use de_lobby_model::{GameListing, GameSetup, Token, UserWithPassword, UsernameAndPassword}; +use de_lobby_model::{Game, GameListing, GameSetup, Token, UserWithPassword, UsernameAndPassword}; use reqwest::{header::HeaderValue, Method, Request}; use serde::Serialize; use url::Url; @@ -95,6 +95,28 @@ impl LobbyRequestCreator for ListGamesRequest { } } +pub struct GetGameRequest(String); + +impl GetGameRequest { + pub fn new(id: impl ToString) -> Self { + Self(id.to_string()) + } +} + +impl LobbyRequest for GetGameRequest { + type Response = Game; +} + +impl LobbyRequestCreator for GetGameRequest { + fn path(&self) -> Cow { + encode(&["a", "games", self.0.as_str()]) + } + + fn create(&self, url: Url) -> Request { + Request::new(Method::GET, url) + } +} + pub struct JoinGameRequest(String); impl JoinGameRequest { diff --git a/crates/lobby_client/src/lib.rs b/crates/lobby_client/src/lib.rs index f65fc853..db94a133 100644 --- a/crates/lobby_client/src/lib.rs +++ b/crates/lobby_client/src/lib.rs @@ -34,6 +34,7 @@ impl PluginGroup for LobbyClientPluginGroup { .add(EndpointPlugin::::default()) .add(EndpointPlugin::::default()) .add(EndpointPlugin::::default()) + .add(EndpointPlugin::::default()) .add(EndpointPlugin::::default()) .add(EndpointPlugin::::default()) } diff --git a/crates/menu/src/multiplayer/gamelisting.rs b/crates/menu/src/multiplayer/gamelisting.rs index bd8cb8d3..1affb7c0 100644 --- a/crates/menu/src/multiplayer/gamelisting.rs +++ b/crates/menu/src/multiplayer/gamelisting.rs @@ -5,7 +5,7 @@ use de_gui::{ButtonCommands, GuiCommands, LabelCommands, OuterStyle, ToastEvent} use de_lobby_client::{ListGamesRequest, RequestEvent, ResponseEvent}; use de_lobby_model::GamePartial; -use super::MultiplayerState; +use super::{joining::JoinGameEvent, MultiplayerState}; use crate::menu::Menu; const REFRESH_INTERVAL: Duration = Duration::from_secs(10); @@ -30,7 +30,7 @@ struct GamesTable(Entity); #[derive(Component)] enum ButtonAction { Create, - Join, + Join(String), } fn setup( @@ -141,7 +141,7 @@ fn row(commands: &mut GuiCommands, game: &GamePartial) -> Entity { }, "Join", ) - .insert(ButtonAction::Join) + .insert(ButtonAction::Join(game.config().name().to_owned())) .id(); commands.entity(row_id).add_child(button_id); } @@ -186,14 +186,14 @@ fn list_games_system( fn button_system( mut next_state: ResMut>, interactions: Query<(&Interaction, &ButtonAction), Changed>, - mut toasts: EventWriter, + mut joins: EventWriter, ) { for (&interaction, action) in interactions.iter() { if let Interaction::Pressed = interaction { match action { ButtonAction::Create => next_state.set(MultiplayerState::GameCreation), - ButtonAction::Join => { - toasts.send(ToastEvent::new("Not yet implemented (issue #301).")) + ButtonAction::Join(name) => { + joins.send(JoinGameEvent::new(name.to_owned())); } } } diff --git a/crates/menu/src/multiplayer/joining.rs b/crates/menu/src/multiplayer/joining.rs new file mode 100644 index 00000000..76bc9270 --- /dev/null +++ b/crates/menu/src/multiplayer/joining.rs @@ -0,0 +1,140 @@ +use std::net::SocketAddr; + +use bevy::prelude::*; +use de_gui::ToastEvent; +use de_lobby_client::{GetGameRequest, JoinGameRequest}; +use de_multiplayer::{ + ConnectionType, GameJoinedEvent, NetGameConf, ShutdownMultiplayerEvent, StartMultiplayerEvent, +}; + +use super::{ + requests::{Receiver, RequestsPlugin, Sender}, + MultiplayerState, +}; +use crate::MenuState; + +pub(crate) struct JoiningGamePlugin; + +impl Plugin for JoiningGamePlugin { + fn build(&self, app: &mut App) { + app.add_plugins(( + RequestsPlugin::::new(), + RequestsPlugin::::new(), + )) + .add_event::() + .add_systems(OnEnter(MultiplayerState::GameJoining), get_game) + .add_systems(OnExit(MultiplayerState::GameJoining), cleanup) + .add_systems( + PreUpdate, + handle_join_event.run_if(in_state(MenuState::Multiplayer)), + ) + .add_systems( + Update, + ( + handle_get_response, + handle_join_response.run_if(resource_exists::()), + handle_joined_event.run_if(on_event::()), + ) + .run_if(in_state(MultiplayerState::GameJoining)), + ); + } +} + +/// Send this event to initiate joining of a multiplayer game. +/// +/// The game will be joined at both DE Lobby and the DE Connector. Once this is +/// done, the menu transitions to [`MultiplayerState::GameJoined`]. +#[derive(Event)] +pub(super) struct JoinGameEvent(String); + +impl JoinGameEvent { + pub(super) fn new(name: String) -> Self { + Self(name) + } +} + +#[derive(Resource)] +pub(crate) struct GameNameRes(String); + +#[derive(Resource)] +pub(crate) struct GameSocketAddrRes(SocketAddr); + +fn handle_join_event( + mut commands: Commands, + mut next_state: ResMut>, + mut events: EventReader, +) { + let Some(event) = events.iter().last() else { + return; + }; + + commands.insert_resource(GameNameRes(event.0.to_owned())); + next_state.set(MultiplayerState::GameJoining); +} + +fn cleanup( + mut commands: Commands, + state: Res>, + mut shutdown: EventWriter, +) { + commands.remove_resource::(); + commands.remove_resource::(); + + if state.as_ref() != &MultiplayerState::GameJoined { + shutdown.send(ShutdownMultiplayerEvent); + } +} + +fn get_game(game_name: Res, mut sender: Sender) { + sender.send(GetGameRequest::new(game_name.0.to_owned())); +} + +fn handle_get_response( + mut commands: Commands, + mut next_state: ResMut>, + mut receiver: Receiver, + mut sender: Sender, + mut toasts: EventWriter, +) { + while let Some(result) = receiver.receive() { + match result { + Ok(game) => { + sender.send(JoinGameRequest::new( + game.setup().config().name().to_owned(), + )); + commands.insert_resource(GameSocketAddrRes(game.setup().server())); + } + Err(error) => { + toasts.send(ToastEvent::new(error)); + next_state.set(MultiplayerState::SignIn); + } + } + } +} + +fn handle_join_response( + server: Res, + mut next_state: ResMut>, + mut receiver: Receiver, + mut multiplayer: EventWriter, + mut toasts: EventWriter, +) { + while let Some(result) = receiver.receive() { + match result { + Ok(_) => { + multiplayer.send(StartMultiplayerEvent::new(NetGameConf::new( + server.0.ip(), + ConnectionType::JoinGame(server.0.port()), + ))); + } + Err(error) => { + toasts.send(ToastEvent::new(error)); + next_state.set(MultiplayerState::SignIn); + } + } + } +} + +fn handle_joined_event(mut next_state: ResMut>) { + next_state.set(MultiplayerState::GameJoined); +} diff --git a/crates/menu/src/multiplayer/mod.rs b/crates/menu/src/multiplayer/mod.rs index 7df1de34..429a4d86 100644 --- a/crates/menu/src/multiplayer/mod.rs +++ b/crates/menu/src/multiplayer/mod.rs @@ -4,13 +4,14 @@ use de_multiplayer::MultiplayerShuttingDownEvent; use self::{ create::CreateGamePlugin, gamelisting::GameListingPlugin, joined::JoinedGamePlugin, - setup::SetupGamePlugin, signin::SignInPlugin, + joining::JoiningGamePlugin, setup::SetupGamePlugin, signin::SignInPlugin, }; use crate::{menu::ScreenStatePlugin, MenuState}; mod create; mod gamelisting; mod joined; +mod joining; mod requests; mod setup; mod signin; @@ -26,6 +27,7 @@ impl Plugin for MultiplayerPlugin { GameListingPlugin, CreateGamePlugin, SetupGamePlugin, + JoiningGamePlugin, JoinedGamePlugin, )) .add_systems( @@ -46,6 +48,7 @@ nested_state!( GameListing, GameCreation, GameSetup, + GameJoining, GameJoined, } ); diff --git a/crates/multiplayer/src/game.rs b/crates/multiplayer/src/game.rs index b45d1e61..b47eb820 100644 --- a/crates/multiplayer/src/game.rs +++ b/crates/multiplayer/src/game.rs @@ -19,6 +19,7 @@ pub(crate) struct GamePlugin; impl Plugin for GamePlugin { fn build(&self, app: &mut App) { app.add_event::() + .add_event::() .add_systems(OnEnter(NetState::Connected), (setup, open_or_join)) .add_systems(OnEnter(NetState::None), cleanup) .add_systems( @@ -40,6 +41,10 @@ impl Plugin for GamePlugin { #[derive(Event)] pub struct GameOpenedEvent(pub SocketAddr); +/// A game was just joined. +#[derive(Event)] +pub struct GameJoinedEvent; + #[derive(Resource)] pub(crate) struct Players { local: Option, @@ -115,6 +120,7 @@ fn process_from_game( mut inputs: EventReader, mut fatals: EventWriter, state: Res>, + mut joined_events: EventWriter, mut next_state: ResMut>, ) { for event in inputs.iter() { @@ -132,6 +138,7 @@ fn process_from_game( info!("Joined game as Player {player}."); players.local = Some(player); next_state.set(NetState::Joined); + joined_events.send(GameJoinedEvent); } Err(err) => { fatals.send(FatalErrorEvent::new(format!( diff --git a/crates/multiplayer/src/lib.rs b/crates/multiplayer/src/lib.rs index 4f22f053..f8604f49 100644 --- a/crates/multiplayer/src/lib.rs +++ b/crates/multiplayer/src/lib.rs @@ -15,7 +15,7 @@ use stats::StatsPlugin; pub use crate::{ config::{ConnectionType, NetGameConf}, - game::GameOpenedEvent, + game::{GameJoinedEvent, GameOpenedEvent}, lifecycle::{MultiplayerShuttingDownEvent, ShutdownMultiplayerEvent, StartMultiplayerEvent}, netstate::NetState, };