From 9932b1209fba0af96363d99521fb0e77625fbce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sallai=20J=C3=B3zsef?= Date: Wed, 20 Jul 2022 16:07:24 +0300 Subject: [PATCH] very initial gamepad support --- Cargo.toml | 1 + src/framework/context.rs | 5 +- src/framework/gamepad.rs | 160 ++++++++++++++++ src/framework/mod.rs | 7 +- src/input/gamepad_player_controller.rs | 247 +++++++++++++++++++++++++ src/input/mod.rs | 1 + src/lib.rs | 30 +++ src/settings.rs | 110 ++++++++++- src/shared_game_state.rs | 16 +- 9 files changed, 569 insertions(+), 8 deletions(-) create mode 100644 src/framework/gamepad.rs create mode 100644 src/input/gamepad_player_controller.rs diff --git a/Cargo.toml b/Cargo.toml index 1c06b37..ef857d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ cpal = "0.13" directories = "3" downcast = "0.11" funty = "=1.1.0" # https://github.com/bitvecto-rs/bitvec/issues/105 +gilrs = { version = "0.9.0", features = ["serde-serialize"] } glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "8dd457b9adb7dbac7ade337246b6356c784272d9", optional = true, default_features = false, features = ["x11"] } imgui = "0.8.0" image = { version = "0.23", default-features = false, features = ["png", "bmp"] } diff --git a/src/framework/context.rs b/src/framework/context.rs index 40f1a27..8ea8a00 100644 --- a/src/framework/context.rs +++ b/src/framework/context.rs @@ -1,15 +1,17 @@ use crate::framework::backend::{init_backend, BackendRenderer}; use crate::framework::error::GameResult; use crate::framework::filesystem::Filesystem; +use crate::framework::gamepad::GamepadContext; use crate::framework::keyboard::KeyboardContext; -use crate::Game; use crate::graphics::VSyncMode; +use crate::Game; pub struct Context { pub headless: bool, pub size_hint: (u16, u16), pub(crate) filesystem: Filesystem, pub(crate) renderer: Option>, + pub(crate) gamepad_context: GamepadContext, pub(crate) keyboard_context: KeyboardContext, pub(crate) real_screen_size: (u32, u32), pub(crate) screen_size: (f32, f32), @@ -24,6 +26,7 @@ impl Context { size_hint: (640, 480), filesystem: Filesystem::new(), renderer: None, + gamepad_context: GamepadContext::new(), keyboard_context: KeyboardContext::new(), real_screen_size: (320, 240), screen_size: (320.0, 240.0), diff --git a/src/framework/gamepad.rs b/src/framework/gamepad.rs new file mode 100644 index 0000000..955bea9 --- /dev/null +++ b/src/framework/gamepad.rs @@ -0,0 +1,160 @@ +use std::collections::HashMap; + +use gilrs::{Axis, Button, Gamepad, GamepadId}; +use serde::{Deserialize, Serialize}; + +use crate::{framework::context::Context, settings::PlayerControllerInputType}; + +#[derive(Clone, Debug)] +pub enum AxisDirection { + None, + Up, + Left, + Right, + Down, +} + +impl AxisDirection { + pub fn compare(&self, value: f64, axis_sensitivity: f64) -> bool { + match self { + AxisDirection::None => false, + AxisDirection::Up => value > axis_sensitivity, + AxisDirection::Left => value < -axis_sensitivity, + AxisDirection::Right => value > axis_sensitivity, + AxisDirection::Down => value < -axis_sensitivity, + } + } +} + +#[derive(Clone, Debug)] +pub struct GamepadData { + left_x: f64, + left_y: f64, + right_x: f64, + right_y: f64, + axis_sensitivity: f64, +} + +impl GamepadData { + pub(crate) fn new(axis_sensitivity: f64) -> Self { + GamepadData { left_x: 0.0, left_y: 0.0, right_x: 0.0, right_y: 0.0, axis_sensitivity } + } +} + +#[derive(Clone, Debug)] +pub struct GamepadContext { + gamepads: HashMap, +} + +impl GamepadContext { + pub(crate) fn new() -> Self { + Self { gamepads: HashMap::new() } + } + + fn gamepad_exists(&self, gamepad: &Gamepad) -> bool { + self.gamepads.contains_key(&gamepad.id()) + } + + pub(crate) fn add_gamepad(&mut self, gamepad: &Gamepad, axis_sensitivity: f64) { + self.gamepads.insert(gamepad.id(), GamepadData::new(axis_sensitivity)); + } + + pub(crate) fn remove_gamepad(&mut self, gamepad: &Gamepad) { + self.gamepads.remove(&gamepad.id()); + } + + pub(crate) fn is_active( + &self, + gamepad: &Gamepad, + input_type: &PlayerControllerInputType, + axis_direction: AxisDirection, + ) -> bool { + match input_type { + PlayerControllerInputType::ButtonInput(button) => self.is_button_active(gamepad, *button), + PlayerControllerInputType::AxisInput(axis) => self.is_axis_active(gamepad, *axis, axis_direction), + PlayerControllerInputType::Either(button, axis) => { + self.is_button_active(gamepad, *button) || self.is_axis_active(gamepad, *axis, axis_direction) + } + } + } + + pub(crate) fn is_button_active(&self, gamepad: &Gamepad, button: Button) -> bool { + if !self.gamepad_exists(gamepad) { + return false; + } + + gamepad.is_pressed(button) + } + + pub(crate) fn is_axis_active(&self, gamepad: &Gamepad, axis: Axis, direction: AxisDirection) -> bool { + if !self.gamepad_exists(gamepad) { + return false; + } + + let data = self.gamepads.get(&gamepad.id()).unwrap(); + + match axis { + Axis::LeftStickX => direction.compare(data.left_x, data.axis_sensitivity), + Axis::LeftStickY => direction.compare(data.left_y, data.axis_sensitivity), + Axis::RightStickX => direction.compare(data.right_x, data.axis_sensitivity), + Axis::RightStickY => direction.compare(data.right_y, data.axis_sensitivity), + _ => false, + } + } + + pub(crate) fn update_axes(&mut self, gamepad: &Gamepad) { + if !self.gamepad_exists(gamepad) { + return; + } + + let data = self.gamepads.get_mut(&gamepad.id()).unwrap(); + + let mut axes = [ + (&mut data.left_x, Axis::LeftStickX), + (&mut data.left_y, Axis::LeftStickY), + (&mut data.right_x, Axis::RightStickX), + (&mut data.right_y, Axis::RightStickY), + ]; + + for (axis_val, id) in axes.iter_mut() { + if let Some(axis) = gamepad.axis_data(*id) { + **axis_val = if axis.value().abs() < 0.12 { 0.0 } else { axis.value() } as f64; + } + } + } +} + +impl Default for GamepadContext { + fn default() -> Self { + Self::new() + } +} + +pub fn add_gamepad(context: &mut Context, gamepad: &Gamepad, axis_sensitivity: f64) { + context.gamepad_context.add_gamepad(gamepad, axis_sensitivity); +} + +pub fn remove_gamepad(context: &mut Context, gamepad: &Gamepad) { + context.gamepad_context.remove_gamepad(gamepad); +} + +pub fn is_active( + ctx: &Context, + gamepad: &Gamepad, + input_type: &PlayerControllerInputType, + axis_direction: AxisDirection, +) -> bool { + ctx.gamepad_context.is_active(gamepad, input_type, axis_direction) +} + +pub fn is_button_active(ctx: &Context, gamepad: &Gamepad, button: Button) -> bool { + ctx.gamepad_context.is_button_active(gamepad, button) +} + +pub fn is_axis_active(ctx: &Context, gamepad: &Gamepad, axis: Axis, direction: AxisDirection) -> bool { + ctx.gamepad_context.is_axis_active(gamepad, axis, direction) +} + +pub fn update_axes(ctx: &mut Context, gamepad: &Gamepad) { + ctx.gamepad_context.update_axes(gamepad); +} diff --git a/src/framework/mod.rs b/src/framework/mod.rs index ac54730..a2ed9b0 100644 --- a/src/framework/mod.rs +++ b/src/framework/mod.rs @@ -1,16 +1,17 @@ #![allow(unused)] pub mod backend; -pub mod backend_null; #[cfg(feature = "backend-glutin")] pub mod backend_glutin; -#[cfg(feature = "render-opengl")] -mod gl; +pub mod backend_null; #[cfg(feature = "backend-sdl")] pub mod backend_sdl2; pub mod context; pub mod error; pub mod filesystem; +pub mod gamepad; +#[cfg(feature = "render-opengl")] +mod gl; pub mod graphics; pub mod keyboard; #[cfg(feature = "render-opengl")] diff --git a/src/input/gamepad_player_controller.rs b/src/input/gamepad_player_controller.rs new file mode 100644 index 0000000..cd57b50 --- /dev/null +++ b/src/input/gamepad_player_controller.rs @@ -0,0 +1,247 @@ +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::gamepad::{self, AxisDirection}; +use crate::input::player_controller::PlayerController; +use crate::player::TargetPlayer; +use crate::shared_game_state::SharedGameState; +use crate::{bitfield, settings::PlayerControllerInputType}; + +use gilrs::{Button, GamepadId}; + +bitfield! { + #[derive(Clone, Copy)] + pub struct KeyState(u16); + impl Debug; + + pub left, set_left: 0; + pub right, set_right: 1; + pub up, set_up: 2; + pub down, set_down: 3; + pub map, set_map: 4; + pub inventory, set_inventory: 5; + pub jump, set_jump: 6; + pub shoot, set_shoot: 7; + pub next_weapon, set_next_weapon: 8; + pub prev_weapon, set_prev_weapon: 9; + pub escape, set_escape: 10; + pub enter, set_enter: 11; + pub skip, set_skip: 12; + pub strafe, set_strafe: 13; +} + +#[derive(Clone)] +pub struct GamepadController { + gamepad_id: GamepadId, + target: TargetPlayer, + state: KeyState, + old_state: KeyState, + trigger: KeyState, +} + +impl GamepadController { + pub fn new(gamepad_id: GamepadId, target: TargetPlayer) -> GamepadController { + GamepadController { gamepad_id, target, state: KeyState(0), old_state: KeyState(0), trigger: KeyState(0) } + } +} + +impl PlayerController for GamepadController { + fn update(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + let button_map = match self.target { + TargetPlayer::Player1 => &state.settings.player1_controller_button_map, + TargetPlayer::Player2 => &state.settings.player2_controller_button_map, + }; + + if let Some(gilrs) = &state.gilrs { + if let Some(gamepad) = gilrs.connected_gamepad(self.gamepad_id) { + gamepad::update_axes(ctx, &gamepad); + + self.state.set_up(gamepad::is_active(ctx, &gamepad, &button_map.up, AxisDirection::Up)); + self.state.set_down(gamepad::is_active(ctx, &gamepad, &button_map.down, AxisDirection::Down)); + self.state.set_left(gamepad::is_active(ctx, &gamepad, &button_map.left, AxisDirection::Left)); + self.state.set_right(gamepad::is_active(ctx, &gamepad, &button_map.right, AxisDirection::Right)); + self.state.set_map(gamepad::is_active(ctx, &gamepad, &button_map.map, AxisDirection::None)); + self.state.set_inventory(gamepad::is_active(ctx, &gamepad, &button_map.inventory, AxisDirection::None)); + self.state.set_jump(gamepad::is_active(ctx, &gamepad, &button_map.jump, AxisDirection::None)); + self.state.set_shoot(gamepad::is_active(ctx, &gamepad, &button_map.shoot, AxisDirection::None)); + self.state.set_next_weapon(gamepad::is_active( + ctx, + &gamepad, + &button_map.next_weapon, + AxisDirection::None, + )); + self.state.set_prev_weapon(gamepad::is_active( + ctx, + &gamepad, + &button_map.prev_weapon, + AxisDirection::None, + )); + self.state.set_escape(gamepad::is_active( + ctx, + &gamepad, + &PlayerControllerInputType::ButtonInput(Button::Start), + AxisDirection::None, + )); + self.state.set_enter(gamepad::is_active(ctx, &gamepad, &button_map.jump, AxisDirection::None)); + self.state.set_skip(gamepad::is_active(ctx, &gamepad, &button_map.skip, AxisDirection::None)); + self.state.set_strafe(gamepad::is_active(ctx, &gamepad, &button_map.strafe, AxisDirection::None)); + } + } + + Ok(()) + } + + fn update_trigger(&mut self) { + let mut trigger = self.state.0 ^ self.old_state.0; + trigger &= self.state.0; + self.old_state = self.state; + self.trigger = KeyState(trigger); + } + + fn move_up(&self) -> bool { + self.state.up() + } + + fn move_left(&self) -> bool { + self.state.left() + } + + fn move_down(&self) -> bool { + self.state.down() + } + + fn move_right(&self) -> bool { + self.state.right() + } + + fn prev_weapon(&self) -> bool { + self.state.prev_weapon() + } + + fn next_weapon(&self) -> bool { + self.state.next_weapon() + } + + fn map(&self) -> bool { + self.state.map() + } + + fn inventory(&self) -> bool { + self.state.inventory() + } + + fn jump(&self) -> bool { + self.state.jump() + } + + fn shoot(&self) -> bool { + self.state.shoot() + } + + fn skip(&self) -> bool { + self.state.skip() + } + + fn strafe(&self) -> bool { + self.state.strafe() + } + + fn trigger_up(&self) -> bool { + self.trigger.up() + } + + fn trigger_left(&self) -> bool { + self.trigger.left() + } + + fn trigger_down(&self) -> bool { + self.trigger.down() + } + + fn trigger_right(&self) -> bool { + self.trigger.right() + } + + fn trigger_prev_weapon(&self) -> bool { + self.trigger.prev_weapon() + } + + fn trigger_next_weapon(&self) -> bool { + self.trigger.next_weapon() + } + + fn trigger_map(&self) -> bool { + self.trigger.map() + } + + fn trigger_inventory(&self) -> bool { + self.trigger.inventory() + } + + fn trigger_jump(&self) -> bool { + self.trigger.jump() + } + + fn trigger_shoot(&self) -> bool { + self.trigger.shoot() + } + + fn trigger_skip(&self) -> bool { + self.trigger.skip() + } + + fn trigger_strafe(&self) -> bool { + self.trigger.strafe() + } + + fn trigger_menu_ok(&self) -> bool { + self.trigger.jump() || self.trigger.enter() + } + + fn trigger_menu_back(&self) -> bool { + self.trigger.shoot() || self.trigger.escape() + } + + fn trigger_menu_pause(&self) -> bool { + self.trigger.escape() + } + + fn look_up(&self) -> bool { + self.state.up() + } + + fn look_left(&self) -> bool { + self.state.left() + } + + fn look_down(&self) -> bool { + self.state.down() + } + + fn look_right(&self) -> bool { + self.state.right() + } + + fn move_analog_x(&self) -> f64 { + if self.state.left() && self.state.right() { + 0.0 + } else if self.state.left() { + -1.0 + } else if self.state.right() { + 1.0 + } else { + 0.0 + } + } + + fn move_analog_y(&self) -> f64 { + if self.state.up() && self.state.down() { + 0.0 + } else if self.state.up() { + -1.0 + } else if self.state.down() { + 1.0 + } else { + 0.0 + } + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs index e0e62e3..c1901a5 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1,5 +1,6 @@ pub mod combined_menu_controller; pub mod dummy_player_controller; +pub mod gamepad_player_controller; pub mod keyboard_player_controller; pub mod player_controller; pub mod replay_player_controller; diff --git a/src/lib.rs b/src/lib.rs index 9451f32..29914ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,8 @@ use std::sync::Mutex; use std::time::{Duration, Instant}; use directories::ProjectDirs; +use framework::gamepad; +use gilrs::EventType; use lazy_static::lazy_static; use crate::builtin_fs::BuiltinFS; @@ -110,6 +112,34 @@ impl Game { } fn update(&mut self, ctx: &mut Context) -> GameResult { + { + let state_ref = unsafe { &mut *self.state.get() }; + + if let Some(gilrs) = &mut state_ref.gilrs { + while let Some(e) = gilrs.next_event() { + let gamepad = gilrs.gamepad(e.id); + + match e.event { + EventType::Connected => { + log::info!("Gamepad connected: {} (ID: {})", gamepad.name(), gamepad.id()); + + let axis_sensitivity = state_ref.settings.get_gamepad_axis_sensitivity(gamepad.id()); + gamepad::add_gamepad(ctx, &gamepad, axis_sensitivity); + + // TODO: replace the controller of all players that use this gamepad from keyboard to gamepad + } + EventType::Disconnected => { + log::info!("Gamepad disconnected: {} (ID: {})", gamepad.name(), gamepad.id()); + gamepad::remove_gamepad(ctx, &gamepad); + + // TODO: fall back to keyboard for all players that use this gamepad + } + _ => {} + } + } + } + } + if let Some(scene) = &mut self.scene { let state_ref = unsafe { &mut *self.state.get() }; diff --git a/src/settings.rs b/src/settings.rs index e1c3f67..7514ebe 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,8 +1,11 @@ +use gilrs::{Axis, Button, GamepadId}; + use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::framework::filesystem::{user_create, user_open}; use crate::framework::keyboard::ScanCode; use crate::graphics::VSyncMode; +use crate::input::gamepad_player_controller::GamepadController; use crate::input::keyboard_player_controller::KeyboardController; use crate::input::player_controller::PlayerController; use crate::input::touch_player_controller::TouchPlayerController; @@ -34,10 +37,22 @@ pub struct Settings { pub timing_mode: TimingMode, #[serde(default = "default_interpolation")] pub organya_interpolation: InterpolationMode, + #[serde(default = "default_controller_type")] + pub player1_controller_type: ControllerType, + #[serde(default = "default_controller_type")] + pub player2_controller_type: ControllerType, #[serde(default = "p1_default_keymap")] pub player1_key_map: PlayerKeyMap, #[serde(default = "p2_default_keymap")] pub player2_key_map: PlayerKeyMap, + #[serde(default = "player_default_controller_button_map")] + pub player1_controller_button_map: PlayerControllerButtonMap, + #[serde(default = "player_default_controller_button_map")] + pub player2_controller_button_map: PlayerControllerButtonMap, + #[serde(default = "default_controller_axis_sensitivity")] + pub player1_controller_axis_sensitivity: f64, + #[serde(default = "default_controller_axis_sensitivity")] + pub player2_controller_axis_sensitivity: f64, #[serde(skip, default = "default_speed")] pub speed: f64, #[serde(skip)] @@ -65,7 +80,7 @@ fn default_true() -> bool { #[inline(always)] fn current_version() -> u32 { - 11 + 12 } #[inline(always)] @@ -108,6 +123,11 @@ fn default_screen_shake_intensity() -> ScreenShakeIntensity { ScreenShakeIntensity::Full } +#[inline(always)] +fn default_controller_type() -> ControllerType { + ControllerType::Keyboard +} + impl Settings { pub fn load(ctx: &Context) -> GameResult { if let Ok(file) = user_open(ctx, "/settings.json") { @@ -170,6 +190,16 @@ impl Settings { self.window_mode = default_window_mode(); } + if self.version == 11 { + self.version = 12; + self.player1_controller_type = default_controller_type(); + self.player2_controller_type = default_controller_type(); + self.player1_controller_button_map = player_default_controller_button_map(); + self.player2_controller_button_map = player_default_controller_button_map(); + self.player1_controller_axis_sensitivity = default_controller_axis_sensitivity(); + self.player2_controller_axis_sensitivity = default_controller_axis_sensitivity(); + } + if self.version != initial_version { log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version); } @@ -189,11 +219,27 @@ impl Settings { return Box::new(TouchPlayerController::new()); } - Box::new(KeyboardController::new(TargetPlayer::Player1)) + match self.player1_controller_type { + ControllerType::Keyboard => Box::new(KeyboardController::new(TargetPlayer::Player1)), + ControllerType::Gamepad(id) => Box::new(GamepadController::new(id, TargetPlayer::Player1)), + } } pub fn create_player2_controller(&self) -> Box { - Box::new(KeyboardController::new(TargetPlayer::Player2)) + match self.player2_controller_type { + ControllerType::Keyboard => Box::new(KeyboardController::new(TargetPlayer::Player2)), + ControllerType::Gamepad(id) => Box::new(GamepadController::new(id, TargetPlayer::Player2)), + } + } + + pub fn get_gamepad_axis_sensitivity(&self, id: GamepadId) -> f64 { + if self.player1_controller_type == ControllerType::Gamepad(id) { + self.player1_controller_axis_sensitivity + } else if self.player2_controller_type == ControllerType::Gamepad(id) { + self.player2_controller_axis_sensitivity + } else { + default_controller_axis_sensitivity() + } } } @@ -213,8 +259,14 @@ impl Default for Settings { sfx_volume: 1.0, timing_mode: default_timing(), organya_interpolation: InterpolationMode::Linear, + player1_controller_type: default_controller_type(), + player2_controller_type: default_controller_type(), player1_key_map: p1_default_keymap(), player2_key_map: p2_default_keymap(), + player1_controller_button_map: player_default_controller_button_map(), + player2_controller_button_map: player_default_controller_button_map(), + player1_controller_axis_sensitivity: default_controller_axis_sensitivity(), + player2_controller_axis_sensitivity: default_controller_axis_sensitivity(), speed: 1.0, god_mode: false, infinite_booster: false, @@ -281,3 +333,55 @@ fn p2_default_keymap() -> PlayerKeyMap { strafe: ScanCode::RShift, } } + +#[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq)] +pub enum ControllerType { + Keyboard, + Gamepad(GamepadId), +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub enum PlayerControllerInputType { + ButtonInput(Button), + AxisInput(Axis), + Either(Button, Axis), +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct PlayerControllerButtonMap { + pub left: PlayerControllerInputType, + pub up: PlayerControllerInputType, + pub right: PlayerControllerInputType, + pub down: PlayerControllerInputType, + pub prev_weapon: PlayerControllerInputType, + pub next_weapon: PlayerControllerInputType, + pub jump: PlayerControllerInputType, + pub shoot: PlayerControllerInputType, + pub skip: PlayerControllerInputType, + pub inventory: PlayerControllerInputType, + pub map: PlayerControllerInputType, + pub strafe: PlayerControllerInputType, +} + +#[inline(always)] +pub fn player_default_controller_button_map() -> PlayerControllerButtonMap { + PlayerControllerButtonMap { + left: PlayerControllerInputType::Either(Button::DPadLeft, Axis::LeftStickX), + up: PlayerControllerInputType::Either(Button::DPadUp, Axis::LeftStickY), + right: PlayerControllerInputType::Either(Button::DPadRight, Axis::LeftStickX), + down: PlayerControllerInputType::Either(Button::DPadDown, Axis::LeftStickY), + prev_weapon: PlayerControllerInputType::ButtonInput(Button::LeftTrigger), + next_weapon: PlayerControllerInputType::ButtonInput(Button::RightTrigger), + jump: PlayerControllerInputType::ButtonInput(Button::East), + shoot: PlayerControllerInputType::ButtonInput(Button::South), + skip: PlayerControllerInputType::ButtonInput(Button::LeftTrigger2), + strafe: PlayerControllerInputType::ButtonInput(Button::RightTrigger2), + inventory: PlayerControllerInputType::ButtonInput(Button::North), + map: PlayerControllerInputType::ButtonInput(Button::West), + } +} + +#[inline(always)] +pub fn default_controller_axis_sensitivity() -> f64 { + 0.3 +} diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index d0dc4dc..def06ab 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -15,7 +15,7 @@ use crate::framework::error::GameResult; use crate::framework::graphics::{create_texture_mutable, set_render_target}; use crate::framework::keyboard::ScanCode; use crate::framework::vfs::OpenOptions; -use crate::framework::{filesystem, graphics}; +use crate::framework::{filesystem, gamepad, graphics}; #[cfg(feature = "hooks")] use crate::hooks::init_hooks; use crate::i18n::Locale; @@ -38,6 +38,8 @@ use crate::stage::StageData; use crate::texture_set::TextureSet; use crate::vanilla::VanillaExtractor; +use gilrs::Gilrs; + #[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)] pub enum TimingMode { _50Hz, @@ -287,6 +289,7 @@ pub struct SharedGameState { pub super_quake_counter: u16, pub teleporter_slots: Vec<(u16, u16)>, pub carets: Vec, + pub gilrs: Option, pub touch_controls: TouchControls, pub mod_path: Option, pub mod_list: ModList, @@ -331,6 +334,16 @@ impl SharedGameState { let mut sound_manager = SoundManager::new(ctx)?; let settings = Settings::load(ctx)?; let mod_requirements = ModRequirements::load(ctx)?; + let mut gilrs = Gilrs::new().ok(); + + if let Some(gilrs) = &mut gilrs { + for (id, gamepad) in gilrs.gamepads() { + log::info!("Found gamepad {} (ID {})", gamepad.name(), id); + + let axis_sensitivity = settings.get_gamepad_axis_sensitivity(id); + gamepad::add_gamepad(ctx, &gamepad, axis_sensitivity); + } + } let vanilla_extractor = VanillaExtractor::from(ctx, "Doukutsu.exe".to_string()); if vanilla_extractor.is_some() { @@ -424,6 +437,7 @@ impl SharedGameState { super_quake_counter: 0, teleporter_slots: Vec::with_capacity(8), carets: Vec::with_capacity(32), + gilrs, touch_controls: TouchControls::new(), mod_path: None, mod_list,