From a651091312aba6db73d235d474f285f6c2735715 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Wed, 30 Nov 2022 14:42:34 -0600 Subject: [PATCH 1/3] Make Matchmaker Sensitive to Jumpy Version --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/ui/main_menu/matchmaking.rs | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d81bc947c2..752ed5592d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2474,7 +2474,7 @@ dependencies = [ [[package]] name = "jumpy" -version = "0.4.3" +version = "0.5.0-dev" dependencies = [ "anyhow", "async-channel", diff --git a/Cargo.toml b/Cargo.toml index 7af5685346..59118d7c3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jumpy" -version = "0.4.3" +version = "0.5.0-dev" description = "A tactical 2D shooter" authors = ["The Fish Fight Game & Spicy Lobster Developers"] license = "MIT OR Apache-2.0" diff --git a/src/ui/main_menu/matchmaking.rs b/src/ui/main_menu/matchmaking.rs index c4c3a2e5dc..479cee1275 100644 --- a/src/ui/main_menu/matchmaking.rs +++ b/src/ui/main_menu/matchmaking.rs @@ -332,9 +332,16 @@ async fn impl_start_matchmaking( let (mut send, recv) = conn.open_bi().await?; + let jumpy_version = env!("CARGO_PKG_VERSION"); + let message = MatchmakerRequest::RequestMatch(MatchInfo { client_count: player_count, - match_data: b"jumpy_default_game".to_vec(), + match_data: b"jumpy_" + .iter() + .chain(jumpy_version.as_bytes().iter()) + .chain(b"_default".iter()) + .cloned() + .collect(), }); let message = postcard::to_allocvec(&message)?; From fe1ec20b9744cf1db6daa9a10a0e80aefecd2692 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Wed, 30 Nov 2022 15:13:52 -0600 Subject: [PATCH 2/3] Synchronize Online Pause State --- src/main.rs | 38 ++++++++++----------- src/map/elements/grenade.rs | 6 ++-- src/map/elements/player_spawner.rs | 2 +- src/map/elements/sproinger.rs | 4 +-- src/map/elements/sword.rs | 4 +-- src/player/input.rs | 55 ++++++++++++++++++++++++------ src/prelude.rs | 2 +- src/ui/editor.rs | 11 +++--- src/ui/main_menu.rs | 2 +- src/ui/main_menu/map_select.rs | 6 ---- src/ui/pause_menu.rs | 10 +++--- src/utils.rs | 2 +- 12 files changed, 84 insertions(+), 58 deletions(-) diff --git a/src/main.rs b/src/main.rs index da2d2bbbb1..5d6548bd76 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,7 +76,7 @@ use crate::{ random::RandomPlugin, session::SessionPlugin, ui::UiPlugin, - utils::{is_in_game_run_criteria, UtilsPlugin}, + utils::{run_criteria_game_not_paused, UtilsPlugin}, workarounds::WorkaroundsPlugin, }; @@ -94,17 +94,21 @@ pub enum GameState { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum InGameState { Playing, - Editing, Paused, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum GameEditorState { + Hidden, + Visible, +} + #[derive(StageLabel)] pub enum RollbackStage { + Input, First, PreUpdate, - PreUpdateInGame, Update, - UpdateInGame, PostUpdate, Last, } @@ -154,7 +158,8 @@ pub fn main() { // Set initial game state app.add_loopless_state(GameState::LoadingPlatformStorage) - .add_loopless_state(InGameState::Playing); + .add_loopless_state(InGameState::Playing) + .add_loopless_state(GameEditorState::Hidden); // Create the GGRS rollback schedule and plugin let mut rollback_schedule = Schedule::default(); @@ -162,36 +167,31 @@ pub fn main() { // Add fixed update stagesrefs/branchless/2fd80952e26d905aa258ebb7e6175a7cfc4cb76f rollback_schedule - .add_stage(RollbackStage::First, SystemStage::parallel()) + .add_stage(RollbackStage::Input, SystemStage::parallel()) .add_stage_after( + RollbackStage::Input, RollbackStage::First, - RollbackStage::PreUpdate, - SystemStage::parallel(), + SystemStage::parallel().with_run_criteria(run_criteria_game_not_paused), ) .add_stage_after( + RollbackStage::First, RollbackStage::PreUpdate, - RollbackStage::Update, - SystemStage::parallel(), + SystemStage::parallel().with_run_criteria(run_criteria_game_not_paused), ) .add_stage_after( RollbackStage::PreUpdate, - RollbackStage::PreUpdateInGame, - SystemStage::parallel().with_run_criteria(is_in_game_run_criteria), - ) - .add_stage_after( RollbackStage::Update, - RollbackStage::PostUpdate, - SystemStage::parallel(), + SystemStage::parallel().with_run_criteria(run_criteria_game_not_paused), ) .add_stage_after( RollbackStage::Update, - RollbackStage::UpdateInGame, - SystemStage::parallel().with_run_criteria(is_in_game_run_criteria), + RollbackStage::PostUpdate, + SystemStage::parallel().with_run_criteria(run_criteria_game_not_paused), ) .add_stage_after( RollbackStage::PostUpdate, RollbackStage::Last, - SystemStage::parallel(), + SystemStage::parallel().with_run_criteria(run_criteria_game_not_paused), ); // Add the rollback schedule and plugin as resources, temporarily. diff --git a/src/map/elements/grenade.rs b/src/map/elements/grenade.rs index b6ed714fd3..5f3a815c22 100644 --- a/src/map/elements/grenade.rs +++ b/src/map/elements/grenade.rs @@ -38,12 +38,12 @@ impl Plugin for GrenadePlugin { fn build(&self, app: &mut App) { app.extend_rollback_schedule(|schedule| { schedule - .add_system_to_stage(RollbackStage::PreUpdateInGame, pre_update_in_game) + .add_system_to_stage(RollbackStage::PreUpdate, pre_update_in_game) .add_system_to_stage( - RollbackStage::UpdateInGame, + RollbackStage::Update, update_lit_grenades.before(update_idle_grenades), ) - .add_system_to_stage(RollbackStage::UpdateInGame, update_idle_grenades); + .add_system_to_stage(RollbackStage::Update, update_idle_grenades); }) .extend_rollback_plugin(|plugin| { plugin diff --git a/src/map/elements/player_spawner.rs b/src/map/elements/player_spawner.rs index daafaff020..00df729f1b 100644 --- a/src/map/elements/player_spawner.rs +++ b/src/map/elements/player_spawner.rs @@ -7,7 +7,7 @@ impl Plugin for PlayerSpawnerPlugin { fn build(&self, app: &mut App) { app.init_resource::() .extend_rollback_schedule(|schedule| { - schedule.add_system_to_stage(RollbackStage::PreUpdateInGame, pre_update_in_game); + schedule.add_system_to_stage(RollbackStage::PreUpdate, pre_update_in_game); }) .extend_rollback_plugin(|plugin| { plugin diff --git a/src/map/elements/sproinger.rs b/src/map/elements/sproinger.rs index 3fa1e6fd06..38a5385e0e 100644 --- a/src/map/elements/sproinger.rs +++ b/src/map/elements/sproinger.rs @@ -7,8 +7,8 @@ impl Plugin for SproingerPlugin { fn build(&self, app: &mut App) { app.extend_rollback_schedule(|schedule| { schedule - .add_system_to_stage(RollbackStage::PreUpdateInGame, pre_update_in_game) - .add_system_to_stage(RollbackStage::UpdateInGame, update_in_game); + .add_system_to_stage(RollbackStage::PreUpdate, pre_update_in_game) + .add_system_to_stage(RollbackStage::Update, update_in_game); }) .extend_rollback_plugin(|plugin| plugin.register_rollback_type::()); } diff --git a/src/map/elements/sword.rs b/src/map/elements/sword.rs index a94d16a159..f3928a9656 100644 --- a/src/map/elements/sword.rs +++ b/src/map/elements/sword.rs @@ -5,8 +5,8 @@ impl Plugin for SwordPlugin { fn build(&self, app: &mut App) { app.extend_rollback_schedule(|schedule| { schedule - .add_system_to_stage(RollbackStage::PreUpdateInGame, pre_update_in_game) - .add_system_to_stage(RollbackStage::UpdateInGame, update_in_game); + .add_system_to_stage(RollbackStage::PreUpdate, pre_update_in_game) + .add_system_to_stage(RollbackStage::Update, update_in_game); }) .extend_rollback_plugin(|plugin| plugin.register_rollback_type::()); } diff --git a/src/player/input.rs b/src/player/input.rs index a9dc6d8853..4e96402eae 100644 --- a/src/player/input.rs +++ b/src/player/input.rs @@ -25,12 +25,18 @@ impl Plugin for PlayerInputPlugin { .add_system_to_stage(CoreStage::Last, clear_input_buffer) .extend_rollback_plugin(|plugin| plugin.register_rollback_type::()) .extend_rollback_schedule(|schedule| { - schedule.add_system_to_stage(RollbackStage::PreUpdate, update_user_input); - // .add_system_to_stage(FixedUpdateStage::Last, reset_input); + schedule.add_system_to_stage(RollbackStage::Input, update_user_input); }); } } +/// This is a resource that gets inserted every time the menu wants to modify the pause state of the +/// game. +/// +/// The resource is removed every time it is read, so it will only be present in the world when +/// there is an intent to change the pause state. +pub struct WantsGamePause(pub bool); + /// A buffer holding the player inputs until they are read by the game simulation. #[derive(Reflect, Default)] pub struct LocalPlayerInputBuffer { @@ -79,10 +85,20 @@ fn clear_input_buffer(mut buffer: ResMut) { /// The GGRS input system pub fn input_system( player_handle: In, + mut commands: Commands, mut buffer: ResMut, + wants_game_pause: Option>, ) -> DensePlayerControl { buffer.has_been_read = true; - buffer.players[player_handle.0] + let mut input = buffer.players[player_handle.0]; + + if let Some(wants_game_pause) = wants_game_pause { + commands.remove_resource::(); + input.set_wants_to_set_pause(true); + input.set_pause_value(wants_game_pause.0); + } + + input } /// The control inputs that a player may make. @@ -100,7 +116,7 @@ pub enum PlayerAction { #[reflect(Default, Resource)] pub struct PlayerInputs { /// This will be `true` if _all_ of the inputs for all players for this frame have been - /// confirmed and will not be rolled back. + /// confirmed ( so presumably will not be rolled back ). pub is_confirmed: bool, pub players: Vec, } @@ -157,21 +173,24 @@ bitfield::bitfield! { /// This is used when sending player inputs across the network. #[derive(bytemuck::Pod, bytemuck::Zeroable, Copy, Clone, PartialEq, Eq, Reflect)] #[repr(transparent)] - pub struct DensePlayerControl(u16); + pub struct DensePlayerControl(u32); impl Debug; jump_pressed, set_jump_pressed: 0; shoot_pressed, set_shoot_pressed: 1; grab_pressed, set_grab_pressed: 2; slide_pressed, set_slide_pressed: 3; from into DenseMoveDirection, move_direction, set_move_direction: 15, 4; + /// This bit will be set if this player wants to try and pause or un-pause the game + wants_to_set_pause, set_wants_to_set_pause: 16; + /// This value is only relevant if `wants_to_set_pause` is true, and it indicates whether the + /// player wants to pause or unpause the game. + pause_value, set_pause_value: 17; } impl Default for DensePlayerControl { fn default() -> Self { let mut control = Self(0); - control.set_move_direction(default()); - control } } @@ -183,10 +202,10 @@ struct DenseMoveDirection(pub Vec2); /// This is the specific [`Quantized`] type that we use to represent movement directions in /// [`DenseMoveDirection`]. -type MoveDirQuant = Quantized>; +type MoveDirQuant = Quantized>; -impl From for DenseMoveDirection { - fn from(bits: u16) -> Self { +impl From for DenseMoveDirection { + fn from(bits: u32) -> Self { // maximum movement value representable, we use 6 bits to represent each movement direction. let max = 0b111111; // The first six bits represent the x movement @@ -208,7 +227,7 @@ impl From for DenseMoveDirection { } } -impl From for u16 { +impl From for u32 { fn from(dir: DenseMoveDirection) -> Self { let x_bits = MoveDirQuant::from_f32(dir.x).raw(); let y_bits = MoveDirQuant::from_f32(dir.y).raw(); @@ -219,6 +238,7 @@ impl From for u16 { /// Updates the [`PlayerInputs`] resource from input collected from GGRS. fn update_user_input( + mut commands: Commands, inputs: Res>, mut player_inputs: ResMut, ) { @@ -227,6 +247,19 @@ fn update_user_input( .map(|x| x.1) .all(|x| x == InputStatus::Confirmed); + let someone_wants_to_pause = inputs + .iter() + .any(|x| x.0.wants_to_set_pause() && x.0.pause_value()); + let someone_wants_to_unpause = inputs + .iter() + .any(|x| x.0.wants_to_set_pause() && !x.0.pause_value()); + + if someone_wants_to_pause { + commands.insert_resource(NextState(InGameState::Paused)); + } else if someone_wants_to_unpause { + commands.insert_resource(NextState(InGameState::Playing)); + } + for (player_idx, (input, _)) in inputs.iter().enumerate() { let PlayerInput { control, diff --git a/src/prelude.rs b/src/prelude.rs index d24375c0c7..00b35f5865 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -3,7 +3,7 @@ pub use crate::{ audio::{EffectsChannel, MusicChannel}, schedule::RollbackScheduleAppExt, utils::event::FixedUpdateEventAppExt, - GameState, InGameState, RollbackStage, + GameEditorState, GameState, InGameState, RollbackStage, }; pub use bevy::prelude::*; pub use bevy_ggrs::{Rollback, RollbackIdProvider}; diff --git a/src/ui/editor.rs b/src/ui/editor.rs index 40bde7d88e..94b94c35e1 100644 --- a/src/ui/editor.rs +++ b/src/ui/editor.rs @@ -27,18 +27,17 @@ impl Plugin for EditorPlugin { .add_system( editor_update .run_in_state(GameState::InGame) - .run_in_state(InGameState::Editing), + .run_in_state(GameEditorState::Visible), ) .add_system( editor_ui_system .into_conditional_exclusive() .run_in_state(GameState::InGame) - .run_in_state(InGameState::Editing) + .run_in_state(GameEditorState::Visible) .at_end(), ) - .add_enter_system(InGameState::Editing, setup_editor) - .add_exit_system(InGameState::Editing, cleanup_editor) - .add_exit_system(GameState::InGame, cleanup_editor); + .add_enter_system(GameEditorState::Visible, setup_editor) + .add_exit_system(GameEditorState::Visible, cleanup_editor); } } @@ -227,7 +226,7 @@ impl<'w, 's> WidgetSystem for EditorTopBar<'w, 's> { params .camera_commands_resetcontroller .p1() - .insert_resource(NextState(InGameState::Playing)); + .insert_resource(NextState(GameEditorState::Hidden)); } let mut reset_controller = params.camera_commands_resetcontroller.p2(); diff --git a/src/ui/main_menu.rs b/src/ui/main_menu.rs index a333648e2b..5ddc952145 100644 --- a/src/ui/main_menu.rs +++ b/src/ui/main_menu.rs @@ -314,7 +314,7 @@ impl<'w, 's> WidgetSystem for HomeMenu<'w, 's> { { params .commands - .insert_resource(NextState(InGameState::Editing)); + .insert_resource(NextState(GameEditorState::Visible)); params .commands .insert_resource(NextState(GameState::InGame)); diff --git a/src/ui/main_menu/map_select.rs b/src/ui/main_menu/map_select.rs index 171925b63c..9498804044 100644 --- a/src/ui/main_menu/map_select.rs +++ b/src/ui/main_menu/map_select.rs @@ -138,9 +138,6 @@ impl<'w, 's> WidgetSystem for MapSelectMenu<'w, 's> { params .commands .insert_resource(NextState(GameState::InGame)); - params - .commands - .insert_resource(NextState(InGameState::Playing)); if let Some(client) = &mut params.client { client.send_reliable( @@ -175,9 +172,6 @@ fn handle_match_setup_messages(params: &mut MapSelectMenu) { params .commands .insert_resource(NextState(GameState::InGame)); - params - .commands - .insert_resource(NextState(InGameState::Playing)); } other => warn!("Unexpected message: {other:?}"), }, diff --git a/src/ui/pause_menu.rs b/src/ui/pause_menu.rs index e8b0035090..de6f896832 100644 --- a/src/ui/pause_menu.rs +++ b/src/ui/pause_menu.rs @@ -5,6 +5,7 @@ use crate::{ localization::LocalizationExt, metadata::{GameMeta, MapMeta}, networking::client::NetClient, + player::input::WantsGamePause, prelude::*, session::SessionManager, ui::input::MenuAction, @@ -60,7 +61,7 @@ impl Plugin for PausePlugin { fn pause_system(mut commands: Commands, input: Query<&ActionState>) { let input = input.single(); if input.just_pressed(MenuAction::Pause) { - commands.insert_resource(NextState(InGameState::Paused)); + commands.insert_resource(WantsGamePause(true)); } } @@ -73,7 +74,7 @@ fn unpause_system( let input = input.single(); if input.just_pressed(MenuAction::Pause) { *pause_page = default(); - commands.insert_resource(NextState(InGameState::Playing)); + commands.insert_resource(WantsGamePause(false)); } } @@ -150,7 +151,7 @@ pub fn pause_menu_default( } if continue_button.clicked() { - commands.insert_resource(NextState(InGameState::Playing)); + commands.insert_resource(WantsGamePause(false)); } ui.scope(|ui| { @@ -175,7 +176,6 @@ pub fn pause_menu_default( .show(ui) .clicked() { - commands.insert_resource(NextState(InGameState::Playing)); let map_handle = map_handle.get_single().ok().cloned(); reset_controller.reset_world(); @@ -196,7 +196,7 @@ pub fn pause_menu_default( .show(ui) .clicked() { - commands.insert_resource(NextState(InGameState::Editing)); + commands.insert_resource(NextState(GameEditorState::Visible)); } }); diff --git a/src/utils.rs b/src/utils.rs index 7381fd623b..d44192fdc9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -111,7 +111,7 @@ impl<'w, 's> ResetManager<'w, 's> { } /// Heper stage run criteria that only runs if we are in a gameplay state. -pub fn is_in_game_run_criteria( +pub fn run_criteria_game_not_paused( game_state: Option>>, in_game_state: Option>>, ) -> ShouldRun { From 9233592a67095294407ab8171d71095de4a4bf61 Mon Sep 17 00:00:00 2001 From: Zicklag Date: Wed, 30 Nov 2022 16:15:50 -0600 Subject: [PATCH 3/3] Tweaks Helping Network Game Map Changes Later --- src/networking.rs | 46 ++++++++++++++++++++++++++++++++++++++--- src/networking/proto.rs | 12 +++++------ src/session.rs | 12 +++++++++-- src/utils.rs | 13 +++--------- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/src/networking.rs b/src/networking.rs index 27e8513166..265ec5713f 100644 --- a/src/networking.rs +++ b/src/networking.rs @@ -1,11 +1,51 @@ -use crate::prelude::*; +use bevy_ggrs::ggrs::P2PSession; + +use crate::{ + prelude::*, session::SessionManager, ui::main_menu::MenuPage, utils::ResetManager, GgrsConfig, +}; + +use self::{ + client::NetClient, + proto::{match_setup::MatchSetupMessage, ReliableGameMessageKind}, +}; pub mod client; pub mod proto; -// pub mod server; pub struct NetworkingPlugin; impl Plugin for NetworkingPlugin { - fn build(&self, _app: &mut App) {} + fn build(&self, app: &mut App) { + app.add_system(listen_for_map_changes.run_if_resource_exists::>()); + } +} + +// TODO: Map changes aren't working on network games for now. +fn listen_for_map_changes( + mut commands: Commands, + client: Res, + mut reset_manager: ResetManager, + mut session_manager: SessionManager, + mut menu_page: ResMut, + mut ridp: ResMut, +) { + while let Some(message) = client.recv_reliable() { + match message.kind { + ReliableGameMessageKind::MatchSetup(setup) => match setup { + MatchSetupMessage::SelectMap(map_handle) => { + info!("Other player selected map, starting game"); + *menu_page = MenuPage::Home; + reset_manager.reset_world(); + + commands + .spawn() + .insert(map_handle) + .insert(Rollback::new(ridp.next_id())); + commands.insert_resource(NextState(GameState::InGame)); + session_manager.start_session(); + } + other => warn!("Unexpected message during match: {other:?}"), + }, + } + } } diff --git a/src/networking/proto.rs b/src/networking/proto.rs index 814c001d4e..ddca46e916 100644 --- a/src/networking/proto.rs +++ b/src/networking/proto.rs @@ -7,6 +7,12 @@ pub enum ReliableGameMessageKind { MatchSetup(match_setup::MatchSetupMessage), } +impl From for ReliableGameMessageKind { + fn from(x: match_setup::MatchSetupMessage) -> Self { + Self::MatchSetup(x) + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct RecvReliableGameMessage { pub from_player_idx: usize, @@ -30,12 +36,6 @@ pub struct RecvUnreliableGameMessage { pub kind: UnreliableGameMessageKind, } -impl From for ReliableGameMessageKind { - fn from(x: match_setup::MatchSetupMessage) -> Self { - Self::MatchSetup(x) - } -} - /// A resource indicating which player this game client represents, and how many players there are /// in the match.j #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/src/session.rs b/src/session.rs index 8907020f13..e98ee4c890 100644 --- a/src/session.rs +++ b/src/session.rs @@ -5,8 +5,8 @@ use bevy::ecs::system::SystemParam; use bevy_ggrs::{ - ggrs::{self, NonBlockingSocket, SessionBuilder}, - SessionType, + ggrs::{self, NonBlockingSocket, P2PSession, SessionBuilder, SyncTestSession}, + ResetGGRSSession, SessionType, }; use jumpy_matchmaker_proto::TargetClient; @@ -69,6 +69,14 @@ impl NonBlockingSocket for NetClient { } impl<'w, 's> SessionManager<'w, 's> { + pub fn drop_session(&mut self) { + self.commands.insert_resource(ResetGGRSSession); + self.commands.remove_resource::(); + self.commands.remove_resource::>(); + self.commands + .remove_resource::>(); + } + /// Setup the game session pub fn start_session(&mut self) { const INPUT_DELAY: usize = 1; diff --git a/src/utils.rs b/src/utils.rs index d44192fdc9..b3021b8e54 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,12 +1,8 @@ use bevy::ecs::{schedule::ShouldRun, system::SystemParam}; -use bevy_ggrs::{ - ggrs::{P2PSession, SyncTestSession}, - ResetGGRSSession, SessionType, -}; use crate::{ loading::PlayerInputCollector, map::elements::player_spawner::CurrentPlayerSpawner, prelude::*, - run_criteria::ShouldRunExt, ui::input::MenuAction, GgrsConfig, + run_criteria::ShouldRunExt, session::SessionManager, ui::input::MenuAction, }; pub mod event; @@ -80,6 +76,7 @@ pub struct ResetManager<'w, 's> { ), >, current_player_spawner: ResMut<'w, CurrentPlayerSpawner>, + session_manager: SessionManager<'w, 's>, } impl<'w, 's> ResetManager<'w, 's> { @@ -102,11 +99,7 @@ impl<'w, 's> ResetManager<'w, 's> { **self.current_player_spawner = 0; // Clear the game session - self.commands.insert_resource(ResetGGRSSession); - self.commands.remove_resource::(); - self.commands - .remove_resource::>(); - self.commands.remove_resource::>(); + self.session_manager.drop_session(); } }