DEVELOPMENT ENVIRONMENT

~liljamo/deck-builder

2ccd2b2f548d428701111ce22f2209b9ae0af86f — Jonni Liljamo 1 year, 11 months ago 1e00855
Finish register/login, load configs on startup
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 sdbclient/src/plugins/menu/accountlogin/mod.rs => sdbclient/src/plugins/menu/accountlogin/mod.rs +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 sdbclient/src/plugins/menu/accountregister/mod.rs => sdbclient/src/plugins/menu/accountregister/mod.rs +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 sdbclient/src/plugins/menu/accountscreenloggedin.rs => sdbclient/src/plugins/menu/accountscreenloggedin.rs +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 sdbclient/src/plugins/menu/accountscreenloggedout.rs => sdbclient/src/plugins/menu/accountscreenloggedout.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 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 sdbclient/src/plugins/menu/mod.rs => sdbclient/src/plugins/menu/mod.rs +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 sdbclient/src/plugins/menu/settingsaudioscreen.rs => sdbclient/src/plugins/menu/settingsaudioscreen.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 settings volume screen
#[derive(Component)]

M sdbclient/src/plugins/menu/settingsdisplayscreen.rs => sdbclient/src/plugins/menu/settingsdisplayscreen.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 settings display screen
#[derive(Component)]

M sdbclient/src/plugins/menu/settingsmenuscreen.rs => sdbclient/src/plugins/menu/settingsmenuscreen.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 settings menu screen
#[derive(Component)]

M sdbclient/src/plugins/menu/settingsmiscscreen.rs => sdbclient/src/plugins/menu/settingsmiscscreen.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 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()
    }
}