M client/src/api/game/join.rs => client/src/api/game/join.rs +2 -2
@@ 9,10 9,10 @@
use crate::{NetworkingOptions, post_request_auth};
// NOTE: this response isn't actually used
-type JoinResponse = Result<String, ()>;
+pub type JoinResponse = Result<String, ()>;
pub fn join(no: &NetworkingOptions, game_id: &str) -> JoinResponse {
- let res = post_request_auth!(no, &format!("/game/{}/join)", &game_id), &{});
+ let res = post_request_auth!(no, &format!("/game/{}/join", &game_id), &{});
Ok(res.text().unwrap())
}
M client/src/macros/async_task.rs => client/src/macros/async_task.rs +22 -0
@@ 111,4 111,26 @@ macro_rules! async_task_handle_call {
}
}
};
+
+ ($call_type:ty, |$response:ident, $global:ident, $game_data:ident| $handler_func:expr) => {
+ pub fn handle_call(
+ mut commands: Commands,
+ mut tasks: Query<(Entity, &mut $call_type)>,
+ mut $game_data: ResMut<GameData>,
+ ) {
+ match tasks.get_single_mut() {
+ Ok((entity, mut task)) => {
+ if let Some($response) = future::block_on(future::poll_once(&mut task.0)) {
+ $handler_func;
+
+ // remove the task
+ commands.entity(entity).remove::<$call_type>();
+ commands.entity(entity).despawn_recursive();
+ }
+ }
+ // NOTE: don't do anything if the wanted thingy doesn't exist
+ _ => {}
+ }
+ }
+ };
}
M client/src/main.rs => client/src/main.rs +4 -0
@@ 60,12 60,14 @@ fn main() {
app.insert_resource(Global {
user: None,
+ cur_game_id: "".to_string(),
});
// has handlers for all async tasks
app.add_plugin(plugins::AsyncTasksPlugin);
app.add_plugin(plugins::MenuPlugin);
+ app.add_plugin(plugins::GamePlugin);
app.add_startup_system(setup);
app.run();
@@ 110,4 112,6 @@ pub struct NetworkingOptions {
pub struct Global {
/// details of the logged in user
user: Option<User>,
+ /// current game id, set from browse
+ cur_game_id: String,
}
M client/src/plugins/async_tasks/mod.rs => client/src/plugins/async_tasks/mod.rs +12 -0
@@ 17,12 17,18 @@ pub use req_register::RegisterCallEvent;
mod req_game_create;
pub use req_game_create::GameCreateCallEvent;
+mod req_game_join;
+pub use req_game_join::GameJoinCallEvent;
+
mod req_game_forming;
pub use req_game_forming::GameFormingCallEvent;
mod req_game_mygames;
pub use req_game_mygames::GameMyGamesCallEvent;
+mod req_game_details;
+pub use req_game_details::GameDetailsCallEvent;
+
pub struct AsyncTasksPlugin;
impl Plugin for AsyncTasksPlugin {
@@ 31,8 37,10 @@ impl Plugin for AsyncTasksPlugin {
.add_event::<LoginCallEvent>()
.add_event::<RegisterCallEvent>()
.add_event::<GameCreateCallEvent>()
+ .add_event::<GameJoinCallEvent>()
.add_event::<GameFormingCallEvent>()
.add_event::<GameMyGamesCallEvent>()
+ .add_event::<GameDetailsCallEvent>()
.add_systems(
(
req_login::start_call,
@@ 41,10 49,14 @@ impl Plugin for AsyncTasksPlugin {
req_register::handle_call,
req_game_create::start_call,
req_game_create::handle_call,
+ req_game_join::start_call,
+ req_game_join::handle_call,
req_game_forming::start_call,
req_game_forming::handle_call,
req_game_mygames::start_call,
req_game_mygames::handle_call,
+ req_game_details::start_call,
+ req_game_details::handle_call,
).chain()
);
}
A client/src/plugins/async_tasks/req_game_details.rs => client/src/plugins/async_tasks/req_game_details.rs +40 -0
@@ 0,0 1,40 @@
+/*
+ * This file is part of laurelin_client
+ * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * Licensed under GPL-3.0-only.
+ * See LICENSE for licensing information.
+ */
+
+use crate::{
+ async_task_start_call, async_task_handle_call,
+ api::{self, game::DetailsResponse}, NetworkingOptions,
+ plugins::game::GameData,
+};
+
+use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
+use futures_lite::future;
+
+#[derive(Component)]
+pub struct GameDetailsCall(Task<DetailsResponse>);
+
+#[derive(Clone)]
+pub struct GameDetailsCallEvent {
+ pub game_id: String,
+}
+
+async_task_start_call!(GameDetailsCallEvent, GameDetailsCall, |ev, no| {
+ let res = api::game::details(&no, &ev.game_id);
+
+ res
+});
+
+async_task_handle_call!(GameDetailsCall, |response, _global, game_data| {
+ match response {
+ Err(_err) => panic!("game details failed, handle me"),
+ Ok(resp) => {
+ info!("game details for {}", resp.id);
+ game_data.game = Some(resp);
+ }
+ }
+});
A client/src/plugins/async_tasks/req_game_join.rs => client/src/plugins/async_tasks/req_game_join.rs +39 -0
@@ 0,0 1,39 @@
+/*
+ * This file is part of laurelin_client
+ * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * Licensed under GPL-3.0-only.
+ * See LICENSE for licensing information.
+ */
+
+use crate::{
+ async_task_start_call, async_task_handle_call,
+ api::{self, game::JoinResponse}, NetworkingOptions,
+ plugins::menu::MenuData,
+};
+
+use bevy::{prelude::*, tasks::{Task, AsyncComputeTaskPool}};
+use futures_lite::future;
+
+#[derive(Component)]
+pub struct GameJoinCall(Task<JoinResponse>);
+
+#[derive(Clone)]
+pub struct GameJoinCallEvent {
+ pub game_id: String,
+}
+
+async_task_start_call!(GameJoinCallEvent, GameJoinCall, |ev, no| {
+ let res = api::game::join(&no, &ev.game_id);
+
+ res
+});
+
+async_task_handle_call!(GameJoinCall, |response, _menu_data| {
+ match response {
+ Err(_err) => panic!("game join failed, handle me"),
+ Ok(resp) => {
+ info!("joined game {}", resp);
+ }
+ }
+});
A client/src/plugins/game/mod.rs => client/src/plugins/game/mod.rs +47 -0
@@ 0,0 1,47 @@
+/*
+ * This file is part of laurelin_client
+ * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * Licensed under GPL-3.0-only.
+ * See LICENSE for licensing information.
+ */
+
+use bevy::prelude::*;
+
+use crate::{api::game::Game, Global, AppState};
+
+use super::GameDetailsCallEvent;
+
+mod ui;
+
+pub struct GamePlugin;
+
+impl Plugin for GamePlugin {
+ fn build(&self, app: &mut App) {
+ app.insert_resource(GameData::default())
+ .add_system(game_setup.in_schedule(OnEnter(AppState::InGame)))
+ .add_system(ui::ui.run_if(in_state(AppState::InGame)));
+ }
+}
+
+#[derive(Resource)]
+pub struct GameData {
+ pub game: Option<Game>,
+}
+
+impl Default for GameData {
+ fn default() -> Self {
+ Self {
+ game: None,
+ }
+ }
+}
+
+fn game_setup(
+ mut details_ev_w: EventWriter<GameDetailsCallEvent>,
+ global: Res<Global>,
+) {
+ details_ev_w.send(GameDetailsCallEvent {
+ game_id: global.cur_game_id.clone(),
+ });
+}
A client/src/plugins/game/ui/mod.rs => client/src/plugins/game/ui/mod.rs +42 -0
@@ 0,0 1,42 @@
+/*
+ * This file is part of laurelin_client
+ * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * Licensed under GPL-3.0-only.
+ * See LICENSE for licensing information.
+ */
+
+use bevy::prelude::*;
+use bevy_egui::{egui, EguiContexts};
+
+use crate::{plugins::GameDetailsCallEvent, Global};
+
+use super::GameData;
+
+pub fn ui(
+ mut commands: Commands,
+ mut contexts: EguiContexts,
+ global: Res<Global>,
+ mut game_data: ResMut<GameData>,
+ mut details_ev_w: EventWriter<GameDetailsCallEvent>,
+) {
+ egui::Window::new("Game Details")
+ .show(contexts.ctx_mut(), |ui| {
+ let Some(game) = &game_data.game else {
+ // early return if game is None
+ return;
+ };
+ if ui.button("Refresh").clicked() {
+ details_ev_w.send(GameDetailsCallEvent {
+ game_id: game_data.game.as_ref().unwrap().id.clone(),
+ });
+ }
+
+ ui.separator();
+
+ ui.collapsing("Details", |ui| {
+ ui.label(format!("Host: {}", game.host.as_ref().unwrap().username));
+ ui.label(format!("Guest: {}", game.guest.as_ref().unwrap().username));
+ });
+ });
+}
M => +31 -2
@@ 11,17 11,21 @@ use bevy_egui::egui;
use crate::{plugins::{
GameCreateCallEvent, BrowseState,
GameFormingCallEvent, GameMyGamesCallEvent
}, api::game::{GAMESTATE_INPROGRESS, GAMESTATE_FINISHED}};
GameFormingCallEvent, GameMyGamesCallEvent,
GameJoinCallEvent, menu::MenuState
}, api::game::{GAMESTATE_INPROGRESS, GAMESTATE_FINISHED}, Global, AppState};
use super::{MenuData, MenuUIState};
pub fn view(
mut commands: &mut Commands,
ui: &mut egui::Ui,
mut global: &mut Global,
data: &mut MenuData,
create_ev_w: &mut EventWriter<GameCreateCallEvent>,
forming_ev_w: &mut EventWriter<GameFormingCallEvent>,
mygames_ev_w: &mut EventWriter<GameMyGamesCallEvent>,
join_ev_w: &mut EventWriter<GameJoinCallEvent>,
) {
egui::SidePanel::left("browse_side_panel")
.resizable(false)
@@ 86,6 90,16 @@ pub fn view(
"Host: {}",
game.host.as_ref().unwrap().username
));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if game.host_id == global.user.as_ref().unwrap().id {
ui.add_enabled(false, egui::Button::new("Host"));
} else if ui.button("Join").clicked() {
join_ev_w.send(GameJoinCallEvent {
game_id: game.id.clone(),
});
}
});
});
});
}
@@ 105,6 119,12 @@ pub fn view(
"Host: {}",
game.host.as_ref().unwrap().username
));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Enter").clicked() {
enter_game(&mut commands, &mut global, &game.id);
}
});
});
});
}
@@ 132,3 152,12 @@ pub fn view(
);
});
}
fn enter_game(commands: &mut Commands, global: &mut Global, game_id: &str) {
global.cur_game_id = game_id.to_string();
commands.insert_resource(NextState(Some(MenuState::None)));
commands.insert_resource(NextState(Some(AppState::InGame)));
// upon entering the InGame state, fetch details of the game
}
M => +7 -1
@@ 9,7 9,7 @@
use bevy::prelude::*;
use bevy_egui::{EguiContexts, egui};
use crate::plugins::{LoginCallEvent, RegisterCallEvent, GameCreateCallEvent, GameFormingCallEvent, GameMyGamesCallEvent};
use crate::{plugins::{LoginCallEvent, RegisterCallEvent, GameCreateCallEvent, GameFormingCallEvent, GameMyGamesCallEvent, GameJoinCallEvent}, Global};
pub use super::{MenuData, MenuUIState};
@@ 19,13 19,16 @@ mod register;
mod browse;
pub fn ui(
mut commands: Commands,
mut contexts: EguiContexts,
mut global: ResMut<Global>,
mut data: ResMut<MenuData>,
mut login_ev_w: EventWriter<LoginCallEvent>,
mut register_ev_w: EventWriter<RegisterCallEvent>,
mut create_ev_w: EventWriter<GameCreateCallEvent>,
mut forming_ev_w: EventWriter<GameFormingCallEvent>,
mut mygames_ev_w: EventWriter<GameMyGamesCallEvent>,
mut join_ev_w: EventWriter<GameJoinCallEvent>,
) {
egui::Window::new(format!("{:?}", data.ui_state))
.collapsible(false)
@@ 47,11 50,14 @@ pub fn ui(
}
}
MenuUIState::Browse => browse::view(
&mut commands,
ui,
&mut global,
&mut data,
&mut create_ev_w,
&mut forming_ev_w,
&mut mygames_ev_w,
&mut join_ev_w,
),
}
});
M client/src/plugins/mod.rs => client/src/plugins/mod.rs +3 -0
@@ 9,5 9,8 @@
mod menu;
pub use menu::*;
+mod game;
+pub use game::*;
+
mod async_tasks;
pub use async_tasks::*;