From 9647fee80aed1ca3bde7403ab3ffb95737341553 Mon Sep 17 00:00:00 2001 From: cooltexture <124379016+cooltexture1@users.noreply.github.com> Date: Tue, 5 Mar 2024 17:17:54 +0100 Subject: [PATCH] Simplifying the settings and keybinds systems (#309) --- src/auth.rs | 30 --- src/{console/mod.rs => console.rs} | 287 +++++------------------------ src/main.rs | 56 ++---- src/render/hud.rs | 12 +- src/screen/background.rs | 23 ++- src/screen/launcher.rs | 25 ++- src/screen/login.rs | 20 +- src/screen/server_list.rs | 5 +- src/screen/settings_menu.rs | 90 ++++----- src/settings.rs | 267 --------------------------- src/settings/config_var.rs | 228 +++++++++++++++++++++++ src/settings/default_config.rs | 161 ++++++++++++++++ src/settings/default_keybinds.rs | 96 ++++++++++ src/settings/keybinds.rs | 189 +++++++++++++++++++ src/settings/mod.rs | 10 + 15 files changed, 840 insertions(+), 659 deletions(-) delete mode 100644 src/auth.rs rename src/{console/mod.rs => console.rs} (51%) delete mode 100644 src/settings.rs create mode 100644 src/settings/config_var.rs create mode 100644 src/settings/default_config.rs create mode 100644 src/settings/default_keybinds.rs create mode 100644 src/settings/keybinds.rs create mode 100644 src/settings/mod.rs diff --git a/src/auth.rs b/src/auth.rs deleted file mode 100644 index 2403435c..00000000 --- a/src/auth.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2016 Matthew Collins -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::console; -use std::marker::PhantomData; - -pub const AUTH_CLIENT_TOKEN: console::CVar = console::CVar { - ty: PhantomData, - name: "auth_client_token", - description: r#"auth_client_token is a token that stays static between sessions. -Used to identify this client vs others."#, - mutable: false, - serializable: true, - default: &String::new, -}; - -pub fn register_vars(vars: &mut console::Vars) { - vars.register(AUTH_CLIENT_TOKEN); -} diff --git a/src/console/mod.rs b/src/console.rs similarity index 51% rename from src/console/mod.rs rename to src/console.rs index eb9813de..40c1ef68 100644 --- a/src/console/mod.rs +++ b/src/console.rs @@ -1,247 +1,15 @@ -// Copyright 2016 Matthew Collins -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::paths; - -use std::any::Any; -use std::cell::{Ref, RefCell}; -use std::collections::HashMap; -use std::fs; -use std::io::{BufRead, BufReader, BufWriter, Write}; -use std::marker::PhantomData; -use std::str::FromStr; -use std::sync::Arc; - -use crate::format::{Color, Component, ComponentType}; -use crate::render; -use crate::ui; +use log::Level; use parking_lot::Mutex; -const FILTERED_CRATES: &[&str] = &[ - //"reqwest", // TODO: needed? - "mime", -]; - -pub struct CVar { - pub name: &'static str, - pub ty: PhantomData, - pub description: &'static str, - pub mutable: bool, - pub serializable: bool, - pub default: &'static dyn Fn() -> T, -} - -pub const LOG_LEVEL_TERM: CVar = CVar { - ty: PhantomData, - name: "log_level_term", - description: "log level of messages to log to the terminal", - mutable: false, - serializable: true, - default: &|| "info".to_owned(), -}; - -pub const LOG_LEVEL_FILE: CVar = CVar { - ty: PhantomData, - name: "log_level_file", - description: "log level of messages to log to the log file", - mutable: false, - serializable: true, - default: &|| "trace".to_owned(), -}; - -pub fn register_vars(vars: &mut Vars) { - vars.register(LOG_LEVEL_TERM); - vars.register(LOG_LEVEL_FILE); -} - -fn log_level_from_str(s: &str, default: log::Level) -> log::Level { - // TODO: no opposite of FromStr in log crate? - use log::Level::*; - match s { - "trace" => Trace, - "debug" => Debug, - "info" => Info, - "warn" => Warn, - "error" => Error, - _ => default, - } -} - -impl Var for CVar { - fn serialize(&self, val: &Box) -> String { - val.downcast_ref::().unwrap().to_string() - } - - fn deserialize(&self, input: &str) -> Box { - Box::new(input.parse::().unwrap()) - } - - fn description(&self) -> &'static str { - self.description - } - - fn can_serialize(&self) -> bool { - self.serializable - } -} - -impl Var for CVar { - fn serialize(&self, val: &Box) -> String { - val.downcast_ref::().unwrap().to_string() - } - - fn deserialize(&self, input: &str) -> Box { - Box::new(input.parse::().unwrap()) - } - - fn description(&self) -> &'static str { - self.description - } - - fn can_serialize(&self) -> bool { - self.serializable - } -} - -impl Var for CVar { - fn serialize(&self, val: &Box) -> String { - val.downcast_ref::().unwrap().to_string() - } - - fn deserialize(&self, input: &str) -> Box { - Box::new(input.parse::().unwrap()) - } - - fn description(&self) -> &'static str { - self.description - } - - fn can_serialize(&self) -> bool { - self.serializable - } -} - -impl Var for CVar { - fn serialize(&self, val: &Box) -> String { - format!("\"{}\"", val.downcast_ref::().unwrap()) - } - - fn deserialize(&self, input: &str) -> Box { - Box::new(input[1..input.len() - 1].to_owned()) - } - - fn description(&self) -> &'static str { - self.description - } - fn can_serialize(&self) -> bool { - self.serializable - } -} - -pub trait Var { - fn serialize(&self, val: &Box) -> String; - fn deserialize(&self, input: &str) -> Box; - fn description(&self) -> &'static str; - fn can_serialize(&self) -> bool; -} - -#[derive(Default)] -pub struct Vars { - names: HashMap, - vars: HashMap<&'static str, Box>, - var_values: HashMap<&'static str, RefCell>>, -} - -impl Vars { - pub fn new() -> Vars { - Default::default() - } - - pub fn register(&mut self, var: CVar) - where - CVar: Var, - { - if self.vars.contains_key(var.name) { - panic!("Key registered twice {}", var.name); - } - self.names.insert(var.name.to_owned(), var.name); - self.var_values - .insert(var.name, RefCell::new(Box::new((var.default)()))); - self.vars.insert(var.name, Box::new(var)); - } - - pub fn get(&self, var: CVar) -> Ref - where - CVar: Var, - { - // Should never fail - let var = self.var_values.get(var.name).unwrap().borrow(); - Ref::map(var, |v| v.downcast_ref::().unwrap()) - } - - pub fn set(&self, var: CVar, val: T) - where - CVar: Var, - { - *self.var_values.get(var.name).unwrap().borrow_mut() = Box::new(val); - self.save_config(); - } - - pub fn load_config(&mut self) { - if let Ok(file) = fs::File::open(paths::get_config_dir().join("conf.cfg")) { - let reader = BufReader::new(file); - for line in reader.lines() { - let line = line.unwrap(); - if line.starts_with('#') || line.is_empty() { - continue; - } - let parts = line - .splitn(2, ' ') - .map(|v| v.to_owned()) - .collect::>(); - let (name, arg) = (&parts[0], &parts[1]); - if let Some(var_name) = self.names.get(name) { - let var = self.vars.get(var_name).unwrap(); - let val = var.deserialize(arg); - if var.can_serialize() { - self.var_values.insert(var_name, RefCell::new(val)); - } - } - } - } - } +use crate::format::{Color, Component, ComponentType}; +use crate::settings::SettingStore; +use crate::{paths, ui}; +use crate::{render, StringSetting}; - pub fn save_config(&self) { - let mut file = - BufWriter::new(fs::File::create(paths::get_config_dir().join("conf.cfg")).unwrap()); - for (name, var) in &self.vars { - if !var.can_serialize() { - continue; - } - for line in var.description().lines() { - writeln!(file, "# {}", line).unwrap(); - } - write!( - file, - "{} {}\n\n", - name, - var.serialize(&self.var_values.get(name).unwrap().borrow()) - ) - .unwrap(); - } - } -} +use std::fs; +use std::io::Write; +use std::str::FromStr; +use std::sync::Arc; pub struct Console { history: Vec, @@ -287,9 +55,9 @@ impl Console { log::Level::from_str(&variable_string).ok() } - pub fn configure(&mut self, vars: &Vars) { - self.log_level_term = log_level_from_str(&vars.get(LOG_LEVEL_TERM), log::Level::Info); - self.log_level_file = log_level_from_str(&vars.get(LOG_LEVEL_FILE), log::Level::Debug); + pub fn configure(&mut self, settings: &SettingStore) { + self.log_level_term = term_log_level(settings).unwrap_or(Level::Info); + self.log_level_file = file_log_level(settings).unwrap_or(Level::Debug); for name in ["RUST_LOG", "LOG_LEVEL"].iter() { if let Some(level) = Console::log_level_from_env(name) { @@ -305,7 +73,7 @@ impl Console { } } - pub fn is_active(&self) -> bool { + pub fn _is_active(&self) -> bool { self.active } @@ -313,7 +81,7 @@ impl Console { self.active = !self.active; } - pub fn activate(&mut self) { + pub fn _activate(&mut self) { self.active = true; } @@ -447,6 +215,33 @@ impl Console { } } +fn _log_level_from_str(s: &str) -> Option { + // TODO: no opposite of FromStr in log crate? + use log::Level::*; + match s { + "trace" => Some(Trace), + "debug" => Some(Debug), + "info" => Some(Info), + "warn" => Some(Warn), + "error" => Some(Error), + _ => None, + } +} + +fn term_log_level(store: &SettingStore) -> Option { + let val = store.get_string(StringSetting::LogLevelTerm); + Level::from_str(&val).ok() +} +fn file_log_level(store: &SettingStore) -> Option { + let val = store.get_string(StringSetting::LogLevelFile); + Level::from_str(&val).ok() +} + +const FILTERED_CRATES: &[&str] = &[ + //"reqwest", // TODO: needed? + "mime", +]; + pub struct ConsoleProxy { console: Arc>, } diff --git a/src/main.rs b/src/main.rs index 2e0cb288..21a56218 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ #![allow(clippy::many_single_char_names)] // short variable names provide concise clarity #![allow(clippy::float_cmp)] // float comparison used to check if changed +mod console; use copypasta::nop_clipboard; use copypasta::ClipboardContext; use copypasta::ClipboardProvider; @@ -62,9 +63,7 @@ use leafish_protocol::nbt; use leafish_protocol::protocol; pub mod gl; use leafish_protocol::types; -pub mod auth; pub mod chunk_builder; -pub mod console; pub mod entity; mod inventory; pub mod model; @@ -80,12 +79,12 @@ pub mod world; use crate::entity::Rotation; use crate::render::hud::HudContext; +use crate::settings::*; use leafish_protocol::protocol::login::Account; use leafish_protocol::protocol::Error; use parking_lot::Mutex; use parking_lot::RwLock; use std::cell::RefCell; -use std::marker::PhantomData; use std::rc::Rc; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -93,23 +92,14 @@ use std::thread; // TODO: Improve calculate light performance and fix capturesnapshot -const CL_BRAND: console::CVar = console::CVar { - ty: PhantomData, - name: "cl_brand", - description: "cl_brand has the value of the clients current 'brand'. e.g. \"Leafish\" or \ - \"Vanilla\"", - mutable: false, - serializable: false, - default: &|| "Leafish".to_owned(), -}; - pub struct Game { renderer: Arc, screen_sys: Arc, resource_manager: Arc>, clipboard_provider: Mutex>, console: Arc>, - vars: Rc, + settings: Rc, + keybinds: Rc, should_close: bool, server: Option>, @@ -245,18 +235,12 @@ fn main() { info!("Starting Leafish..."); - let (vars, mut vsync) = { - let mut vars = console::Vars::new(); - vars.register(CL_BRAND); - console::register_vars(&mut vars); - auth::register_vars(&mut vars); - settings::register_vars(&mut vars); - vars.load_config(); - vars.save_config(); - con.lock().configure(&vars); - let vsync = *vars.get(settings::R_VSYNC); - (Rc::new(vars), vsync) - }; + let settings = Rc::new(SettingStore::new()); + let keybinds = Rc::new(KeybindStore::new()); + info!("settings all loaded!"); + + con.lock().configure(&settings); + let vsync = settings.get_bool(BoolSetting::Vsync); let (res, mut resui) = resources::Manager::new(); let resource_manager = Arc::new(RwLock::new(res)); @@ -358,7 +342,7 @@ fn main() { let screen_sys = Arc::new(screen::ScreenSystem::new()); let active_account = Arc::new(Mutex::new(None)); screen_sys.add_screen(Box::new(screen::background::Background::new( - vars.clone(), + settings.clone(), screen_sys.clone(), ))); let mut accounts = screen::launcher::load_accounts().unwrap_or_default(); @@ -424,7 +408,6 @@ fn main() { screen_sys, resource_manager: resource_manager.clone(), console: con, - vars, should_close: false, chunk_builder: chunk_builder::ChunkBuilder::new(resource_manager, textures), connect_error: None, @@ -439,6 +422,8 @@ fn main() { default_protocol_version, clipboard_provider: Mutex::new(clipboard), current_account: active_account, + settings, + keybinds, }; if opt.network_debug { protocol::enable_network_debug(); @@ -487,7 +472,7 @@ fn main() { &mut last_frame, &mut resui, &mut last_resource_version, - &mut vsync, + vsync, ); if DEBUG { let dist = Instant::now().checked_duration_since(start); @@ -513,7 +498,7 @@ fn tick_all( last_frame: &mut Instant, resui: &mut resources::ManagerUI, last_resource_version: &mut usize, - vsync: &mut bool, + vsync: bool, ) { if game.server.is_some() { if !game.server.as_ref().unwrap().is_connected() { @@ -577,14 +562,14 @@ fn tick_all( }; *last_resource_version = version; - let vsync_changed = *game.vars.get(settings::R_VSYNC); - if *vsync != vsync_changed { + let vsync_changed = game.settings.get_bool(BoolSetting::Vsync); + if vsync != vsync_changed { error!("Changing vsync currently requires restarting"); game.should_close = true; // TODO: after changing to wgpu and the new renderer, allow changing vsync on a Window //vsync = vsync_changed; } - let fps_cap = *game.vars.get(settings::R_MAX_FPS); + let fps_cap = game.settings.get_int(IntSetting::MaxFps); if let Some(server) = game.server.as_ref() { server.clone().tick(delta, game); // TODO: Improve perf in load screen! @@ -647,7 +632,7 @@ fn tick_all( .unwrap(); } - if fps_cap > 0 && !*vsync { + if fps_cap > 0 && !vsync { let frame_time = now.elapsed(); let sleep_interval = Duration::from_millis(1000 / fps_cap as u64); if frame_time < sleep_interval { @@ -673,20 +658,19 @@ fn handle_window_event( }, .. } => { + let mouse_sens: f64 = game.settings.get_float(FloatSetting::MouseSense); let (rx, ry) = if xrel > 1000.0 || yrel > 1000.0 { // Heuristic for if we were passed an absolute value instead of relative // Workaround https://github.com/tomaka/glutin/issues/1084 MouseMotion event returns absolute instead of relative values, when running Linux in a VM // Note SDL2 had a hint to handle this scenario: // sdl2::hint::set_with_priority("SDL_MOUSE_RELATIVE_MODE_WARP", "1", &sdl2::hint::Hint::Override); let s = 8000.0 + 0.01; - let mouse_sens: f64 = *game.vars.get(settings::R_MOUSE_SENS); ( ((xrel - game.last_mouse_xrel) / s) * mouse_sens, ((yrel - game.last_mouse_yrel) / s) * mouse_sens, ) } else { let s = 2000.0 + 0.01; - let mouse_sens: f64 = *game.vars.get(settings::R_MOUSE_SENS); ((xrel / s) * mouse_sens, (yrel / s) * mouse_sens) }; diff --git a/src/render/hud.rs b/src/render/hud.rs index 20685dfd..a37a6ac9 100644 --- a/src/render/hud.rs +++ b/src/render/hud.rs @@ -31,7 +31,7 @@ use crate::screen::{Screen, ScreenSystem, ScreenType}; use crate::server::Server; use crate::ui; use crate::ui::{Container, FormattedRef, HAttach, ImageRef, TextRef, VAttach}; -use crate::{format, screen, settings, Game}; +use crate::{format, screen, Game}; use leafish_protocol::protocol::packet::play::serverbound::HeldItemChange; use leafish_protocol::types::GameMode; use std::sync::atomic::AtomicBool; @@ -490,15 +490,19 @@ impl Screen for Hud { fn on_key_press(&mut self, key: (Key, PhysicalKey), down: bool, game: &mut Game) { if key.0 == Key::Named(NamedKey::Escape) && !down && game.focused { game.screen_sys - .add_screen(Box::new(screen::SettingsMenu::new(game.vars.clone(), true))); + .add_screen(Box::new(screen::SettingsMenu::new( + game.settings.clone(), + true, + ))); return; } match key.1 { PhysicalKey::Code(code) => { - if let Some(action_key) = settings::Actionkey::get_by_keycode(code, &game.vars) { + if let Some(action_key) = game.keybinds.get(code) { + // if let Some(action_key) = settings::Actionkey::get_by_keycode(code, &game.vars) { game.server.as_ref().unwrap().key_press( down, - action_key, + action_key.action, &mut game.focused.clone(), ); } diff --git a/src/screen/background.rs b/src/screen/background.rs index c64a0ecf..73b9a8ef 100644 --- a/src/screen/background.rs +++ b/src/screen/background.rs @@ -1,14 +1,14 @@ use crate::render::Renderer; use crate::screen::{Screen, ScreenSystem}; -use crate::settings::BACKGROUND_IMAGE; +use crate::settings::*; +use crate::ui; use crate::ui::Container; -use crate::{console, ui}; use std::rc::Rc; use std::sync::Arc; pub struct Background { background: Option, - vars: Rc, + settings: Rc, screen_sys: Arc, active: bool, delay: f64, @@ -17,15 +17,15 @@ pub struct Background { impl Clone for Background { fn clone(&self) -> Self { - Self::new(self.vars.clone(), self.screen_sys.clone()) + Self::new(self.settings.clone(), self.screen_sys.clone()) } } impl Background { - pub fn new(vars: Rc, screen_sys: Arc) -> Self { + pub fn new(settings: Rc, screen_sys: Arc) -> Self { Self { background: None, - vars, + settings, screen_sys, active: false, delay: 0.0, @@ -41,14 +41,17 @@ impl Screen for Background { renderer: Arc, ui_container: &mut Container, ) { - let path = self.vars.get(BACKGROUND_IMAGE); - self.last_path = (*path).clone(); + let path = self.settings.get_string(StringSetting::BackgroundImage); + self.last_path = path.clone(); let background = if Renderer::get_texture_optional(renderer.get_textures_ref(), &path).is_some() { Some( ui::ImageBuilder::new() .draw_index(i16::MIN as isize) - .texture(path.as_str()) + .texture(&*format!( + "#{}", + self.settings.get_string(StringSetting::BackgroundImage) + )) .size( renderer.screen_data.read().safe_width as f64, renderer.screen_data.read().safe_height as f64, @@ -109,7 +112,7 @@ impl Screen for Background { self.init(screen_sys, renderer, ui_container); return; } - let curr_path = (*self.vars.get(BACKGROUND_IMAGE)).clone(); + let curr_path = self.settings.get_string(StringSetting::BackgroundImage); if !self.last_path.eq(&curr_path) { self.last_path = curr_path; self.deinit(screen_sys, renderer.clone(), ui_container); diff --git a/src/screen/launcher.rs b/src/screen/launcher.rs index a411a686..4b610abf 100644 --- a/src/screen/launcher.rs +++ b/src/screen/launcher.rs @@ -15,15 +15,14 @@ use std::fs; use std::sync::Arc; -use crate::auth; use crate::paths; use crate::protocol; use crate::ui; use crate::render::Renderer; use crate::screen::{Screen, ScreenSystem, ServerList}; -use crate::settings::BACKGROUND_IMAGE; use crate::ui::Container; +use crate::StringSetting; use leafish_protocol::protocol::login::{Account, AccountType}; use parking_lot::Mutex; use rand::Rng; @@ -105,7 +104,10 @@ impl super::Screen for Launcher { options.add_click_func(|_, game| { game.screen_sys .clone() - .add_screen(Box::new(super::SettingsMenu::new(game.vars.clone(), false))); + .add_screen(Box::new(super::SettingsMenu::new( + game.settings.clone(), + false, + ))); true }); } @@ -152,7 +154,7 @@ impl super::Screen for Launcher { screen_sys.pop_screen(); save_accounts(&accounts.lock()); }), - game.vars.clone(), + game.settings.clone(), ))); true }) @@ -181,7 +183,8 @@ impl super::Screen for Launcher { .pick_file(); if let Some(files) = files { let file_name = files.as_path().to_str().unwrap(); - game.vars.set(BACKGROUND_IMAGE, format!("#{}", file_name)); + game.settings + .set_string(StringSetting::BackgroundImage, file_name); } true }) @@ -216,7 +219,7 @@ impl super::Screen for Launcher { back.add_click_func(move |_, game| { let accounts = accounts.clone(); let account_type = account_type.clone(); - let mut client_token = game.vars.get(auth::AUTH_CLIENT_TOKEN).clone(); + let mut client_token = game.settings.get_string(StringSetting::AuthClientToken); if client_token.is_empty() { client_token = std::iter::repeat(()) .map(|()| { @@ -224,9 +227,10 @@ impl super::Screen for Launcher { }) .take(20) .collect(); - game.vars.set(auth::AUTH_CLIENT_TOKEN, client_token); + game.settings + .set_string(StringSetting::AuthClientToken, &client_token); } - let client_token = game.vars.get(auth::AUTH_CLIENT_TOKEN).clone(); + let client_token = game.settings.get_string(StringSetting::AuthClientToken); let result = protocol::login::ACCOUNT_IMPLS .get(&account.account_type) .unwrap() @@ -250,7 +254,7 @@ impl super::Screen for Launcher { )), Rc::new(move |game, name, password| { let client_token = - game.vars.get(auth::AUTH_CLIENT_TOKEN).clone(); + game.settings.get_string(StringSetting::AuthClientToken); let account = crate::screen::login::try_login( false, name, @@ -373,7 +377,8 @@ impl super::Screen for Launcher { super::edit_account::EditAccountEntry::new( Some((aname.clone(), apw.clone())), Rc::new(move |game, name, password| { - let client_token = game.vars.get(auth::AUTH_CLIENT_TOKEN).clone(); + let client_token = + game.settings.get_string(StringSetting::AuthClientToken); let account = crate::screen::login::try_login( false, name, diff --git a/src/screen/login.rs b/src/screen/login.rs index 2419be04..c3553442 100644 --- a/src/screen/login.rs +++ b/src/screen/login.rs @@ -19,19 +19,17 @@ use std::thread; use rand::Rng; -use crate::auth; -use crate::console; -use crate::console::Vars; -use crate::protocol; use crate::render; use crate::screen::{Screen, ScreenSystem}; +use crate::settings::SettingStore; use crate::ui; +use crate::{protocol, StringSetting}; use leafish_protocol::protocol::login::{Account, AccountType}; use leafish_protocol::protocol::Error; use std::ops::Deref; pub struct Login { - vars: Rc, + settings: Rc, elements: Option, callback: Arc)>, } @@ -39,7 +37,7 @@ pub struct Login { impl Clone for Login { fn clone(&self) -> Self { Login { - vars: self.vars.clone(), + settings: self.settings.clone(), elements: None, callback: self.callback.clone(), } @@ -63,9 +61,9 @@ struct UIElements { } impl Login { - pub fn new(callback: Arc)>, vars: Rc) -> Self { + pub fn new(callback: Arc)>, vars: Rc) -> Self { Login { - vars, + settings: vars, elements: None, callback, } @@ -214,16 +212,16 @@ impl super::Screen for Login { elements.login_res = Some(rx); elements.login_btn.borrow_mut().disabled = true; elements.login_btn_text.borrow_mut().text = "Logging in...".into(); - let mut client_token = self.vars.get(auth::AUTH_CLIENT_TOKEN).clone(); + let mut client_token = self.settings.get_string(StringSetting::AuthClientToken); // Generate random token if it wasn't supplied if client_token.is_empty() { client_token = std::iter::repeat(()) .map(|()| rand::thread_rng().sample(rand::distributions::Alphanumeric) as char) .take(20) .collect(); - self.vars.set(auth::AUTH_CLIENT_TOKEN, client_token); + self.settings + .set_string(StringSetting::AuthClientToken, &client_token); } - let client_token = self.vars.get(auth::AUTH_CLIENT_TOKEN).clone(); let username = elements.username_txt.borrow().input.clone(); let password = elements.password_txt.borrow().input.clone(); let refresh = elements.refresh; diff --git a/src/screen/server_list.rs b/src/screen/server_list.rs index 81bbd11e..a7dff867 100644 --- a/src/screen/server_list.rs +++ b/src/screen/server_list.rs @@ -446,7 +446,10 @@ impl ServerList { options.add_click_func(|_, game| { game.screen_sys .clone() - .add_screen(Box::new(super::SettingsMenu::new(game.vars.clone(), false))); + .add_screen(Box::new(super::SettingsMenu::new( + game.settings.clone(), + false, + ))); true }); } diff --git a/src/screen/settings_menu.rs b/src/screen/settings_menu.rs index 2a8a015c..b154f242 100644 --- a/src/screen/settings_menu.rs +++ b/src/screen/settings_menu.rs @@ -1,9 +1,11 @@ -use crate::console; use crate::render; -use crate::settings; +use crate::settings::SettingStore; use crate::ui; use crate::screen::{Screen, ScreenSystem}; +use crate::BoolSetting; +use crate::FloatSetting; +use crate::IntSetting; use std::rc::Rc; use std::sync::Arc; @@ -14,7 +16,7 @@ pub struct UIElements { } pub struct SettingsMenu { - _vars: Rc, + settings: Rc, elements: Option, show_disconnect_button: bool, } @@ -22,7 +24,7 @@ pub struct SettingsMenu { impl Clone for SettingsMenu { fn clone(&self) -> Self { SettingsMenu { - _vars: self._vars.clone(), + settings: self.settings.clone(), elements: None, show_disconnect_button: self.show_disconnect_button, } @@ -30,9 +32,9 @@ impl Clone for SettingsMenu { } impl SettingsMenu { - pub fn new(vars: Rc, show_disconnect_button: bool) -> Self { + pub fn new(settings: Rc, show_disconnect_button: bool) -> Self { SettingsMenu { - _vars: vars, + settings, elements: None, show_disconnect_button, } @@ -71,7 +73,7 @@ impl super::Screen for SettingsMenu { audio_settings.add_click_func(|_, game| { game.screen_sys .clone() - .add_screen(Box::new(AudioSettingsMenu::new(game.vars.clone()))); + .add_screen(Box::new(AudioSettingsMenu::new(game.settings.clone()))); true }); } @@ -92,7 +94,7 @@ impl super::Screen for SettingsMenu { video_settings.add_click_func(|_, game| { game.screen_sys .clone() - .add_screen(Box::new(VideoSettingsMenu::new(game.vars.clone()))); + .add_screen(Box::new(VideoSettingsMenu::new(game.settings.clone()))); true }); } @@ -113,7 +115,7 @@ impl super::Screen for SettingsMenu { controls_settings.add_click_func(|_, game| { game.screen_sys .clone() - .add_screen(Box::new(ControlsMenu::new(game.vars.clone()))); + .add_screen(Box::new(ControlsMenu::new(game.settings.clone()))); true }); } @@ -149,7 +151,7 @@ impl super::Screen for SettingsMenu { skin_settings.add_click_func(|_, game| { game.screen_sys .clone() - .add_screen(Box::new(SkinSettingsMenu::new(game.vars.clone()))); + .add_screen(Box::new(SkinSettingsMenu::new(game.settings.clone()))); true }); } @@ -252,23 +254,23 @@ impl super::Screen for SettingsMenu { } pub struct VideoSettingsMenu { - vars: Rc, + settings: Rc, elements: Option, } impl Clone for VideoSettingsMenu { fn clone(&self) -> Self { VideoSettingsMenu { - vars: self.vars.clone(), + settings: self.settings.clone(), elements: None, } } } impl VideoSettingsMenu { - pub fn new(vars: Rc) -> Self { + pub fn new(settings: Rc) -> Self { VideoSettingsMenu { - vars, + settings, elements: None, } } @@ -291,9 +293,9 @@ impl super::Screen for VideoSettingsMenu { let mut buttons = vec![]; // Load defaults - let r_max_fps = *self.vars.get(settings::R_MAX_FPS); - let r_fov = *self.vars.get(settings::R_FOV); - let r_vsync = *self.vars.get(settings::R_VSYNC); + let r_max_fps = self.settings.get_int(IntSetting::MaxFps); + let r_fov = self.settings.get_int(IntSetting::FOV); + let r_vsync = self.settings.get_bool(BoolSetting::Vsync); // Setting buttons // TODO: Slider @@ -336,10 +338,10 @@ impl super::Screen for VideoSettingsMenu { let txt_vsync = txt.clone(); vsync_setting.add_text(txt); vsync_setting.add_click_func(move |_, game| { - let r_vsync = !*game.vars.get(settings::R_VSYNC); + let r_vsync = !game.settings.get_bool(BoolSetting::Vsync); txt_vsync.borrow_mut().text = format!("VSync: {}", if r_vsync { "Enabled" } else { "Disabled" }); - game.vars.set(settings::R_VSYNC, r_vsync); + game.settings.set_bool(BoolSetting::Vsync, r_vsync); true }); } @@ -436,23 +438,23 @@ impl super::Screen for VideoSettingsMenu { } pub struct AudioSettingsMenu { - _vars: Rc, + _settings: Rc, elements: Option, } impl Clone for AudioSettingsMenu { fn clone(&self) -> Self { AudioSettingsMenu { - _vars: self._vars.clone(), + _settings: self._settings.clone(), elements: None, } } } impl AudioSettingsMenu { - pub fn new(vars: Rc) -> AudioSettingsMenu { + pub fn new(_settings: Rc) -> AudioSettingsMenu { AudioSettingsMenu { - _vars: vars, + _settings, elements: None, } } @@ -546,23 +548,23 @@ impl super::Screen for AudioSettingsMenu { } pub struct SkinSettingsMenu { - vars: Rc, + settings: Rc, elements: Option, } impl Clone for SkinSettingsMenu { fn clone(&self) -> Self { SkinSettingsMenu { - vars: self.vars.clone(), + settings: self.settings.clone(), elements: None, } } } impl SkinSettingsMenu { - pub fn new(vars: Rc) -> Self { + pub fn new(settings: Rc) -> Self { SkinSettingsMenu { - vars, + settings, elements: None, } } @@ -585,13 +587,13 @@ impl super::Screen for SkinSettingsMenu { let mut buttons = vec![]; // Load defaults - let s_hat = *self.vars.get(settings::S_HAT); - let _s_jacket = *self.vars.get(settings::S_JACKET); - let _s_cape = *self.vars.get(settings::S_CAPE); - let _s_right_sleeve = *self.vars.get(settings::S_RIGHT_SLEEVE); - let _s_left_sleeve = *self.vars.get(settings::S_LEFT_SLEEVE); - let _s_right_pants = *self.vars.get(settings::S_RIGHT_PANTS); - let _s_left_pants = *self.vars.get(settings::S_LEFT_PANTS); + let s_hat = self.settings.get_bool(BoolSetting::HatVisible); + let _s_jacket = self.settings.get_bool(BoolSetting::JacketVisible); + let _s_cape = self.settings.get_bool(BoolSetting::CapeVisible); + let _s_right_sleeve = self.settings.get_bool(BoolSetting::RightSleeveVisible); + let _s_left_sleeve = self.settings.get_bool(BoolSetting::LeftSleeveVisible); + let _s_right_pants = self.settings.get_bool(BoolSetting::RightPantsVisible); + let _s_left_pants = self.settings.get_bool(BoolSetting::LeftPantsVisible); // Setting buttons let hat_setting = ui::ButtonBuilder::new() @@ -614,7 +616,7 @@ impl super::Screen for SkinSettingsMenu { let txt_hat = txt.clone(); hat_setting.add_text(txt); hat_setting.add_click_func(move |_, game| { - let s_hat = !*game.vars.get(settings::S_HAT); + let s_hat = !game.settings.get_bool(BoolSetting::HatVisible); txt_hat.borrow_mut().text = format!( "Hat: {}", match s_hat { @@ -622,7 +624,7 @@ impl super::Screen for SkinSettingsMenu { false => "Off", } ); - game.vars.set(settings::S_HAT, s_hat); + game.settings.set_bool(BoolSetting::HatVisible, s_hat); false }); } @@ -746,23 +748,23 @@ impl super::Screen for SkinSettingsMenu { } pub struct ControlsMenu { - vars: Rc, + settings: Rc, elements: Option, } impl Clone for ControlsMenu { fn clone(&self) -> Self { ControlsMenu { - vars: self.vars.clone(), + settings: self.settings.clone(), elements: None, } } } impl ControlsMenu { - pub fn new(vars: Rc) -> Self { + pub fn new(settings: Rc) -> Self { ControlsMenu { - vars, + settings, elements: None, } } @@ -777,7 +779,7 @@ impl super::Screen for ControlsMenu { ) { let mut buttons = vec![]; let mut sliders = vec![]; - let r_mouse_sens = *self.vars.get(settings::R_MOUSE_SENS); + let r_mouse_sens = self.settings.get_float(FloatSetting::MouseSense); let background = ui::ImageBuilder::new() .texture("leafish:solid") @@ -824,8 +826,8 @@ impl super::Screen for ControlsMenu { //update button position slider_btn.borrow_mut().x = (game.last_mouse_x) - screen_width / 2.0 - this.x; //update game setting based on button position - game.vars.set( - settings::R_MOUSE_SENS, + game.settings.set_float( + FloatSetting::MouseSense, (slider_btn.borrow().x + 150.0) / 30.0, ); //update text in button @@ -835,7 +837,7 @@ impl super::Screen for ControlsMenu { .borrow_mut() .text = format!( "Mouse Sensetivity: {:.2}x", - *game.vars.get(settings::R_MOUSE_SENS) + game.settings.get_float(FloatSetting::MouseSense) ); true }); diff --git a/src/settings.rs b/src/settings.rs deleted file mode 100644 index ef2805fd..00000000 --- a/src/settings.rs +++ /dev/null @@ -1,267 +0,0 @@ -use winit::keyboard::KeyCode; - -use crate::console; -use crate::console::CVar; -use std::marker::PhantomData; - -pub const R_MAX_FPS: console::CVar = console::CVar { - ty: PhantomData, - name: "r_max_fps", - description: "fps_max caps the maximum FPS for the rendering engine", - mutable: true, - serializable: true, - default: &|| 60, -}; - -pub const R_FOV: console::CVar = console::CVar { - ty: PhantomData, - name: "r_fov", - description: "Setting for controlling the client field of view", - mutable: true, - serializable: true, - default: &|| 90, -}; - -pub const R_VSYNC: console::CVar = console::CVar { - ty: PhantomData, - name: "r_vsync", - description: "Toggle to enable/disable vsync", - mutable: true, - serializable: true, - default: &|| false, -}; - -pub const R_MOUSE_SENS: console::CVar = console::CVar { - ty: PhantomData, - name: "r_mouse_sens", - description: "Mouse Sensitivity", - mutable: true, - serializable: true, - default: &|| 1.0, -}; - -pub const CL_MASTER_VOLUME: console::CVar = console::CVar { - ty: PhantomData, - name: "cl_master_volume", - description: "Main volume control", - mutable: true, - serializable: true, - default: &|| 100, -}; - -// https://github.com/SpigotMC/BungeeCord/blob/bda160562792a913cba3a65ba4996de60d0d6d68/proxy/src/main/java/net/md_5/bungee/PlayerSkinConfiguration.java#L20 -pub const S_CAPE: console::CVar = console::CVar { - ty: PhantomData, - name: "s_cape", - description: "Toggle your cape", - mutable: true, - serializable: true, - default: &|| false, -}; - -pub const S_JACKET: console::CVar = console::CVar { - ty: PhantomData, - name: "s_jacket", - description: "Toggle your jacket", - mutable: true, - serializable: true, - default: &|| false, -}; - -pub const S_LEFT_SLEEVE: console::CVar = console::CVar { - ty: PhantomData, - name: "s_left_sleeve", - description: "Toggle your left sleeve", - mutable: true, - serializable: true, - default: &|| false, -}; - -pub const S_RIGHT_SLEEVE: console::CVar = console::CVar { - ty: PhantomData, - name: "s_right_sleeve", - description: "Toggle your right sleeve", - mutable: true, - serializable: true, - default: &|| false, -}; - -pub const S_LEFT_PANTS: console::CVar = console::CVar { - ty: PhantomData, - name: "s_left_pants", - description: "Toggle your left pants", - mutable: true, - serializable: true, - default: &|| false, -}; - -pub const S_RIGHT_PANTS: console::CVar = console::CVar { - ty: PhantomData, - name: "s_right_pants", - description: "Toggle your right pants", - mutable: true, - serializable: true, - default: &|| false, -}; - -pub const S_HAT: console::CVar = console::CVar { - ty: PhantomData, - name: "s_hat", - description: "Toggle your hat", - mutable: true, - serializable: true, - default: &|| false, -}; - -macro_rules! create_keybind { - ($keycode:ident, $name:expr, $description:expr) => { - console::CVar { - ty: PhantomData, - name: $name, - description: $description, - mutable: true, - serializable: true, - default: &|| KeyCode::$keycode as i64, - } - }; -} - -pub const CL_KEYBIND_FORWARD: console::CVar = - create_keybind!(KeyW, "cl_keybind_forward", "Keybinding for moving forward"); -pub const CL_KEYBIND_BACKWARD: console::CVar = create_keybind!( - KeyS, - "cl_keybind_backward", - "Keybinding for moving backward" -); -pub const CL_KEYBIND_LEFT: console::CVar = - create_keybind!(KeyA, "cl_keybind_left", "Keybinding for moving the left"); -pub const CL_KEYBIND_RIGHT: console::CVar = create_keybind!( - KeyD, - "cl_keybind_right", - "Keybinding for moving to the right" -); -pub const CL_KEYBIND_OPEN_INV: console::CVar = create_keybind!( - KeyE, - "cl_keybind_open_inv", - "Keybinding for opening the inventory" -); -pub const CL_KEYBIND_SNEAK: console::CVar = - create_keybind!(ShiftLeft, "cl_keybind_sneak", "Keybinding for sneaking"); -pub const CL_KEYBIND_SPRINT: console::CVar = - create_keybind!(ControlLeft, "cl_keybind_sprint", "Keybinding for sprinting"); -pub const CL_KEYBIND_JUMP: console::CVar = - create_keybind!(Space, "cl_keybind_jump", "Keybinding for jumping"); -pub const CL_KEYBIND_TOGGLE_HUD: console::CVar = create_keybind!( - F1, - "cl_keybind_toggle_hud", - "Keybinding for toggling the hud" -); -pub const CL_KEYBIND_TOGGLE_DEBUG: console::CVar = create_keybind!( - F3, - "cl_keybind_toggle_debug", - "Keybinding for toggling the debug info" -); -pub const CL_KEYBIND_TOGGLE_CHAT: console::CVar = create_keybind!( - KeyT, - "cl_keybind_toggle_chat", - "Keybinding for toggling the chat" -); - -pub const BACKGROUND_IMAGE: console::CVar = CVar { - ty: PhantomData, - name: "background", - description: "Select the background image", - mutable: true, - serializable: true, - default: &|| String::from("leafish:gui/background"), -}; - -pub const DOUBLE_JUMP_MS: u32 = 100; - -pub fn register_vars(vars: &mut console::Vars) { - vars.register(R_MAX_FPS); - vars.register(R_FOV); - vars.register(R_VSYNC); - vars.register(R_MOUSE_SENS); - vars.register(CL_MASTER_VOLUME); - vars.register(CL_KEYBIND_FORWARD); - vars.register(CL_KEYBIND_BACKWARD); - vars.register(CL_KEYBIND_LEFT); - vars.register(CL_KEYBIND_RIGHT); - vars.register(CL_KEYBIND_OPEN_INV); - vars.register(CL_KEYBIND_SNEAK); - vars.register(CL_KEYBIND_SPRINT); - vars.register(CL_KEYBIND_JUMP); - vars.register(CL_KEYBIND_TOGGLE_HUD); - vars.register(CL_KEYBIND_TOGGLE_DEBUG); - vars.register(CL_KEYBIND_TOGGLE_CHAT); - vars.register(S_CAPE); - vars.register(S_JACKET); - vars.register(S_LEFT_SLEEVE); - vars.register(S_RIGHT_SLEEVE); - vars.register(S_LEFT_PANTS); - vars.register(S_RIGHT_PANTS); - vars.register(S_HAT); - vars.register(BACKGROUND_IMAGE); -} - -#[derive(Hash, PartialEq, Eq, Debug, Copy, Clone)] -pub enum Actionkey { - Forward, - Backward, - Left, - Right, - OpenInv, - Sneak, - Sprint, - Jump, - ToggleHud, - ToggleDebug, - ToggleChat, -} - -impl Actionkey { - const VALUES: [Actionkey; 11] = [ - Actionkey::Forward, - Actionkey::Backward, - Actionkey::Left, - Actionkey::Right, - Actionkey::OpenInv, - Actionkey::Sneak, - Actionkey::Sprint, - Actionkey::Jump, - Actionkey::ToggleHud, - Actionkey::ToggleDebug, - Actionkey::ToggleChat, - ]; - - #[inline] - pub fn values() -> &'static [Actionkey] { - &Self::VALUES - } - - pub fn get_by_keycode(keycode: KeyCode, vars: &console::Vars) -> Option { - for action_key in Actionkey::values() { - if keycode as i64 == *vars.get(action_key.get_cvar()) { - return Some(*action_key); - } - } - None - } - - pub fn get_cvar(&self) -> console::CVar { - match *self { - Actionkey::Forward => CL_KEYBIND_FORWARD, - Actionkey::Backward => CL_KEYBIND_BACKWARD, - Actionkey::Left => CL_KEYBIND_LEFT, - Actionkey::Right => CL_KEYBIND_RIGHT, - Actionkey::OpenInv => CL_KEYBIND_OPEN_INV, - Actionkey::Sneak => CL_KEYBIND_SNEAK, - Actionkey::Sprint => CL_KEYBIND_SPRINT, - Actionkey::Jump => CL_KEYBIND_JUMP, - Actionkey::ToggleHud => CL_KEYBIND_TOGGLE_HUD, - Actionkey::ToggleDebug => CL_KEYBIND_TOGGLE_DEBUG, - Actionkey::ToggleChat => CL_KEYBIND_TOGGLE_CHAT, - } - } -} diff --git a/src/settings/config_var.rs b/src/settings/config_var.rs new file mode 100644 index 00000000..6d49d575 --- /dev/null +++ b/src/settings/config_var.rs @@ -0,0 +1,228 @@ +#![allow(clippy::redundant_closure)] +use crate::paths; +use log::{info, warn}; +use parking_lot::Mutex; +use std::collections::HashMap; +use std::fs; +use std::io::{BufRead, BufReader, BufWriter, Write}; + +use super::default_config::default_vars; + +#[derive(Clone)] +pub enum SettingValue { + String(String), + Num(i32), + Float(f64), + Bool(bool), +} + +#[derive(Clone)] +pub struct ConfigVar { + pub name: &'static str, + pub description: &'static str, + pub serializable: bool, + pub value: SettingValue, +} + +#[derive(PartialEq, PartialOrd, Hash, Eq, Ord, Clone, Copy)] +pub enum SettingType { + Int(IntSetting), + Bool(BoolSetting), + Float(FloatSetting), + String(StringSetting), +} + +#[derive(PartialEq, PartialOrd, Hash, Eq, Ord, Clone, Copy)] +pub enum IntSetting { + MaxFps, + FOV, + MasterVolume, +} + +#[derive(PartialEq, PartialOrd, Hash, Eq, Ord, Clone, Copy)] +pub enum BoolSetting { + Vsync, + CapeVisible, + JacketVisible, + RightSleeveVisible, + LeftSleeveVisible, + RightPantsVisible, + LeftPantsVisible, + HatVisible, + AutomaticOfflineAccounts, +} + +#[derive(PartialEq, PartialOrd, Hash, Eq, Ord, Clone, Copy)] +pub enum FloatSetting { + MouseSense, +} + +#[derive(PartialEq, PartialOrd, Hash, Eq, Ord, Clone, Copy)] +pub enum StringSetting { + AuthClientToken, + BackgroundImage, + LogLevelFile, + LogLevelTerm, +} + +#[rustfmt::skip] +impl SettingValue { + fn as_string(&self) -> Option { + if let Self::String(s) = self { Some(s.clone()) } else { None } + } + fn as_int(&self) -> Option { + if let Self::Num(n) = self { Some(*n) } else { None } + } + fn as_float(&self) -> Option { + if let Self::Float(f) = self { Some(*f) } else { None } + } + fn as_bool(&self) -> Option { + if let Self::Bool(b) = self { Some(*b) } else { None } + } +} + +// stores all game settings, except keybinds +pub struct SettingStore(Mutex>); + +impl SettingStore { + pub fn new() -> Self { + let mut store = SettingStore(Mutex::new(HashMap::new())); + store.load_defaults(); + store.load_config(); + store.save_config(); + store + } + + fn set(&self, s_type: SettingType, val: SettingValue) { + self.0.lock().get_mut(&s_type).unwrap().value = val; + self.save_config(); + } + + pub fn set_bool(&self, setting: BoolSetting, val: bool) { + Self::set(self, SettingType::Bool(setting), SettingValue::Bool(val)); + } + + pub fn set_int(&self, setting: IntSetting, val: i32) { + Self::set(self, SettingType::Int(setting), SettingValue::Num(val)); + } + + pub fn set_float(&self, setting: FloatSetting, val: f64) { + Self::set(self, SettingType::Float(setting), SettingValue::Float(val)); + } + + pub fn set_string(&self, setting: StringSetting, val: &str) { + Self::set( + self, + SettingType::String(setting), + SettingValue::String(val.to_owned()), + ); + } + + fn get_value(&self, input: SettingType) -> SettingValue { + self.0.lock().get(&input).unwrap().value.clone() + } + + pub fn get_bool(&self, input: BoolSetting) -> bool { + Self::get_value(self, SettingType::Bool(input)) + .as_bool() + .unwrap() + } + + pub fn get_int(&self, input: IntSetting) -> i32 { + Self::get_value(self, SettingType::Int(input)) + .as_int() + .unwrap() + } + + pub fn get_float(&self, input: FloatSetting) -> f64 { + Self::get_value(self, SettingType::Float(input)) + .as_float() + .unwrap() + } + + pub fn get_string(&self, input: StringSetting) -> String { + Self::get_value(self, SettingType::String(input)) + .as_string() + .unwrap() + } + + fn load_config(&mut self) { + if let Ok(file) = fs::File::open(paths::get_config_dir().join("conf.cfg")) { + let reader = BufReader::new(file); + for line in reader.lines() { + let line = line.unwrap(); + if line.starts_with('#') || line.is_empty() { + continue; + } + let parts = line + .splitn(2, ' ') + .map(|v| v.to_owned()) + .collect::>(); + let (name, arg) = (&parts[0], &parts[1]); + if name.starts_with("keybind_") { + continue; + } + let mut store = self.0.lock(); + if let Some((s_type, setting)) = store.clone().iter().find(|(_, e)| e.name == name) + { + let Some(val) = deserialize_value(arg, setting.value.clone()) else { + warn!("a config value couldnt be loaded from file: {name}"); + continue; + }; + if setting.serializable { + store.get_mut(s_type).unwrap().value = val; + } + } else { + info!("a unknwon config option was specified: {name}"); + } + } + } + } + + fn save_config(&self) { + let mut file = + BufWriter::new(fs::File::create(paths::get_config_dir().join("conf.cfg")).unwrap()); + for var in self.0.lock().values() { + if !var.serializable { + continue; + } + for line in var.description.lines() { + if let Err(err) = writeln!(file, "# {}", line) { + warn!("couldnt write a setting description to config file: {err}, {line}"); + } + } + let name = var.name; + + if let Err(err) = match &var.value { + SettingValue::Float(f) => write!(file, "{name} {f}\n\n"), + SettingValue::Num(n) => write!(file, "{name} {n}\n\n"), + SettingValue::Bool(b) => write!(file, "{name} {b}\n\n"), + SettingValue::String(s) => write!(file, "{name} {s}\n\n"), + } { + warn!("couldnt write a setting to config file: {err}, {name}"); + } + } + } + + fn load_defaults(&self) { + let mut s = self.0.lock(); + for (var_type, var) in default_vars() { + s.insert(var_type, var); + } + } +} + +fn deserialize_value(input: &str, old: SettingValue) -> Option { + match old { + SettingValue::Num(_) => input.parse::().ok().map(|num| SettingValue::Num(num)), + SettingValue::Float(_) => input.parse::().ok().map(|f| SettingValue::Float(f)), + SettingValue::Bool(_) => input.parse::().ok().map(|b| SettingValue::Bool(b)), + SettingValue::String(_) => Some(SettingValue::String(input.to_owned())), + } +} + +impl Default for SettingStore { + fn default() -> Self { + Self::new() + } +} diff --git a/src/settings/default_config.rs b/src/settings/default_config.rs new file mode 100644 index 00000000..c10335d1 --- /dev/null +++ b/src/settings/default_config.rs @@ -0,0 +1,161 @@ +use super::*; + +pub fn default_vars() -> Vec<(SettingType, ConfigVar)> { + vec![ + ( + SettingType::Int(IntSetting::MaxFps), + ConfigVar { + name: "max_fps", + description: "fps_max caps the maximum FPS for the rendering engine", + serializable: true, + value: SettingValue::Num(60), + }, + ), + ( + SettingType::Int(IntSetting::FOV), + ConfigVar { + name: "fov", + description: "Setting for controlling the client field of view", + serializable: true, + value: SettingValue::Num(90), + }, + ), + ( + SettingType::Bool(BoolSetting::Vsync), + ConfigVar { + name: "vsync", + description: "Toggle to enable/disable vsync", + serializable: true, + value: SettingValue::Bool(false), + }, + ), + ( + SettingType::Float(FloatSetting::MouseSense), + ConfigVar { + name: "mouse_sens", + description: "Mouse Sensitivity", + serializable: true, + value: SettingValue::Float(1.0), + }, + ), + ( + SettingType::Int(IntSetting::MasterVolume), + ConfigVar { + name: "master_volume", + description: "Main volume control", + serializable: true, + value: SettingValue::Num(100), + }, + ), + ( + SettingType::Bool(BoolSetting::CapeVisible), + ConfigVar { + name: "cape", + description: "Toggle your cape", + serializable: true, + value: SettingValue::Bool(false), + }, + ), + ( + SettingType::Bool(BoolSetting::JacketVisible), + ConfigVar { + name: "jacket", + description: "Toggle your jacket", + serializable: true, + value: SettingValue::Bool(false), + }, + ), + ( + SettingType::Bool(BoolSetting::LeftSleeveVisible), + ConfigVar { + name: "left_sleeve", + description: "Toggle your left sleeve", + serializable: true, + value: SettingValue::Bool(false), + }, + ), + ( + SettingType::Bool(BoolSetting::RightSleeveVisible), + ConfigVar { + name: "right_sleeve", + description: "Toggle your right sleeve", + serializable: true, + value: SettingValue::Bool(false), + }, + ), + ( + SettingType::Bool(BoolSetting::LeftPantsVisible), + ConfigVar { + name: "left_pants", + description: "Toggle your left pants", + serializable: true, + value: SettingValue::Bool(false), + }, + ), + ( + SettingType::Bool(BoolSetting::RightPantsVisible), + ConfigVar { + name: "right_pants", + description: "Toggle your right pants", + serializable: true, + value: SettingValue::Bool(false), + }, + ), + ( + SettingType::Bool(BoolSetting::HatVisible), + ConfigVar { + name: "hat", + description: "Toggle your hat", + serializable: true, + value: SettingValue::Bool(false), + }, + ), + ( + SettingType::Bool(BoolSetting::AutomaticOfflineAccounts), + ConfigVar { + name: "automatic_offline_accounts", + description: + "Enables using no password in the login screen for creating offline accounts", + serializable: true, + value: SettingValue::Bool(false), + }, + ), + ( + SettingType::String(StringSetting::LogLevelTerm), + ConfigVar { + name: "log_level_term", + description: "log level of messages to log to the terminal", + serializable: true, + value: SettingValue::String("info".to_owned()), + }, + ), + ( + SettingType::String(StringSetting::LogLevelFile), + ConfigVar { + name: "log_level_file", + description: "log level of messages to log to the file", + serializable: true, + value: SettingValue::String("trace".to_owned()), + }, + ), + ( + SettingType::String(StringSetting::BackgroundImage), + ConfigVar { + name: "background", + description: "Select the background image", + serializable: true, + value: SettingValue::String("leafish:gui/background".to_owned()), + }, + ), + ( + SettingType::String(StringSetting::AuthClientToken), + ConfigVar { + name: "auth_client_token", + description: r#"auth_client_token is a token that stays static between sessions. +Used to identify this client vs others."#, + serializable: true, + value: SettingValue::String("".to_owned()), + }, + ), + ] +} diff --git a/src/settings/default_keybinds.rs b/src/settings/default_keybinds.rs new file mode 100644 index 00000000..1d3eb4de --- /dev/null +++ b/src/settings/default_keybinds.rs @@ -0,0 +1,96 @@ +use winit::keyboard::KeyCode; + +use super::*; + +pub fn create_keybinds() -> Vec<(KeyCode, Keybind)> { + vec![ + ( + KeyCode::KeyW, + Keybind { + name: "keybind_forward", + description: "Keybinding for moving forward", + action: Actionkey::Forward, + }, + ), + ( + KeyCode::KeyS, + Keybind { + name: "keybind_backward", + description: "Keybinding for moving backward", + action: Actionkey::Backward, + }, + ), + ( + KeyCode::KeyA, + Keybind { + name: "keybind_left", + description: "Keybinding for moving to the left", + action: Actionkey::Left, + }, + ), + ( + KeyCode::KeyD, + Keybind { + name: "keybind_right", + description: "Keybinding for moving to the right", + action: Actionkey::Right, + }, + ), + ( + KeyCode::KeyE, + Keybind { + name: "keybind_open_inv", + description: "Keybinding for opening the inventory", + action: Actionkey::OpenInv, + }, + ), + ( + KeyCode::ShiftLeft, + Keybind { + name: "keybind_sneak", + description: "Keybinding for sneaking", + action: Actionkey::Sneak, + }, + ), + ( + KeyCode::ControlLeft, + Keybind { + name: "keybind_sprint", + description: "Keybinding for sprinting", + action: Actionkey::Sprint, + }, + ), + ( + KeyCode::Space, + Keybind { + name: "keybind_jump", + description: "Keybinding for jumping", + action: Actionkey::Jump, + }, + ), + ( + KeyCode::F1, + Keybind { + name: "keybind_toggle_hud", + description: "Keybinding for toggeling the hud", + action: Actionkey::ToggleHud, + }, + ), + ( + KeyCode::F3, + Keybind { + name: "keybind_toggle_debug_info", + description: "Keybinding for toggeling the debug info", + action: Actionkey::ToggleDebug, + }, + ), + ( + KeyCode::KeyT, + Keybind { + name: "keybind_toggle_chat", + description: "Keybinding for toggeling the chat", + action: Actionkey::ToggleChat, + }, + ), + ] +} diff --git a/src/settings/keybinds.rs b/src/settings/keybinds.rs new file mode 100644 index 00000000..11d03da3 --- /dev/null +++ b/src/settings/keybinds.rs @@ -0,0 +1,189 @@ +use log::{info, warn}; +use parking_lot::Mutex; +use std::collections::HashMap; +use std::fs; +use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::str::FromStr; + +use winit::keyboard::KeyCode; + +use crate::paths; + +use super::default_keybinds::create_keybinds; + +#[derive(Clone, Copy)] +pub struct Keybind { + pub name: &'static str, + pub description: &'static str, + pub action: Actionkey, +} + +pub struct KeybindStore(Mutex>); + +impl KeybindStore { + pub fn new() -> Self { + let mut store = KeybindStore(Mutex::new(HashMap::new())); + store.load_defaults(); + store.load_config(); + store.save_config(); + store + } + + pub fn get(&self, key: KeyCode) -> Option { + self.0.lock().get(&(key as i32)).copied() + } + + pub fn set(&self, key: i32, action: Actionkey) { + let old_key = *self + .0 + .lock() + .iter() + .find(|(_, v)| v.action == action) + .expect("a action was not bound to a key?") + .0; + + let old_val = self.0.lock().remove(&old_key).unwrap(); + self.0.lock().insert(key, old_val); + self.save_config(); + } + + fn load_config(&mut self) { + if let Ok(file) = fs::File::open(paths::get_config_dir().join("keybinds.cfg")) { + let reader = BufReader::new(file); + for line in reader.lines() { + let Ok(line) = line else { + warn!("failed reading a line in the config file"); + continue; + }; + if line.starts_with('#') || line.is_empty() { + continue; + } + let parts = line + .splitn(2, ' ') + .map(|v| v.to_owned()) + .collect::>(); + let (name, arg) = (&parts[0], &parts[1]); + if !name.starts_with("keybind_") { + continue; + } + let mut store = self.0.lock(); + if let Some(action) = store + .values() + .find(|v| Actionkey::from_str(name).is_ok_and(|k| k == v.action)) + { + if let Some(new_key) = deserialize_key(arg) { + let key = *store + .iter() + .find(|(_, v)| v.action == action.action) + .expect("a action was not bound to a key?") + .0; + + let old_val = store.remove(&key).unwrap(); + store.insert(new_key, old_val); + } + } else { + info!("a unknown keybind was specified: {name}"); + } + } + } + } + + fn save_config(&self) { + let mut file = + BufWriter::new(fs::File::create(paths::get_config_dir().join("keybinds.cfg")).unwrap()); + for (key, keybind) in self.0.lock().iter() { + for line in keybind.description.lines() { + if let Err(err) = writeln!(file, "# {}", line) { + warn!( + "couldnt write a keybind description to config file {err}, {}", + keybind.name + ); + } + } + if let Err(err) = write!(file, "{} {}\n\n", keybind.name, *key) { + warn!( + "couldnt write a keybind to config file {err}, {}", + keybind.name + ); + }; + } + } + + fn load_defaults(&self) { + let mut s = self.0.lock(); + for bind in create_keybinds() { + s.insert(bind.0 as i32, bind.1); + } + } +} + +fn deserialize_key(input: &str) -> Option { + match input.parse::() { + Ok(num) => Some(num), + Err(err) => { + warn!("couldnt deserialize keybind: {err}, {input}"); + None + } + } +} + +#[derive(Hash, PartialEq, Eq, Debug, Copy, Clone)] +pub enum Actionkey { + Forward, + Backward, + Left, + Right, + OpenInv, + Sneak, + Sprint, + Jump, + ToggleHud, + ToggleDebug, + ToggleChat, +} + +impl FromStr for Actionkey { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + "keybind_forward" => Ok(Actionkey::Forward), + "keybind_backward" => Ok(Actionkey::Backward), + "keybind_left" => Ok(Actionkey::Left), + "keybind_right" => Ok(Actionkey::Right), + "keybind_open_inv" => Ok(Actionkey::OpenInv), + "keybind_sneak" => Ok(Actionkey::Sneak), + "keybind_sprint" => Ok(Actionkey::Sprint), + "keybind_jump" => Ok(Actionkey::Jump), + "keybind_toggle_hud" => Ok(Actionkey::ToggleHud), + "keybind_toggle_debug_info" => Ok(Actionkey::ToggleDebug), + "keybind_toggle_chat" => Ok(Actionkey::ToggleChat), + _ => Err(()), + } + } +} + +impl Actionkey { + const VALUES: [Actionkey; 11] = [ + Actionkey::Forward, + Actionkey::Backward, + Actionkey::Left, + Actionkey::Right, + Actionkey::OpenInv, + Actionkey::Sneak, + Actionkey::Sprint, + Actionkey::Jump, + Actionkey::ToggleHud, + Actionkey::ToggleDebug, + Actionkey::ToggleChat, + ]; + + pub fn values() -> &'static [Actionkey] { + &Self::VALUES + } +} + +impl Default for KeybindStore { + fn default() -> Self { + Self::new() + } +} diff --git a/src/settings/mod.rs b/src/settings/mod.rs new file mode 100644 index 00000000..c5080100 --- /dev/null +++ b/src/settings/mod.rs @@ -0,0 +1,10 @@ +mod config_var; +mod keybinds; + +mod default_config; +mod default_keybinds; + +pub use config_var::*; +pub use keybinds::*; + +pub const DOUBLE_JUMP_MS: u32 = 100;