mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2024-11-22 13:42:47 +00:00
add controls settings menu
This commit is contained in:
parent
03e9c9db0c
commit
ffaf12cca8
|
@ -114,6 +114,8 @@
|
||||||
"soundtrack": "Soundtrack: {soundtrack}"
|
"soundtrack": "Soundtrack: {soundtrack}"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"controls": "Controls...",
|
||||||
|
|
||||||
"language": "Language...",
|
"language": "Language...",
|
||||||
|
|
||||||
"behavior": "Behavior...",
|
"behavior": "Behavior...",
|
||||||
|
@ -125,6 +127,38 @@
|
||||||
},
|
},
|
||||||
"pause_on_focus_loss": "Pause on focus loss:"
|
"pause_on_focus_loss": "Pause on focus loss:"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"controls_menu": {
|
||||||
|
"select_player": {
|
||||||
|
"entry": "Select player:",
|
||||||
|
"player_1": "Player 1",
|
||||||
|
"player_2": "Player 2"
|
||||||
|
},
|
||||||
|
"controller": {
|
||||||
|
"entry": "Controller:",
|
||||||
|
"keyboard": "Keyboard"
|
||||||
|
},
|
||||||
|
"rebind": "Rebind...",
|
||||||
|
"rebind_menu": {
|
||||||
|
"up": "Up",
|
||||||
|
"down": "Down",
|
||||||
|
"left": "Left",
|
||||||
|
"right": "Right",
|
||||||
|
"jump": "Jump",
|
||||||
|
"shoot": "Shoot",
|
||||||
|
"prev_weapon": "Previous weapon",
|
||||||
|
"next_weapon": "Next weapon",
|
||||||
|
"inventory": "Inventory",
|
||||||
|
"map": "Map system",
|
||||||
|
"skip": "Skip",
|
||||||
|
"strafe": "Strafe"
|
||||||
|
},
|
||||||
|
|
||||||
|
"rebind_confirm_menu": {
|
||||||
|
"title": "Press button for \"{control}\"",
|
||||||
|
"cancel": "(Esc to cancel)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,8 @@
|
||||||
"soundtrack": "サウンドトラック: {soundtrack}"
|
"soundtrack": "サウンドトラック: {soundtrack}"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"controls": "ボタン変更",
|
||||||
|
|
||||||
"language": "言語",
|
"language": "言語",
|
||||||
|
|
||||||
"behavior": "動作",
|
"behavior": "動作",
|
||||||
|
@ -117,8 +119,41 @@
|
||||||
},
|
},
|
||||||
"pause_on_focus_loss": "フォーカスが外れた時のポーズ:"
|
"pause_on_focus_loss": "フォーカスが外れた時のポーズ:"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"controls_menu": {
|
||||||
|
"select_player": {
|
||||||
|
"entry": "プレイヤーを選択:",
|
||||||
|
"player_1": "プレーヤー 1",
|
||||||
|
"player_2": "プレーヤー 2"
|
||||||
|
},
|
||||||
|
"controller": {
|
||||||
|
"entry": "コントローラ:",
|
||||||
|
"keyboard": "キーボード"
|
||||||
|
},
|
||||||
|
"rebind": "再バインド",
|
||||||
|
"rebind_menu": {
|
||||||
|
"up": "うえ",
|
||||||
|
"down": "した",
|
||||||
|
"left": "ひだり",
|
||||||
|
"right": "みぎ",
|
||||||
|
"jump": "ジャンプ",
|
||||||
|
"shoot": "ショット",
|
||||||
|
"prev_weapon": "前の武器",
|
||||||
|
"next_weapon": "次の武器",
|
||||||
|
"inventory": "在庫",
|
||||||
|
"map": "マップシステム",
|
||||||
|
"skip": "スキップ",
|
||||||
|
"strafe": "ストレイフ"
|
||||||
|
},
|
||||||
|
|
||||||
|
"rebind_confirm_menu": {
|
||||||
|
"title": "新しい「ジャンプ」ボタンを押す",
|
||||||
|
"cancel": "(Escキーを押してキャンセル)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"soundtrack": {
|
"soundtrack": {
|
||||||
"organya": "オルガーニャ",
|
"organya": "オルガーニャ",
|
||||||
"remastered": "リマスター",
|
"remastered": "リマスター",
|
||||||
|
|
|
@ -33,6 +33,26 @@ pub enum AxisDirection {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AxisDirection {
|
impl AxisDirection {
|
||||||
|
pub fn from_axis_data(axis: Axis, value: f64) -> Self {
|
||||||
|
match axis {
|
||||||
|
Axis::LeftX | Axis::RightX => {
|
||||||
|
if value < 0.0 {
|
||||||
|
AxisDirection::Left
|
||||||
|
} else {
|
||||||
|
AxisDirection::Right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Axis::LeftY | Axis::RightY => {
|
||||||
|
if value < 0.0 {
|
||||||
|
AxisDirection::Up
|
||||||
|
} else {
|
||||||
|
AxisDirection::Down
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Axis::TriggerLeft | Axis::TriggerRight => AxisDirection::Either,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compare(&self, value: f64, axis_sensitivity: f64) -> bool {
|
pub fn compare(&self, value: f64, axis_sensitivity: f64) -> bool {
|
||||||
match self {
|
match self {
|
||||||
AxisDirection::None => false,
|
AxisDirection::None => false,
|
||||||
|
@ -143,6 +163,26 @@ impl GamepadData {
|
||||||
|
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_gamepad_name(&self) -> String {
|
||||||
|
let name = if let Some(controller_type) = self.controller_type {
|
||||||
|
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 => "PlayStation Controller".to_string(),
|
||||||
|
sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_XBOX360
|
||||||
|
| sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_XBOXONE => "Xbox Controller".to_string(),
|
||||||
|
sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO => {
|
||||||
|
"Nintendo Switch Controller".to_string()
|
||||||
|
}
|
||||||
|
_ => "Unknown Controller".to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"Unknown controller".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GamepadContext {
|
pub struct GamepadContext {
|
||||||
|
@ -258,6 +298,28 @@ impl GamepadContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_gamepads(&self) -> &Vec<GamepadData> {
|
||||||
|
&self.gamepads
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pressed_buttons(&self, gamepad_index: u32) -> HashSet<Button> {
|
||||||
|
if let Some(gamepad) = self.get_gamepad_by_index(gamepad_index as usize) {
|
||||||
|
return gamepad.pressed_buttons_set.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn active_axes(&self, gamepad_index: u32) -> HashMap<Axis, f64> {
|
||||||
|
if let Some(gamepad) = self.get_gamepad_by_index(gamepad_index as usize) {
|
||||||
|
let mut active_axes = gamepad.axis_values.clone();
|
||||||
|
active_axes.retain(|_, v| v.abs() > gamepad.axis_sensitivity);
|
||||||
|
return active_axes;
|
||||||
|
}
|
||||||
|
|
||||||
|
HashMap::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GamepadContext {
|
impl Default for GamepadContext {
|
||||||
|
@ -293,3 +355,15 @@ pub fn is_button_active(ctx: &Context, gamepad_index: u32, button: Button) -> bo
|
||||||
pub fn is_axis_active(ctx: &Context, gamepad_index: u32, axis: Axis, direction: AxisDirection) -> bool {
|
pub fn is_axis_active(ctx: &Context, gamepad_index: u32, axis: Axis, direction: AxisDirection) -> bool {
|
||||||
ctx.gamepad_context.is_axis_active(gamepad_index, axis, direction)
|
ctx.gamepad_context.is_axis_active(gamepad_index, axis, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_gamepads(ctx: &Context) -> &Vec<GamepadData> {
|
||||||
|
ctx.gamepad_context.get_gamepads()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pressed_buttons(ctx: &Context, gamepad_index: u32) -> HashSet<Button> {
|
||||||
|
ctx.gamepad_context.pressed_buttons(gamepad_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_axes(ctx: &Context, gamepad_index: u32) -> HashMap<Axis, f64> {
|
||||||
|
ctx.gamepad_context.active_axes(gamepad_index)
|
||||||
|
}
|
||||||
|
|
823
src/menu/controls_menu.rs
Normal file
823
src/menu/controls_menu.rs
Normal file
|
@ -0,0 +1,823 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameResult;
|
||||||
|
use crate::framework::gamepad::{self, Axis, AxisDirection, Button, PlayerControllerInputType};
|
||||||
|
use crate::framework::keyboard::ScanCode;
|
||||||
|
use crate::input::combined_menu_controller::CombinedMenuController;
|
||||||
|
use crate::settings::{ControllerType, PlayerControllerButtonMap, PlayerKeyMap};
|
||||||
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
|
||||||
|
use super::{ControlMenuData, Menu, MenuEntry, MenuSelectionResult};
|
||||||
|
|
||||||
|
const FORBIDDEN_SCANCODES: [ScanCode; 12] = [
|
||||||
|
ScanCode::F1,
|
||||||
|
ScanCode::F2,
|
||||||
|
ScanCode::F3,
|
||||||
|
ScanCode::F4,
|
||||||
|
ScanCode::F5,
|
||||||
|
ScanCode::F6,
|
||||||
|
ScanCode::F7,
|
||||||
|
ScanCode::F8,
|
||||||
|
ScanCode::F9,
|
||||||
|
ScanCode::F10,
|
||||||
|
ScanCode::F11,
|
||||||
|
ScanCode::F12,
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
enum CurrentMenu {
|
||||||
|
ControllerMenu,
|
||||||
|
RebindMenu,
|
||||||
|
ConfirmRebindMenu,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
enum ControllerMenuEntry {
|
||||||
|
SelectedPlayer,
|
||||||
|
Controller,
|
||||||
|
Rebind,
|
||||||
|
Back,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ControllerMenuEntry {
|
||||||
|
fn default() -> Self {
|
||||||
|
ControllerMenuEntry::SelectedPlayer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
enum RebindMenuEntry {
|
||||||
|
Control(ControlEntry),
|
||||||
|
Back,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RebindMenuEntry {
|
||||||
|
fn default() -> Self {
|
||||||
|
RebindMenuEntry::Control(ControlEntry::Up)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
enum Player {
|
||||||
|
Player1,
|
||||||
|
Player2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
fn controller_type(self, state: &SharedGameState) -> ControllerType {
|
||||||
|
match self {
|
||||||
|
Player::Player1 => state.settings.player1_controller_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||||
|
enum ControlEntry {
|
||||||
|
Left,
|
||||||
|
Up,
|
||||||
|
Right,
|
||||||
|
Down,
|
||||||
|
PrevWeapon,
|
||||||
|
NextWeapon,
|
||||||
|
Jump,
|
||||||
|
Shoot,
|
||||||
|
Skip,
|
||||||
|
Inventory,
|
||||||
|
Map,
|
||||||
|
Strafe,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlEntry {
|
||||||
|
fn to_string(&self, state: &SharedGameState) -> String {
|
||||||
|
match self {
|
||||||
|
ControlEntry::Left => state.t("menus.controls_menu.rebind_menu.left"),
|
||||||
|
ControlEntry::Up => state.t("menus.controls_menu.rebind_menu.up"),
|
||||||
|
ControlEntry::Right => state.t("menus.controls_menu.rebind_menu.right"),
|
||||||
|
ControlEntry::Down => state.t("menus.controls_menu.rebind_menu.down"),
|
||||||
|
ControlEntry::PrevWeapon => state.t("menus.controls_menu.rebind_menu.prev_weapon"),
|
||||||
|
ControlEntry::NextWeapon => state.t("menus.controls_menu.rebind_menu.next_weapon"),
|
||||||
|
ControlEntry::Jump => state.t("menus.controls_menu.rebind_menu.jump"),
|
||||||
|
ControlEntry::Shoot => state.t("menus.controls_menu.rebind_menu.shoot"),
|
||||||
|
ControlEntry::Skip => state.t("menus.controls_menu.rebind_menu.skip"),
|
||||||
|
ControlEntry::Inventory => state.t("menus.controls_menu.rebind_menu.inventory"),
|
||||||
|
ControlEntry::Map => state.t("menus.controls_menu.rebind_menu.map"),
|
||||||
|
ControlEntry::Strafe => state.t("menus.controls_menu.rebind_menu.strafe"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ControlsMenu {
|
||||||
|
current: CurrentMenu,
|
||||||
|
controller: Menu<ControllerMenuEntry>,
|
||||||
|
rebind: Menu<RebindMenuEntry>,
|
||||||
|
confirm_rebind: Menu<usize>,
|
||||||
|
|
||||||
|
selected_player: Player,
|
||||||
|
selected_controller: ControllerType,
|
||||||
|
selected_control: Option<ControlEntry>,
|
||||||
|
|
||||||
|
player1_key_map: Vec<(ControlEntry, ScanCode)>,
|
||||||
|
player2_key_map: Vec<(ControlEntry, ScanCode)>,
|
||||||
|
player1_controller_button_map: Vec<(ControlEntry, PlayerControllerInputType)>,
|
||||||
|
player2_controller_button_map: Vec<(ControlEntry, PlayerControllerInputType)>,
|
||||||
|
|
||||||
|
input_busy: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControlsMenu {
|
||||||
|
pub fn new() -> ControlsMenu {
|
||||||
|
let controller = Menu::new(0, 0, 220, 0);
|
||||||
|
let rebind = Menu::new(0, 0, 220, 0);
|
||||||
|
let confirm_rebind = Menu::new(0, 0, 220, 0);
|
||||||
|
|
||||||
|
ControlsMenu {
|
||||||
|
current: CurrentMenu::ControllerMenu,
|
||||||
|
controller,
|
||||||
|
rebind,
|
||||||
|
confirm_rebind,
|
||||||
|
|
||||||
|
selected_player: Player::Player1,
|
||||||
|
selected_controller: ControllerType::Keyboard,
|
||||||
|
selected_control: None,
|
||||||
|
|
||||||
|
player1_key_map: Vec::new(),
|
||||||
|
player2_key_map: Vec::new(),
|
||||||
|
player1_controller_button_map: Vec::new(),
|
||||||
|
player2_controller_button_map: Vec::new(),
|
||||||
|
|
||||||
|
input_busy: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||||
|
self.controller.push_entry(
|
||||||
|
ControllerMenuEntry::SelectedPlayer,
|
||||||
|
MenuEntry::Options(
|
||||||
|
state.t("menus.controls_menu.select_player.entry"),
|
||||||
|
self.selected_player as usize,
|
||||||
|
vec![
|
||||||
|
state.t("menus.controls_menu.select_player.player_1"),
|
||||||
|
state.t("menus.controls_menu.select_player.player_2"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.controller.push_entry(ControllerMenuEntry::Controller, MenuEntry::Hidden);
|
||||||
|
self.controller
|
||||||
|
.push_entry(ControllerMenuEntry::Rebind, MenuEntry::Active(state.t("menus.controls_menu.rebind")));
|
||||||
|
self.controller.push_entry(ControllerMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
|
||||||
|
|
||||||
|
self.player1_key_map = self.init_key_map(&state.settings.player1_key_map);
|
||||||
|
self.player2_key_map = self.init_key_map(&state.settings.player2_key_map);
|
||||||
|
self.player1_controller_button_map =
|
||||||
|
self.init_controller_button_map(&state.settings.player1_controller_button_map);
|
||||||
|
self.player2_controller_button_map =
|
||||||
|
self.init_controller_button_map(&state.settings.player2_controller_button_map);
|
||||||
|
|
||||||
|
self.confirm_rebind.draw_cursor = false;
|
||||||
|
self.confirm_rebind.non_interactive = true;
|
||||||
|
|
||||||
|
self.update_controller_options(state, ctx);
|
||||||
|
self.update_rebind_menu(state, ctx);
|
||||||
|
self.update_sizes(state);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_sizes(&mut self, state: &SharedGameState) {
|
||||||
|
self.controller.update_width(state);
|
||||||
|
self.controller.update_height();
|
||||||
|
self.controller.x = ((state.canvas_size.0 - self.controller.width as f32) / 2.0).floor() as isize;
|
||||||
|
self.controller.y = ((state.canvas_size.1 - self.controller.height as f32) / 2.0).floor() as isize;
|
||||||
|
|
||||||
|
self.rebind.update_width(state);
|
||||||
|
self.rebind.update_height();
|
||||||
|
self.rebind.x = ((state.canvas_size.0 - self.rebind.width as f32) / 2.0).floor() as isize;
|
||||||
|
self.rebind.y = ((state.canvas_size.1 - self.rebind.height as f32) / 2.0).floor() as isize;
|
||||||
|
|
||||||
|
self.confirm_rebind.update_width(state);
|
||||||
|
self.confirm_rebind.update_height();
|
||||||
|
self.confirm_rebind.x = ((state.canvas_size.0 - self.confirm_rebind.width as f32) / 2.0).floor() as isize;
|
||||||
|
self.confirm_rebind.y = ((state.canvas_size.1 - self.confirm_rebind.height as f32) / 2.0).floor() as isize;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_key_map(&self, settings_key_map: &PlayerKeyMap) -> Vec<(ControlEntry, ScanCode)> {
|
||||||
|
let mut map = Vec::new();
|
||||||
|
|
||||||
|
map.push((ControlEntry::Up, settings_key_map.up));
|
||||||
|
map.push((ControlEntry::Down, settings_key_map.down));
|
||||||
|
map.push((ControlEntry::Left, settings_key_map.left));
|
||||||
|
map.push((ControlEntry::Right, settings_key_map.right));
|
||||||
|
map.push((ControlEntry::Jump, settings_key_map.jump));
|
||||||
|
map.push((ControlEntry::Shoot, settings_key_map.shoot));
|
||||||
|
map.push((ControlEntry::PrevWeapon, settings_key_map.prev_weapon));
|
||||||
|
map.push((ControlEntry::NextWeapon, settings_key_map.next_weapon));
|
||||||
|
map.push((ControlEntry::Inventory, settings_key_map.inventory));
|
||||||
|
map.push((ControlEntry::Map, settings_key_map.map));
|
||||||
|
map.push((ControlEntry::Skip, settings_key_map.skip));
|
||||||
|
map.push((ControlEntry::Strafe, settings_key_map.strafe));
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_controller_button_map(
|
||||||
|
&self,
|
||||||
|
settings_controller_button_map: &PlayerControllerButtonMap,
|
||||||
|
) -> Vec<(ControlEntry, PlayerControllerInputType)> {
|
||||||
|
let mut map = Vec::new();
|
||||||
|
|
||||||
|
map.push((ControlEntry::Up, settings_controller_button_map.up));
|
||||||
|
map.push((ControlEntry::Down, settings_controller_button_map.down));
|
||||||
|
map.push((ControlEntry::Left, settings_controller_button_map.left));
|
||||||
|
map.push((ControlEntry::Right, settings_controller_button_map.right));
|
||||||
|
map.push((ControlEntry::Jump, settings_controller_button_map.jump));
|
||||||
|
map.push((ControlEntry::Shoot, settings_controller_button_map.shoot));
|
||||||
|
map.push((ControlEntry::PrevWeapon, settings_controller_button_map.prev_weapon));
|
||||||
|
map.push((ControlEntry::NextWeapon, settings_controller_button_map.next_weapon));
|
||||||
|
map.push((ControlEntry::Inventory, settings_controller_button_map.inventory));
|
||||||
|
map.push((ControlEntry::Map, settings_controller_button_map.map));
|
||||||
|
map.push((ControlEntry::Skip, settings_controller_button_map.skip));
|
||||||
|
map.push((ControlEntry::Strafe, settings_controller_button_map.strafe));
|
||||||
|
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_rebind_menu(&mut self, state: &SharedGameState, ctx: &Context) {
|
||||||
|
self.rebind.entries.clear();
|
||||||
|
|
||||||
|
match self.selected_player {
|
||||||
|
Player::Player1 => {
|
||||||
|
if self.selected_controller == ControllerType::Keyboard {
|
||||||
|
for (k, v) in self.player1_key_map.iter() {
|
||||||
|
self.rebind.push_entry(
|
||||||
|
RebindMenuEntry::Control(*k),
|
||||||
|
MenuEntry::Control(
|
||||||
|
k.to_string(state).to_owned(),
|
||||||
|
ControlMenuData::String(format!("{:?}", v)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (k, v) in self.player1_controller_button_map.iter() {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.rebind.push_entry(
|
||||||
|
RebindMenuEntry::Control(*k),
|
||||||
|
MenuEntry::Control(
|
||||||
|
k.to_string(state).to_owned(),
|
||||||
|
ControlMenuData::Rect(v.get_rect(gamepad_sprite_offset, &state.constants)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Player::Player2 => {
|
||||||
|
if self.selected_controller == ControllerType::Keyboard {
|
||||||
|
for (k, v) in self.player2_key_map.iter() {
|
||||||
|
self.rebind.push_entry(
|
||||||
|
RebindMenuEntry::Control(*k),
|
||||||
|
MenuEntry::Control(
|
||||||
|
k.to_string(state).to_owned(),
|
||||||
|
ControlMenuData::String(format!("{:?}", v)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (k, v) in self.player2_controller_button_map.iter() {
|
||||||
|
let gamepad_sprite_offset = match state.settings.player2_controller_type {
|
||||||
|
ControllerType::Keyboard => 1,
|
||||||
|
ControllerType::Gamepad(index) => {
|
||||||
|
ctx.gamepad_context.get_gamepad_sprite_offset(index as usize)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.rebind.push_entry(
|
||||||
|
RebindMenuEntry::Control(*k),
|
||||||
|
MenuEntry::Control(
|
||||||
|
k.to_string(state).to_owned(),
|
||||||
|
ControlMenuData::Rect(v.get_rect(gamepad_sprite_offset, &state.constants)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rebind.push_entry(RebindMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_controller_options(&mut self, state: &SharedGameState, ctx: &Context) {
|
||||||
|
let mut controllers = Vec::new();
|
||||||
|
controllers.push(state.t("menus.controls_menu.controller.keyboard"));
|
||||||
|
|
||||||
|
let gamepads = gamepad::get_gamepads(ctx);
|
||||||
|
|
||||||
|
for i in 0..gamepads.len() {
|
||||||
|
controllers.push(format!("{} {}", gamepads[i].get_gamepad_name(), i + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller_type = match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_type,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let ControllerType::Gamepad(index) = controller_type {
|
||||||
|
if index as usize >= gamepads.len() {
|
||||||
|
self.selected_controller = ControllerType::Keyboard;
|
||||||
|
} else {
|
||||||
|
self.selected_controller = controller_type;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.selected_controller = controller_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller_idx = match self.selected_controller {
|
||||||
|
ControllerType::Keyboard => 0,
|
||||||
|
ControllerType::Gamepad(idx) => idx as usize + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.controller.set_entry(
|
||||||
|
ControllerMenuEntry::Controller,
|
||||||
|
MenuEntry::Options(
|
||||||
|
state.t("menus.controls_menu.controller.entry"),
|
||||||
|
controller_idx as usize,
|
||||||
|
controllers.clone(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_confirm_controls_menu(&mut self, state: &SharedGameState) {
|
||||||
|
match self.selected_control {
|
||||||
|
Some(control) => {
|
||||||
|
self.confirm_rebind.entries.clear();
|
||||||
|
|
||||||
|
self.confirm_rebind.push_entry(
|
||||||
|
0,
|
||||||
|
MenuEntry::DisabledWhite(state.tt(
|
||||||
|
"menus.controls_menu.rebind_confirm_menu.title",
|
||||||
|
HashMap::from([("control".to_string(), control.to_string(state))]),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
self.confirm_rebind
|
||||||
|
.push_entry(1, MenuEntry::Disabled(state.t("menus.controls_menu.rebind_confirm_menu.cancel")));
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_key_occupied(&self, scan_code: ScanCode) -> bool {
|
||||||
|
let keymap = match self.selected_player {
|
||||||
|
Player::Player1 => &self.player2_key_map,
|
||||||
|
Player::Player2 => &self.player1_key_map,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (_, v) in keymap.iter() {
|
||||||
|
if *v == scan_code {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_key(&mut self, state: &mut SharedGameState, scan_code: ScanCode, ctx: &Context) -> GameResult {
|
||||||
|
if self.selected_control.is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.selected_control.unwrap() {
|
||||||
|
ControlEntry::Left => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.left = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.left = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::Up => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.up = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.up = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::Right => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.right = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.right = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::Down => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.down = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.down = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::PrevWeapon => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.prev_weapon = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.prev_weapon = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::NextWeapon => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.next_weapon = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.next_weapon = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::Jump => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.jump = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.jump = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::Shoot => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.shoot = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.shoot = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::Skip => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.skip = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.skip = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::Inventory => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.inventory = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.inventory = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::Map => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.map = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.map = scan_code,
|
||||||
|
},
|
||||||
|
ControlEntry::Strafe => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_key_map.strafe = scan_code,
|
||||||
|
Player::Player2 => state.settings.player2_key_map.strafe = scan_code,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
state.settings.save(ctx)?;
|
||||||
|
|
||||||
|
let keymap = match self.selected_player {
|
||||||
|
Player::Player1 => &mut self.player1_key_map,
|
||||||
|
Player::Player2 => &mut self.player2_key_map,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (entry, value) in keymap.iter_mut() {
|
||||||
|
if *entry == self.selected_control.unwrap() {
|
||||||
|
*value = scan_code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_controller_input(
|
||||||
|
&mut self,
|
||||||
|
state: &mut SharedGameState,
|
||||||
|
input_type: PlayerControllerInputType,
|
||||||
|
ctx: &Context,
|
||||||
|
) -> GameResult {
|
||||||
|
if self.selected_control.is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.selected_control.unwrap() {
|
||||||
|
ControlEntry::Left => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.left = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.left = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::Up => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.up = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.up = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::Right => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.right = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.right = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::Down => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.down = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.down = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::PrevWeapon => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.prev_weapon = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.prev_weapon = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::NextWeapon => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.next_weapon = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.next_weapon = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::Jump => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.jump = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.jump = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::Shoot => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.shoot = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.shoot = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::Skip => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.skip = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.skip = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::Inventory => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.inventory = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.inventory = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::Map => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.map = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.map = input_type,
|
||||||
|
},
|
||||||
|
ControlEntry::Strafe => match self.selected_player {
|
||||||
|
Player::Player1 => state.settings.player1_controller_button_map.strafe = input_type,
|
||||||
|
Player::Player2 => state.settings.player2_controller_button_map.strafe = input_type,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
state.settings.save(ctx)?;
|
||||||
|
|
||||||
|
let button_map = match self.selected_player {
|
||||||
|
Player::Player1 => &mut self.player1_controller_button_map,
|
||||||
|
Player::Player2 => &mut self.player2_controller_button_map,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (entry, value) in button_map.iter_mut() {
|
||||||
|
if *entry == self.selected_control.unwrap() {
|
||||||
|
*value = input_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_gamepad_input(&self, input: PlayerControllerInputType) -> PlayerControllerInputType {
|
||||||
|
match input {
|
||||||
|
PlayerControllerInputType::ButtonInput(Button::DPadUp) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadUp, Axis::LeftY, AxisDirection::Up)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::ButtonInput(Button::DPadDown) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadDown, Axis::LeftY, AxisDirection::Down)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::ButtonInput(Button::DPadLeft) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadLeft, Axis::LeftX, AxisDirection::Left)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::ButtonInput(Button::DPadRight) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadRight, Axis::LeftX, AxisDirection::Right)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::AxisInput(Axis::LeftY, AxisDirection::Up) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadUp, Axis::LeftY, AxisDirection::Up)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::AxisInput(Axis::LeftY, AxisDirection::Down) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadDown, Axis::LeftY, AxisDirection::Down)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::AxisInput(Axis::LeftX, AxisDirection::Left) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadLeft, Axis::LeftX, AxisDirection::Left)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::AxisInput(Axis::LeftX, AxisDirection::Right) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadRight, Axis::LeftX, AxisDirection::Right)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::AxisInput(Axis::RightY, AxisDirection::Up) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadUp, Axis::RightY, AxisDirection::Up)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::AxisInput(Axis::RightY, AxisDirection::Down) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadDown, Axis::RightY, AxisDirection::Down)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::AxisInput(Axis::RightX, AxisDirection::Left) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadLeft, Axis::RightX, AxisDirection::Left)
|
||||||
|
}
|
||||||
|
PlayerControllerInputType::AxisInput(Axis::RightX, AxisDirection::Right) => {
|
||||||
|
PlayerControllerInputType::Either(Button::DPadRight, Axis::RightX, AxisDirection::Right)
|
||||||
|
}
|
||||||
|
_ => input,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(
|
||||||
|
&mut self,
|
||||||
|
exit_action: &mut dyn FnMut(),
|
||||||
|
controller: &mut CombinedMenuController,
|
||||||
|
state: &mut SharedGameState,
|
||||||
|
ctx: &mut Context,
|
||||||
|
) -> GameResult {
|
||||||
|
self.update_sizes(state);
|
||||||
|
|
||||||
|
match self.current {
|
||||||
|
CurrentMenu::ControllerMenu => match self.controller.tick(controller, state) {
|
||||||
|
MenuSelectionResult::Selected(ControllerMenuEntry::SelectedPlayer, toggle)
|
||||||
|
| MenuSelectionResult::Left(ControllerMenuEntry::SelectedPlayer, toggle, _)
|
||||||
|
| MenuSelectionResult::Right(ControllerMenuEntry::SelectedPlayer, toggle, _) => {
|
||||||
|
if let MenuEntry::Options(_, value, _) = toggle {
|
||||||
|
let (new_player, new_value) = match *value {
|
||||||
|
0 => (Player::Player2, 1),
|
||||||
|
1 => (Player::Player1, 0),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
*value = new_value;
|
||||||
|
|
||||||
|
self.selected_player = new_player;
|
||||||
|
self.selected_controller = new_player.controller_type(state);
|
||||||
|
|
||||||
|
self.update_controller_options(state, ctx);
|
||||||
|
self.update_rebind_menu(state, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuSelectionResult::Selected(ControllerMenuEntry::Controller, toggle)
|
||||||
|
| MenuSelectionResult::Right(ControllerMenuEntry::Controller, toggle, _) => {
|
||||||
|
if self.input_busy {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let MenuEntry::Options(_, value, entries) = toggle {
|
||||||
|
if *value == entries.len() - 1 {
|
||||||
|
self.selected_controller = ControllerType::Keyboard;
|
||||||
|
*value = 0;
|
||||||
|
} else {
|
||||||
|
self.selected_controller = ControllerType::Gamepad(*value as u32);
|
||||||
|
*value = *value + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.selected_player == Player::Player1 {
|
||||||
|
state.settings.player1_controller_type = self.selected_controller;
|
||||||
|
} else {
|
||||||
|
state.settings.player2_controller_type = self.selected_controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = state.settings.save(ctx);
|
||||||
|
|
||||||
|
let mut new_menu_controller = CombinedMenuController::new();
|
||||||
|
new_menu_controller.add(state.settings.create_player1_controller());
|
||||||
|
new_menu_controller.add(state.settings.create_player2_controller());
|
||||||
|
self.input_busy = true;
|
||||||
|
*controller = new_menu_controller;
|
||||||
|
|
||||||
|
self.update_rebind_menu(state, ctx);
|
||||||
|
}
|
||||||
|
MenuSelectionResult::Left(ControllerMenuEntry::Controller, toggle, _) => {
|
||||||
|
if self.input_busy {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let MenuEntry::Options(_, value, entries) = toggle {
|
||||||
|
if *value == 1 {
|
||||||
|
self.selected_controller = ControllerType::Keyboard;
|
||||||
|
*value = 0;
|
||||||
|
} else {
|
||||||
|
self.selected_controller = ControllerType::Gamepad(*value as u32);
|
||||||
|
|
||||||
|
if *value == 0 {
|
||||||
|
*value = entries.len() - 1;
|
||||||
|
} else {
|
||||||
|
*value = *value - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.selected_player == Player::Player1 {
|
||||||
|
state.settings.player1_controller_type = self.selected_controller;
|
||||||
|
} else {
|
||||||
|
state.settings.player2_controller_type = self.selected_controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = state.settings.save(ctx);
|
||||||
|
|
||||||
|
let mut new_menu_controller = CombinedMenuController::new();
|
||||||
|
new_menu_controller.add(state.settings.create_player1_controller());
|
||||||
|
new_menu_controller.add(state.settings.create_player2_controller());
|
||||||
|
self.input_busy = true;
|
||||||
|
*controller = new_menu_controller;
|
||||||
|
|
||||||
|
self.update_rebind_menu(state, ctx);
|
||||||
|
}
|
||||||
|
MenuSelectionResult::Selected(ControllerMenuEntry::Rebind, _) => {
|
||||||
|
self.current = CurrentMenu::RebindMenu;
|
||||||
|
}
|
||||||
|
MenuSelectionResult::Selected(ControllerMenuEntry::Back, _) | MenuSelectionResult::Canceled => {
|
||||||
|
exit_action()
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
CurrentMenu::RebindMenu => match self.rebind.tick(controller, state) {
|
||||||
|
MenuSelectionResult::Selected(RebindMenuEntry::Back, _) | MenuSelectionResult::Canceled => {
|
||||||
|
self.current = CurrentMenu::ControllerMenu;
|
||||||
|
}
|
||||||
|
MenuSelectionResult::Selected(RebindMenuEntry::Control(control), _) => {
|
||||||
|
if !self.input_busy {
|
||||||
|
self.selected_control = Some(control);
|
||||||
|
self.update_confirm_controls_menu(state);
|
||||||
|
self.input_busy = true;
|
||||||
|
self.current = CurrentMenu::ConfirmRebindMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
CurrentMenu::ConfirmRebindMenu => match self.confirm_rebind.tick(controller, state) {
|
||||||
|
_ => {
|
||||||
|
let pressed_keys: Vec<_> = ctx.keyboard_context.pressed_keys().into_iter().collect();
|
||||||
|
|
||||||
|
for key in pressed_keys.clone() {
|
||||||
|
if *key == ScanCode::Escape {
|
||||||
|
state.sound_manager.play_sfx(5);
|
||||||
|
self.current = CurrentMenu::RebindMenu;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.selected_controller {
|
||||||
|
ControllerType::Keyboard => {
|
||||||
|
if pressed_keys.len() == 1 {
|
||||||
|
if !self.input_busy {
|
||||||
|
self.input_busy = true;
|
||||||
|
|
||||||
|
let key = **pressed_keys.first().unwrap();
|
||||||
|
|
||||||
|
if self.is_key_occupied(key)
|
||||||
|
|| FORBIDDEN_SCANCODES.contains(&key)
|
||||||
|
|| self.selected_controller != ControllerType::Keyboard
|
||||||
|
{
|
||||||
|
state.sound_manager.play_sfx(12);
|
||||||
|
} else {
|
||||||
|
self.set_key(state, key, ctx)?;
|
||||||
|
self.update_rebind_menu(state, ctx);
|
||||||
|
self.selected_control = None;
|
||||||
|
state.sound_manager.play_sfx(18);
|
||||||
|
self.current = CurrentMenu::RebindMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ControllerType::Gamepad(idx) => {
|
||||||
|
let pressed_gamepad_buttons: Vec<_> =
|
||||||
|
ctx.gamepad_context.pressed_buttons(idx).into_iter().collect();
|
||||||
|
|
||||||
|
if pressed_gamepad_buttons.len() == 1 {
|
||||||
|
if !self.input_busy {
|
||||||
|
self.input_busy = true;
|
||||||
|
|
||||||
|
if self.selected_player.controller_type(state) != self.selected_controller {
|
||||||
|
state.sound_manager.play_sfx(12);
|
||||||
|
} else {
|
||||||
|
let button = *pressed_gamepad_buttons.first().unwrap();
|
||||||
|
let normalized_input = self
|
||||||
|
.normalize_gamepad_input(PlayerControllerInputType::ButtonInput(button));
|
||||||
|
|
||||||
|
self.set_controller_input(state, normalized_input, ctx)?;
|
||||||
|
self.update_rebind_menu(state, ctx);
|
||||||
|
self.selected_control = None;
|
||||||
|
state.sound_manager.play_sfx(18);
|
||||||
|
self.current = CurrentMenu::RebindMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let active_axes: Vec<_> = ctx.gamepad_context.active_axes(idx).into_iter().collect();
|
||||||
|
|
||||||
|
if active_axes.len() == 1 {
|
||||||
|
if !self.input_busy {
|
||||||
|
self.input_busy = true;
|
||||||
|
|
||||||
|
if self.selected_player.controller_type(state) != self.selected_controller {
|
||||||
|
state.sound_manager.play_sfx(12);
|
||||||
|
} else {
|
||||||
|
let (axis, value) = *active_axes.first().unwrap();
|
||||||
|
let direction = AxisDirection::from_axis_data(axis, value);
|
||||||
|
let normalized_input = self.normalize_gamepad_input(
|
||||||
|
PlayerControllerInputType::AxisInput(axis, direction),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.set_controller_input(state, normalized_input, ctx)?;
|
||||||
|
self.update_rebind_menu(state, ctx);
|
||||||
|
self.selected_control = None;
|
||||||
|
state.sound_manager.play_sfx(18);
|
||||||
|
self.current = CurrentMenu::RebindMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pressed_keys.is_empty() && pressed_gamepad_buttons.is_empty() && active_axes.is_empty() {
|
||||||
|
self.input_busy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.input_busy {
|
||||||
|
let pressed_keys = ctx.keyboard_context.pressed_keys();
|
||||||
|
|
||||||
|
if let ControllerType::Gamepad(idx) = self.selected_controller {
|
||||||
|
let pressed_buttons = ctx.gamepad_context.pressed_buttons(idx);
|
||||||
|
let active_axes = ctx.gamepad_context.active_axes(idx);
|
||||||
|
|
||||||
|
if pressed_keys.is_empty() && pressed_buttons.is_empty() && active_axes.is_empty() {
|
||||||
|
self.input_busy = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if pressed_keys.is_empty() {
|
||||||
|
self.input_busy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||||
|
match self.current {
|
||||||
|
CurrentMenu::ControllerMenu => self.controller.draw(state, ctx)?,
|
||||||
|
CurrentMenu::RebindMenu => self.rebind.draw(state, ctx)?,
|
||||||
|
CurrentMenu::ConfirmRebindMenu => self.confirm_rebind.draw(state, ctx)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ use crate::input::combined_menu_controller::CombinedMenuController;
|
||||||
use crate::menu::save_select_menu::MenuSaveInfo;
|
use crate::menu::save_select_menu::MenuSaveInfo;
|
||||||
use crate::shared_game_state::{GameDifficulty, MenuCharacter, SharedGameState};
|
use crate::shared_game_state::{GameDifficulty, MenuCharacter, SharedGameState};
|
||||||
|
|
||||||
|
pub mod controls_menu;
|
||||||
pub mod coop_menu;
|
pub mod coop_menu;
|
||||||
pub mod pause_menu;
|
pub mod pause_menu;
|
||||||
pub mod save_select_menu;
|
pub mod save_select_menu;
|
||||||
|
@ -16,6 +17,12 @@ pub mod settings_menu;
|
||||||
|
|
||||||
const MENU_MIN_PADDING: f32 = 30.0;
|
const MENU_MIN_PADDING: f32 = 30.0;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ControlMenuData {
|
||||||
|
String(String),
|
||||||
|
Rect(Rect<u16>),
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum MenuEntry {
|
pub enum MenuEntry {
|
||||||
|
@ -31,6 +38,7 @@ pub enum MenuEntry {
|
||||||
SaveDataSingle(MenuSaveInfo),
|
SaveDataSingle(MenuSaveInfo),
|
||||||
NewSave,
|
NewSave,
|
||||||
PlayerSkin,
|
PlayerSkin,
|
||||||
|
Control(String, ControlMenuData),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuEntry {
|
impl MenuEntry {
|
||||||
|
@ -48,6 +56,7 @@ impl MenuEntry {
|
||||||
MenuEntry::SaveDataSingle(_) => 32.0,
|
MenuEntry::SaveDataSingle(_) => 32.0,
|
||||||
MenuEntry::NewSave => 32.0,
|
MenuEntry::NewSave => 32.0,
|
||||||
MenuEntry::PlayerSkin => 24.0,
|
MenuEntry::PlayerSkin => 24.0,
|
||||||
|
MenuEntry::Control(_, _) => 16.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +74,7 @@ impl MenuEntry {
|
||||||
MenuEntry::SaveDataSingle(_) => true,
|
MenuEntry::SaveDataSingle(_) => true,
|
||||||
MenuEntry::NewSave => true,
|
MenuEntry::NewSave => true,
|
||||||
MenuEntry::PlayerSkin => true,
|
MenuEntry::PlayerSkin => true,
|
||||||
|
MenuEntry::Control(_, _) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +98,7 @@ pub struct Menu<T: std::cmp::PartialEq> {
|
||||||
anim_wait: u16,
|
anim_wait: u16,
|
||||||
custom_cursor: Cell<bool>,
|
custom_cursor: Cell<bool>,
|
||||||
pub draw_cursor: bool,
|
pub draw_cursor: bool,
|
||||||
|
pub non_interactive: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: std::cmp::PartialEq + std::default::Default + Copy> Menu<T> {
|
impl<T: std::cmp::PartialEq + std::default::Default + Copy> Menu<T> {
|
||||||
|
@ -103,6 +114,7 @@ impl<T: std::cmp::PartialEq + std::default::Default + Copy> Menu<T> {
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
custom_cursor: Cell::new(true),
|
custom_cursor: Cell::new(true),
|
||||||
draw_cursor: true,
|
draw_cursor: true,
|
||||||
|
non_interactive: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +189,7 @@ impl<T: std::cmp::PartialEq + std::default::Default + Copy> Menu<T> {
|
||||||
MenuEntry::SaveDataSingle(_) => {}
|
MenuEntry::SaveDataSingle(_) => {}
|
||||||
MenuEntry::NewSave => {}
|
MenuEntry::NewSave => {}
|
||||||
MenuEntry::PlayerSkin => {}
|
MenuEntry::PlayerSkin => {}
|
||||||
|
MenuEntry::Control(_, _) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,6 +607,39 @@ impl<T: std::cmp::PartialEq + std::default::Default + Copy> Menu<T> {
|
||||||
|
|
||||||
draw_number(right_edge - 36.0, y, save.life as usize, Alignment::Right, state, ctx)?;
|
draw_number(right_edge - 36.0, y, save.life as usize, Alignment::Right, state, ctx)?;
|
||||||
}
|
}
|
||||||
|
MenuEntry::Control(name, data) => {
|
||||||
|
state.font.draw_text(
|
||||||
|
name.chars(),
|
||||||
|
self.x as f32 + 20.0,
|
||||||
|
y,
|
||||||
|
&state.constants,
|
||||||
|
&mut state.texture_set,
|
||||||
|
ctx,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match data {
|
||||||
|
ControlMenuData::String(value) => {
|
||||||
|
let text_width = state.font.text_width(value.chars(), &state.constants);
|
||||||
|
|
||||||
|
state.font.draw_text(
|
||||||
|
value.chars(),
|
||||||
|
self.x as f32 + self.width as f32 - 5.0 - text_width,
|
||||||
|
y,
|
||||||
|
&state.constants,
|
||||||
|
&mut state.texture_set,
|
||||||
|
ctx,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
ControlMenuData::Rect(value) => {
|
||||||
|
let rect_width = value.width() as f32;
|
||||||
|
let y = y + rect.height() as f32 / 2.0 - state.font.line_height(&state.constants) + 4.0;
|
||||||
|
|
||||||
|
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "buttons")?;
|
||||||
|
batch.add_rect(self.x as f32 + self.width as f32 - 5.0 - rect_width, y, &value);
|
||||||
|
batch.draw(ctx)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -622,6 +668,10 @@ impl<T: std::cmp::PartialEq + std::default::Default + Copy> Menu<T> {
|
||||||
controller: &mut CombinedMenuController,
|
controller: &mut CombinedMenuController,
|
||||||
state: &mut SharedGameState,
|
state: &mut SharedGameState,
|
||||||
) -> MenuSelectionResult<T> {
|
) -> MenuSelectionResult<T> {
|
||||||
|
if self.non_interactive {
|
||||||
|
return MenuSelectionResult::None;
|
||||||
|
}
|
||||||
|
|
||||||
if controller.trigger_back() {
|
if controller.trigger_back() {
|
||||||
state.sound_manager.play_sfx(5);
|
state.sound_manager.play_sfx(5);
|
||||||
return MenuSelectionResult::Canceled;
|
return MenuSelectionResult::Canceled;
|
||||||
|
@ -709,6 +759,15 @@ impl<T: std::cmp::PartialEq + std::default::Default + Copy> Menu<T> {
|
||||||
state.sound_manager.play_sfx(1);
|
state.sound_manager.play_sfx(1);
|
||||||
return MenuSelectionResult::Right(self.selected, entry, 1);
|
return MenuSelectionResult::Right(self.selected, entry, 1);
|
||||||
}
|
}
|
||||||
|
MenuEntry::Control(_, _) => {
|
||||||
|
if self.selected == idx && controller.trigger_ok()
|
||||||
|
|| state.touch_controls.consume_click_in(entry_bounds)
|
||||||
|
{
|
||||||
|
state.sound_manager.play_sfx(18);
|
||||||
|
self.selected = idx;
|
||||||
|
return MenuSelectionResult::Selected(idx, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ use crate::shared_game_state::{Language, ScreenShakeIntensity, SharedGameState,
|
||||||
use crate::sound::InterpolationMode;
|
use crate::sound::InterpolationMode;
|
||||||
use crate::{graphics, VSyncMode};
|
use crate::{graphics, VSyncMode};
|
||||||
|
|
||||||
|
use super::controls_menu::ControlsMenu;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -20,6 +22,7 @@ enum CurrentMenu {
|
||||||
MainMenu,
|
MainMenu,
|
||||||
GraphicsMenu,
|
GraphicsMenu,
|
||||||
SoundMenu,
|
SoundMenu,
|
||||||
|
ControlsMenu,
|
||||||
SoundtrackMenu,
|
SoundtrackMenu,
|
||||||
LanguageMenu,
|
LanguageMenu,
|
||||||
BehaviorMenu,
|
BehaviorMenu,
|
||||||
|
@ -29,6 +32,7 @@ enum CurrentMenu {
|
||||||
enum MainMenuEntry {
|
enum MainMenuEntry {
|
||||||
Graphics,
|
Graphics,
|
||||||
Sound,
|
Sound,
|
||||||
|
Controls,
|
||||||
Language,
|
Language,
|
||||||
Behavior,
|
Behavior,
|
||||||
DiscordLink,
|
DiscordLink,
|
||||||
|
@ -123,6 +127,7 @@ pub struct SettingsMenu {
|
||||||
soundtrack: Menu<SoundtrackMenuEntry>,
|
soundtrack: Menu<SoundtrackMenuEntry>,
|
||||||
language: Menu<LanguageMenuEntry>,
|
language: Menu<LanguageMenuEntry>,
|
||||||
behavior: Menu<BehaviorMenuEntry>,
|
behavior: Menu<BehaviorMenuEntry>,
|
||||||
|
controls_menu: ControlsMenu,
|
||||||
pub on_title: bool,
|
pub on_title: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,6 +142,8 @@ impl SettingsMenu {
|
||||||
let language = Menu::new(0, 0, 120, 0);
|
let language = Menu::new(0, 0, 120, 0);
|
||||||
let behavior = Menu::new(0, 0, 220, 0);
|
let behavior = Menu::new(0, 0, 220, 0);
|
||||||
|
|
||||||
|
let controls_menu = ControlsMenu::new();
|
||||||
|
|
||||||
SettingsMenu {
|
SettingsMenu {
|
||||||
current: CurrentMenu::MainMenu,
|
current: CurrentMenu::MainMenu,
|
||||||
main,
|
main,
|
||||||
|
@ -145,6 +152,7 @@ impl SettingsMenu {
|
||||||
soundtrack,
|
soundtrack,
|
||||||
language,
|
language,
|
||||||
behavior,
|
behavior,
|
||||||
|
controls_menu,
|
||||||
on_title: false,
|
on_title: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,6 +272,9 @@ impl SettingsMenu {
|
||||||
self.main.push_entry(MainMenuEntry::Graphics, MenuEntry::Active(state.t("menus.options_menu.graphics")));
|
self.main.push_entry(MainMenuEntry::Graphics, MenuEntry::Active(state.t("menus.options_menu.graphics")));
|
||||||
self.main.push_entry(MainMenuEntry::Sound, MenuEntry::Active(state.t("menus.options_menu.sound")));
|
self.main.push_entry(MainMenuEntry::Sound, MenuEntry::Active(state.t("menus.options_menu.sound")));
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
self.main.push_entry(MainMenuEntry::Controls, MenuEntry::Active(state.t("menus.options_menu.controls")));
|
||||||
|
|
||||||
self.language.push_entry(LanguageMenuEntry::Title, MenuEntry::Disabled(state.t("menus.options_menu.language")));
|
self.language.push_entry(LanguageMenuEntry::Title, MenuEntry::Disabled(state.t("menus.options_menu.language")));
|
||||||
|
|
||||||
for language in Language::values() {
|
for language in Language::values() {
|
||||||
|
@ -374,6 +385,8 @@ impl SettingsMenu {
|
||||||
|
|
||||||
self.behavior.push_entry(BehaviorMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
|
self.behavior.push_entry(BehaviorMenuEntry::Back, MenuEntry::Active(state.t("common.back")));
|
||||||
|
|
||||||
|
self.controls_menu.init(state, ctx)?;
|
||||||
|
|
||||||
self.update_sizes(state);
|
self.update_sizes(state);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -428,6 +441,9 @@ impl SettingsMenu {
|
||||||
MenuSelectionResult::Selected(MainMenuEntry::Sound, _) => {
|
MenuSelectionResult::Selected(MainMenuEntry::Sound, _) => {
|
||||||
self.current = CurrentMenu::SoundMenu;
|
self.current = CurrentMenu::SoundMenu;
|
||||||
}
|
}
|
||||||
|
MenuSelectionResult::Selected(MainMenuEntry::Controls, _) => {
|
||||||
|
self.current = CurrentMenu::ControlsMenu;
|
||||||
|
}
|
||||||
MenuSelectionResult::Selected(MainMenuEntry::Language, _) => {
|
MenuSelectionResult::Selected(MainMenuEntry::Language, _) => {
|
||||||
self.language.selected = LanguageMenuEntry::Language(state.settings.locale);
|
self.language.selected = LanguageMenuEntry::Language(state.settings.locale);
|
||||||
self.current = CurrentMenu::LanguageMenu;
|
self.current = CurrentMenu::LanguageMenu;
|
||||||
|
@ -643,6 +659,17 @@ impl SettingsMenu {
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
|
CurrentMenu::ControlsMenu => {
|
||||||
|
let cm = &mut self.current;
|
||||||
|
self.controls_menu.tick(
|
||||||
|
&mut || {
|
||||||
|
*cm = CurrentMenu::MainMenu;
|
||||||
|
},
|
||||||
|
controller,
|
||||||
|
state,
|
||||||
|
ctx,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
CurrentMenu::LanguageMenu => match self.language.tick(controller, state) {
|
CurrentMenu::LanguageMenu => match self.language.tick(controller, state) {
|
||||||
MenuSelectionResult::Selected(LanguageMenuEntry::Language(new_locale), entry) => {
|
MenuSelectionResult::Selected(LanguageMenuEntry::Language(new_locale), entry) => {
|
||||||
if let MenuEntry::Active(_) = entry {
|
if let MenuEntry::Active(_) = entry {
|
||||||
|
@ -727,6 +754,7 @@ impl SettingsMenu {
|
||||||
CurrentMenu::GraphicsMenu => self.graphics.draw(state, ctx)?,
|
CurrentMenu::GraphicsMenu => self.graphics.draw(state, ctx)?,
|
||||||
CurrentMenu::SoundMenu => self.sound.draw(state, ctx)?,
|
CurrentMenu::SoundMenu => self.sound.draw(state, ctx)?,
|
||||||
CurrentMenu::SoundtrackMenu => self.soundtrack.draw(state, ctx)?,
|
CurrentMenu::SoundtrackMenu => self.soundtrack.draw(state, ctx)?,
|
||||||
|
CurrentMenu::ControlsMenu => self.controls_menu.draw(state, ctx)?,
|
||||||
CurrentMenu::LanguageMenu => self.language.draw(state, ctx)?,
|
CurrentMenu::LanguageMenu => self.language.draw(state, ctx)?,
|
||||||
CurrentMenu::BehaviorMenu => self.behavior.draw(state, ctx)?,
|
CurrentMenu::BehaviorMenu => self.behavior.draw(state, ctx)?,
|
||||||
}
|
}
|
||||||
|
|
|
@ -389,7 +389,7 @@ fn p2_default_keymap() -> PlayerKeyMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq)]
|
#[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
pub enum ControllerType {
|
pub enum ControllerType {
|
||||||
Keyboard,
|
Keyboard,
|
||||||
Gamepad(u32),
|
Gamepad(u32),
|
||||||
|
|
Loading…
Reference in a new issue