Carbonhell/keybindings

This commit is contained in:
Carbonhell 2020-04-08 17:36:37 +00:00 committed by Songtronix
parent 611359e52f
commit 198c875559
8 changed files with 493 additions and 508 deletions

View File

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added daily Mac builds - Added daily Mac builds
- Allow spawning individual pet species, not just generic body kinds. - Allow spawning individual pet species, not just generic body kinds.
- Configurable fonts - Configurable fonts
- Configurable keybindings from the Controls menu
- Tanslation status tracking - Tanslation status tracking
- Added gamma setting - Added gamma setting
- Added new orc hairstyles - Added new orc hairstyles

View File

@ -250,90 +250,7 @@ Enjoy your stay in the World of Veloren."#,
"hud.settings.sound_effect_volume": "Sound Effects Volume", "hud.settings.sound_effect_volume": "Sound Effects Volume",
"hud.settings.audio_device": "Audio Device", "hud.settings.audio_device": "Audio Device",
// Control list "hud.settings.awaitingkey": "Press a key...",
"hud.settings.control_names": r#"Free Cursor
Toggle Help Window
Toggle Interface
Toggle FPS and Debug Info
Take Screenshot
Toggle Nametags
Toggle Fullscreen
Move Forward
Move Left
Move Right
Move Backwards
Jump
Glider
Dodge
Roll
Climb
Climb down
Auto Walk
Sheathe/Draw Weapons
Put on/Remove Helmet
Sit
Mount
Interact
Basic Attack
Secondary Attack/Block/Aim
Skillbar Slot 1
Skillbar Slot 2
Skillbar Slot 3
Skillbar Slot 4
Skillbar Slot 5
Skillbar Slot 6
Skillbar Slot 7
Skillbar Slot 8
Skillbar Slot 9
Skillbar Slot 10
Pause Menu
Settings
Social
Map
Spellbook
Character
Questlog
Bag
Send Chat Message
Scroll Chat
Free look
Chat commands:
/alias [Name] - Change your Chat Name
/tp [Name] - Teleports you to another player
/jump <dx> <dy> <dz> - Offset your position
/goto <x> <y> <z> - Teleport to a position
/kill - Kill yourself
/pig - Spawn pig NPC
/wolf - Spawn wolf NPC
/help - Display chat commands"#,
"hud.social": "Social", "hud.social": "Social",
"hud.social.online": "Online", "hud.social.online": "Online",
@ -348,6 +265,48 @@ Chat commands:
/// End HUD section /// End HUD section
/// Start GameInput section
"gameinput.primary": "Basic Attack",
"gameinput.secondary": "Secondary Attack/Block/Aim",
"gameinput.ability3": "Hotbar Slot 1",
"gameinput.swaploadout": "Swap Loadout",
"gameinput.togglecursor": "Free Cursor",
"gameinput.help": "Toggle Help Window",
"gameinput.toggleinterface": "Toggle Interface",
"gameinput.toggledebug": "Toggle FPS and Debug Info",
"gameinput.screenshot": "Take Screenshot",
"gameinput.toggleingameui": "Toggle Nametags",
"gameinput.fullscreen": "Toggle Fullscreen",
"gameinput.moveforward": "Move Forward",
"gameinput.moveleft": "Move Left",
"gameinput.moveright": "Move Right",
"gameinput.moveback": "Move Backwards",
"gameinput.jump": "Jump",
"gameinput.glide": "Glider",
"gameinput.roll": "Roll",
"gameinput.climb": "Climb",
"gameinput.climbdown": "Climb Down",
"gameinput.wallleap": "Wall Leap",
"gameinput.mount": "Mount",
"gameinput.enter": "Enter",
"gameinput.command": "Command",
"gameinput.escape": "Escape",
"gameinput.map": "Map",
"gameinput.bag": "Bag",
"gameinput.social": "Social",
"gameinput.sit": "Sit",
"gameinput.spellbook": "Spell Book",
"gameinput.settings": "Settings",
"gameinput.respawn": "Respawn",
"gameinput.charge": "Charge",
"gameinput.togglewield": "Toggle Wield",
"gameinput.interact": "Interact",
"gameinput.freelook": "Free look behavior",
/// End GameInput section
/// Start chracter selection section /// Start chracter selection section
"char_selection.delete_permanently": "Permanently delete this Character?", "char_selection.delete_permanently": "Permanently delete this Character?",
"char_selection.change_server": "Change Server", "char_selection.change_server": "Change Server",

View File

@ -237,6 +237,7 @@ pub enum Event {
Logout, Logout,
Quit, Quit,
ChangeLanguage(LanguageMetadata), ChangeLanguage(LanguageMetadata),
ChangeBinding(GameInput),
ChangeFreeLookBehavior(PressBehavior), ChangeFreeLookBehavior(PressBehavior),
} }
@ -1494,66 +1495,70 @@ impl Hud {
.set(self.ids.num_figures, ui_widgets); .set(self.ids.num_figures, ui_widgets);
// Help Window // Help Window
Text::new( if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
&self Text::new(
.voxygen_i18n &self
.get("hud.press_key_to_toggle_keybindings_fmt") .voxygen_i18n
.replace( .get("hud.press_key_to_toggle_keybindings_fmt")
"{key}", .replace("{key}", help_key.to_string().as_str()),
&format!("{:?}", global_state.settings.controls.help), )
), .color(TEXT_COLOR)
) .down_from(self.ids.num_figures, 5.0)
.color(TEXT_COLOR) .font_id(self.fonts.cyri.conrod_id)
.down_from(self.ids.num_figures, 5.0) .font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id) .set(self.ids.help_info, ui_widgets);
.font_size(self.fonts.cyri.scale(14)) }
.set(self.ids.help_info, ui_widgets);
// Info about Debug Shortcut // Info about Debug Shortcut
Text::new( if let Some(toggle_debug_key) = global_state
&self .settings
.voxygen_i18n .controls
.get("hud.press_key_to_toggle_debug_info_fmt") .get_binding(GameInput::ToggleDebug)
.replace( {
"{key}", Text::new(
&format!("{:?}", global_state.settings.controls.toggle_debug), &self
), .voxygen_i18n
) .get("hud.press_key_to_toggle_debug_info_fmt")
.color(TEXT_COLOR) .replace("{key}", toggle_debug_key.to_string().as_str()),
.down_from(self.ids.help_info, 5.0) )
.font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR)
.font_size(self.fonts.cyri.scale(14)) .down_from(self.ids.help_info, 5.0)
.set(self.ids.debug_info, ui_widgets); .font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(14))
.set(self.ids.debug_info, ui_widgets);
}
} else { } else {
// Help Window // Help Window
Text::new( if let Some(help_key) = global_state.settings.controls.get_binding(GameInput::Help) {
&self Text::new(
.voxygen_i18n &self
.get("hud.press_key_to_show_keybindings_fmt") .voxygen_i18n
.replace( .get("hud.press_key_to_show_keybindings_fmt")
"{key}", .replace("{key}", help_key.to_string().as_str()),
&format!("{:?}", global_state.settings.controls.help), )
), .color(TEXT_COLOR)
) .top_left_with_margins_on(ui_widgets.window, 5.0, 5.0)
.color(TEXT_COLOR) .font_id(self.fonts.cyri.conrod_id)
.top_left_with_margins_on(ui_widgets.window, 5.0, 5.0) .font_size(self.fonts.cyri.scale(16))
.font_id(self.fonts.cyri.conrod_id) .set(self.ids.help_info, ui_widgets);
.font_size(self.fonts.cyri.scale(16)) }
.set(self.ids.help_info, ui_widgets);
// Info about Debug Shortcut // Info about Debug Shortcut
Text::new( if let Some(toggle_debug_key) = global_state
&self .settings
.voxygen_i18n .controls
.get("hud.press_key_to_show_debug_info_fmt") .get_binding(GameInput::ToggleDebug)
.replace( {
"{key}", Text::new(
&format!("{:?}", global_state.settings.controls.toggle_debug), &self
), .voxygen_i18n
) .get("hud.press_key_to_show_debug_info_fmt")
.color(TEXT_COLOR) .replace("{key}", toggle_debug_key.to_string().as_str()),
.down_from(self.ids.help_info, 5.0) )
.font_id(self.fonts.cyri.conrod_id) .color(TEXT_COLOR)
.font_size(self.fonts.cyri.scale(12)) .down_from(self.ids.help_info, 5.0)
.set(self.ids.debug_info, ui_widgets); .font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(12))
.set(self.ids.debug_info, ui_widgets);
}
} }
// Help Text // Help Text
@ -1822,6 +1827,9 @@ impl Hud {
settings_window::Event::AdjustWindowSize(new_size) => { settings_window::Event::AdjustWindowSize(new_size) => {
events.push(Event::AdjustWindowSize(new_size)); events.push(Event::AdjustWindowSize(new_size));
}, },
settings_window::Event::ChangeBinding(game_input) => {
events.push(Event::ChangeBinding(game_input));
},
settings_window::Event::ChangeFreeLookBehavior(behavior) => { settings_window::Event::ChangeFreeLookBehavior(behavior) => {
events.push(Event::ChangeFreeLookBehavior(behavior)); events.push(Event::ChangeFreeLookBehavior(behavior));
}, },

View File

@ -6,12 +6,15 @@ use crate::{
i18n::{list_localizations, LanguageMetadata, VoxygenLocalization}, i18n::{list_localizations, LanguageMetadata, VoxygenLocalization},
render::{AaMode, CloudMode, FluidMode}, render::{AaMode, CloudMode, FluidMode},
ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton}, ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton},
window::GameInput,
GlobalState, GlobalState,
}; };
use conrod_core::{ use conrod_core::{
color, color,
position::Relative,
widget::{self, Button, DropDownList, Image, Rectangle, Scrollbar, Text}, widget::{self, Button, DropDownList, Image, Rectangle, Scrollbar, Text},
widget_ids, Color, Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
WidgetCommon,
}; };
const FPS_CHOICES: [u32; 11] = [15, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500]; const FPS_CHOICES: [u32; 11] = [15, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500];
@ -27,8 +30,9 @@ widget_ids! {
settings_r, settings_r,
settings_l, settings_l,
settings_scrollbar, settings_scrollbar,
controls_text, controls_texts[],
controls_controls, controls_buttons[],
controls_alignment_rectangle,
button_help, button_help,
button_help2, button_help2,
show_help_label, show_help_label,
@ -221,6 +225,7 @@ pub enum Event {
SctPlayerBatch(bool), SctPlayerBatch(bool),
SctDamageBatch(bool), SctDamageBatch(bool),
ChangeLanguage(LanguageMetadata), ChangeLanguage(LanguageMetadata),
ChangeBinding(GameInput),
ChangeFreeLookBehavior(PressBehavior), ChangeFreeLookBehavior(PressBehavior),
} }
@ -1328,142 +1333,86 @@ impl<'a> Widget for SettingsWindow<'a> {
// Contents // Contents
if let SettingsTab::Controls = self.show.settings_tab { if let SettingsTab::Controls = self.show.settings_tab {
let controls = &self.global_state.settings.controls; let controls = &self.global_state.settings.controls;
Text::new(&self.localized_strings.get("hud.settings.control_names")) if controls.keybindings.len() > state.ids.controls_texts.len()
.color(TEXT_COLOR) || controls.keybindings.len() > state.ids.controls_buttons.len()
.top_left_with_margins_on(state.ids.settings_content, 5.0, 5.0) {
.font_id(self.fonts.cyri.conrod_id) state.update(|s| {
.font_size(self.fonts.cyri.scale(18)) s.ids
.set(state.ids.controls_text, ui); .controls_texts
// TODO: Replace with buttons that show actual keybinds and allow the user to .resize(controls.keybindings.len(), &mut ui.widget_id_generator());
// change them. s.ids
#[rustfmt::skip] .controls_buttons
Text::new(&format!( .resize(controls.keybindings.len(), &mut ui.widget_id_generator());
"{}\n\ });
{}\n\ }
{}\n\ // Used for sequential placement in a flow-down pattern
{}\n\ let mut previous_text_id = None;
{}\n\ let mut keybindings_vec: Vec<&GameInput> = controls.keybindings.keys().collect();
{}\n\ keybindings_vec.sort();
{}\n\ // Loop all existing keybindings and the ids for text and button widgets
\n\ for (game_input, (&text_id, &button_id)) in keybindings_vec.into_iter().zip(
\n\ state
{}\n\ .ids
{}\n\ .controls_texts
{}\n\ .iter()
{}\n\ .zip(state.ids.controls_buttons.iter()),
\n\ ) {
{}\n\ if let Some(key) = controls.get_binding(*game_input) {
\n\ let loc_key = self
{}\n\ .localized_strings
\n\ .get(game_input.get_localization_key());
{}\n\ let key_string = match self.global_state.window.remapping_keybindings {
\n\ Some(game_input_binding) => {
{}\n\ if *game_input == game_input_binding {
\n\ String::from(self.localized_strings.get("hud.settings.awaitingkey"))
{}\n\ } else {
\n\ key.to_string()
{}\n\ }
\n\ },
{}\n\ None => key.to_string(),
\n\ };
{}\n\
\n\ let text_widget = Text::new(loc_key)
{}\n\ .color(TEXT_COLOR)
\n\ .font_id(self.fonts.cyri.conrod_id)
{}\n\ .font_size(self.fonts.cyri.scale(18));
\n\ let button_widget = Button::new()
{}\n\ .label(&key_string)
\n\ .label_color(TEXT_COLOR)
{}\n\ .label_font_id(self.fonts.cyri.conrod_id)
\n\ .label_font_size(self.fonts.cyri.scale(15))
\n\ .w(150.0)
{}\n\ .rgba(0.0, 0.0, 0.0, 0.0)
{}\n\ .border_rgba(0.0, 0.0, 0.0, 255.0)
\n\ .label_y(Relative::Scalar(3.0));
\n\ // Place top-left if it's the first text, else under the previous one
{}\n\ let text_widget = match previous_text_id {
{}\n\ None => text_widget.top_left_with_margins_on(
{}\n\ state.ids.settings_content,
{}\n\ 10.0,
{}\n\ 5.0,
{}\n\ ),
{}\n\ Some(prev_id) => text_widget.down_from(prev_id, 10.0),
{}\n\ };
{}\n\ let text_width = text_widget.get_w(ui).unwrap_or(0.0);
{}\n\ text_widget.set(text_id, ui);
\n\ if button_widget
\n\ .right_from(text_id, 350.0 - text_width)
{}\n\ .set(button_id, ui)
{}\n\ .was_clicked()
{}\n\ {
{}\n\ events.push(Event::ChangeBinding(*game_input));
{}\n\ }
{}\n\ // Set the previous id to the current one for the next cycle
\n\ previous_text_id = Some(text_id);
\n\ }
\n\ }
{}\n\ // Add an empty text widget to simulate some bottom margin, because conrod sucks
{}\n\ if let Some(prev_id) = previous_text_id {
\n\ Rectangle::fill_with([1.0, 1.0], color::TRANSPARENT)
\n\ .down_from(prev_id, 10.0)
{}\n\ .set(state.ids.controls_alignment_rectangle, ui);
\n\ }
\n\
\n\
\n\
",
controls.toggle_cursor,
controls.help,
controls.toggle_interface,
controls.toggle_debug,
controls.screenshot,
controls.toggle_ingame_ui,
controls.fullscreen,
controls.move_forward,
controls.move_left,
controls.move_back,
controls.move_right,
controls.jump,
controls.glide,
"??", // Dodge
controls.roll,
controls.climb,
controls.climb_down,
"??", // Auto Walk
controls.toggle_wield,
"??", // Put on/Remove Helmet
controls.sit,
controls.mount,
controls.interact,
controls.primary,
controls.secondary,
"1", // Skillbar Slot 1
"2", // Skillbar Slot 2
"3", // Skillbar Slot 3
"4", // Skillbar Slot 4
"5", // Skillbar Slot 5
"6", // Skillbar Slot 6
"7", // Skillbar Slot 7
"8", // Skillbar Slot 8
"9", // Skillbar Slot 9
"0", // Skillbar Slot 10
controls.escape,
controls.settings,
controls.social,
controls.map,
controls.spellbook,
//controls.character_window,
//controls.quest_log,
controls.bag,
controls.enter,
"Mouse Wheel", // Scroll chat
controls.free_look
))
.color(TEXT_COLOR)
.right_from(state.ids.controls_text, 0.0)
.font_id(self.fonts.cyri.conrod_id)
.font_size(self.fonts.cyri.scale(18))
.set(state.ids.controls_controls, ui);
} }
// 4) Video Tab ----------------------------------- // 4) Video Tab -----------------------------------

View File

@ -5,6 +5,7 @@ use super::{
use crate::{ use crate::{
i18n::{i18n_asset_key, VoxygenLocalization}, i18n::{i18n_asset_key, VoxygenLocalization},
ui::fonts::ConrodVoxygenFonts, ui::fonts::ConrodVoxygenFonts,
window::GameInput,
GlobalState, GlobalState,
}; };
use common::{ use common::{
@ -300,36 +301,45 @@ impl<'a> Widget for Skillbar<'a> {
.set(state.ids.level_down, ui); .set(state.ids.level_down, ui);
// Death message // Death message
if self.stats.is_dead { if self.stats.is_dead {
Text::new(&localized_strings.get("hud.you_died")) if let Some(key) = self
.middle_of(ui.window) .global_state
.font_size(self.fonts.cyri.scale(50)) .settings
.controls
.get_binding(GameInput::Respawn)
{
Text::new(&localized_strings.get("hud.you_died"))
.middle_of(ui.window)
.font_size(self.fonts.cyri.scale(50))
.font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.set(state.ids.death_message_1_bg, ui);
Text::new(
&localized_strings
.get("hud.press_key_to_respawn")
.replace("{key}", key.to_string().as_str()),
)
.mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0)
.font_size(self.fonts.cyri.scale(30))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) .color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.set(state.ids.death_message_1_bg, ui); .set(state.ids.death_message_2_bg, ui);
Text::new(&localized_strings.get("hud.press_key_to_respawn").replace( Text::new(&localized_strings.get("hud.you_died"))
"{key}", .bottom_left_with_margins_on(state.ids.death_message_1_bg, 2.0, 2.0)
&format!("{:?}", self.global_state.settings.controls.respawn), .font_size(self.fonts.cyri.scale(50))
)) .font_id(self.fonts.cyri.conrod_id)
.mid_bottom_with_margin_on(state.ids.death_message_1_bg, -120.0) .color(CRITICAL_HP_COLOR)
.font_size(self.fonts.cyri.scale(30)) .set(state.ids.death_message_1, ui);
.font_id(self.fonts.cyri.conrod_id) Text::new(
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0)) &localized_strings
.set(state.ids.death_message_2_bg, ui); .get("hud.press_key_to_respawn")
Text::new(&localized_strings.get("hud.you_died")) .replace("{key}", key.to_string().as_str()),
.bottom_left_with_margins_on(state.ids.death_message_1_bg, 2.0, 2.0) )
.font_size(self.fonts.cyri.scale(50)) .bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0)
.font_size(self.fonts.cyri.scale(30))
.font_id(self.fonts.cyri.conrod_id) .font_id(self.fonts.cyri.conrod_id)
.color(CRITICAL_HP_COLOR) .color(CRITICAL_HP_COLOR)
.set(state.ids.death_message_1, ui); .set(state.ids.death_message_2, ui);
Text::new(&localized_strings.get("hud.press_key_to_respawn").replace( }
"{key}",
&format!("{:?}", self.global_state.settings.controls.respawn),
))
.bottom_left_with_margins_on(state.ids.death_message_2_bg, 2.0, 2.0)
.font_size(self.fonts.cyri.scale(30))
.font_id(self.fonts.cyri.conrod_id)
.color(CRITICAL_HP_COLOR)
.set(state.ids.death_message_2, ui);
} }
// Experience-Bar // Experience-Bar
match self.global_state.settings.gameplay.xp_bar { match self.global_state.settings.gameplay.xp_bar {

View File

@ -713,6 +713,9 @@ impl PlayState for SessionState {
global_state.settings.graphics.window_size = new_size; global_state.settings.graphics.window_size = new_size;
global_state.settings.save_to_file_warn(); global_state.settings.save_to_file_warn();
}, },
HudEvent::ChangeBinding(game_input) => {
global_state.window.set_keybinding_mode(game_input);
},
HudEvent::ChangeFreeLookBehavior(behavior) => { HudEvent::ChangeFreeLookBehavior(behavior) => {
global_state.settings.gameplay.free_look_behavior = behavior; global_state.settings.gameplay.free_look_behavior = behavior;
}, },

View File

@ -3,7 +3,7 @@ use crate::{
i18n, i18n,
render::{AaMode, CloudMode, FluidMode}, render::{AaMode, CloudMode, FluidMode},
ui::ScaleMode, ui::ScaleMode,
window::KeyMouse, window::{GameInput, KeyMouse},
}; };
use directories::{ProjectDirs, UserDirs}; use directories::{ProjectDirs, UserDirs};
use glutin::{MouseButton, VirtualKeyCode}; use glutin::{MouseButton, VirtualKeyCode};
@ -12,46 +12,47 @@ use log::warn;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::{fs, io::prelude::*, path::PathBuf}; use std::{fs, io::prelude::*, path::PathBuf};
// 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. /// `ControlSettings` contains keybindings.
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)] #[serde(from = "ControlSettingsSerde", into = "ControlSettingsSerde")]
pub struct ControlSettings { pub struct ControlSettings {
pub primary: KeyMouse, pub keybindings: HashMap<GameInput, KeyMouse>,
pub secondary: KeyMouse, pub inverse_keybindings: HashMap<KeyMouse, HashSet<GameInput>>, // used in event loop
pub ability3: KeyMouse, }
pub toggle_cursor: KeyMouse,
pub escape: KeyMouse, impl From<ControlSettingsSerde> for ControlSettings {
pub enter: KeyMouse, fn from(control_serde: ControlSettingsSerde) -> Self {
pub command: KeyMouse, let user_keybindings = control_serde.keybindings;
pub move_forward: KeyMouse, let mut control_settings = ControlSettings::default();
pub move_left: KeyMouse, for (k, v) in user_keybindings {
pub move_back: KeyMouse, control_settings.modify_binding(k, v);
pub move_right: KeyMouse, }
pub jump: KeyMouse, control_settings
pub sit: KeyMouse, }
pub glide: KeyMouse,
pub climb: KeyMouse,
pub climb_down: KeyMouse,
pub wall_leap: KeyMouse,
pub mount: KeyMouse,
pub map: KeyMouse,
pub bag: KeyMouse,
pub social: KeyMouse,
pub spellbook: KeyMouse,
pub settings: KeyMouse,
pub help: KeyMouse,
pub toggle_interface: KeyMouse,
pub toggle_debug: KeyMouse,
pub fullscreen: KeyMouse,
pub screenshot: KeyMouse,
pub toggle_ingame_ui: KeyMouse,
pub roll: KeyMouse,
pub respawn: KeyMouse,
pub interact: KeyMouse,
pub toggle_wield: KeyMouse,
pub swap_loadout: KeyMouse,
pub charge: KeyMouse,
pub free_look: KeyMouse,
} }
/// Since Macbook trackpads lack middle click, on OS X we default to LShift /// Since Macbook trackpads lack middle click, on OS X we default to LShift
@ -64,46 +65,135 @@ const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Key(VirtualKeyCode::LShift);
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Mouse(MouseButton::Middle); 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);
}
pub fn default_binding(game_input: GameInput) -> KeyMouse {
// If a new GameInput is added, be sure to update ControlSettings::default()
// too!
match game_input {
GameInput::Primary => KeyMouse::Mouse(MouseButton::Left),
GameInput::Secondary => KeyMouse::Mouse(MouseButton::Right),
GameInput::ToggleCursor => KeyMouse::Key(VirtualKeyCode::Tab),
GameInput::Escape => KeyMouse::Key(VirtualKeyCode::Escape),
GameInput::Enter => 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::Glide => KeyMouse::Key(VirtualKeyCode::LShift),
GameInput::Climb => KeyMouse::Key(VirtualKeyCode::Space),
GameInput::ClimbDown => KeyMouse::Key(VirtualKeyCode::LControl),
GameInput::WallLeap => MIDDLE_CLICK_KEY,
GameInput::Mount => KeyMouse::Key(VirtualKeyCode::F),
GameInput::Map => KeyMouse::Key(VirtualKeyCode::M),
GameInput::Bag => KeyMouse::Key(VirtualKeyCode::B),
GameInput::Social => KeyMouse::Key(VirtualKeyCode::O),
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::Mouse(MouseButton::Right),
GameInput::ToggleWield => KeyMouse::Key(VirtualKeyCode::T),
GameInput::Charge => KeyMouse::Key(VirtualKeyCode::Key1),
GameInput::FreeLook => KeyMouse::Key(VirtualKeyCode::L),
GameInput::Ability3 => KeyMouse::Key(VirtualKeyCode::Key1),
GameInput::SwapLoadout => KeyMouse::Key(VirtualKeyCode::LAlt),
}
}
}
impl Default for ControlSettings { impl Default for ControlSettings {
fn default() -> Self { fn default() -> Self {
Self { let mut new_settings = Self {
primary: KeyMouse::Mouse(MouseButton::Left), keybindings: HashMap::new(),
secondary: KeyMouse::Mouse(MouseButton::Right), inverse_keybindings: HashMap::new(),
ability3: KeyMouse::Key(VirtualKeyCode::Key1), };
toggle_cursor: KeyMouse::Key(VirtualKeyCode::Tab), // Sets the initial keybindings for those GameInputs. If a new one is created in
escape: KeyMouse::Key(VirtualKeyCode::Escape), // future, you'll have to update default_binding, and you should update this vec
enter: KeyMouse::Key(VirtualKeyCode::Return), // too.
command: KeyMouse::Key(VirtualKeyCode::Slash), let game_inputs = vec![
move_forward: KeyMouse::Key(VirtualKeyCode::W), GameInput::Primary,
move_left: KeyMouse::Key(VirtualKeyCode::A), GameInput::Secondary,
move_back: KeyMouse::Key(VirtualKeyCode::S), GameInput::ToggleCursor,
move_right: KeyMouse::Key(VirtualKeyCode::D), GameInput::MoveForward,
jump: KeyMouse::Key(VirtualKeyCode::Space), GameInput::MoveBack,
sit: KeyMouse::Key(VirtualKeyCode::K), GameInput::MoveLeft,
glide: KeyMouse::Key(VirtualKeyCode::LShift), GameInput::MoveRight,
climb: KeyMouse::Key(VirtualKeyCode::Space), GameInput::Jump,
climb_down: KeyMouse::Key(VirtualKeyCode::LControl), GameInput::Sit,
wall_leap: MIDDLE_CLICK_KEY, GameInput::Glide,
mount: KeyMouse::Key(VirtualKeyCode::F), GameInput::Climb,
map: KeyMouse::Key(VirtualKeyCode::M), GameInput::ClimbDown,
bag: KeyMouse::Key(VirtualKeyCode::B), GameInput::WallLeap,
social: KeyMouse::Key(VirtualKeyCode::O), GameInput::Mount,
spellbook: KeyMouse::Key(VirtualKeyCode::P), GameInput::Enter,
settings: KeyMouse::Key(VirtualKeyCode::N), GameInput::Command,
help: KeyMouse::Key(VirtualKeyCode::F1), GameInput::Escape,
toggle_interface: KeyMouse::Key(VirtualKeyCode::F2), GameInput::Map,
toggle_debug: KeyMouse::Key(VirtualKeyCode::F3), GameInput::Bag,
fullscreen: KeyMouse::Key(VirtualKeyCode::F11), GameInput::Social,
screenshot: KeyMouse::Key(VirtualKeyCode::F4), GameInput::Spellbook,
toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6), GameInput::Settings,
roll: MIDDLE_CLICK_KEY, GameInput::ToggleInterface,
respawn: KeyMouse::Key(VirtualKeyCode::Space), GameInput::Help,
interact: KeyMouse::Mouse(MouseButton::Right), GameInput::ToggleDebug,
toggle_wield: KeyMouse::Key(VirtualKeyCode::T), GameInput::Fullscreen,
swap_loadout: KeyMouse::Key(VirtualKeyCode::Q), GameInput::Screenshot,
charge: KeyMouse::Key(VirtualKeyCode::Key1), GameInput::ToggleIngameUi,
free_look: KeyMouse::Key(VirtualKeyCode::L), GameInput::Roll,
GameInput::Respawn,
GameInput::Interact,
GameInput::ToggleWield,
GameInput::Charge,
GameInput::FreeLook,
GameInput::Ability3,
GameInput::SwapLoadout,
];
for game_input in game_inputs {
new_settings.insert_binding(game_input, ControlSettings::default_binding(game_input));
} }
new_settings
} }
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
controller::*, controller::*,
render::{Renderer, WinColorFmt, WinDepthFmt}, render::{Renderer, WinColorFmt, WinDepthFmt},
settings::Settings, settings::{ControlSettings, Settings},
ui, Error, ui, Error,
}; };
use gilrs::{EventType, Gilrs}; use gilrs::{EventType, Gilrs};
@ -12,7 +12,7 @@ use std::fmt;
use vek::*; use vek::*;
/// Represents a key that the game recognises after input mapping. /// Represents a key that the game recognises after input mapping.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub enum GameInput { pub enum GameInput {
Primary, Primary,
Secondary, Secondary,
@ -34,8 +34,6 @@ pub enum GameInput {
Escape, Escape,
Map, Map,
Bag, Bag,
QuestLog,
CharacterWindow,
Social, Social,
Spellbook, Spellbook,
Settings, Settings,
@ -54,6 +52,49 @@ pub enum GameInput {
FreeLook, FreeLook,
} }
impl GameInput {
pub fn get_localization_key(&self) -> &str {
match *self {
GameInput::Primary => "gameinput.primary",
GameInput::Secondary => "gameinput.secondary",
GameInput::ToggleCursor => "gameinput.togglecursor",
GameInput::MoveForward => "gameinput.moveforward",
GameInput::MoveLeft => "gameinput.moveleft",
GameInput::MoveRight => "gameinput.moveright",
GameInput::MoveBack => "gameinput.moveback",
GameInput::Jump => "gameinput.jump",
GameInput::Sit => "gameinput.sit",
GameInput::Glide => "gameinput.glide",
GameInput::Climb => "gameinput.climb",
GameInput::ClimbDown => "gameinput.climbdown",
GameInput::WallLeap => "gameinput.wallleap",
GameInput::Mount => "gameinput.mount",
GameInput::Enter => "gameinput.enter",
GameInput::Command => "gameinput.command",
GameInput::Escape => "gameinput.escape",
GameInput::Map => "gameinput.map",
GameInput::Bag => "gameinput.bag",
GameInput::Social => "gameinput.social",
GameInput::Spellbook => "gameinput.spellbook",
GameInput::Settings => "gameinput.settings",
GameInput::ToggleInterface => "gameinput.toggleinterface",
GameInput::Help => "gameinput.help",
GameInput::ToggleDebug => "gameinput.toggledebug",
GameInput::Fullscreen => "gameinput.fullscreen",
GameInput::Screenshot => "gameinput.screenshot",
GameInput::ToggleIngameUi => "gameinput.toggleingameui",
GameInput::Roll => "gameinput.roll",
GameInput::Respawn => "gameinput.respawn",
GameInput::Interact => "gameinput.interact",
GameInput::ToggleWield => "gameinput.togglewield",
GameInput::Charge => "gameinput.charge",
GameInput::FreeLook => "gameinput.freelook",
GameInput::Ability3 => "gameinput.ability3",
GameInput::SwapLoadout => "gameinput.swaploadout",
}
}
}
/// Represents a key that the game menus recognise after input mapping /// Represents a key that the game menus recognise after input mapping
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum MenuInput { pub enum MenuInput {
@ -319,8 +360,8 @@ pub struct Window {
pub mouse_y_inversion: bool, pub mouse_y_inversion: bool,
fullscreen: bool, fullscreen: bool,
needs_refresh_resize: bool, needs_refresh_resize: bool,
key_map: HashMap<KeyMouse, Vec<GameInput>>,
keypress_map: HashMap<GameInput, glutin::ElementState>, keypress_map: HashMap<GameInput, glutin::ElementState>,
pub remapping_keybindings: Option<GameInput>,
supplement_events: Vec<Event>, supplement_events: Vec<Event>,
focused: bool, focused: bool,
gilrs: Option<Gilrs>, gilrs: Option<Gilrs>,
@ -355,116 +396,6 @@ impl Window {
) )
.map_err(|err| Error::BackendError(Box::new(err)))?; .map_err(|err| Error::BackendError(Box::new(err)))?;
let mut map: HashMap<_, Vec<_>> = HashMap::new();
map.entry(settings.controls.primary)
.or_default()
.push(GameInput::Primary);
map.entry(settings.controls.secondary)
.or_default()
.push(GameInput::Secondary);
map.entry(settings.controls.ability3)
.or_default()
.push(GameInput::Ability3);
map.entry(settings.controls.toggle_cursor)
.or_default()
.push(GameInput::ToggleCursor);
map.entry(settings.controls.escape)
.or_default()
.push(GameInput::Escape);
map.entry(settings.controls.enter)
.or_default()
.push(GameInput::Enter);
map.entry(settings.controls.command)
.or_default()
.push(GameInput::Command);
map.entry(settings.controls.move_forward)
.or_default()
.push(GameInput::MoveForward);
map.entry(settings.controls.move_left)
.or_default()
.push(GameInput::MoveLeft);
map.entry(settings.controls.move_back)
.or_default()
.push(GameInput::MoveBack);
map.entry(settings.controls.move_right)
.or_default()
.push(GameInput::MoveRight);
map.entry(settings.controls.jump)
.or_default()
.push(GameInput::Jump);
map.entry(settings.controls.sit)
.or_default()
.push(GameInput::Sit);
map.entry(settings.controls.glide)
.or_default()
.push(GameInput::Glide);
map.entry(settings.controls.climb)
.or_default()
.push(GameInput::Climb);
map.entry(settings.controls.climb_down)
.or_default()
.push(GameInput::ClimbDown);
map.entry(settings.controls.wall_leap)
.or_default()
.push(GameInput::WallLeap);
map.entry(settings.controls.mount)
.or_default()
.push(GameInput::Mount);
map.entry(settings.controls.map)
.or_default()
.push(GameInput::Map);
map.entry(settings.controls.bag)
.or_default()
.push(GameInput::Bag);
map.entry(settings.controls.social)
.or_default()
.push(GameInput::Social);
map.entry(settings.controls.spellbook)
.or_default()
.push(GameInput::Spellbook);
map.entry(settings.controls.settings)
.or_default()
.push(GameInput::Settings);
map.entry(settings.controls.help)
.or_default()
.push(GameInput::Help);
map.entry(settings.controls.toggle_interface)
.or_default()
.push(GameInput::ToggleInterface);
map.entry(settings.controls.toggle_debug)
.or_default()
.push(GameInput::ToggleDebug);
map.entry(settings.controls.fullscreen)
.or_default()
.push(GameInput::Fullscreen);
map.entry(settings.controls.screenshot)
.or_default()
.push(GameInput::Screenshot);
map.entry(settings.controls.toggle_ingame_ui)
.or_default()
.push(GameInput::ToggleIngameUi);
map.entry(settings.controls.roll)
.or_default()
.push(GameInput::Roll);
map.entry(settings.controls.respawn)
.or_default()
.push(GameInput::Respawn);
map.entry(settings.controls.interact)
.or_default()
.push(GameInput::Interact);
map.entry(settings.controls.toggle_wield)
.or_default()
.push(GameInput::ToggleWield);
map.entry(settings.controls.swap_loadout)
.or_default()
.push(GameInput::SwapLoadout);
map.entry(settings.controls.charge)
.or_default()
.push(GameInput::Charge);
map.entry(settings.controls.free_look)
.or_default()
.push(GameInput::FreeLook);
let keypress_map = HashMap::new(); let keypress_map = HashMap::new();
let gilrs = match Gilrs::new() { let gilrs = match Gilrs::new() {
@ -510,8 +441,8 @@ impl Window {
mouse_y_inversion: settings.gameplay.mouse_y_inversion, mouse_y_inversion: settings.gameplay.mouse_y_inversion,
fullscreen: false, fullscreen: false,
needs_refresh_resize: false, needs_refresh_resize: false,
key_map: map,
keypress_map, keypress_map,
remapping_keybindings: None,
supplement_events: vec![], supplement_events: vec![],
focused: true, focused: true,
gilrs, gilrs,
@ -543,8 +474,9 @@ impl Window {
let cursor_grabbed = self.cursor_grabbed; let cursor_grabbed = self.cursor_grabbed;
let renderer = &mut self.renderer; let renderer = &mut self.renderer;
let window = &mut self.window; let window = &mut self.window;
let remapping_keybindings = &mut self.remapping_keybindings;
let focused = &mut self.focused; let focused = &mut self.focused;
let key_map = &self.key_map; let controls = &mut settings.controls;
let keypress_map = &mut self.keypress_map; let keypress_map = &mut self.keypress_map;
let pan_sensitivity = self.pan_sensitivity; let pan_sensitivity = self.pan_sensitivity;
let zoom_sensitivity = self.zoom_sensitivity; let zoom_sensitivity = self.zoom_sensitivity;
@ -577,23 +509,27 @@ impl Window {
}, },
glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)), glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)),
glutin::WindowEvent::MouseInput { button, state, .. } => { glutin::WindowEvent::MouseInput { button, state, .. } => {
if let (true, Some(game_inputs)) = if let Some(game_inputs) = Window::map_input(
(cursor_grabbed, key_map.get(&KeyMouse::Mouse(button))) KeyMouse::Mouse(button),
{ controls,
remapping_keybindings,
) {
for game_input in game_inputs { for game_input in game_inputs {
events.push(Event::InputUpdate( events.push(Event::InputUpdate(
*game_input, *game_input,
state == glutin::ElementState::Pressed, state == glutin::ElementState::Pressed,
)); ));
} }
events.push(Event::MouseButton(button, state));
} }
events.push(Event::MouseButton(button, state));
}, },
glutin::WindowEvent::KeyboardInput { input, .. } => match input.virtual_keycode glutin::WindowEvent::KeyboardInput { input, .. } => {
{ if let Some(key) = input.virtual_keycode {
Some(key) => { if let Some(game_inputs) = Window::map_input(
let game_inputs = key_map.get(&KeyMouse::Key(key)); KeyMouse::Key(key),
if let Some(game_inputs) = game_inputs { controls,
remapping_keybindings,
) {
for game_input in game_inputs { for game_input in game_inputs {
match game_input { match game_input {
GameInput::Fullscreen => { GameInput::Fullscreen => {
@ -631,9 +567,9 @@ impl Window {
} }
} }
} }
}, }
_ => {},
}, },
glutin::WindowEvent::Focused(state) => { glutin::WindowEvent::Focused(state) => {
*focused = state; *focused = state;
events.push(Event::Focused(state)); events.push(Event::Focused(state));
@ -1000,4 +936,33 @@ impl Window {
) { ) {
map.insert(input, state); map.insert(input, state);
} }
// Function used to handle Mouse and Key events. It first checks if we're in
// remapping mode for a specific GameInput. If we are, we modify the binding
// of that GameInput with the KeyMouse passed. Else, we return an iterator of
// the GameInputs for that KeyMouse.
fn map_input<'a>(
key_mouse: KeyMouse,
controls: &'a mut ControlSettings,
remapping: &mut Option<GameInput>,
) -> Option<impl Iterator<Item = &'a GameInput>> {
match *remapping {
Some(game_input) => {
controls.modify_binding(game_input, key_mouse);
*remapping = None;
None
},
None => {
if let Some(game_inputs) = controls.get_associated_game_inputs(&key_mouse) {
Some(game_inputs.iter())
} else {
None
}
},
}
}
pub fn set_keybinding_mode(&mut self, game_input: GameInput) {
self.remapping_keybindings = Some(game_input);
}
} }