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/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/ui.rs b/crates/menu/src/multiplayer/joined/ui.rs index d0069916..a5942fd8 100644 --- a/crates/menu/src/multiplayer/joined/ui.rs +++ b/crates/menu/src/multiplayer/joined/ui.rs @@ -1,6 +1,8 @@ use bevy::prelude::*; -use de_gui::{GuiCommands, LabelCommands, OuterStyle}; +use de_gui::{ButtonCommands, GuiCommands, LabelCommands, OuterStyle}; use de_lobby_model::GamePlayer; +use de_messages::Readiness; +use de_multiplayer::SetReadinessEvent; use crate::{menu::Menu, multiplayer::MultiplayerState}; @@ -11,6 +13,10 @@ impl Plugin for JoinedGameUiPlugin { app.add_event::() .add_systems(OnEnter(MultiplayerState::GameJoined), setup) .add_systems(OnExit(MultiplayerState::GameJoined), cleanup) + .add_systems( + Update, + button_system.run_if(in_state(MultiplayerState::GameJoined)), + ) .add_systems( PostUpdate, refresh @@ -32,21 +38,47 @@ impl RefreshPlayersEvent { #[derive(Resource)] struct PlayersBoxRes(Entity); +#[derive(Clone, Copy, Component)] +enum ButtonAction { + Ready, +} + fn setup(mut commands: GuiCommands, menu: Res) { - let players_box_id = players_box(&mut commands, menu.root_node()); + let mid_panel_id = mid_panel(&mut commands, menu.root_node()); + let players_box_id = players_box(&mut commands, mid_panel_id); commands.insert_resource(PlayersBoxRes(players_box_id)); + ready_button(&mut commands, mid_panel_id); } fn cleanup(mut commands: Commands) { commands.remove_resource::(); } +fn mid_panel(commands: &mut GuiCommands, parent_id: Entity) -> Entity { + let panel_id = commands + .spawn(NodeBundle { + style: Style { + flex_direction: FlexDirection::Column, + width: Val::Percent(80.), + height: Val::Percent(80.), + margin: UiRect::all(Val::Auto), + align_items: AlignItems::Center, + justify_content: JustifyContent::FlexStart, + ..default() + }, + ..default() + }) + .id(); + commands.entity(parent_id).add_child(panel_id); + panel_id +} + fn players_box(commands: &mut GuiCommands, parent_id: Entity) -> Entity { let column_id = commands .spawn(NodeBundle { style: Style { flex_direction: FlexDirection::Column, - width: Val::Percent(80.), + width: Val::Percent(100.), height: Val::Percent(80.), margin: UiRect::all(Val::Auto), align_items: AlignItems::Center, @@ -119,3 +151,33 @@ fn row(commands: &mut GuiCommands, player: &GamePlayer) -> Entity { row_id } + +fn ready_button(commands: &mut GuiCommands, parent: Entity) { + let button_id = commands + .spawn_button( + OuterStyle { + width: Val::Percent(100.), + height: Val::Percent(8.), + margin: UiRect::top(Val::Percent(12.)), + }, + "Ready", + ) + .insert(ButtonAction::Ready) + .id(); + commands.entity(parent).add_child(button_id); +} + +fn button_system( + interactions: Query<(&Interaction, &ButtonAction), Changed>, + mut events: EventWriter, +) { + for (&interaction, &action) in interactions.iter() { + if let Interaction::Pressed = interaction { + match action { + ButtonAction::Ready => { + events.send(SetReadinessEvent::from(Readiness::Ready)); + } + } + } + } +} diff --git a/crates/multiplayer/src/game.rs b/crates/multiplayer/src/game.rs index fde6a457..6d70ab59 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,8 @@ impl Plugin for GamePlugin { .add_event::() .add_event::() .add_event::() + .add_event::() + .add_event::() .add_systems(OnEnter(NetState::Connected), open_or_join) .add_systems( PreMovement, @@ -34,6 +36,13 @@ impl Plugin for GamePlugin { .after(MessagesSet::RecvMessages), ), ) + .add_systems( + PostUpdate, + set_readiness + .run_if(in_state(NetState::Joined)) + .run_if(on_event::()) + .before(MessagesSet::SendMessages), + ) .add_systems(OnEnter(NetState::ShuttingDown), leave); } } @@ -76,6 +85,20 @@ impl PeerLeftEvent { } } +/// This event is sent when game readiness stage of the joined game changes. +#[derive(Event, Deref)] +pub struct GameReadinessEvent(Readiness); + +/// Send this event to change player readiness stage. +#[derive(Event)] +pub struct SetReadinessEvent(Readiness); + +impl From for SetReadinessEvent { + fn from(readiness: Readiness) -> Self { + Self(readiness) + } +} + fn open_or_join( conf: Res, mut main_server: EventWriter, @@ -133,6 +156,7 @@ fn process_from_server( } } +#[allow(clippy::too_many_arguments)] fn process_from_game( mut inputs: EventReader, mut fatals: EventWriter, @@ -140,6 +164,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,11 +224,23 @@ fn process_from_game( } FromGame::GameReadiness(readiness) => { info!("Game readiness changed to: {readiness:?}"); + readiness_events.send(GameReadinessEvent(*readiness)); } } } } +fn set_readiness( + mut readiness_events: EventReader, + mut message_events: EventWriter>, +) { + let Some(readiness) = readiness_events.iter().last() else { + return; + }; + + message_events.send(ToGameServerEvent::from(ToGame::Readiness(readiness.0))); +} + fn leave(mut server: EventWriter>) { info!("Sending leave game message."); // Send this even if not yet joined because the join / open-game request diff --git a/crates/multiplayer/src/lib.rs b/crates/multiplayer/src/lib.rs index dbea8dc1..d066fc89 100644 --- a/crates/multiplayer/src/lib.rs +++ b/crates/multiplayer/src/lib.rs @@ -15,7 +15,10 @@ use stats::StatsPlugin; pub use crate::{ config::{ConnectionType, NetGameConf}, - game::{GameJoinedEvent, GameOpenedEvent, PeerJoinedEvent, PeerLeftEvent}, + game::{ + GameJoinedEvent, GameOpenedEvent, GameReadinessEvent, PeerJoinedEvent, PeerLeftEvent, + SetReadinessEvent, + }, lifecycle::{MultiplayerShuttingDownEvent, ShutdownMultiplayerEvent, StartMultiplayerEvent}, netstate::NetState, };