use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::framework::filesystem::{user_create, user_open}; use crate::framework::gamepad::{Axis, AxisDirection, Button, PlayerControllerInputType}; use crate::framework::graphics::VSyncMode; use crate::framework::keyboard::ScanCode; use crate::game::player::TargetPlayer; use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, TimingMode, WindowMode}; use crate::input::combined_player_controller::CombinedPlayerController; 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::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, #[serde(default = "default_true")] pub display_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_pause_on_focus_loss")] pub pause_on_focus_loss: bool, #[serde(default = "default_interpolation")] pub organya_interpolation: InterpolationMode, #[serde(default = "default_p1_controller_type")] pub player1_controller_type: ControllerType, #[serde(default = "default_p2_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(default = "default_rumble")] pub player1_rumble: bool, #[serde(default = "default_rumble")] pub player2_rumble: bool, #[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: String, #[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, pub more_rust: bool, #[serde(default = "default_cutscene_skip_mode")] pub cutscene_skip_mode: CutsceneSkipMode, #[serde(default = "default_true")] pub discord_rpc: bool, #[serde(default = "default_true")] pub allow_strafe: bool, } fn default_true() -> bool { true } #[inline(always)] fn current_version() -> u32 { 24 } #[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() -> String { "en".to_string() } #[inline(always)] fn default_vsync() -> VSyncMode { VSyncMode::VSync } #[inline(always)] fn default_screen_shake_intensity() -> ScreenShakeIntensity { ScreenShakeIntensity::Full } #[inline(always)] fn default_p1_controller_type() -> ControllerType { if cfg!(any(target_os = "horizon")) { ControllerType::Gamepad(0) } else { ControllerType::Keyboard } } #[inline(always)] fn default_p2_controller_type() -> ControllerType { if cfg!(any(target_os = "horizon")) { ControllerType::Gamepad(1) } else { ControllerType::Keyboard } } #[inline(always)] fn default_pause_on_focus_loss() -> bool { true } #[inline(always)] fn default_rumble() -> bool { false } #[inline(always)] fn default_cutscene_skip_mode() -> CutsceneSkipMode { CutsceneSkipMode::Hold } 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_p1_controller_type(); self.player2_controller_type = default_p2_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 == 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 == 14 { self.version = 15; self.pause_on_focus_loss = default_pause_on_focus_loss(); } if self.version == 15 { self.version = 16; self.player1_key_map.menu_ok = self.player1_key_map.jump; self.player1_key_map.menu_back = self.player1_key_map.shoot; self.player1_controller_button_map.menu_ok = self.player1_controller_button_map.jump; self.player1_controller_button_map.menu_back = self.player1_controller_button_map.shoot; self.player2_key_map.menu_ok = self.player2_key_map.jump; self.player2_key_map.menu_back = self.player2_key_map.shoot; self.player2_controller_button_map.menu_ok = self.player2_controller_button_map.jump; self.player2_controller_button_map.menu_back = self.player2_controller_button_map.shoot; } if self.version == 16 { self.version = 17; if self.player1_controller_button_map.shoot == PlayerControllerInputType::ButtonInput(Button::East) { self.player1_controller_button_map.shoot = PlayerControllerInputType::ButtonInput(Button::West); } if self.player2_controller_button_map.shoot == PlayerControllerInputType::ButtonInput(Button::East) { self.player2_controller_button_map.shoot = PlayerControllerInputType::ButtonInput(Button::West); } if self.player1_controller_button_map.map == PlayerControllerInputType::ButtonInput(Button::West) { self.player1_controller_button_map.map = PlayerControllerInputType::ButtonInput(Button::East); } if self.player2_controller_button_map.map == PlayerControllerInputType::ButtonInput(Button::West) { self.player2_controller_button_map.map = PlayerControllerInputType::ButtonInput(Button::East); } } if self.version == 17 { self.version = 18; self.player1_rumble = default_rumble(); self.player2_rumble = default_rumble(); } if self.version == 18 { self.version = 19; self.more_rust = false; } if self.version == 19 { self.version = 20; self.cutscene_skip_mode = CutsceneSkipMode::Hold; } if self.version == 20 { self.version = 21; self.locale = match self.locale.as_str() { "English" => "en".to_string(), "Japanese" => "jp".to_string(), _ => default_locale(), }; } if self.version == 21 { self.version = 22; self.discord_rpc = true; } if self.version == 22 { self.version = 23; self.display_touch_controls = true; } if self.version == 23 { self.version = 24; self.allow_strafe = true; } 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(index) => { let keyboard_controller = Box::new(KeyboardController::new(TargetPlayer::Player1)); let mut gamepad_controller = Box::new(GamepadController::new(index, TargetPlayer::Player1)); gamepad_controller.set_rumble_enabled(self.player1_rumble); let mut combined_player_controller = CombinedPlayerController::new(); combined_player_controller.add(keyboard_controller); combined_player_controller.add(gamepad_controller); Box::new(combined_player_controller) } } } pub fn create_player2_controller(&self) -> Box { match self.player2_controller_type { ControllerType::Keyboard => Box::new(KeyboardController::new(TargetPlayer::Player2)), ControllerType::Gamepad(index) => { let keyboard_controller = Box::new(KeyboardController::new(TargetPlayer::Player2)); let mut gamepad_controller = Box::new(GamepadController::new(index, TargetPlayer::Player2)); gamepad_controller.set_rumble_enabled(self.player2_rumble); let mut combined_player_controller = CombinedPlayerController::new(); combined_player_controller.add(keyboard_controller); combined_player_controller.add(gamepad_controller); Box::new(combined_player_controller) } } } 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"), display_touch_controls: true, soundtrack: "Organya".to_string(), bgm_volume: 1.0, sfx_volume: 1.0, timing_mode: default_timing(), pause_on_focus_loss: default_pause_on_focus_loss(), organya_interpolation: InterpolationMode::Linear, player1_controller_type: default_p1_controller_type(), player2_controller_type: default_p2_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(), player1_rumble: default_rumble(), player2_rumble: default_rumble(), speed: 1.0, god_mode: false, infinite_booster: false, debug_outlines: false, fps_counter: false, locale: default_locale(), window_mode: WindowMode::Windowed, vsync_mode: VSyncMode::VSync, screen_shake_intensity: ScreenShakeIntensity::Full, debug_mode: false, noclip: false, more_rust: false, cutscene_skip_mode: CutsceneSkipMode::Hold, discord_rpc: true, allow_strafe: true, } } } #[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, pub menu_ok: ScanCode, pub menu_back: ScanCode, } #[inline(always)] pub 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, menu_ok: ScanCode::Z, menu_back: ScanCode::X, } } #[inline(always)] pub 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, menu_ok: ScanCode::B, menu_back: ScanCode::N, } } #[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq, Copy, Clone)] pub enum ControllerType { Keyboard, Gamepad(u32), } #[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, pub menu_ok: PlayerControllerInputType, pub menu_back: PlayerControllerInputType, } #[inline(always)] pub fn player_default_controller_button_map() -> PlayerControllerButtonMap { PlayerControllerButtonMap { 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::East), shoot: PlayerControllerInputType::ButtonInput(Button::South), skip: PlayerControllerInputType::ButtonInput(Button::West), strafe: PlayerControllerInputType::AxisInput(Axis::TriggerRight, AxisDirection::Either), inventory: PlayerControllerInputType::ButtonInput(Button::West), map: PlayerControllerInputType::ButtonInput(Button::North), menu_ok: PlayerControllerInputType::ButtonInput(Button::South), menu_back: PlayerControllerInputType::ButtonInput(Button::East), } } #[inline(always)] pub fn default_controller_axis_sensitivity() -> f64 { 0.3 }