diff --git a/src/builtin/builtin_data/buttons.png b/src/builtin/builtin_data/buttons.png new file mode 100644 index 0000000..58a2ec8 Binary files /dev/null and b/src/builtin/builtin_data/buttons.png differ diff --git a/src/builtin_fs.rs b/src/builtin_fs.rs index e3b4d48..4fc6075 100644 --- a/src/builtin_fs.rs +++ b/src/builtin_fs.rs @@ -106,6 +106,10 @@ impl BuiltinFS { include_bytes!("builtin/organya-wavetable-doukutsu.bin"), ), FSNode::File("touch.png", include_bytes!("builtin/touch.png")), + FSNode::Directory( + "builtin_data", + vec![FSNode::File("buttons.png", include_bytes!("builtin/builtin_data/buttons.png"))], + ), FSNode::Directory( "shaders", vec![ diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index 049cc4f..2b7d301 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -11,6 +11,7 @@ use crate::engine_constants::npcs::NPCConsts; use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::framework::filesystem; +use crate::framework::gamepad::{Axis, Button}; use crate::i18n::Locale; use crate::player::ControlMode; use crate::scripting::tsc::text_script::TextScriptEncoding; @@ -287,6 +288,29 @@ impl Clone for TitleConsts { } } +#[derive(Debug)] +pub struct GamepadConsts { + pub button_rects: HashMap; 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), }