M sdbclient/src/api/mod.rs => sdbclient/src/api/mod.rs +6 -4
@@ 22,10 22,12 @@ pub struct APIInfo {
pub ver: String,
}
-pub fn info(api_address: String) -> APIInfo {
+pub fn info(api_address: String) -> Result<APIInfo, String> {
let client = reqwest::blocking::Client::new();
- let resp = client.get(&format!("{}/info", api_address)).send().unwrap();
-
- resp.json().unwrap()
+ let resp = client.get(&format!("{}/info", api_address)).send();
+ match resp {
+ Ok(r) => Ok(r.json().unwrap()),
+ Err(_) => Err("Could not reach API".to_string()),
+ }
}
M sdbclient/src/api/user/mod.rs => sdbclient/src/api/user/mod.rs +26 -0
@@ 81,3 81,29 @@ pub fn register(
resp.json().unwrap()
}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ResultUserInfoP {
+ pub id: String,
+ pub username: String,
+ pub email: String,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum ResponseUserInfoP {
+ Error { error: String },
+ Valid(ResultUserInfoP),
+}
+
+pub fn userinfop(api_address: String, token: String) -> ResponseUserInfoP {
+ let client = reqwest::blocking::Client::new();
+
+ let resp = client
+ .get(&format!("{}/user/register", api_address))
+ .header("Authorization", token)
+ .send()
+ .unwrap();
+
+ resp.json().unwrap()
+}
M sdbclient/src/cfg/mod.rs => sdbclient/src/cfg/mod.rs +30 -0
@@ 30,6 30,16 @@ pub struct CfgSettings {
pub resolution: (f32, f32),
}
+impl Default for CfgSettings {
+ fn default() -> Self {
+ CfgSettings {
+ volume_master: 7,
+ fullscreen: false,
+ resolution: (1280., 720.),
+ }
+ }
+}
+
/// User details and status
#[derive(Serialize, Deserialize, Resource, Debug, Component, PartialEq, Clone)]
pub struct CfgUser {
@@ 45,9 55,29 @@ pub struct CfgUser {
pub email: String,
}
+impl Default for CfgUser {
+ fn default() -> Self {
+ CfgUser {
+ logged_in: false,
+ user_token: "".to_string(),
+ id: "".to_string(),
+ username: "".to_string(),
+ email: "".to_string(),
+ }
+ }
+}
+
/// Settings that the user has no access to, or can only access through developer settings
#[derive(Serialize, Deserialize, Resource, Debug, Component, PartialEq, Clone)]
pub struct CfgDev {
/// API Server
pub api_server: String,
}
+
+impl Default for CfgDev {
+ fn default() -> Self {
+ CfgDev {
+ api_server: "http://localhost:8080/api".to_string(),
+ }
+ }
+}
M sdbclient/src/main.rs => sdbclient/src/main.rs +9 -17
@@ 17,6 17,7 @@ use bevy_inspector_egui::WorldInspectorPlugin;
mod api;
mod cfg;
+mod constants;
mod plugins;
mod util;
@@ 24,6 25,7 @@ mod util;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum GameState {
Splash,
+ Loading,
MainMenu,
Game,
}
@@ 75,26 77,16 @@ fn main() {
.expect("failed to get project directories"),
));
- app.insert_resource(cfg::CfgSettings {
- volume_master: 7,
- fullscreen: false,
- resolution: (1280., 720.),
- })
- .insert_resource(cfg::CfgUser {
- logged_in: false,
- user_token: "".to_string(),
- id: "".to_string(),
- username: "".to_string(),
- email: "".to_string(),
- })
- .insert_resource(cfg::CfgDev {
- api_server: "http://localhost:8080/api".to_string(),
- });
+ app.insert_resource(cfg::CfgSettings::default())
+ .insert_resource(cfg::CfgUser::default())
+ .insert_resource(cfg::CfgDev::default());
app.add_startup_system(setup).add_state(GameState::Splash);
- app.add_plugin(plugins::connection_check::ConnectionCheckPlugin)
- .add_plugin(plugins::splash::SplashPlugin)
+ app.add_plugin(plugins::config::ConfigPlugin)
+ .add_plugin(plugins::connection_check::ConnectionCheckPlugin)
+ .add_plugin(plugins::phases::splash::SplashPlugin)
+ .add_plugin(plugins::phases::loading::LoadingPlugin)
.add_plugin(plugins::menu::MenuPlugin);
app.run();
A sdbclient/src/plugins/config/mod.rs => sdbclient/src/plugins/config/mod.rs +114 -0
@@ 0,0 1,114 @@
+/*
+ * This file is part of sdbclient
+ * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * Licensed under GPL-3.0-only.
+ * See LICENSE for licensing information.
+ */
+
+use bevy::prelude::*;
+use bevy_console::PrintConsoleLine;
+
+use crate::{cfg, util};
+
+/// This plugin will handle config related tasks, like saving and loading
+pub struct ConfigPlugin;
+
+impl Plugin for ConfigPlugin {
+ fn build(&self, app: &mut App) {
+ app.init_resource::<Events<LoadEvent>>()
+ .init_resource::<Events<SaveEvent>>()
+ .add_system(handle_load_events)
+ .add_system(handle_save_events);
+ }
+}
+
+pub enum LoadEventValue {
+ Settings,
+ User,
+ Dev,
+}
+
+pub struct LoadEvent {
+ pub value: LoadEventValue,
+}
+
+fn handle_load_events(
+ mut events: EventReader<LoadEvent>,
+ mut cfg_settings: ResMut<cfg::CfgSettings>,
+ mut cfg_user: ResMut<cfg::CfgUser>,
+ mut cfg_dev: ResMut<cfg::CfgDev>,
+ cfg_dirs: ResMut<cfg::CfgDirs>,
+ mut console: EventWriter<PrintConsoleLine>,
+) {
+ for event in events.iter() {
+ match &event.value {
+ LoadEventValue::Settings => {
+ *cfg_settings = util::sl::load(
+ cfg_dirs.0.data_local_dir().to_str().unwrap(),
+ cfg::FILE_CFG_SETTINGS,
+ &mut console,
+ );
+ }
+ LoadEventValue::User => {
+ *cfg_user = util::sl::load(
+ cfg_dirs.0.data_local_dir().to_str().unwrap(),
+ cfg::FILE_CFG_USER,
+ &mut console,
+ );
+ }
+ LoadEventValue::Dev => {
+ *cfg_dev = util::sl::load(
+ cfg_dirs.0.data_local_dir().to_str().unwrap(),
+ cfg::FILE_CFG_DEV,
+ &mut console,
+ );
+ }
+ }
+ }
+}
+
+pub enum SaveEventValue {
+ Settings(cfg::CfgSettings),
+ User(cfg::CfgUser),
+ Dev(cfg::CfgDev),
+}
+
+pub struct SaveEvent {
+ pub value: SaveEventValue,
+}
+
+fn handle_save_events(
+ mut events: EventReader<SaveEvent>,
+ cfg_dirs: Res<cfg::CfgDirs>,
+ mut console: EventWriter<PrintConsoleLine>,
+) {
+ for event in events.iter() {
+ match &event.value {
+ SaveEventValue::Settings(value) => {
+ util::sl::save(
+ cfg_dirs.0.data_local_dir().to_str().unwrap(),
+ cfg::FILE_CFG_SETTINGS,
+ &value,
+ &mut console,
+ );
+ }
+ SaveEventValue::User(value) => {
+ util::sl::save(
+ cfg_dirs.0.data_local_dir().to_str().unwrap(),
+ cfg::FILE_CFG_USER,
+ &value,
+ &mut console,
+ );
+ }
+ SaveEventValue::Dev(value) => {
+ util::sl::save(
+ cfg_dirs.0.data_local_dir().to_str().unwrap(),
+ cfg::FILE_CFG_DEV,
+ &value,
+ &mut console,
+ );
+ }
+ }
+ }
+}
M sdbclient/src/plugins/connection_check/mod.rs => sdbclient/src/plugins/connection_check/mod.rs +19 -9
@@ 29,7 29,7 @@ impl Plugin for ConnectionCheckPlugin {
}
#[derive(Component)]
-struct ConnectionCheck(Task<api::APIInfo>);
+struct ConnectionCheck(Task<Result<api::APIInfo, String>>);
fn start_connection_check(mut commands: Commands, cfg_dev: Res<CfgDev>) {
let api_address = cfg_dev.api_server.clone();
@@ 48,14 48,24 @@ fn handle_connection_check(
mut console: EventWriter<PrintConsoleLine>,
) {
for (entity, mut task) in &mut connection_check_tasks {
- if let Some(api_info) = future::block_on(future::poll_once(&mut task.0)) {
- console.send(PrintConsoleLine::new(
- "API connection check passed".to_string(),
- ));
- console.send(PrintConsoleLine::new(format!(
- "API version: {}",
- api_info.ver
- )));
+ if let Some(res) = future::block_on(future::poll_once(&mut task.0)) {
+ match res {
+ Ok(api_info) => {
+ console.send(PrintConsoleLine::new(
+ "API connection check passed".to_string(),
+ ));
+ console.send(PrintConsoleLine::new(format!(
+ "API version: {}",
+ api_info.ver
+ )));
+ }
+ Err(err) => {
+ console.send(PrintConsoleLine::new(
+ "API connection check FAILED with following error:".to_string(),
+ ));
+ console.send(PrintConsoleLine::new(err));
+ }
+ }
// Remove the task, since it's done now
commands.entity(entity).remove::<ConnectionCheck>();
M => +55 -27
@@ 15,9 15,12 @@ use bevy_console::PrintConsoleLine;
use futures_lite::future;
use crate::{
api::{self, user::ResponseToken},
cfg::{self, CfgDev, CfgDirs, CfgUser},
util,
api::{
self,
user::{ResponseToken, ResponseUserInfoP},
},
cfg::{CfgDev, CfgUser},
plugins::config::{SaveEvent, SaveEventValue},
};
use super::MenuState;
@@ 52,8 55,13 @@ pub enum LoginState {
LoggingIn,
}
struct LoginCallResponse {
token: ResponseToken,
user_infop: Option<ResponseUserInfoP>,
}
#[derive(Component)]
struct LoginCall(Task<ResponseToken>);
struct LoginCall(Task<LoginCallResponse>);
fn start_login_call(
mut commands: Commands,
@@ 65,9 73,21 @@ fn start_login_call(
let thread_pool = AsyncComputeTaskPool::get();
let task = thread_pool.spawn(async move {
let token_response = api::user::token(api_address, i.email, i.password);
let token_response = api::user::token(api_address.clone(), i.email, i.password);
token_response
let mut user_infop_response = None;
match &token_response {
ResponseToken::Valid(res) => {
user_infop_response = Some(api::user::userinfop(api_address, res.token.clone()));
}
#[allow(unused_variables)]
ResponseToken::Error { error } => {}
}
LoginCallResponse {
token: token_response,
user_infop: user_infop_response,
}
});
commands.spawn(LoginCall(task));
}
@@ 79,36 99,44 @@ fn handle_login_call(
mut login_state: ResMut<State<LoginState>>,
mut menu_state: ResMut<State<MenuState>>,
mut cfg_user: ResMut<CfgUser>,
cfg_dirs: Res<CfgDirs>,
mut save_event_writer: EventWriter<SaveEvent>,
mut console: EventWriter<PrintConsoleLine>,
) {
let (entity, mut task) = login_call_tasks.single_mut();
if let Some(login_response) = future::block_on(future::poll_once(&mut task.0)) {
match login_response {
if let Some(login_call_response) = future::block_on(future::poll_once(&mut task.0)) {
match login_call_response.token {
ResponseToken::Valid(res) => {
console.send(PrintConsoleLine::new(format!(
"Logged in with: {}",
inputs.email
)));
// TODO: We need to fetch the user details at some point.
*cfg_user = CfgUser {
logged_in: true,
user_token: res.token,
id: res.id,
username: "NOT SET".to_string(),
email: "NOT SET".to_string(),
};
util::sl::save(
cfg_dirs.0.config_dir().to_str().unwrap(),
cfg::FILE_CFG_USER,
&cfg_user.into_inner(),
console,
);
login_state.set(LoginState::None).unwrap();
menu_state.set(MenuState::AccountLoggedIn).unwrap();
match login_call_response.user_infop.unwrap() {
ResponseUserInfoP::Valid(infop) => {
*cfg_user = CfgUser {
logged_in: true,
user_token: res.token,
id: res.id,
username: infop.username,
email: infop.email,
};
save_event_writer.send(SaveEvent {
value: SaveEventValue::User(cfg_user.into_inner().clone()),
});
login_state.set(LoginState::None).unwrap();
menu_state.set(MenuState::AccountLoggedIn).unwrap();
}
ResponseUserInfoP::Error { error } => {
console.send(PrintConsoleLine::new(format!(
"Something went wrong with getting the user information after logging in, got error: '{}'", error
)));
login_state.set(LoginState::None).unwrap();
menu_state.set(MenuState::AccountLoggedIn).unwrap();
}
}
}
ResponseToken::Error { error } => {
inputs.error = error;
M => +12 -12
@@ 19,8 19,8 @@ use crate::{
self,
user::{ResponseRegister, ResponseToken},
},
cfg::{self, CfgDev, CfgDirs, CfgUser},
util,
cfg::{CfgDev, CfgUser},
plugins::config::{SaveEvent, SaveEventValue},
};
use super::MenuState;
@@ 99,7 99,7 @@ fn handle_register_call(
mut register_state: ResMut<State<RegisterState>>,
mut menu_state: ResMut<State<MenuState>>,
mut cfg_user: ResMut<CfgUser>,
cfg_dirs: Res<CfgDirs>,
mut save_event_writer: EventWriter<SaveEvent>,
mut console: EventWriter<PrintConsoleLine>,
) {
let (entity, mut task) = register_call_tasks.single_mut();
@@ 121,12 121,12 @@ fn handle_register_call(
email: register_res.email,
};
util::sl::save(
cfg_dirs.0.config_dir().to_str().unwrap(),
cfg::FILE_CFG_USER,
&cfg_user.into_inner(),
console,
);
save_event_writer.send(SaveEvent {
value: SaveEventValue::User(cfg_user.into_inner().clone()),
});
register_state.set(RegisterState::None).unwrap();
menu_state.set(MenuState::AccountLoggedIn).unwrap();
}
ResponseToken::Error { error } => {
// TODO: Handle? Is it possible to even get here without the server shitting itself between the register and token calls?
@@ 134,11 134,11 @@ fn handle_register_call(
console.send(PrintConsoleLine::new(format!(
"Something went wrong with getting the user token after registering, got error: '{}'", error
)));
register_state.set(RegisterState::None).unwrap();
menu_state.set(MenuState::AccountLoggedOut).unwrap();
}
}
register_state.set(RegisterState::None).unwrap();
menu_state.set(MenuState::AccountLoggedIn).unwrap();
}
ResponseRegister::Error { error } => {
inputs.error = error;
M => +2 -2
@@ 11,9 11,9 @@ use bevy::{
ui::{JustifyContent, Size, Style, Val},
};
use crate::cfg::CfgUser;
use crate::{cfg::CfgUser, constants::TEXT_COLOR};
use super::{MenuButtonAction, NORMAL_BUTTON, TEXT_COLOR};
use super::{MenuButtonAction, NORMAL_BUTTON};
/// Tag component for tagging entities on settings menu screen
#[derive(Component)]
M => +3 -1
@@ 11,7 11,9 @@ use bevy::{
ui::{JustifyContent, Size, Style, Val},
};
use super::{MenuButtonAction, NORMAL_BUTTON, TEXT_COLOR};
use crate::constants::TEXT_COLOR;
use super::{MenuButtonAction, NORMAL_BUTTON};
/// Tag component for tagging entities on settings menu screen
#[derive(Component)]
M sdbclient/src/plugins/menu/mainmenuscreen.rs => sdbclient/src/plugins/menu/mainmenuscreen.rs +3 -1
@@ 11,7 11,9 @@ use bevy::{
ui::{JustifyContent, Size, Style, Val},
};
-use super::{MenuButtonAction, NORMAL_BUTTON, TEXT_COLOR};
+use crate::constants::TEXT_COLOR;
+
+use super::{MenuButtonAction, NORMAL_BUTTON};
/// Tag component for tagging entities on menu screen
#[derive(Component)]
M => +0 -2
@@ 36,8 36,6 @@ use accountscreenloggedin::*;
mod accountlogin;
mod accountregister;
const TEXT_COLOR: Color = Color::rgb(0.9, 0.9, 0.9);
pub struct MenuPlugin;
impl Plugin for MenuPlugin {
M => +3 -1
@@ 11,7 11,9 @@ use bevy::{
ui::{JustifyContent, Size, Style, Val},
};
use super::{MenuButtonAction, NORMAL_BUTTON, TEXT_COLOR};
use crate::constants::TEXT_COLOR;
use super::{MenuButtonAction, NORMAL_BUTTON};
/// Tag component for tagging entities on settings volume screen
#[derive(Component)]
M => +3 -1
@@ 11,7 11,9 @@ use bevy::{
ui::{JustifyContent, Size, Style, Val},
};
use super::{MenuButtonAction, NORMAL_BUTTON, TEXT_COLOR};
use crate::constants::TEXT_COLOR;
use super::{MenuButtonAction, NORMAL_BUTTON};
/// Tag component for tagging entities on settings display screen
#[derive(Component)]
M => +3 -1
@@ 11,7 11,9 @@ use bevy::{
ui::{JustifyContent, Size, Style, Val},
};
use super::{MenuButtonAction, NORMAL_BUTTON, TEXT_COLOR};
use crate::constants::TEXT_COLOR;
use super::{MenuButtonAction, NORMAL_BUTTON};
/// Tag component for tagging entities on settings menu screen
#[derive(Component)]
M => +3 -1
@@ 11,7 11,9 @@ use bevy::{
ui::{JustifyContent, Size, Style, Val},
};
use super::{MenuButtonAction, NORMAL_BUTTON, TEXT_COLOR};
use crate::constants::TEXT_COLOR;
use super::{MenuButtonAction, NORMAL_BUTTON};
/// Tag component for tagging entities on settings misc screen
#[derive(Component)]
M sdbclient/src/plugins/mod.rs => sdbclient/src/plugins/mod.rs +2 -1
@@ 6,6 6,7 @@
* See LICENSE for licensing information.
*/
+pub mod config;
pub mod connection_check;
pub mod menu;
-pub mod splash;
+pub mod phases;
A sdbclient/src/plugins/phases/loading/mod.rs => sdbclient/src/plugins/phases/loading/mod.rs +94 -0
@@ 0,0 1,94 @@
+/*
+ * This file is part of sdbclient
+ * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * Licensed under GPL-3.0-only.
+ * See LICENSE for licensing information.
+ */
+
+// FIXME: I hate this. Need to rework whole loading system at some point with a real loading state and screen.
+
+use bevy::{
+ prelude::*,
+ ui::{JustifyContent, Size, Style, Val},
+};
+
+use crate::{
+ constants::TEXT_COLOR,
+ despawn_screen,
+ plugins::config::{LoadEvent, LoadEventValue},
+ GameState,
+};
+
+/// This plugin is used to load all configs during startup
+pub struct LoadingPlugin;
+
+impl Plugin for LoadingPlugin {
+ fn build(&self, app: &mut App) {
+ app
+ // Load the loading screen when we enter the Loading state
+ .add_system_set(SystemSet::on_enter(GameState::Loading).with_system(loading_setup))
+ // Despawn when we exit the Loading state
+ .add_system_set(
+ SystemSet::on_exit(GameState::Loading)
+ .with_system(despawn_screen::<OnLoadingScreen>),
+ );
+ }
+}
+
+/// Tag component for tagging entities on the loading screen
+#[derive(Component)]
+struct OnLoadingScreen;
+
+fn loading_setup(
+ mut commands: Commands,
+ asset_server: Res<AssetServer>,
+ mut game_state: ResMut<State<GameState>>,
+ mut load_event_writer: EventWriter<LoadEvent>,
+) {
+ let font = asset_server.load("fonts/FiraMono-Regular.ttf");
+
+ commands
+ .spawn((
+ NodeBundle {
+ style: Style {
+ align_items: AlignItems::Center,
+ justify_content: JustifyContent::Center,
+ size: Size::new(Val::Percent(100.), Val::Percent(100.)),
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ OnLoadingScreen,
+ ))
+ .with_children(|parent| {
+ parent.spawn(TextBundle {
+ text: Text::from_section(
+ "...",
+ TextStyle {
+ font: font.clone(),
+ font_size: 80.0,
+ color: TEXT_COLOR,
+ },
+ ),
+ style: Style {
+ size: Size::new(Val::Px(1000.), Val::Auto),
+ ..Default::default()
+ },
+ ..Default::default()
+ });
+ });
+
+ // Queue up load events for all configs
+ load_event_writer.send(LoadEvent {
+ value: LoadEventValue::Settings,
+ });
+ load_event_writer.send(LoadEvent {
+ value: LoadEventValue::User,
+ });
+ load_event_writer.send(LoadEvent {
+ value: LoadEventValue::Dev,
+ });
+
+ game_state.overwrite_set(GameState::MainMenu).unwrap();
+}
A sdbclient/src/plugins/phases/mod.rs => sdbclient/src/plugins/phases/mod.rs +10 -0
@@ 0,0 1,10 @@
+/*
+ * This file is part of sdbclient
+ * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
+ *
+ * Licensed under GPL-3.0-only.
+ * See LICENSE for licensing information.
+ */
+
+pub mod loading;
+pub mod splash;
R sdbclient/src/plugins/splash/mod.rs => sdbclient/src/plugins/phases/splash/mod.rs +1 -1
@@ 75,6 75,6 @@ fn splash_timer(
mut timer: ResMut<SplashTimer>,
) {
if timer.tick(time.delta()).finished() {
- game_state.set(GameState::MainMenu).unwrap()
+ game_state.set(GameState::Loading).unwrap()
}
}