From fb4ac0dae882bb7f621003a9303f50a0328715f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sallai=20J=C3=B3zsef?= Date: Sat, 23 Jul 2022 18:45:08 +0300 Subject: [PATCH] add gamepad input icons --- src/builtin/builtin_data/buttons.png | Bin 0 -> 282973 bytes src/builtin_fs.rs | 4 + src/engine_constants/mod.rs | 52 +++++++++++++ src/framework/backend_sdl2.rs | 9 ++- src/framework/gamepad.rs | 101 ++++++++++++++++++++----- src/input/gamepad_player_controller.rs | 41 ++++------ src/scene/game_scene.rs | 36 ++++++--- src/settings.rs | 31 ++++---- 8 files changed, 205 insertions(+), 69 deletions(-) create mode 100644 src/builtin/builtin_data/buttons.png diff --git a/src/builtin/builtin_data/buttons.png b/src/builtin/builtin_data/buttons.png new file mode 100644 index 0000000000000000000000000000000000000000..58a2ec887532d3aae27295d1c6960a2613b40610 GIT binary patch literal 282973 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 4]>, + pub axis_rects: HashMap; 4]>, +} + +impl Clone for GamepadConsts { + fn clone(&self) -> GamepadConsts { + GamepadConsts { button_rects: self.button_rects.clone(), axis_rects: self.axis_rects.clone() } + } +} + +impl GamepadConsts { + fn rects(base: Rect) -> [Rect; 4] { + [ + base, + Rect::new(base.left + 64, base.top, base.right + 64, base.bottom), + Rect::new(base.left + 128, base.top, base.right + 128, base.bottom), + Rect::new(base.left + 192, base.top, base.right + 192, base.bottom), + ] + } +} + #[derive(Debug)] pub struct EngineConstants { pub base_paths: Vec, @@ -315,6 +339,7 @@ pub struct EngineConstants { pub string_table: HashMap, pub missile_flags: Vec, pub locales: HashMap, + pub gamepad: GamepadConsts, } impl Clone for EngineConstants { @@ -346,6 +371,7 @@ impl Clone for EngineConstants { string_table: self.string_table.clone(), missile_flags: self.missile_flags.clone(), locales: self.locales.clone(), + gamepad: self.gamepad.clone(), } } } @@ -1358,6 +1384,7 @@ impl EngineConstants { "bkSunset" => (320, 240), // nxengine "bkSunset480fix" => (480, 272), // nxengine "bkWater" => (32, 48), + "buttons" => (256, 256), "Bullet" => (320, 176), "Caret" => (320, 240), "casts" => (320, 240), @@ -1630,6 +1657,30 @@ impl EngineConstants { string_table: HashMap::new(), missile_flags: vec![200, 201, 202, 218, 550, 766, 880, 920, 1551], locales: HashMap::new(), + gamepad: GamepadConsts { + button_rects: HashMap::from([ + (Button::North, GamepadConsts::rects(Rect::new(0, 0, 32, 16))), + (Button::South, GamepadConsts::rects(Rect::new(0, 16, 32, 32))), + (Button::East, GamepadConsts::rects(Rect::new(0, 32, 32, 48))), + (Button::West, GamepadConsts::rects(Rect::new(0, 48, 32, 64))), + (Button::DPadDown, GamepadConsts::rects(Rect::new(0, 64, 32, 80))), + (Button::DPadUp, GamepadConsts::rects(Rect::new(0, 80, 32, 96))), + (Button::DPadRight, GamepadConsts::rects(Rect::new(0, 96, 32, 112))), + (Button::DPadLeft, GamepadConsts::rects(Rect::new(0, 112, 32, 128))), + (Button::LeftShoulder, GamepadConsts::rects(Rect::new(32, 32, 64, 48))), + (Button::RightShoulder, GamepadConsts::rects(Rect::new(32, 48, 64, 64))), + (Button::Start, GamepadConsts::rects(Rect::new(32, 96, 64, 112))), + (Button::Guide, GamepadConsts::rects(Rect::new(32, 112, 64, 128))), + ]), + axis_rects: HashMap::from([ + (Axis::LeftX, GamepadConsts::rects(Rect::new(32, 0, 64, 16))), + (Axis::LeftY, GamepadConsts::rects(Rect::new(32, 0, 64, 16))), + (Axis::RightX, GamepadConsts::rects(Rect::new(32, 16, 64, 32))), + (Axis::RightY, GamepadConsts::rects(Rect::new(32, 16, 64, 32))), + (Axis::TriggerLeft, GamepadConsts::rects(Rect::new(32, 64, 64, 80))), + (Axis::TriggerRight, GamepadConsts::rects(Rect::new(32, 80, 64, 96))), + ]), + }, } } @@ -1709,6 +1760,7 @@ impl EngineConstants { pub fn rebuild_path_list(&mut self, mod_path: Option, season: Season, settings: &Settings) { self.base_paths.clear(); self.base_paths.push("/".to_owned()); + self.base_paths.push("/builtin/builtin_data/".to_owned()); if self.is_cs_plus { self.base_paths.insert(0, "/base/".to_owned()); diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index d4ad534..09ac50d 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -306,10 +306,17 @@ impl BackendEventLoop for SDL2EventLoop { if game_controller.is_game_controller(which) { let controller = game_controller.open(which).unwrap(); - log::info!("Connected gamepad: {} (ID: {})", controller.name(), controller.instance_id()); + let id = controller.instance_id(); + + log::info!("Connected gamepad: {} (ID: {})", controller.name(), id); let axis_sensitivity = state.settings.get_gamepad_axis_sensitivity(which); ctx.gamepad_context.add_gamepad(controller, axis_sensitivity); + + unsafe { + let controller_type = sdl2_sys::SDL_GameControllerTypeForIndex(id as _); + ctx.gamepad_context.set_gamepad_type(id, controller_type); + } } } Event::ControllerDeviceRemoved { which, .. } => { diff --git a/src/framework/gamepad.rs b/src/framework/gamepad.rs index f58916d..7822568 100644 --- a/src/framework/gamepad.rs +++ b/src/framework/gamepad.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use sdl2::controller::GameController; use serde::{Deserialize, Serialize}; -use crate::{framework::context::Context, settings::PlayerControllerInputType}; +use crate::{common::Rect, engine_constants::EngineConstants, framework::context::Context}; #[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] #[repr(u32)] @@ -16,7 +16,13 @@ pub enum Axis { TriggerRight, } -#[derive(Clone, Debug)] +impl Axis { + pub fn get_rect(&self, offset: usize, constants: &EngineConstants) -> Rect { + constants.gamepad.axis_rects.get(self).unwrap()[offset] + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum AxisDirection { None, Either, @@ -58,8 +64,32 @@ pub enum Button { DPadRight, } +impl Button { + pub fn get_rect(&self, offset: usize, constants: &EngineConstants) -> Rect { + constants.gamepad.button_rects.get(self).unwrap()[offset] + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub enum PlayerControllerInputType { + ButtonInput(Button), + AxisInput(Axis, AxisDirection), + Either(Button, Axis, AxisDirection), +} + +impl PlayerControllerInputType { + pub fn get_rect(&self, offset: usize, constants: &EngineConstants) -> Rect { + match self { + PlayerControllerInputType::ButtonInput(button) => button.get_rect(offset, constants), + PlayerControllerInputType::AxisInput(axis, _) => axis.get_rect(offset, constants), + PlayerControllerInputType::Either(button, axis, _) => button.get_rect(offset, constants), + } + } +} + pub struct GamepadData { controller: GameController, + controller_type: Option, left_x: f64, left_y: f64, @@ -78,6 +108,7 @@ impl GamepadData { pub(crate) fn new(game_controller: GameController, axis_sensitivity: f64) -> Self { GamepadData { controller: game_controller, + controller_type: None, left_x: 0.0, left_y: 0.0, @@ -92,6 +123,26 @@ impl GamepadData { axis_values: HashMap::with_capacity(8), } } + + pub(crate) fn set_gamepad_type(&mut self, controller_type: sdl2_sys::SDL_GameControllerType) { + self.controller_type = Some(controller_type); + } + + pub(crate) fn get_gamepad_sprite_offset(&self) -> usize { + if let Some(controller_type) = self.controller_type { + return match controller_type { + sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_PS3 + | sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_PS4 + | sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_PS5 => 0, + sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_XBOX360 + | sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_XBOXONE => 1, + sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO => 3, + _ => 1, + }; + } + + 1 + } } pub struct GamepadContext { @@ -123,6 +174,20 @@ impl GamepadContext { self.gamepads.retain(|data| data.controller.instance_id() != gamepad_id); } + pub(crate) fn set_gamepad_type(&mut self, gamepad_id: u32, controller_type: sdl2_sys::SDL_GameControllerType) { + if let Some(gamepad) = self.get_gamepad_mut(gamepad_id) { + gamepad.set_gamepad_type(controller_type); + } + } + + pub(crate) fn get_gamepad_sprite_offset(&self, gamepad_index: usize) -> usize { + if let Some(gamepad) = self.get_gamepad_by_index(gamepad_index) { + return gamepad.get_gamepad_sprite_offset(); + } + + 1 + } + pub(crate) fn set_button(&mut self, gamepad_id: u32, button: Button, pressed: bool) { if let Some(gamepad) = self.get_gamepad_mut(gamepad_id) { if pressed { @@ -139,18 +204,15 @@ impl GamepadContext { } } - pub(crate) fn is_active( - &self, - gamepad_index: u32, - input_type: &PlayerControllerInputType, - axis_direction: AxisDirection, - ) -> bool { + pub(crate) fn is_active(&self, gamepad_index: u32, input_type: &PlayerControllerInputType) -> bool { match input_type { PlayerControllerInputType::ButtonInput(button) => self.is_button_active(gamepad_index, *button), - PlayerControllerInputType::AxisInput(axis) => self.is_axis_active(gamepad_index, *axis, axis_direction), - PlayerControllerInputType::Either(button, axis) => { + PlayerControllerInputType::AxisInput(axis, axis_direction) => { + self.is_axis_active(gamepad_index, *axis, *axis_direction) + } + PlayerControllerInputType::Either(button, axis, axis_direction) => { self.is_button_active(gamepad_index, *button) - || self.is_axis_active(gamepad_index, *axis, axis_direction) + || self.is_axis_active(gamepad_index, *axis, *axis_direction) } } } @@ -212,13 +274,16 @@ pub fn remove_gamepad(context: &mut Context, gamepad_id: u32) { context.gamepad_context.remove_gamepad(gamepad_id); } -pub fn is_active( - ctx: &Context, - gamepad_index: u32, - input_type: &PlayerControllerInputType, - axis_direction: AxisDirection, -) -> bool { - ctx.gamepad_context.is_active(gamepad_index, input_type, axis_direction) +pub fn set_gamepad_type(context: &mut Context, gamepad_id: u32, controller_type: sdl2_sys::SDL_GameControllerType) { + context.gamepad_context.set_gamepad_type(gamepad_id, controller_type); +} + +pub fn get_gamepad_sprite_offset(context: &Context, gamepad_index: usize) -> usize { + context.gamepad_context.get_gamepad_sprite_offset(gamepad_index) +} + +pub fn is_active(ctx: &Context, gamepad_index: u32, input_type: &PlayerControllerInputType) -> bool { + ctx.gamepad_context.is_active(gamepad_index, input_type) } pub fn is_button_active(ctx: &Context, gamepad_index: u32, button: Button) -> bool { diff --git a/src/input/gamepad_player_controller.rs b/src/input/gamepad_player_controller.rs index eee52c2..52f599d 100644 --- a/src/input/gamepad_player_controller.rs +++ b/src/input/gamepad_player_controller.rs @@ -1,10 +1,10 @@ +use crate::bitfield; use crate::framework::context::Context; use crate::framework::error::GameResult; -use crate::framework::gamepad::{self, AxisDirection, Button}; +use crate::framework::gamepad::{self, Button, PlayerControllerInputType}; use crate::input::player_controller::PlayerController; use crate::player::TargetPlayer; use crate::shared_game_state::SharedGameState; -use crate::{bitfield, settings::PlayerControllerInputType}; bitfield! { #[derive(Clone, Copy)] @@ -49,35 +49,24 @@ impl PlayerController for GamepadController { TargetPlayer::Player2 => &state.settings.player2_controller_button_map, }; - self.state.set_up(gamepad::is_active(ctx, self.gamepad_id, &button_map.up, AxisDirection::Up)); - self.state.set_down(gamepad::is_active(ctx, self.gamepad_id, &button_map.down, AxisDirection::Down)); - self.state.set_left(gamepad::is_active(ctx, self.gamepad_id, &button_map.left, AxisDirection::Left)); - self.state.set_right(gamepad::is_active(ctx, self.gamepad_id, &button_map.right, AxisDirection::Right)); - self.state.set_map(gamepad::is_active(ctx, self.gamepad_id, &button_map.map, AxisDirection::None)); - self.state.set_inventory(gamepad::is_active(ctx, self.gamepad_id, &button_map.inventory, AxisDirection::None)); - self.state.set_jump(gamepad::is_active(ctx, self.gamepad_id, &button_map.jump, AxisDirection::None)); - self.state.set_shoot(gamepad::is_active(ctx, self.gamepad_id, &button_map.shoot, AxisDirection::None)); - self.state.set_next_weapon(gamepad::is_active( - ctx, - self.gamepad_id, - &button_map.next_weapon, - AxisDirection::None, - )); - self.state.set_prev_weapon(gamepad::is_active( - ctx, - self.gamepad_id, - &button_map.prev_weapon, - AxisDirection::None, - )); + self.state.set_up(gamepad::is_active(ctx, self.gamepad_id, &button_map.up)); + self.state.set_down(gamepad::is_active(ctx, self.gamepad_id, &button_map.down)); + self.state.set_left(gamepad::is_active(ctx, self.gamepad_id, &button_map.left)); + self.state.set_right(gamepad::is_active(ctx, self.gamepad_id, &button_map.right)); + self.state.set_map(gamepad::is_active(ctx, self.gamepad_id, &button_map.map)); + self.state.set_inventory(gamepad::is_active(ctx, self.gamepad_id, &button_map.inventory)); + self.state.set_jump(gamepad::is_active(ctx, self.gamepad_id, &button_map.jump)); + self.state.set_shoot(gamepad::is_active(ctx, self.gamepad_id, &button_map.shoot)); + self.state.set_next_weapon(gamepad::is_active(ctx, self.gamepad_id, &button_map.next_weapon)); + self.state.set_prev_weapon(gamepad::is_active(ctx, self.gamepad_id, &button_map.prev_weapon)); self.state.set_escape(gamepad::is_active( ctx, self.gamepad_id, &PlayerControllerInputType::ButtonInput(Button::Start), - AxisDirection::None, )); - self.state.set_enter(gamepad::is_active(ctx, self.gamepad_id, &button_map.jump, AxisDirection::None)); - self.state.set_skip(gamepad::is_active(ctx, self.gamepad_id, &button_map.skip, AxisDirection::Either)); - self.state.set_strafe(gamepad::is_active(ctx, self.gamepad_id, &button_map.strafe, AxisDirection::Either)); + self.state.set_enter(gamepad::is_active(ctx, self.gamepad_id, &button_map.jump)); + self.state.set_skip(gamepad::is_active(ctx, self.gamepad_id, &button_map.skip)); + self.state.set_strafe(gamepad::is_active(ctx, self.gamepad_id, &button_map.strafe)); Ok(()) } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index f5a415f..e4edeb0 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -46,6 +46,7 @@ use crate::scene::title_scene::TitleScene; use crate::scene::Scene; use crate::scripting::tsc::credit_script::CreditScriptVM; use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM}; +use crate::settings::ControllerType; use crate::shared_game_state::{Language, PlayerCount, ReplayState, SharedGameState, TileSize}; use crate::stage::{BackgroundType, Stage, StageTexturePaths}; use crate::texture_set::SpriteBatch; @@ -2170,15 +2171,30 @@ impl Scene for GameScene { self.text_boxes.draw(state, ctx, &self.frame)?; if self.skip_counter > 1 || state.tutorial_counter > 0 { - let text = state.tt( - "game.cutscene_skip", - HashMap::from(if !state.settings.touch_controls { - [("key".to_owned(), format!("{:?}", state.settings.player1_key_map.inventory))] + let key = { + if state.settings.touch_controls { + ">>".to_owned() } else { - [("key".to_owned(), ">>".to_owned())] - }), - ); - let width = state.font.text_width(text.chars(), &state.constants); + match state.settings.player1_controller_type { + ControllerType::Keyboard => format!("{:?}", state.settings.player1_key_map.skip), + ControllerType::Gamepad(_) => "=".to_owned(), + } + } + }; + + let text = state.tt("game.cutscene_skip", HashMap::from([("key".to_owned(), key)])); + + let gamepad_sprite_offset = match state.settings.player1_controller_type { + ControllerType::Keyboard => 1, + ControllerType::Gamepad(index) => ctx.gamepad_context.get_gamepad_sprite_offset(index as usize), + }; + + let rect_map = HashMap::from([( + '=', + state.settings.player1_controller_button_map.skip.get_rect(gamepad_sprite_offset, &state.constants), + )]); + + let width = state.font.text_width_with_rects(text.chars(), &rect_map, &state.constants); let pos_x = state.canvas_size.0 - width - 20.0; let pos_y = 0.0; let line_height = state.font.line_height(&state.constants); @@ -2199,12 +2215,14 @@ impl Scene for GameScene { rect.right = rect.left + (w * state.scale).ceil() as isize; draw_rect(ctx, rect, Color::from_rgb(128, 128, 160))?; - state.font.draw_text_with_shadow( + state.font.draw_text_with_shadow_and_rects( text.chars(), pos_x + 10.0, pos_y + 5.0, &state.constants, &mut state.texture_set, + &rect_map, + Some("buttons".into()), ctx, )?; } diff --git a/src/settings.rs b/src/settings.rs index 681f499..f986ee7 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,7 +1,7 @@ use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::framework::filesystem::{user_create, user_open}; -use crate::framework::gamepad::{Axis, Button}; +use crate::framework::gamepad::{Axis, AxisDirection, Button, PlayerControllerInputType}; use crate::framework::keyboard::ScanCode; use crate::graphics::VSyncMode; use crate::input::gamepad_player_controller::GamepadController; @@ -79,7 +79,7 @@ fn default_true() -> bool { #[inline(always)] fn current_version() -> u32 { - 13 + 14 } #[inline(always)] @@ -215,6 +215,14 @@ impl Settings { self.player2_controller_button_map = player_default_controller_button_map(); } + if self.version == 13 { + self.version = 14; + + // reset controller mappings again since we have new enums + self.player1_controller_button_map = player_default_controller_button_map(); + self.player2_controller_button_map = player_default_controller_button_map(); + } + if self.version != initial_version { log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version); } @@ -355,13 +363,6 @@ pub enum ControllerType { Gamepad(u32), } -#[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, @@ -381,16 +382,16 @@ pub struct PlayerControllerButtonMap { #[inline(always)] pub fn player_default_controller_button_map() -> PlayerControllerButtonMap { PlayerControllerButtonMap { - left: PlayerControllerInputType::Either(Button::DPadLeft, Axis::LeftX), - up: PlayerControllerInputType::Either(Button::DPadUp, Axis::LeftY), - right: PlayerControllerInputType::Either(Button::DPadRight, Axis::LeftX), - down: PlayerControllerInputType::Either(Button::DPadDown, Axis::LeftY), + left: PlayerControllerInputType::Either(Button::DPadLeft, Axis::LeftX, AxisDirection::Left), + up: PlayerControllerInputType::Either(Button::DPadUp, Axis::LeftY, AxisDirection::Up), + right: PlayerControllerInputType::Either(Button::DPadRight, Axis::LeftX, AxisDirection::Right), + down: PlayerControllerInputType::Either(Button::DPadDown, Axis::LeftY, AxisDirection::Down), prev_weapon: PlayerControllerInputType::ButtonInput(Button::LeftShoulder), next_weapon: PlayerControllerInputType::ButtonInput(Button::RightShoulder), jump: PlayerControllerInputType::ButtonInput(Button::South), shoot: PlayerControllerInputType::ButtonInput(Button::East), - skip: PlayerControllerInputType::AxisInput(Axis::TriggerLeft), - strafe: PlayerControllerInputType::AxisInput(Axis::TriggerRight), + skip: PlayerControllerInputType::AxisInput(Axis::TriggerLeft, AxisDirection::Either), + strafe: PlayerControllerInputType::AxisInput(Axis::TriggerRight, AxisDirection::Either), inventory: PlayerControllerInputType::ButtonInput(Button::North), map: PlayerControllerInputType::ButtonInput(Button::West), }