Skip to content

Commit

Permalink
feat(networking): Update map select for online matches, disable pause
Browse files Browse the repository at this point in the history
menu when online, trigger network session runner from menu.
  • Loading branch information
MaxCWhitehead committed Jan 13, 2024
1 parent e9740e0 commit 72330f4
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 38 deletions.
4 changes: 3 additions & 1 deletion src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub struct MatchPlugin {
pub player_info: [PlayerInput; MAX_PLAYERS],
/// The lua plugins to enable for this match.
pub plugins: Arc<Vec<Handle<LuaPlugin>>>,

pub session_runner: Box<dyn SessionRunner>,
}

pub struct MatchPlayerInfo {
Expand Down Expand Up @@ -87,7 +89,7 @@ impl SessionPlugin for MatchPlugin {
session.world.insert_resource(MatchInputs {
players: self.player_info,
});
session.runner = Box::<JumpyDefaultMatchRunner>::default();
session.runner = self.session_runner;
}
}

Expand Down
1 change: 1 addition & 0 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fn collect_player_controls(game: &mut Game) {
let gamepad = game.shared_resource::<GamepadInputs>().unwrap();
collector.apply_inputs(&mapping, &keyboard, &gamepad);
collector.update_just_pressed();
collector.advance_frame();
GlobalPlayerControls(
collector
.get_current_controls()
Expand Down
3 changes: 2 additions & 1 deletion src/sessions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::prelude::*;
use crate::{core::JumpyDefaultMatchRunner, prelude::*};

pub struct SessionNames;
impl SessionNames {
Expand Down Expand Up @@ -43,6 +43,7 @@ impl SessionExt for Sessions {
map,
player_info,
plugins,
session_runner: Box::<JumpyDefaultMatchRunner>::default(),
});
} else {
panic!("Cannot restart game when game is not running");
Expand Down
102 changes: 99 additions & 3 deletions src/ui/main_menu/map_select.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,68 @@
use crate::core::MatchPlugin;
use crate::core::{JumpyDefaultMatchRunner, MatchPlugin};
use crate::prelude::*;

use crate::ui::map_select::{map_select_menu, MapSelectAction};

use super::player_select::PlayerSelectState;
use super::MenuPage;

#[cfg(not(target_arch = "wasm32"))]
use bones_framework::networking::{GgrsSessionRunner, GgrsSessionRunnerInfo, NetworkMatchSocket};

/// Network message that may be sent when selecting a map.
#[derive(Serialize, Deserialize)]
pub enum MapSelectMessage {
SelectMap(NetworkHandle<MapMeta>),
}

pub fn widget(
ui: In<&mut egui::Ui>,
world: &World,
meta: Root<GameMeta>,
mut sessions: ResMut<Sessions>,
mut session_options: ResMut<SessionOptions>,
assets: Res<AssetServer>,

#[cfg(not(target_arch = "wasm32"))] network_socket: Option<Res<NetworkMatchSocket>>,
) {
let select_action = world.run_system(map_select_menu, ());
let mut select_action = MapSelectAction::None;

// Get map select action from network
#[cfg(not(target_arch = "wasm32"))]
if let Some(MapSelectAction::SelectMap(map_meta)) =
handle_match_setup_messages(&network_socket, &assets)
{
select_action = MapSelectAction::SelectMap(map_meta);
}

// If no network action - update action from UI
if matches!(select_action, MapSelectAction::None) {
select_action = world.run_system(map_select_menu, ());

#[cfg(not(target_arch = "wasm32"))]
// Replicate local action
replicate_map_select_action(&select_action, &network_socket, &assets);
}

match select_action {
MapSelectAction::None => (),
MapSelectAction::SelectMap(map_meta) => {
MapSelectAction::SelectMap(map_handle) => {
session_options.delete = true;
ui.ctx().set_state(MenuPage::Home);

#[cfg(not(target_arch = "wasm32"))]
let session_runner: Box<dyn SessionRunner> = match network_socket {
Some(socket) => Box::new(GgrsSessionRunner::<NetworkInputConfig>::new(
FPS,
GgrsSessionRunnerInfo::from(socket.as_ref()),
)),
None => Box::<JumpyDefaultMatchRunner>::default(),
};
#[cfg(target_arch = "wasm32")]
let session_runner = Box::<JumpyDefaultMatchRunner>::default();

let map_meta = assets.get(map_handle).clone();

let player_select_state = ui.ctx().get_state::<PlayerSelectState>();
sessions.start_game(MatchPlugin {
map: map_meta,
Expand All @@ -35,12 +76,67 @@ pub fn widget(
control_source: slot.control_source,
editor_input: default(),
control: default(),
is_ai: slot.is_ai,
}
}),
plugins: meta.get_plugins(&assets),
session_runner,
});
ui.ctx().set_state(PlayerSelectState::default());
}
MapSelectAction::GoBack => ui.ctx().set_state(MenuPage::PlayerSelect),
}
}

/// Send a MapSelectMessage over network if local player has selected a map.
#[cfg(not(target_arch = "wasm32"))]
fn replicate_map_select_action(
action: &MapSelectAction,
socket: &Option<Res<NetworkMatchSocket>>,
asset_server: &AssetServer,
) {
use bones_framework::networking::SocketTarget;
if let Some(socket) = socket {
if let MapSelectAction::SelectMap(map) = action {
info!("Sending network SelectMap message.");
socket.send_reliable(
SocketTarget::All,
&postcard::to_allocvec(&MapSelectMessage::SelectMap(
map.network_handle(asset_server),
))
.unwrap(),
);
}
}
}

#[cfg(not(target_arch = "wasm32"))]
fn handle_match_setup_messages(
socket: &Option<Res<NetworkMatchSocket>>,
asset_server: &AssetServer,
) -> Option<MapSelectAction> {
if let Some(socket) = socket {
let datas: Vec<(usize, Vec<u8>)> = socket.recv_reliable();

for (_player, data) in datas {
match postcard::from_bytes::<MapSelectMessage>(&data) {
Ok(message) => match message {
MapSelectMessage::SelectMap(map_handle) => {
info!("Map select message received, starting game");

let handle = map_handle.into_handle(asset_server);
return Some(MapSelectAction::SelectMap(handle));
}
},
Err(e) => {
// TODO: The second player in an online match is having this triggered by
// picking up a `ConfirmSelection` message, that might have been sent to
// _itself_.
warn!("Ignoring network message that was not understood: {e} data: {data:?}");
}
}
}
}

None
}
91 changes: 61 additions & 30 deletions src/ui/map_select.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
use crate::{prelude::*, PackMeta};

use super::main_menu::MenuPage;

#[derive(Clone, Debug, Default)]
pub enum MapSelectAction {
#[default]
None,
SelectMap(MapMeta),
SelectMap(Handle<MapMeta>),
GoBack,
}

/// Network message that may be sent when selecting a map.
#[derive(Serialize, Deserialize)]
pub enum MapSelectMessage {
SelectMap(NetworkHandle<MapMeta>),
}

pub fn map_select_menu(
asset_server: Res<AssetServer>,
meta: Root<GameMeta>,
Expand Down Expand Up @@ -45,48 +53,71 @@ pub fn map_select_menu(

ui.add_space(meta.theme.font_styles.normal.size);

egui::ScrollArea::vertical()
.show(ui, |ui| {
ui.vertical_centered_justified(|ui| {
for (i, handle) in meta.core.stable_maps.iter().enumerate() {
let map_meta = asset_server.get(*handle);

let mut button = BorderedButton::themed(
&meta.theme.buttons.small,
map_meta.name.to_string(),
)
.show(ui);

if i == 0 {
button = button.focus_by_default(ui);
}
let menu_page_state = ui.ctx().get_state::<MenuPage>();
let is_waiting = match menu_page_state {
MenuPage::MapSelect { is_waiting } => is_waiting,
_ => {
warn!(
"On map select page, but MenuPage state is not MenuPage::MapSelect.
Client may be stuck waiting for map select erroneously. "
);
true
}
};
if is_waiting {
ui.label(
meta.theme
.font_styles
.bigger
.rich(localization.get("waiting-for-map")),
);

if button.clicked() {
return MapSelectAction::SelectMap(map_meta.clone());
}
}
MapSelectAction::None
} else {
egui::ScrollArea::vertical()
.show(ui, |ui| {
ui.vertical_centered_justified(|ui| {
for (i, handle) in meta.core.stable_maps.iter().enumerate() {
let map_meta = asset_server.get(*handle);

for pack in asset_server.packs() {
let pack_meta = asset_server.get(pack.root.typed::<PackMeta>());
for map in pack_meta.maps.iter() {
let map_meta = asset_server.get(*map);
let button = BorderedButton::themed(
let mut button = BorderedButton::themed(
&meta.theme.buttons.small,
map_meta.name.to_string(),
)
.show(ui);

if i == 0 {
button = button.focus_by_default(ui);
}

if button.clicked() {
return MapSelectAction::SelectMap(map_meta.clone());
return MapSelectAction::SelectMap(*handle);
}
}

for pack in asset_server.packs() {
let pack_meta =
asset_server.get(pack.root.typed::<PackMeta>());
for map in pack_meta.maps.iter() {
let map_meta = asset_server.get(*map);
let button = BorderedButton::themed(
&meta.theme.buttons.small,
map_meta.name.to_string(),
)
.show(ui);

if button.clicked() {
return MapSelectAction::SelectMap(*map);
}
}
}
}

MapSelectAction::None
MapSelectAction::None
})
.inner
})
.inner
})
.inner
}
})
.inner
})
Expand Down
26 changes: 23 additions & 3 deletions src/ui/pause_menu.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::ops::Deref;

use crate::prelude::*;
#[cfg(not(target_arch = "wasm32"))]
use bones_framework::networking::NetworkMatchSocket;

use crate::{core::JumpyDefaultMatchRunner, prelude::*};

#[derive(Clone, Debug, Copy, Default)]
enum PauseMenuPage {
Expand All @@ -20,7 +23,15 @@ fn pause_menu_system(
controls: Res<GlobalPlayerControls>,
world: &World,
assets: Res<AssetServer>,

#[cfg(not(target_arch = "wasm32"))] socket: Option<Res<NetworkMatchSocket>>,
) {
// TODO allow pause menu in online game
#[cfg(not(target_arch = "wasm32"))]
if socket.is_some() {
return;
}

let mut back_to_menu = false;
let mut restart_game = false;
let mut select_map = None;
Expand Down Expand Up @@ -82,7 +93,9 @@ fn pause_menu_system(
sessions.start_menu();
} else if restart_game {
sessions.restart_game();
} else if let Some(map) = select_map {
} else if let Some(map_handle) = select_map {
let map = assets.get(map_handle).clone();

let match_info = sessions
.get(SessionNames::GAME)
.unwrap()
Expand All @@ -99,6 +112,7 @@ fn pause_menu_system(
..match_info.players[i]
}),
plugins: meta.get_plugins(&assets),
session_runner: Box::<JumpyDefaultMatchRunner>::default(),
})
}
}
Expand All @@ -108,9 +122,16 @@ fn main_pause_menu(
meta: Root<GameMeta>,
localization: Localization<GameMeta>,
controls: Res<GlobalPlayerControls>,

#[cfg(not(target_arch = "wasm32"))] socket: Option<Res<NetworkMatchSocket>>,
) {
let (ui, session, restart_game, back_to_menu) = &mut *param;

#[cfg(not(target_arch = "wasm32"))]
let is_online = socket.is_some();
#[cfg(target_arch = "wasm32")]
let is_online = false;

// Unpause the game
if controls.values().any(|x| x.pause_just_pressed) {
session.active = true;
Expand Down Expand Up @@ -152,7 +173,6 @@ fn main_pause_menu(

// Local game buttons
ui.scope(|ui| {
let is_online = false;
ui.set_enabled(!is_online);

// Map select button
Expand Down

0 comments on commit 72330f4

Please sign in to comment.