#![allow(clippy::type_complexity)] // CLICK // click button, add to score // has login // has online leaderboard mod textbox_plugin; mod util; use std::collections::HashMap; use bevy::{app::AppExit, prelude::*}; use bevy_firebase_auth::{ delete_account, log_in, log_out, AuthState, AuthUrlsEvent, LoginProvider, ProjectId, SelectedProvider, TokenData, }; use bevy_firebase_firestore::{ async_delete_document, async_read_document, async_update_document, value::ValueType, BevyFirestoreClient, Document, DocumentMask, FirestoreState, QueryDirection, QueryResponseEvent, RunQueryEvent, RunQueryResponse, Status, UpdateDocumentEvent, UpdateDocumentRequest, Value, }; use bevy_tokio_tasks::TokioTasksRuntime; use textbox_plugin::TextBoxPlugin; use util::despawn_with; use crate::textbox_plugin::TextBoxBundle; // colours const NORMAL_BUTTON: Color = Color::rgb(0.15, 0.15, 0.15); const HOVERED_BUTTON: Color = Color::rgb(0.25, 0.25, 0.25); const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.75, 0.35); const TEXT_COLOR: Color = Color::rgb(0.9, 0.9, 0.9); #[derive(Default, States, Debug, Clone, Eq, PartialEq, Hash)] enum AuthControllerState { #[default] Start, LogIn, LogOut, Delete, } #[derive(Default, States, Debug, Clone, Eq, PartialEq, Hash)] enum AppScreenState { #[default] Empty, LogInScreen, MainMenu, InGame, Leaderboard, } #[derive(Component)] struct LogInScreenData; #[derive(Component)] struct MainMenuData; #[derive(Component)] struct InGameData; #[derive(Component)] struct LeaderboardData; fn main() { App::new() // PLUGINS .add_plugins(DefaultPlugins) .add_plugins(bevy_firebase_auth::AuthPlugin::default()) .add_plugins(bevy_firebase_firestore::FirestorePlugin::default()) .add_plugins(bevy_tokio_tasks::TokioTasksPlugin::default()) .add_plugins(TextBoxPlugin) // STATES .add_state::() .add_state::() // INIT .add_systems(Startup, setup) // UTILS .add_systems(Update, button_color_system) .add_systems(Update, exit_button_system) // LOGIN .add_systems(OnEnter(AuthControllerState::LogIn), log_in) .add_systems(OnEnter(AuthControllerState::LogOut), log_out) .add_systems(OnEnter(AuthControllerState::Delete), delete_account) .add_systems(OnEnter(AuthState::LoggedIn), logged_in) .add_systems(OnEnter(FirestoreState::Ready), firestore_ready) .add_systems(OnEnter(AuthState::LoggedOut), logged_out) // SCREENS // login .add_systems(OnEnter(AppScreenState::LogInScreen), build_login_screen) .add_systems( OnExit(AppScreenState::LogInScreen), despawn_with::, ) .add_systems( Update, login_button_system.run_if(in_state(AppScreenState::LogInScreen)), ) .add_systems( Update, auth_url_listener.run_if(in_state(AuthControllerState::LogIn)), ) // menu .add_systems(OnEnter(AppScreenState::MainMenu), build_main_menu) .add_systems( Update, play_button_system.run_if(in_state(AppScreenState::MainMenu)), ) .add_systems( Update, nickname_submit_button_system.run_if(in_state(AppScreenState::MainMenu)), ) .add_systems( Update, delete_score_button_system.run_if(in_state(AppScreenState::MainMenu)), ) .add_systems( Update, delete_account_button_system.run_if(in_state(AppScreenState::MainMenu)), ) .add_systems( Update, logout_button_system.run_if(in_state(AppScreenState::MainMenu)), ) .add_systems( Update, leaderboard_button_system.run_if(in_state(AppScreenState::MainMenu)), ) .add_systems( Update, update_welcome_text.run_if(in_state(AppScreenState::MainMenu)), ) .add_systems( OnExit(AppScreenState::MainMenu), despawn_with::, ) // in game .add_systems(OnEnter(AppScreenState::InGame), build_in_game) .add_systems( Update, update_score.run_if(in_state(AppScreenState::InGame)), ) .add_systems( Update, score_button_system.run_if(in_state(AppScreenState::InGame)), ) .add_systems( Update, return_to_menu_button_system.run_if(in_state(AppScreenState::InGame)), ) .add_systems( Update, submit_score_button_system.run_if(in_state(AppScreenState::InGame)), ) .add_systems(OnExit(AppScreenState::InGame), despawn_with::) // leaderboard .add_event::() .add_systems(OnEnter(AppScreenState::Leaderboard), build_leaderboard) .add_systems(Update, query_response_event_handler) .add_systems( Update, return_to_menu_button_system.run_if(in_state(AppScreenState::Leaderboard)), ) .add_systems( Update, update_leaderboard.run_if(in_state(AppScreenState::Leaderboard)), ) .add_systems( OnExit(AppScreenState::Leaderboard), despawn_with::, ) .run(); } // UI #[derive(Resource, Clone)] struct UiSettings { typefaces: TypeFaces, button: ButtonBundle, } #[derive(Clone)] struct TypeFaces { h1: TextStyle, h2: TextStyle, p: TextStyle, } #[derive(Component)] struct UiBase; // LOGIN #[derive(Component)] struct LoginButton((String, String)); #[derive(Component)] struct ExitButton; // MENU #[derive(Component)] struct WelcomeText; #[derive(Component)] struct LogoutButton; #[derive(Component)] struct PlayButton; #[derive(Component)] struct NicknameInput; #[derive(Component)] struct NicknameSubmitButton; #[derive(Component)] struct DeleteScoreButton; #[derive(Component)] struct DeleteAccountButton; #[derive(Component)] struct LeaderboardButton; // IN GAME #[derive(Component)] struct ScoreButton; #[derive(Component)] struct SubmitScoreButton; #[derive(Component)] struct ReturnToMenuButton; #[derive(Component)] struct ScoreText; // LEADERBOARD #[derive(Component)] struct Leaderboard; // GAME LOGIC #[derive(Resource)] struct Score(i64); #[derive(Resource)] struct Nickname(String); fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2dBundle::default()); // SETUP UI let font = asset_server.load("fonts/HackNerdFont-Regular.ttf"); let typefaces = TypeFaces { h1: TextStyle { font: font.clone(), font_size: 60.0, color: TEXT_COLOR, }, h2: TextStyle { font: font.clone(), font_size: 40.0, color: TEXT_COLOR, }, p: TextStyle { font, font_size: 20.0, color: TEXT_COLOR, }, }; let button = ButtonBundle { style: Style { width: Val::Px(300.), height: Val::Px(65.), justify_content: JustifyContent::Center, align_items: AlignItems::Center, margin: UiRect { top: Val::Px(10.), ..Default::default() }, ..default() }, background_color: NORMAL_BUTTON.into(), ..Default::default() }; commands .spawn(NodeBundle { style: Style { height: Val::Percent(100.), width: Val::Percent(100.), align_items: AlignItems::Center, flex_direction: FlexDirection::Column, ..default() }, ..default() }) .insert(UiBase) .with_children(|parent| { parent.spawn(( TextBundle::from_section("CLiCK", typefaces.h1.clone()).with_style(Style { ..Default::default() }), )); }); commands.insert_resource(UiSettings { typefaces, button }); // This is populated on firestore load commands.insert_resource(Score(0)); } // UTILS fn button_color_system( mut q_interaction: Query< (&Interaction, &mut BackgroundColor), (Changed, With