use crate::{ hud::{BarNumbers, BuffPosition, CrosshairType, Intro, PressBehavior, ShortcutNumbers, XpBar}, i18n, render::RenderMode, ui::ScaleMode, window::{FullScreenSettings, GameInput, KeyMouse}, }; use directories_next::UserDirs; use hashbrown::{HashMap, HashSet}; use serde::{Deserialize, Serialize}; use std::{fmt, fs, path::PathBuf}; use tracing::warn; use vek::*; use winit::event::{MouseButton, VirtualKeyCode}; // ControlSetting-like struct used by Serde, to handle not serializing/building // post-deserializing the inverse_keybindings hashmap #[derive(Serialize, Deserialize)] struct ControlSettingsSerde { keybindings: HashMap, } impl From for ControlSettingsSerde { fn from(control_settings: ControlSettings) -> Self { let mut user_bindings: HashMap = HashMap::new(); // Do a delta between default() ControlSettings and the argument, and let // keybindings be only the custom keybindings chosen by the user. for (k, v) in control_settings.keybindings { if ControlSettings::default_binding(k) != v { // Keybinding chosen by the user user_bindings.insert(k, v); } } ControlSettingsSerde { keybindings: user_bindings, } } } /// `ControlSettings` contains keybindings. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(from = "ControlSettingsSerde", into = "ControlSettingsSerde")] pub struct ControlSettings { pub keybindings: HashMap, pub inverse_keybindings: HashMap>, // used in event loop } impl From for ControlSettings { fn from(control_serde: ControlSettingsSerde) -> Self { let user_keybindings = control_serde.keybindings; let mut control_settings = ControlSettings::default(); for (k, v) in user_keybindings { control_settings.modify_binding(k, v); } control_settings } } /// Since Macbook trackpads lack middle click, on OS X we default to LShift /// instead It is an imperfect heuristic, but hopefully it will be a slightly /// better default, and the two places we default to middle click currently /// (roll and wall jump) are both situations where you cannot glide (the other /// default mapping for LShift). #[cfg(target_os = "macos")] const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Key(VirtualKeyCode::LShift); #[cfg(not(target_os = "macos"))] const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Mouse(MouseButton::Middle); impl ControlSettings { pub fn get_binding(&self, game_input: GameInput) -> Option { self.keybindings.get(&game_input).copied() } pub fn get_associated_game_inputs(&self, key_mouse: &KeyMouse) -> Option<&HashSet> { self.inverse_keybindings.get(key_mouse) } pub fn insert_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) { self.keybindings.insert(game_input, key_mouse); self.inverse_keybindings .entry(key_mouse) .or_default() .insert(game_input); } pub fn modify_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) { // For the KeyMouse->GameInput hashmap, we first need to remove the GameInput // from the old binding if let Some(old_binding) = self.get_binding(game_input) { self.inverse_keybindings .entry(old_binding) .or_default() .remove(&game_input); } // then we add the GameInput to the proper key self.inverse_keybindings .entry(key_mouse) .or_default() .insert(game_input); // For the GameInput->KeyMouse hashmap, just overwrite the value self.keybindings.insert(game_input, key_mouse); } /// Return true if this key is used for multiple GameInputs that aren't /// expected to be safe to have bound to the same key at the same time pub fn has_conflicting_bindings(&self, key_mouse: KeyMouse) -> bool { if let Some(game_inputs) = self.inverse_keybindings.get(&key_mouse) { for a in game_inputs.iter() { for b in game_inputs.iter() { if !GameInput::can_share_bindings(*a, *b) { return true; } } } } false } pub fn default_binding(game_input: GameInput) -> KeyMouse { // If a new GameInput is added, be sure to update GameInput::iterator() too! match game_input { GameInput::Primary => KeyMouse::Mouse(MouseButton::Left), GameInput::Secondary => KeyMouse::Mouse(MouseButton::Right), GameInput::ToggleCursor => KeyMouse::Key(VirtualKeyCode::Comma), GameInput::Escape => KeyMouse::Key(VirtualKeyCode::Escape), GameInput::Chat => KeyMouse::Key(VirtualKeyCode::Return), GameInput::Command => KeyMouse::Key(VirtualKeyCode::Slash), GameInput::MoveForward => KeyMouse::Key(VirtualKeyCode::W), GameInput::MoveLeft => KeyMouse::Key(VirtualKeyCode::A), GameInput::MoveBack => KeyMouse::Key(VirtualKeyCode::S), GameInput::MoveRight => KeyMouse::Key(VirtualKeyCode::D), GameInput::Jump => KeyMouse::Key(VirtualKeyCode::Space), GameInput::Sit => KeyMouse::Key(VirtualKeyCode::K), GameInput::Dance => KeyMouse::Key(VirtualKeyCode::J), GameInput::Glide => KeyMouse::Key(VirtualKeyCode::LShift), GameInput::Climb => KeyMouse::Key(VirtualKeyCode::Space), GameInput::ClimbDown => KeyMouse::Key(VirtualKeyCode::LControl), GameInput::SwimUp => KeyMouse::Key(VirtualKeyCode::Space), GameInput::SwimDown => KeyMouse::Key(VirtualKeyCode::LShift), GameInput::Fly => KeyMouse::Key(VirtualKeyCode::H), GameInput::Sneak => KeyMouse::Key(VirtualKeyCode::LControl), GameInput::ToggleLantern => KeyMouse::Key(VirtualKeyCode::G), GameInput::Mount => KeyMouse::Key(VirtualKeyCode::F), GameInput::Map => KeyMouse::Key(VirtualKeyCode::M), GameInput::Bag => KeyMouse::Key(VirtualKeyCode::B), GameInput::Trade => KeyMouse::Key(VirtualKeyCode::R), GameInput::Social => KeyMouse::Key(VirtualKeyCode::O), GameInput::Crafting => KeyMouse::Key(VirtualKeyCode::C), GameInput::Spellbook => KeyMouse::Key(VirtualKeyCode::P), GameInput::Settings => KeyMouse::Key(VirtualKeyCode::N), GameInput::Help => KeyMouse::Key(VirtualKeyCode::F1), GameInput::ToggleInterface => KeyMouse::Key(VirtualKeyCode::F2), GameInput::ToggleDebug => KeyMouse::Key(VirtualKeyCode::F3), GameInput::Fullscreen => KeyMouse::Key(VirtualKeyCode::F11), GameInput::Screenshot => KeyMouse::Key(VirtualKeyCode::F4), GameInput::ToggleIngameUi => KeyMouse::Key(VirtualKeyCode::F6), GameInput::Roll => MIDDLE_CLICK_KEY, GameInput::Respawn => KeyMouse::Key(VirtualKeyCode::Space), GameInput::Interact => KeyMouse::Key(VirtualKeyCode::E), GameInput::ToggleWield => KeyMouse::Key(VirtualKeyCode::T), GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L), GameInput::AutoWalk => KeyMouse::Key(VirtualKeyCode::Period), GameInput::CameraClamp => KeyMouse::Key(VirtualKeyCode::Apostrophe), GameInput::CycleCamera => KeyMouse::Key(VirtualKeyCode::Key0), GameInput::Slot1 => KeyMouse::Key(VirtualKeyCode::Key1), GameInput::Slot2 => KeyMouse::Key(VirtualKeyCode::Key2), GameInput::Slot3 => KeyMouse::Key(VirtualKeyCode::Key3), GameInput::Slot4 => KeyMouse::Key(VirtualKeyCode::Key4), GameInput::Slot5 => KeyMouse::Key(VirtualKeyCode::Key5), GameInput::Slot6 => KeyMouse::Key(VirtualKeyCode::Key6), GameInput::Slot7 => KeyMouse::Key(VirtualKeyCode::Key7), GameInput::Slot8 => KeyMouse::Key(VirtualKeyCode::Key8), GameInput::Slot9 => KeyMouse::Key(VirtualKeyCode::Key9), GameInput::Slot10 => KeyMouse::Key(VirtualKeyCode::Q), GameInput::SwapLoadout => KeyMouse::Key(VirtualKeyCode::Tab), GameInput::Select => KeyMouse::Key(VirtualKeyCode::Y), GameInput::AcceptGroupInvite => KeyMouse::Key(VirtualKeyCode::U), GameInput::DeclineGroupInvite => KeyMouse::Key(VirtualKeyCode::I), } } } impl Default for ControlSettings { fn default() -> Self { let mut new_settings = Self { keybindings: HashMap::new(), inverse_keybindings: HashMap::new(), }; // Sets the initial keybindings for those GameInputs. for game_input in GameInput::iterator() { new_settings.insert_binding(game_input, ControlSettings::default_binding(game_input)); } new_settings } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct GamepadSettings { pub game_buttons: con_settings::GameButtons, pub menu_buttons: con_settings::MenuButtons, pub game_axis: con_settings::GameAxis, pub menu_axis: con_settings::MenuAxis, pub game_analog_buttons: con_settings::GameAnalogButton, pub menu_analog_buttons: con_settings::MenuAnalogButton, pub pan_sensitivity: u32, pub pan_invert_y: bool, pub axis_deadzones: HashMap, pub button_deadzones: HashMap, pub mouse_emulation_sensitivity: u32, pub inverted_axes: Vec, } impl Default for GamepadSettings { fn default() -> Self { Self { game_buttons: con_settings::GameButtons::default(), menu_buttons: con_settings::MenuButtons::default(), game_axis: con_settings::GameAxis::default(), menu_axis: con_settings::MenuAxis::default(), game_analog_buttons: con_settings::GameAnalogButton::default(), menu_analog_buttons: con_settings::MenuAnalogButton::default(), pan_sensitivity: 10, pan_invert_y: false, axis_deadzones: HashMap::new(), button_deadzones: HashMap::new(), mouse_emulation_sensitivity: 12, inverted_axes: Vec::new(), } } } pub mod con_settings { use crate::controller::*; use gilrs::{Axis as GilAxis, Button as GilButton}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct GameButtons { pub primary: Button, pub secondary: Button, pub toggle_cursor: Button, pub escape: Button, pub enter: Button, pub command: Button, pub move_forward: Button, pub move_left: Button, pub move_back: Button, pub move_right: Button, pub jump: Button, pub sit: Button, pub dance: Button, pub glide: Button, pub climb: Button, pub climb_down: Button, pub swimup: Button, pub swimdown: Button, pub sneak: Button, pub toggle_lantern: Button, pub mount: Button, pub map: Button, pub bag: Button, pub quest_log: Button, pub character_window: Button, pub social: Button, pub crafting: Button, pub spellbook: Button, pub settings: Button, pub help: Button, pub toggle_interface: Button, pub toggle_debug: Button, pub fullscreen: Button, pub screenshot: Button, pub toggle_ingame_ui: Button, pub roll: Button, pub respawn: Button, pub interact: Button, pub toggle_wield: Button, pub swap_loadout: Button, } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct MenuButtons { pub up: Button, pub down: Button, pub left: Button, pub right: Button, pub scroll_up: Button, pub scroll_down: Button, pub scroll_left: Button, pub scroll_right: Button, pub home: Button, pub end: Button, pub apply: Button, pub back: Button, pub exit: Button, } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct GameAxis { pub movement_x: Axis, pub movement_y: Axis, pub camera_x: Axis, pub camera_y: Axis, } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct MenuAxis { pub move_x: Axis, pub move_y: Axis, pub scroll_x: Axis, pub scroll_y: Axis, } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct GameAnalogButton {} #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct MenuAnalogButton {} impl Default for GameButtons { fn default() -> Self { // binding to unknown = getting skipped from processing Self { primary: Button::Simple(GilButton::RightTrigger2), secondary: Button::Simple(GilButton::LeftTrigger2), toggle_cursor: Button::Simple(GilButton::Unknown), escape: Button::Simple(GilButton::Start), enter: Button::Simple(GilButton::Unknown), command: Button::Simple(GilButton::Unknown), move_forward: Button::Simple(GilButton::Unknown), move_left: Button::Simple(GilButton::Unknown), move_back: Button::Simple(GilButton::Unknown), move_right: Button::Simple(GilButton::Unknown), jump: Button::Simple(GilButton::South), sit: Button::Simple(GilButton::Unknown), dance: Button::Simple(GilButton::Unknown), glide: Button::Simple(GilButton::LeftTrigger), climb: Button::Simple(GilButton::South), climb_down: Button::Simple(GilButton::East), swimup: Button::Simple(GilButton::South), swimdown: Button::Simple(GilButton::East), sneak: Button::Simple(GilButton::East), toggle_lantern: Button::Simple(GilButton::DPadLeft), mount: Button::Simple(GilButton::North), map: Button::Simple(GilButton::Select), bag: Button::Simple(GilButton::DPadRight), quest_log: Button::Simple(GilButton::Unknown), character_window: Button::Simple(GilButton::Unknown), social: Button::Simple(GilButton::Unknown), crafting: Button::Simple(GilButton::DPadDown), spellbook: Button::Simple(GilButton::Unknown), settings: Button::Simple(GilButton::Unknown), help: Button::Simple(GilButton::Unknown), toggle_interface: Button::Simple(GilButton::Unknown), toggle_debug: Button::Simple(GilButton::Unknown), fullscreen: Button::Simple(GilButton::Unknown), screenshot: Button::Simple(GilButton::Unknown), toggle_ingame_ui: Button::Simple(GilButton::Unknown), roll: Button::Simple(GilButton::RightTrigger), respawn: Button::Simple(GilButton::South), interact: Button::Simple(GilButton::North), toggle_wield: Button::Simple(GilButton::West), swap_loadout: Button::Simple(GilButton::LeftThumb), } } } impl Default for MenuButtons { fn default() -> Self { Self { up: Button::Simple(GilButton::Unknown), down: Button::Simple(GilButton::Unknown), left: Button::Simple(GilButton::Unknown), right: Button::Simple(GilButton::Unknown), scroll_up: Button::Simple(GilButton::Unknown), scroll_down: Button::Simple(GilButton::Unknown), scroll_left: Button::Simple(GilButton::Unknown), scroll_right: Button::Simple(GilButton::Unknown), home: Button::Simple(GilButton::DPadUp), end: Button::Simple(GilButton::DPadDown), apply: Button::Simple(GilButton::South), back: Button::Simple(GilButton::East), exit: Button::Simple(GilButton::Mode), } } } impl Default for GameAxis { fn default() -> Self { Self { movement_x: Axis::Simple(GilAxis::LeftStickX), movement_y: Axis::Simple(GilAxis::LeftStickY), camera_x: Axis::Simple(GilAxis::RightStickX), camera_y: Axis::Simple(GilAxis::RightStickY), } } } impl Default for MenuAxis { fn default() -> Self { Self { move_x: Axis::Simple(GilAxis::RightStickX), move_y: Axis::Simple(GilAxis::RightStickY), scroll_x: Axis::Simple(GilAxis::LeftStickX), scroll_y: Axis::Simple(GilAxis::LeftStickY), } } } impl Default for GameAnalogButton { fn default() -> Self { Self {} } } impl Default for MenuAnalogButton { fn default() -> Self { Self {} } } } /// `InterfaceSettings` contains UI, HUD and Map options. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct InterfaceSettings { pub toggle_debug: bool, pub sct: bool, pub sct_player_batch: bool, pub sct_damage_batch: bool, pub speech_bubble_dark_mode: bool, pub speech_bubble_icon: bool, pub crosshair_transp: f32, pub chat_transp: f32, pub chat_character_name: bool, pub crosshair_type: CrosshairType, pub intro_show: Intro, pub xp_bar: XpBar, pub shortcut_numbers: ShortcutNumbers, pub buff_position: BuffPosition, pub bar_numbers: BarNumbers, pub ui_scale: ScaleMode, pub map_zoom: f64, pub map_drag: Vec2, pub map_show_topo_map: bool, pub map_show_difficulty: bool, pub map_show_towns: bool, pub map_show_dungeons: bool, pub map_show_castles: bool, pub loading_tips: bool, pub map_show_caves: bool, pub map_show_trees: bool, pub minimap_show: bool, pub minimap_face_north: bool, } impl Default for InterfaceSettings { fn default() -> Self { Self { toggle_debug: false, sct: true, sct_player_batch: false, sct_damage_batch: false, speech_bubble_dark_mode: false, speech_bubble_icon: true, crosshair_transp: 0.6, chat_transp: 0.4, chat_character_name: true, crosshair_type: CrosshairType::Round, intro_show: Intro::Show, xp_bar: XpBar::Always, shortcut_numbers: ShortcutNumbers::On, buff_position: BuffPosition::Bar, bar_numbers: BarNumbers::Values, ui_scale: ScaleMode::RelativeToWindow([1920.0, 1080.0].into()), map_zoom: 10.0, map_drag: Vec2 { x: 0.0, y: 0.0 }, map_show_topo_map: false, map_show_difficulty: true, map_show_towns: true, map_show_dungeons: true, map_show_castles: true, loading_tips: true, map_show_caves: true, map_show_trees: true, minimap_show: true, minimap_face_north: false, } } } /// `GameplaySettings` contains sensitivity and gameplay options. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct GameplaySettings { pub pan_sensitivity: u32, pub zoom_sensitivity: u32, pub camera_clamp_angle: u32, pub zoom_inversion: bool, pub mouse_y_inversion: bool, pub smooth_pan_enable: bool, pub free_look_behavior: PressBehavior, pub auto_walk_behavior: PressBehavior, pub camera_clamp_behavior: PressBehavior, pub stop_auto_walk_on_input: bool, pub auto_camera: bool, } impl Default for GameplaySettings { fn default() -> Self { Self { pan_sensitivity: 100, zoom_sensitivity: 100, camera_clamp_angle: 45, zoom_inversion: false, mouse_y_inversion: false, smooth_pan_enable: false, free_look_behavior: PressBehavior::Toggle, auto_walk_behavior: PressBehavior::Toggle, camera_clamp_behavior: PressBehavior::Toggle, stop_auto_walk_on_input: true, auto_camera: false, } } } /// `NetworkingSettings` stores server and networking settings. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct NetworkingSettings { pub username: String, pub servers: Vec, pub default_server: String, pub trusted_auth_servers: HashSet, } impl Default for NetworkingSettings { fn default() -> Self { Self { username: "".to_string(), servers: vec!["server.veloren.net".to_string()], default_server: "server.veloren.net".to_string(), trusted_auth_servers: ["https://auth.veloren.net"] .iter() .map(|s| s.to_string()) .collect(), } } } /// `Log` stores whether we should create a log file #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct Log { // Whether to create a log file or not. // Default is to create one. pub log_to_file: bool, // The path on which the logs will be stored pub logs_path: PathBuf, } impl Default for Log { fn default() -> Self { // Chooses a path to store the logs by the following order: // - The VOXYGEN_LOGS environment variable // - The ProjectsDirs data local directory // This function is only called if there isn't already an entry in the settings // file. However, the VOXYGEN_LOGS environment variable always overrides // the log file path if set. let logs_path = std::env::var_os("VOXYGEN_LOGS") .map(PathBuf::from) .unwrap_or_else(|| { let mut path = voxygen_data_dir(); path.push("logs"); path }); Self { log_to_file: true, logs_path, } } } #[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq)] pub enum Fps { Max(u32), Unlimited, } pub fn get_fps(max_fps: Fps) -> u32 { match max_fps { Fps::Max(x) => x, Fps::Unlimited => u32::MAX, } } impl fmt::Display for Fps { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Fps::Max(x) => write!(f, "{}", x), Fps::Unlimited => write!(f, "Unlimited"), } } } /// `GraphicsSettings` contains settings related to framerate and in-game /// visuals. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct GraphicsSettings { pub view_distance: u32, pub sprite_render_distance: u32, pub particles_enabled: bool, pub figure_lod_render_distance: u32, pub max_fps: Fps, pub fov: u16, pub gamma: f32, pub exposure: f32, pub ambiance: f32, pub render_mode: RenderMode, pub window_size: [u16; 2], pub fullscreen: FullScreenSettings, pub lod_detail: u32, } impl Default for GraphicsSettings { fn default() -> Self { Self { view_distance: 10, sprite_render_distance: 100, particles_enabled: true, figure_lod_render_distance: 300, max_fps: Fps::Max(60), fov: 70, gamma: 1.0, exposure: 1.0, ambiance: 10.0, render_mode: RenderMode::default(), window_size: [1280, 720], fullscreen: FullScreenSettings::default(), lod_detail: 250, } } } #[derive(Clone, Debug, Serialize, Deserialize)] pub enum AudioOutput { /// Veloren's audio system wont work on some systems, /// so you can use this to disable it, and allow the /// game to function // If this option is disabled, functions in the rodio // library MUST NOT be called. Off, #[serde(other)] Automatic, } impl AudioOutput { pub fn is_enabled(&self) -> bool { !matches!(self, Self::Off) } } /// `AudioSettings` controls the volume of different audio subsystems and which /// device is used. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct AudioSettings { pub master_volume: f32, pub music_volume: f32, pub sfx_volume: f32, pub max_sfx_channels: usize, /// Audio Device that Voxygen will use to play audio. pub output: AudioOutput, } impl Default for AudioSettings { fn default() -> Self { Self { master_volume: 1.0, music_volume: 0.4, sfx_volume: 0.6, max_sfx_channels: 30, output: AudioOutput::Automatic, } } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct LanguageSettings { pub selected_language: String, } impl Default for LanguageSettings { fn default() -> Self { Self { selected_language: i18n::REFERENCE_LANG.to_string(), } } } /// `Settings` contains everything that can be configured in the settings.ron /// file. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct Settings { pub controls: ControlSettings, pub interface: InterfaceSettings, pub gameplay: GameplaySettings, pub networking: NetworkingSettings, pub log: Log, pub graphics: GraphicsSettings, pub audio: AudioSettings, pub show_disclaimer: bool, pub send_logon_commands: bool, // TODO: Remove at a later date, for dev testing pub logon_commands: Vec, pub language: LanguageSettings, pub screenshots_path: PathBuf, pub controller: GamepadSettings, } impl Default for Settings { fn default() -> Self { let user_dirs = UserDirs::new().expect("System's $HOME directory path not found!"); // Chooses a path to store the screenshots by the following order: // - The VOXYGEN_SCREENSHOT environment variable // - The user's picture directory // - The executable's directory // This only selects if there isn't already an entry in the settings file let screenshots_path = std::env::var_os("VOXYGEN_SCREENSHOT") .map(PathBuf::from) .or_else(|| user_dirs.picture_dir().map(|dir| dir.join("veloren"))) .or_else(|| { std::env::current_exe() .ok() .and_then(|dir| dir.parent().map(PathBuf::from)) }) .expect("Couldn't choose a place to store the screenshots"); Settings { controls: ControlSettings::default(), interface: InterfaceSettings::default(), gameplay: GameplaySettings::default(), networking: NetworkingSettings::default(), log: Log::default(), graphics: GraphicsSettings::default(), audio: AudioSettings::default(), show_disclaimer: true, send_logon_commands: false, logon_commands: Vec::new(), language: LanguageSettings::default(), screenshots_path, controller: GamepadSettings::default(), } } } impl Settings { pub fn load() -> Self { let path = Self::get_settings_path(); if let Ok(file) = fs::File::open(&path) { match ron::de::from_reader::<_, Self>(file) { Ok(mut s) => { // Override the logs path if it is explicitly set using the VOXYGEN_LOGS // environment variable. This is needed to support package managers that enforce // strict application confinement (e.g. snap). In fact, the veloren snap package // relies on this environment variable to be respected in // order to communicate a path where the snap package is // allowed to write to. if let Some(logs_path_override) = std::env::var_os("VOXYGEN_LOGS").map(PathBuf::from) { s.log.logs_path = logs_path_override; } return s; }, Err(e) => { warn!(?e, "Failed to parse setting file! Fallback to default."); // Rename the corrupted settings file let mut new_path = path.to_owned(); new_path.pop(); new_path.push("settings.invalid.ron"); if let Err(e) = std::fs::rename(&path, &new_path) { warn!(?e, ?path, ?new_path, "Failed to rename settings file."); } }, } } // This is reached if either: // - The file can't be opened (presumably it doesn't exist) // - Or there was an error parsing the file let default_settings = Self::default(); default_settings.save_to_file_warn(); default_settings } pub fn save_to_file_warn(&self) { if let Err(e) = self.save_to_file() { warn!(?e, "Failed to save settings"); } } pub fn save_to_file(&self) -> std::io::Result<()> { let path = Self::get_settings_path(); if let Some(dir) = path.parent() { fs::create_dir_all(dir)?; } let ron = ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default()).unwrap(); fs::write(path, ron.as_bytes()) } pub fn get_settings_path() -> PathBuf { if let Some(path) = std::env::var_os("VOXYGEN_CONFIG") { let settings = PathBuf::from(&path).join("settings.ron"); if settings.exists() || settings.parent().map(|x| x.exists()).unwrap_or(false) { return settings; } warn!(?path, "VOXYGEN_CONFIG points to invalid path."); } let mut path = voxygen_data_dir(); path.push("settings.ron"); path } } pub fn voxygen_data_dir() -> PathBuf { // Note: since voxygen is technically a lib we made need to lift this up to // run.rs let mut path = common_base::userdata_dir_workspace!(); path.push("voxygen"); path }