diff --git a/Cargo.lock b/Cargo.lock index dab6f2cb..0413b445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2499,6 +2499,7 @@ dependencies = [ "de_lobby_client", "de_lobby_model", "de_map", + "de_messages", "de_multiplayer", "futures-lite", "thiserror", diff --git a/crates/core/src/gconfig.rs b/crates/core/src/gconfig.rs index 9884e9d6..0d05630f 100644 --- a/crates/core/src/gconfig.rs +++ b/crates/core/src/gconfig.rs @@ -1,7 +1,7 @@ use std::path::{Path, PathBuf}; use bevy::prelude::Resource; -use tinyvec::ArrayVec; +use tinyvec::{array_vec, ArrayVec}; use crate::player::{Player, PlayerRange}; @@ -30,6 +30,8 @@ impl GameConfig { } } +type PlayerVec = ArrayVec<[Player; Player::MAX_PLAYERS]>; + /// Info about players directly controlled or simulated on this computer. /// /// "Playable" is the player directly controlled by the user of this computer. @@ -39,7 +41,7 @@ impl GameConfig { /// exactly one computer. pub struct LocalPlayers { playable: Player, - locals: ArrayVec<[Player; Player::MAX_PLAYERS]>, + locals: PlayerVec, } impl LocalPlayers { @@ -48,7 +50,11 @@ impl LocalPlayers { } pub fn from_range(playable: Player, locals: PlayerRange) -> Self { - Self::new(playable, locals.collect()) + Self::new(playable, locals.collect::()) + } + + pub fn from_single(playable: Player) -> Self { + Self::new(playable, array_vec!(_ => playable)) } /// # Arguments @@ -57,7 +63,11 @@ impl LocalPlayers { /// /// * `locals` - other players simulated locally on this computer. It must /// include `playable`. - pub fn new(playable: Player, locals: ArrayVec<[Player; Player::MAX_PLAYERS]>) -> Self { + pub fn new(playable: Player, locals: A) -> Self + where + A: Into, + { + let locals = locals.into(); assert!((*locals).contains(&playable)); Self { playable, locals } } diff --git a/crates/map/src/hash.rs b/crates/map/src/hash.rs index 5ca6f88d..50c3292f 100644 --- a/crates/map/src/hash.rs +++ b/crates/map/src/hash.rs @@ -20,7 +20,7 @@ impl MapHash { } /// Constructs the map hash from a hexadecimal string. - pub(crate) fn from_hex(hex: &str) -> Result { + pub fn from_hex(hex: &str) -> Result { if hex.len() != 64 { return Err(HexError::InvalidLenError); } diff --git a/crates/menu/Cargo.toml b/crates/menu/Cargo.toml index 937f6e3d..5c7620bf 100644 --- a/crates/menu/Cargo.toml +++ b/crates/menu/Cargo.toml @@ -19,6 +19,7 @@ de_gui.workspace = true de_lobby_client.workspace = true de_lobby_model.workspace = true de_map.workspace = true +de_messages.workspace = true de_multiplayer.workspace = true # Other diff --git a/crates/menu/src/multiplayer/joined/state.rs b/crates/menu/src/multiplayer/joined/state.rs index 77c5708f..d6c3b2a9 100644 --- a/crates/menu/src/multiplayer/joined/state.rs +++ b/crates/menu/src/multiplayer/joined/state.rs @@ -1,8 +1,17 @@ use bevy::prelude::*; -use de_core::state::AppState; +use de_core::{ + assets::asset_path, + gconfig::{GameConfig, LocalPlayers}, + player::Player, + state::AppState, +}; use de_gui::ToastEvent; use de_lobby_client::GetGameRequest; -use de_multiplayer::{PeerJoinedEvent, PeerLeftEvent, ShutdownMultiplayerEvent}; +use de_map::hash::MapHash; +use de_messages::Readiness; +use de_multiplayer::{ + GameReadinessEvent, PeerJoinedEvent, PeerLeftEvent, ShutdownMultiplayerEvent, +}; use super::ui::RefreshPlayersEvent; use crate::multiplayer::{ @@ -29,7 +38,22 @@ impl Plugin for JoinedGameStatePlugin { } } -fn cleanup(state: Res>, mut shutdown: EventWriter) { +#[derive(Resource)] +pub(super) struct LocalPlayerRes(Player); + +impl LocalPlayerRes { + pub(super) fn new(player: Player) -> Self { + Self(player) + } +} + +fn cleanup( + mut commands: Commands, + state: Res>, + mut shutdown: EventWriter, +) { + commands.remove_resource::(); + if state.as_ref() != &AppState::InGame { shutdown.send(ShutdownMultiplayerEvent); } @@ -40,6 +64,19 @@ fn refresh(game_name: Res, mut sender: Sender) { sender.send(GetGameRequest::new(game_name.name_owned())); } +fn handle_readiness( + mut events: EventReader, + game_name: Res, + mut sender: Sender, +) { + if events.iter().all(|e| **e != Readiness::Ready) { + return; + } + + sender.send(GetGameRequest::new(game_name.name_owned())); + // TODO marek the game as ready +} + fn handle_get_response( mut multi_state: ResMut>, mut receiver: Receiver, @@ -58,3 +95,31 @@ fn handle_get_response( } } } + +fn start( + mut commands: Commands, + player: Res, + mut app_state: ResMut>, + mut multi_state: ResMut>, + mut toasts: EventWriter, +) { + // TODO config via event + + let game_conf = game.setup().config(); + let map_path = match MapHash::from_hex(game_conf.map().hash()) { + Ok(hash) => hash.construct_path(asset_path("maps")), + Err(error) => { + toasts.send(ToastEvent::new(error)); + multi_state.set(MultiplayerState::SignIn); + return; + } + }; + + commands.insert_resource(GameConfig::new( + map_path, + LocalPlayers::from_single(player.0), + )); + app_state.set(AppState::InGame); +} + +// TODO handle ready button event diff --git a/crates/menu/src/multiplayer/joined/ui.rs b/crates/menu/src/multiplayer/joined/ui.rs index d0069916..5c2bedb4 100644 --- a/crates/menu/src/multiplayer/joined/ui.rs +++ b/crates/menu/src/multiplayer/joined/ui.rs @@ -119,3 +119,5 @@ fn row(commands: &mut GuiCommands, player: &GamePlayer) -> Entity { row_id } + +// TODO ready button + click event diff --git a/crates/menu/src/multiplayer/joining.rs b/crates/menu/src/multiplayer/joining.rs index ce5d3b02..dfed4855 100644 --- a/crates/menu/src/multiplayer/joining.rs +++ b/crates/menu/src/multiplayer/joining.rs @@ -8,6 +8,7 @@ use de_multiplayer::{ use super::{ current::GameNameRes, + joined::LocalPlayerRes, requests::{Receiver, Sender}, MultiplayerState, }; @@ -31,10 +32,12 @@ impl Plugin for JoiningGamePlugin { } fn cleanup( + mut commands: Commands, state: Res>, mut shutdown: EventWriter, ) { if state.as_ref() != &MultiplayerState::GameJoined { + commands.remove_resource::(); shutdown.send(ShutdownMultiplayerEvent); } } @@ -67,6 +70,7 @@ fn handle_get_response( } fn handle_joined_event( + mut commands: Commands, game_name: Res, mut events: EventReader, mut sender: Sender, @@ -75,6 +79,7 @@ fn handle_joined_event( return; }; + commands.insert_resource(LocalPlayerRes::new(event.player())); sender.send(JoinGameRequest::new( game_name.name_owned(), GamePlayerInfo::new(event.player().to_num()), diff --git a/crates/menu/src/multiplayer/setup.rs b/crates/menu/src/multiplayer/setup.rs index 3339649a..38ef5bf7 100644 --- a/crates/menu/src/multiplayer/setup.rs +++ b/crates/menu/src/multiplayer/setup.rs @@ -4,11 +4,13 @@ use de_gui::ToastEvent; use de_lobby_client::CreateGameRequest; use de_lobby_model::{GameConfig, GameSetup}; use de_multiplayer::{ - ConnectionType, GameOpenedEvent, NetGameConf, ShutdownMultiplayerEvent, StartMultiplayerEvent, + ConnectionType, GameJoinedEvent, GameOpenedEvent, NetGameConf, ShutdownMultiplayerEvent, + StartMultiplayerEvent, }; use super::{ current::GameNameRes, + joined::LocalPlayerRes, requests::{Receiver, Sender}, MultiplayerState, }; @@ -27,8 +29,16 @@ impl Plugin for SetupGamePlugin { ) .add_systems( Update, - (create_game_in_lobby, handle_lobby_response) + ( + create_game_in_lobby, + handle_lobby_response, + handle_joined_event, + ) .run_if(in_state(MultiplayerState::GameSetup)), + ) + .add_systems( + PostUpdate, + move_once_ready.run_if(in_state(MultiplayerState::GameSetup)), ); } } @@ -52,6 +62,9 @@ impl SetupGameEvent { #[derive(Resource)] pub(crate) struct GameConfigRes(GameConfig); +#[derive(Resource)] +struct JoinedMarker; + fn handle_setup_event( mut commands: Commands, mut next_state: ResMut>, @@ -65,14 +78,26 @@ fn handle_setup_event( next_state.set(MultiplayerState::GameSetup); } +fn move_once_ready( + joined: Option>, + local_player: Option>, + mut next_state: ResMut>, +) { + if joined.is_some() && local_player.is_some() { + next_state.set(MultiplayerState::GameJoined); + } +} + fn cleanup( mut commands: Commands, state: Res>, mut shutdown: EventWriter, ) { commands.remove_resource::(); + commands.remove_resource::(); if state.as_ref() != &MultiplayerState::GameJoined { + commands.remove_resource::(); shutdown.send(ShutdownMultiplayerEvent); } } @@ -109,7 +134,15 @@ fn create_game_in_lobby( sender.send(CreateGameRequest::new(game_setup)); } +fn handle_joined_event(mut commands: Commands, mut events: EventReader) { + let Some(event) = events.iter().last() else { + return; + }; + commands.insert_resource(LocalPlayerRes::new(event.player())); +} + fn handle_lobby_response( + mut commands: Commands, mut next_state: ResMut>, mut receiver: Receiver, mut toasts: EventWriter, @@ -118,7 +151,7 @@ fn handle_lobby_response( match result { Ok(_) => { info!("Game successfully created."); - next_state.set(MultiplayerState::GameJoined); + commands.insert_resource(JoinedMarker); } Err(error) => { toasts.send(ToastEvent::new(error)); diff --git a/crates/multiplayer/src/game.rs b/crates/multiplayer/src/game.rs index fde6a457..6e416169 100644 --- a/crates/multiplayer/src/game.rs +++ b/crates/multiplayer/src/game.rs @@ -2,7 +2,7 @@ use std::net::SocketAddr; use bevy::prelude::*; use de_core::{player::Player, schedule::PreMovement}; -use de_messages::{FromGame, FromServer, GameOpenError, JoinError, ToGame, ToServer}; +use de_messages::{FromGame, FromServer, GameOpenError, JoinError, Readiness, ToGame, ToServer}; use crate::{ config::ConnectionType, @@ -22,6 +22,7 @@ impl Plugin for GamePlugin { .add_event::() .add_event::() .add_event::() + .add_event::() .add_systems(OnEnter(NetState::Connected), open_or_join) .add_systems( PreMovement, @@ -76,6 +77,10 @@ impl PeerLeftEvent { } } +/// This event is sent when game readiness stage of the joined game changes. +#[derive(Event, Deref)] +pub struct GameReadinessEvent(Readiness); + fn open_or_join( conf: Res, mut main_server: EventWriter, @@ -140,6 +145,7 @@ fn process_from_game( mut joined_events: EventWriter, mut peer_joined_events: EventWriter, mut peer_left_events: EventWriter, + mut readiness_events: EventWriter, mut next_state: ResMut>, ) { for event in inputs.iter() { @@ -199,6 +205,7 @@ fn process_from_game( } FromGame::GameReadiness(readiness) => { info!("Game readiness changed to: {readiness:?}"); + readiness_events.send(GameReadinessEvent(*readiness)); } } } diff --git a/crates/multiplayer/src/lib.rs b/crates/multiplayer/src/lib.rs index dbea8dc1..a717b221 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::{GameJoinedEvent, GameOpenedEvent, PeerJoinedEvent, PeerLeftEvent}, + game::{GameJoinedEvent, GameOpenedEvent, PeerJoinedEvent, PeerLeftEvent, GameReadinessEvent}, lifecycle::{MultiplayerShuttingDownEvent, ShutdownMultiplayerEvent, StartMultiplayerEvent}, netstate::NetState, };