1use crate::{
2 game_input::GameInput,
3 window::{KeyMouse, MenuInput},
4};
5use hashbrown::{HashMap, HashSet};
6use serde::{Deserialize, Serialize};
7use strum::IntoEnumIterator;
8use winit::{
9 event::MouseButton,
10 keyboard::{Key, NamedKey},
11};
12
13#[derive(Serialize, Deserialize)]
16#[serde(default)]
17struct ControlSettingsSerde {
18 keybindings: HashMap<GameInput, Option<KeyMouse>>,
19 menubindings: HashMap<MenuInput, Option<KeyMouse>>,
20}
21
22impl From<ControlSettings> for ControlSettingsSerde {
23 fn from(control_settings: ControlSettings) -> Self {
24 let mut user_bindings: HashMap<GameInput, Option<KeyMouse>> = HashMap::new();
25 for (k, v) in control_settings.keybindings {
28 if ControlSettings::default_binding(k) != v {
29 user_bindings.insert(k, v);
31 }
32 }
33
34 let mut menu_bindings: HashMap<MenuInput, Option<KeyMouse>> = HashMap::new();
35 for (k, v) in control_settings.menubindings {
36 if ControlSettings::default_menu_binding(k) != v {
37 menu_bindings.insert(k, v);
39 }
40 }
41
42 ControlSettingsSerde {
43 keybindings: user_bindings,
44 menubindings: menu_bindings,
45 }
46 }
47}
48
49impl Default for ControlSettingsSerde {
50 fn default() -> Self { ControlSettings::default().into() }
51}
52
53#[derive(Clone, Debug, Serialize, Deserialize)]
55#[serde(from = "ControlSettingsSerde", into = "ControlSettingsSerde")]
56pub struct ControlSettings {
57 pub keybindings: HashMap<GameInput, Option<KeyMouse>>,
58 pub inverse_keybindings: HashMap<KeyMouse, HashSet<GameInput>>, pub menubindings: HashMap<MenuInput, Option<KeyMouse>>,
60 pub inverse_menubindings: HashMap<KeyMouse, HashSet<MenuInput>>,
61}
62
63impl From<ControlSettingsSerde> for ControlSettings {
64 fn from(control_serde: ControlSettingsSerde) -> Self {
65 let user_keybindings = control_serde.keybindings;
66 let menu_bindings = control_serde.menubindings;
67 let mut control_settings = ControlSettings::default();
68 for (k, maybe_v) in user_keybindings {
69 match maybe_v {
70 Some(v) => control_settings.modify_binding(k, v),
71 None => control_settings.remove_binding(k),
72 }
73 }
74 for (k, maybe_v) in menu_bindings {
76 match maybe_v {
77 Some(v) => control_settings.modify_menu_binding(k, v),
78 None => control_settings.remove_menu_binding(k),
79 }
80 }
81 control_settings
82 }
83}
84
85#[cfg(target_os = "macos")]
91const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Key(Key::Named(NamedKey::Shift));
92#[cfg(not(target_os = "macos"))]
93const MIDDLE_CLICK_KEY: KeyMouse = KeyMouse::Mouse(MouseButton::Middle);
94
95impl ControlSettings {
96 pub fn remove_binding(&mut self, game_input: GameInput) {
97 if let Some(inverse) = self
98 .keybindings
99 .insert(game_input, None)
100 .flatten()
101 .and_then(|key_mouse| self.inverse_keybindings.get_mut(&key_mouse))
102 {
103 inverse.remove(&game_input);
104 }
105 }
106
107 pub fn remove_menu_binding(&mut self, menu_input: MenuInput) {
108 if let Some(inverse) = self
109 .menubindings
110 .insert(menu_input, None)
111 .flatten()
112 .and_then(|key_mouse| self.inverse_menubindings.get_mut(&key_mouse))
113 {
114 inverse.remove(&menu_input);
115 }
116 }
117
118 pub fn get_binding(&self, game_input: GameInput) -> Option<KeyMouse> {
119 self.keybindings.get(&game_input).cloned().flatten()
120 }
121
122 pub fn get_menu_binding(&self, menu_input: MenuInput) -> Option<KeyMouse> {
123 self.menubindings.get(&menu_input).cloned().flatten()
124 }
125
126 pub fn get_associated_game_inputs(&self, key_mouse: &KeyMouse) -> Option<&HashSet<GameInput>> {
127 self.inverse_keybindings.get(key_mouse)
128 }
129
130 pub fn get_associated_menu_inputs(&self, key_mouse: &KeyMouse) -> Option<&HashSet<MenuInput>> {
131 self.inverse_menubindings.get(key_mouse)
132 }
133
134 pub fn insert_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) {
135 self.keybindings.insert(game_input, Some(key_mouse.clone()));
136 self.inverse_keybindings
137 .entry(key_mouse)
138 .or_default()
139 .insert(game_input);
140 }
141
142 pub fn insert_menu_binding(&mut self, menu_input: MenuInput, key_mouse: KeyMouse) {
143 self.menubindings
144 .insert(menu_input, Some(key_mouse.clone()));
145 self.inverse_menubindings
146 .entry(key_mouse)
147 .or_default()
148 .insert(menu_input);
149 }
150
151 pub fn modify_binding(&mut self, game_input: GameInput, key_mouse: KeyMouse) {
152 if let Some(old_binding) = self.get_binding(game_input) {
155 self.inverse_keybindings
156 .entry(old_binding)
157 .or_default()
158 .remove(&game_input);
159 }
160 self.inverse_keybindings
162 .entry(key_mouse.clone())
163 .or_default()
164 .insert(game_input);
165 self.keybindings.insert(game_input, Some(key_mouse));
167 }
168
169 pub fn modify_menu_binding(&mut self, menu_input: MenuInput, key_mouse: KeyMouse) {
170 if let Some(old_binding) = self.get_menu_binding(menu_input) {
173 self.inverse_menubindings
174 .entry(old_binding)
175 .or_default()
176 .remove(&menu_input);
177 }
178 self.inverse_menubindings
180 .entry(key_mouse.clone())
181 .or_default()
182 .insert(menu_input);
183 self.menubindings.insert(menu_input, Some(key_mouse));
185 }
186
187 pub fn has_conflicting_bindings(&self, key_mouse: KeyMouse) -> bool {
190 if let Some(game_inputs) = self.inverse_keybindings.get(&key_mouse) {
191 for a in game_inputs.iter() {
192 for b in game_inputs.iter() {
193 if !GameInput::can_share_bindings(*a, *b) {
194 return true;
195 }
196 }
197 }
198 }
199 false
200 }
201
202 pub fn has_conflicting_menu_bindings(&self, key_mouse: KeyMouse) -> bool {
205 self.inverse_menubindings
206 .get(&key_mouse)
207 .is_some_and(|menu_inputs| menu_inputs.len() > 1)
208 }
209
210 pub fn default_binding(game_input: GameInput) -> Option<KeyMouse> {
211 let char = |s| Key::Character(winit::keyboard::SmolStr::new(s));
212
213 Some(KeyMouse::Key(match game_input {
215 GameInput::Primary => return Some(KeyMouse::Mouse(MouseButton::Left)),
216 GameInput::Secondary => return Some(KeyMouse::Mouse(MouseButton::Right)),
217 GameInput::Block => Key::Named(NamedKey::Alt),
218 GameInput::ToggleCursor => char(","),
219 GameInput::Escape => Key::Named(NamedKey::Escape),
220 GameInput::Chat => Key::Named(NamedKey::Enter),
221 GameInput::Command => char("/"),
222 GameInput::MoveForward => char("W"),
223 GameInput::MoveLeft => char("A"),
224 GameInput::MoveBack => char("S"),
225 GameInput::MoveRight => char("D"),
226 GameInput::Jump => Key::Named(NamedKey::Space),
227 GameInput::WallJump => Key::Named(NamedKey::Space),
228 GameInput::Sit => char("K"),
229 GameInput::Crawl => Key::Named(NamedKey::ArrowDown),
230 GameInput::Dance => char("J"),
231 GameInput::Greet => char("H"),
232 GameInput::Glide => Key::Named(NamedKey::Control),
233 GameInput::SwimUp => Key::Named(NamedKey::Space),
234 GameInput::SwimDown => Key::Named(NamedKey::Shift),
235 GameInput::Fly => char("H"),
236 GameInput::Sneak => Key::Named(NamedKey::Shift),
237 GameInput::CancelClimb => Key::Named(NamedKey::Shift),
238 GameInput::ToggleLantern => char("G"),
239 GameInput::Mount => char("F"),
240 GameInput::StayFollow => char("V"),
241 GameInput::Map => char("M"),
242 GameInput::Inventory => char("I"),
243 GameInput::Trade => char("T"),
244 GameInput::Social => char("O"),
245 GameInput::Crafting => char("C"),
246 GameInput::Diary => char("P"),
247 GameInput::Settings => Key::Named(NamedKey::F10),
248 GameInput::Controls => Key::Named(NamedKey::F1),
249 GameInput::ToggleInterface => Key::Named(NamedKey::F2),
250 GameInput::ToggleDebug => Key::Named(NamedKey::F3),
251 #[cfg(feature = "egui-ui")]
252 GameInput::ToggleEguiDebug => Key::Named(NamedKey::F7),
253 GameInput::ToggleChat => Key::Named(NamedKey::F5),
254 GameInput::Fullscreen => Key::Named(NamedKey::F11),
255 GameInput::Screenshot => Key::Named(NamedKey::F4),
256 GameInput::ToggleIngameUi => Key::Named(NamedKey::F6),
257 GameInput::Roll => return Some(MIDDLE_CLICK_KEY),
258 GameInput::GiveUp => Key::Named(NamedKey::Space),
259 GameInput::Respawn => Key::Named(NamedKey::Space),
260 GameInput::Interact => char("E"),
261 GameInput::ToggleWield => char("R"),
262 GameInput::FreeLook => char("L"),
263 GameInput::AutoWalk => char("."),
264 GameInput::ZoomIn => char(")"),
265 GameInput::ZoomOut => char("("),
266 GameInput::ZoomLock => return None,
267 GameInput::CameraClamp => char("'"),
268 GameInput::CycleCamera => char("0"),
269 GameInput::Slot1 => char("1"),
270 GameInput::Slot2 => char("2"),
271 GameInput::Slot3 => char("3"),
272 GameInput::Slot4 => char("4"),
273 GameInput::Slot5 => char("5"),
274 GameInput::Slot6 => char("6"),
275 GameInput::Slot7 => char("7"),
276 GameInput::Slot8 => char("8"),
277 GameInput::Slot9 => char("9"),
278 GameInput::Slot10 => char("Q"),
279 GameInput::NextSlot => return None,
280 GameInput::PreviousSlot => return None,
281 GameInput::CurrentSlot => return None,
282 GameInput::SwapLoadout => Key::Named(NamedKey::Tab),
283 GameInput::Select => char("X"),
284 GameInput::AcceptGroupInvite => char("Y"),
285 GameInput::DeclineGroupInvite => char("N"),
286 GameInput::MapZoomIn => char("+"),
287 GameInput::MapZoomOut => char("-"),
288 GameInput::MapSetMarker => return Some(KeyMouse::Mouse(MouseButton::Middle)),
289 GameInput::SpectateSpeedBoost => Key::Named(NamedKey::Control),
290 GameInput::SpectateViewpoint => return Some(KeyMouse::Mouse(MouseButton::Middle)),
291 GameInput::MuteMaster => Key::Named(NamedKey::AudioVolumeMute),
292 GameInput::MuteInactiveMaster => return None,
293 GameInput::MuteMusic => Key::Named(NamedKey::F8),
294 GameInput::MuteSfx => return None,
295 GameInput::MuteAmbience => return None,
296 GameInput::ToggleWalk => char("B"),
297 }))
298 }
299
300 pub fn default_menu_binding(menu_input: MenuInput) -> Option<KeyMouse> {
301 Some(KeyMouse::Key(match menu_input {
304 MenuInput::Up => Key::Named(NamedKey::ArrowUp),
305 MenuInput::Down => Key::Named(NamedKey::ArrowDown),
306 MenuInput::Left => Key::Named(NamedKey::ArrowLeft),
307 MenuInput::Right => Key::Named(NamedKey::ArrowRight),
308 MenuInput::ScrollUp => return None,
309 MenuInput::ScrollDown => return None,
310 MenuInput::ScrollLeft => return None,
311 MenuInput::ScrollRight => return None,
312 MenuInput::PageDown => return None, MenuInput::PageUp => return None, MenuInput::Apply => Key::Named(NamedKey::Enter),
315 MenuInput::Back => Key::Named(NamedKey::Escape),
316 MenuInput::Exit => return None,
317 MenuInput::LocalFocus => return None,
318 MenuInput::GlobalFocus => return None,
319 MenuInput::EmulateLeftClick => return None,
320 MenuInput::EmulateRightClick => return None,
321 }))
322 }
323}
324
325impl Default for ControlSettings {
326 fn default() -> Self {
327 let mut new_settings = Self {
328 keybindings: HashMap::new(),
329 inverse_keybindings: HashMap::new(),
330 menubindings: HashMap::new(),
331 inverse_menubindings: HashMap::new(),
332 };
333 for game_input in GameInput::iter() {
335 match ControlSettings::default_binding(game_input) {
336 None => {},
337 Some(default) => new_settings.insert_binding(game_input, default),
338 };
339 }
340 for menu_input in MenuInput::iter() {
342 if let Some(default) = ControlSettings::default_menu_binding(menu_input) {
343 new_settings.insert_menu_binding(menu_input, default);
344 }
345 }
346 new_settings
347 }
348}