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::keyboard::ScanCode; use crate::graphics::VSyncMode; use crate::input::gamepad_player_controller::GamepadController; use crate::input::keyboard_player_controller::KeyboardController; use crate::input::player_controller::PlayerController; use crate::input::touch_player_controller::TouchPlayerController; use crate::player::TargetPlayer; use crate::shared_game_state::{Language, ScreenShakeIntensity, TimingMode, WindowMode}; use crate::sound::InterpolationMode; #[derive(serde::Serialize, serde::Deserialize)] pub struct Settings { #[serde(default = "current_version")] pub version: u32, #[serde(default = "default_true")] pub seasonal_textures: bool, pub original_textures: bool, pub shader_effects: bool, #[serde(default = "default_true")] pub light_cone: bool, #[serde(default = "default_true")] pub subpixel_coords: bool, #[serde(default = "default_true")] pub motion_interpolation: bool, pub touch_controls: bool, pub soundtrack: String, #[serde(default = "default_vol")] pub bgm_volume: f32, #[serde(default = "default_vol")] pub sfx_volume: f32, #[serde(default = "default_timing")] pub timing_mode: TimingMode, #[serde(default = "default_interpolation")] pub organya_interpolation: InterpolationMode, #[serde(default = "default_controller_type")] pub player1_controller_type: ControllerType, #[serde(default = "default_controller_type")] pub player2_controller_type: ControllerType, #[serde(default = "p1_default_keymap")] pub player1_key_map: PlayerKeyMap, #[serde(default = "p2_default_keymap")] pub player2_key_map: PlayerKeyMap, #[serde(default = "player_default_controller_button_map")] pub player1_controller_button_map: PlayerControllerButtonMap, #[serde(default = "player_default_controller_button_map")] pub player2_controller_button_map: PlayerControllerButtonMap, #[serde(default = "default_controller_axis_sensitivity")] pub player1_controller_axis_sensitivity: f64, #[serde(default = "default_controller_axis_sensitivity")] pub player2_controller_axis_sensitivity: f64, #[serde(skip, default = "default_speed")] pub speed: f64, #[serde(skip)] pub god_mode: bool, #[serde(skip)] pub infinite_booster: bool, #[serde(skip)] pub debug_outlines: bool, pub fps_counter: bool, pub locale: Language, #[serde(default = "default_window_mode")] pub window_mode: WindowMode, #[serde(default = "default_vsync")] pub vsync_mode: VSyncMode, #[serde(default = "default_screen_shake_intensity")] pub screen_shake_intensity: ScreenShakeIntensity, pub debug_mode: bool, #[serde(skip)] pub noclip: bool, } fn default_true() -> bool { true } #[inline(always)] fn current_version() -> u32 { 13 } #[inline(always)] fn default_timing() -> TimingMode { TimingMode::_50Hz } #[inline(always)] fn default_window_mode() -> WindowMode { WindowMode::Windowed } #[inline(always)] fn default_interpolation() -> InterpolationMode { InterpolationMode::Linear } #[inline(always)] fn default_speed() -> f64 { 1.0 } #[inline(always)] fn default_vol() -> f32 { 1.0 } #[inline(always)] fn default_locale() -> Language { Language::English } #[inline(always)] fn default_vsync() -> VSyncMode { VSyncMode::VSync } #[inline(always)] fn default_screen_shake_intensity() -> ScreenShakeIntensity { ScreenShakeIntensity::Full } #[inline(always)] fn default_controller_type() -> ControllerType { ControllerType::Keyboard } impl Settings { pub fn load(ctx: &Context) -> GameResult { if let Ok(file) = user_open(ctx, "/settings.json") { match serde_json::from_reader::<_, Settings>(file) { Ok(settings) => return Ok(settings.upgrade()), Err(err) => log::warn!("Failed to deserialize settings: {}", err), } } Ok(Settings::default()) } fn upgrade(mut self) -> Self { let initial_version = self.version; if self.version == 2 { self.version = 3; self.light_cone = true; } if self.version == 3 { self.version = 4; self.timing_mode = default_timing(); } if self.version == 4 { self.version = 5; self.bgm_volume = default_vol(); self.sfx_volume = default_vol(); } if self.version == 5 { self.version = 6; self.player1_key_map.strafe = ScanCode::LShift; self.player2_key_map.strafe = ScanCode::RShift; } if self.version == 6 { self.version = 7; self.locale = default_locale(); } if self.version == 7 { self.version = 8; self.vsync_mode = default_vsync(); } if self.version == 8 { self.version = 9; self.debug_mode = false; } if self.version == 9 { self.version = 10; self.screen_shake_intensity = default_screen_shake_intensity(); } if self.version == 10 { self.version = 11; self.window_mode = default_window_mode(); } if self.version == 11 { self.version = 12; self.player1_controller_type = default_controller_type(); self.player2_controller_type = default_controller_type(); self.player1_controller_button_map = player_default_controller_button_map(); self.player2_controller_button_map = player_default_controller_button_map(); self.player1_controller_axis_sensitivity = default_controller_axis_sensitivity(); self.player2_controller_axis_sensitivity = default_controller_axis_sensitivity(); } if self.version == 12 { self.version = 13; if self.player1_key_map.skip == ScanCode::E { self.player1_key_map.skip = ScanCode::Q; } if self.player2_key_map.skip == ScanCode::U { self.player2_key_map.skip = ScanCode::T; } // reset controller mappings since we've updated 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); } self } pub fn save(&self, ctx: &Context) -> GameResult { let file = user_create(ctx, "/settings.json")?; serde_json::to_writer_pretty(file, self)?; Ok(()) } pub fn create_player1_controller(&self) -> Box { if self.touch_controls { return Box::new(TouchPlayerController::new()); } match self.player1_controller_type { ControllerType::Keyboard => Box::new(KeyboardController::new(TargetPlayer::Player1)), ControllerType::Gamepad(id) => Box::new(GamepadController::new(id, TargetPlayer::Player1)), } } pub fn create_player2_controller(&self) -> Box { match self.player2_controller_type { ControllerType::Keyboard => Box::new(KeyboardController::new(TargetPlayer::Player2)), ControllerType::Gamepad(id) => Box::new(GamepadController::new(id, TargetPlayer::Player2)), } } pub fn get_gamepad_axis_sensitivity(&self, id: u32) -> f64 { if self.player1_controller_type == ControllerType::Gamepad(id) { self.player1_controller_axis_sensitivity } else if self.player2_controller_type == ControllerType::Gamepad(id) { self.player2_controller_axis_sensitivity } else { default_controller_axis_sensitivity() } } } impl Default for Settings { fn default() -> Self { Settings { version: current_version(), seasonal_textures: true, original_textures: false, shader_effects: false, light_cone: true, subpixel_coords: true, motion_interpolation: true, touch_controls: cfg!(target_os = "android"), soundtrack: "Organya".to_string(), bgm_volume: 1.0, sfx_volume: 1.0, timing_mode: default_timing(), organya_interpolation: InterpolationMode::Linear, player1_controller_type: default_controller_type(), player2_controller_type: default_controller_type(), player1_key_map: p1_default_keymap(), player2_key_map: p2_default_keymap(), player1_controller_button_map: player_default_controller_button_map(), player2_controller_button_map: player_default_controller_button_map(), player1_controller_axis_sensitivity: default_controller_axis_sensitivity(), player2_controller_axis_sensitivity: default_controller_axis_sensitivity(), speed: 1.0, god_mode: false, infinite_booster: false, debug_outlines: false, fps_counter: false, locale: Language::English, window_mode: WindowMode::Windowed, vsync_mode: VSyncMode::VSync, screen_shake_intensity: ScreenShakeIntensity::Full, debug_mode: false, noclip: false, } } } #[derive(serde::Serialize, serde::Deserialize)] pub struct PlayerKeyMap { pub left: ScanCode, pub up: ScanCode, pub right: ScanCode, pub down: ScanCode, pub prev_weapon: ScanCode, pub next_weapon: ScanCode, pub jump: ScanCode, pub shoot: ScanCode, pub skip: ScanCode, pub inventory: ScanCode, pub map: ScanCode, pub strafe: ScanCode, } #[inline(always)] fn p1_default_keymap() -> PlayerKeyMap { PlayerKeyMap { left: ScanCode::Left, up: ScanCode::Up, right: ScanCode::Right, down: ScanCode::Down, prev_weapon: ScanCode::A, next_weapon: ScanCode::S, jump: ScanCode::Z, shoot: ScanCode::X, skip: ScanCode::Q, inventory: ScanCode::Q, map: ScanCode::W, strafe: ScanCode::LShift, } } #[inline(always)] fn p2_default_keymap() -> PlayerKeyMap { PlayerKeyMap { left: ScanCode::Comma, up: ScanCode::L, right: ScanCode::Slash, down: ScanCode::Period, prev_weapon: ScanCode::G, next_weapon: ScanCode::H, jump: ScanCode::B, shoot: ScanCode::N, skip: ScanCode::T, inventory: ScanCode::T, map: ScanCode::Y, strafe: ScanCode::RShift, } } #[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq)] pub enum ControllerType { Keyboard, 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, pub up: PlayerControllerInputType, pub right: PlayerControllerInputType, pub down: PlayerControllerInputType, pub prev_weapon: PlayerControllerInputType, pub next_weapon: PlayerControllerInputType, pub jump: PlayerControllerInputType, pub shoot: PlayerControllerInputType, pub skip: PlayerControllerInputType, pub inventory: PlayerControllerInputType, pub map: PlayerControllerInputType, pub strafe: PlayerControllerInputType, } #[inline(always)] pub fn player_default_controller_button_map() -> PlayerControllerButtonMap { PlayerControllerButtonMap { left: PlayerControllerInputType::Either(Button::DPadLeft, Axis::LeftX), up: PlayerControllerInputType::Either(Button::DPadUp, Axis::LeftY), right: PlayerControllerInputType::Either(Button::DPadRight, Axis::LeftX), down: PlayerControllerInputType::Either(Button::DPadDown, Axis::LeftY), 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), inventory: PlayerControllerInputType::ButtonInput(Button::North), map: PlayerControllerInputType::ButtonInput(Button::West), } } #[inline(always)] pub fn default_controller_axis_sensitivity() -> f64 { 0.3 }