mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
840 lines
30 KiB
Rust
840 lines
30 KiB
Rust
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<GameInput, KeyMouse>,
|
|
}
|
|
|
|
impl From<ControlSettings> for ControlSettingsSerde {
|
|
fn from(control_settings: ControlSettings) -> Self {
|
|
let mut user_bindings: HashMap<GameInput, KeyMouse> = 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<GameInput, KeyMouse>,
|
|
pub inverse_keybindings: HashMap<KeyMouse, HashSet<GameInput>>, // used in event loop
|
|
}
|
|
|
|
impl From<ControlSettingsSerde> 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<KeyMouse> {
|
|
self.keybindings.get(&game_input).copied()
|
|
}
|
|
|
|
pub fn get_associated_game_inputs(&self, key_mouse: &KeyMouse) -> Option<&HashSet<GameInput>> {
|
|
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<crate::controller::Axis, f32>,
|
|
pub button_deadzones: HashMap<crate::controller::AnalogButton, f32>,
|
|
pub mouse_emulation_sensitivity: u32,
|
|
pub inverted_axes: Vec<crate::controller::Axis>,
|
|
}
|
|
|
|
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<f64>,
|
|
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<String>,
|
|
pub default_server: String,
|
|
pub trusted_auth_servers: HashSet<String>,
|
|
}
|
|
|
|
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<String>,
|
|
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
|
|
}
|