diff --git a/src/menu/coop_menu.rs b/src/menu/coop_menu.rs index 9ff88fd..3032dec 100644 --- a/src/menu/coop_menu.rs +++ b/src/menu/coop_menu.rs @@ -10,10 +10,38 @@ pub enum CurrentMenu { PlayerSkin, } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum CoopMenuEntry { + Title, + One, + Two, + Back, +} + +impl Default for CoopMenuEntry { + fn default() -> Self { + CoopMenuEntry::One + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum SkinMenuEntry { + Title, + Skin, + Start, + Back, +} + +impl Default for SkinMenuEntry { + fn default() -> Self { + SkinMenuEntry::Skin + } +} + pub struct PlayerCountMenu { current_menu: CurrentMenu, - coop_menu: Menu, - skin_menu: Menu, + coop_menu: Menu, + skin_menu: Menu, } impl PlayerCountMenu { @@ -28,18 +56,19 @@ impl PlayerCountMenu { self.coop_menu = Menu::new(0, 0, 130, 0); self.skin_menu = Menu::new(0, 0, 130, 0); - self.coop_menu.push_entry(MenuEntry::Disabled(state.t("menus.coop_menu.title"))); - self.coop_menu.push_entry(MenuEntry::Active(state.t("menus.coop_menu.one"))); - self.coop_menu.push_entry(MenuEntry::Active(state.t("menus.coop_menu.two"))); - self.coop_menu.push_entry(MenuEntry::Active(state.t("common.back"))); + self.coop_menu.push_entry(CoopMenuEntry::Title, MenuEntry::Disabled(state.t("menus.coop_menu.title"))); + self.coop_menu.push_entry(CoopMenuEntry::One, MenuEntry::Active(state.t("menus.coop_menu.one"))); + self.coop_menu.push_entry(CoopMenuEntry::Two, MenuEntry::Active(state.t("menus.coop_menu.two"))); + self.coop_menu.push_entry(CoopMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); - self.coop_menu.selected = 1; + self.coop_menu.selected = CoopMenuEntry::One; - self.skin_menu.push_entry(MenuEntry::Disabled(state.t("menus.skin_menu.title"))); - self.skin_menu.push_entry(MenuEntry::PlayerSkin); - self.skin_menu.push_entry(MenuEntry::Active(state.t("menus.main_menu.start"))); - self.skin_menu.push_entry(MenuEntry::Active(state.t("common.back"))); - self.skin_menu.selected = 1; + self.skin_menu.push_entry(SkinMenuEntry::Title, MenuEntry::Disabled(state.t("menus.skin_menu.title"))); + self.skin_menu.push_entry(SkinMenuEntry::Skin, MenuEntry::PlayerSkin); + self.skin_menu.push_entry(SkinMenuEntry::Start, MenuEntry::Active(state.t("menus.main_menu.start"))); + self.skin_menu.push_entry(SkinMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); + + self.skin_menu.selected = SkinMenuEntry::Skin; self.update_sizes(state); @@ -68,13 +97,13 @@ impl PlayerCountMenu { self.update_sizes(state); match self.current_menu { CurrentMenu::CoopMenu => match self.coop_menu.tick(controller, state) { - MenuSelectionResult::Selected(3, _) | MenuSelectionResult::Canceled => exit_action(), - MenuSelectionResult::Selected(1, _) => { + MenuSelectionResult::Selected(CoopMenuEntry::Back, _) | MenuSelectionResult::Canceled => exit_action(), + MenuSelectionResult::Selected(CoopMenuEntry::One, _) => { state.player_count = PlayerCount::One; state.reload_resources(ctx)?; state.load_or_start_game(ctx)?; } - MenuSelectionResult::Selected(2, _) => { + MenuSelectionResult::Selected(CoopMenuEntry::Two, _) => { if state.constants.is_cs_plus { self.current_menu = CurrentMenu::PlayerSkin; } else { @@ -86,13 +115,13 @@ impl PlayerCountMenu { _ => (), }, CurrentMenu::PlayerSkin => match self.skin_menu.tick(controller, state) { - MenuSelectionResult::Selected(3, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(SkinMenuEntry::Back, _) | MenuSelectionResult::Canceled => { self.current_menu = CurrentMenu::CoopMenu; } - MenuSelectionResult::Selected(1, _) => { + MenuSelectionResult::Selected(SkinMenuEntry::Skin, _) => { state.player2_skin += 2; } - MenuSelectionResult::Selected(2, _) => { + MenuSelectionResult::Selected(SkinMenuEntry::Start, _) => { state.player_count = PlayerCount::Two; state.reload_resources(ctx)?; state.load_or_start_game(ctx)?; diff --git a/src/menu/mod.rs b/src/menu/mod.rs index e6c53f3..b699df3 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -9,10 +9,10 @@ use crate::input::combined_menu_controller::CombinedMenuController; use crate::menu::save_select_menu::MenuSaveInfo; use crate::shared_game_state::{GameDifficulty, MenuCharacter, SharedGameState}; +pub mod coop_menu; pub mod pause_menu; pub mod save_select_menu; pub mod settings_menu; -pub mod coop_menu; #[allow(dead_code)] #[derive(Clone)] @@ -62,40 +62,40 @@ impl MenuEntry { MenuEntry::SaveData(_) => true, MenuEntry::SaveDataSingle(_) => true, MenuEntry::NewSave => true, - MenuEntry::PlayerSkin=> true, + MenuEntry::PlayerSkin => true, } } } -pub enum MenuSelectionResult<'a> { +pub enum MenuSelectionResult<'a, T: std::cmp::PartialEq> { None, Canceled, - Selected(usize, &'a mut MenuEntry), - Left(usize, &'a mut MenuEntry, i16), - Right(usize, &'a mut MenuEntry, i16), + Selected(T, &'a mut MenuEntry), + Left(T, &'a mut MenuEntry, i16), + Right(T, &'a mut MenuEntry, i16), } -pub struct Menu { +pub struct Menu { pub x: isize, pub y: isize, pub width: u16, pub height: u16, - pub selected: usize, - pub entries: Vec, + pub selected: T, + pub entries: Vec<(T, MenuEntry)>, anim_num: u16, anim_wait: u16, custom_cursor: Cell, pub draw_cursor: bool, } -impl Menu { - pub fn new(x: isize, y: isize, width: u16, height: u16) -> Menu { +impl Menu { + pub fn new(x: isize, y: isize, width: u16, height: u16) -> Menu { Menu { x, y, width, height, - selected: 0, + selected: T::default(), anim_num: 0, anim_wait: 0, entries: Vec::new(), @@ -104,14 +104,23 @@ impl Menu { } } - pub fn push_entry(&mut self, entry: MenuEntry) { - self.entries.push(entry); + pub fn push_entry(&mut self, id: T, entry: MenuEntry) { + self.entries.push((id, entry)); + } + + pub fn set_entry(&mut self, id: T, entry: MenuEntry) { + for i in 0..self.entries.len() { + if self.entries[i].0 == id { + self.entries[i].1 = entry; + return; + } + } } pub fn update_width(&mut self, state: &SharedGameState) { let mut width = self.width as f32; - for entry in &self.entries { + for (_, entry) in &self.entries { match entry { MenuEntry::Hidden => {} MenuEntry::Active(entry) | MenuEntry::DisabledWhite(entry) | MenuEntry::Disabled(entry) => { @@ -176,7 +185,7 @@ impl Menu { pub fn update_height(&mut self) { let mut height = 8.0; - for entry in &self.entries { + for (_, entry) in &self.entries { height += entry.height(); } @@ -281,7 +290,17 @@ impl Menu { let mut entry_y = 0; if !self.entries.is_empty() { - entry_y = self.entries[0..(self.selected)].iter().map(|e| e.height()).sum::().max(0.0) as u16; + let mut sum = 0.0; + + for (id, entry) in &self.entries { + if *id == self.selected { + break; + } + + sum += entry.height(); + } + + entry_y = sum as u16; } if self.draw_cursor { @@ -340,7 +359,7 @@ impl Menu { } y = self.y as f32 + 8.0; - for entry in &self.entries { + for (_, entry) in &self.entries { match entry { MenuEntry::Active(name) | MenuEntry::DisabledWhite(name) => { state.font.draw_text( @@ -512,7 +531,7 @@ impl Menu { y - 4.0, &Rect::new_size(0, (state.player2_skin).saturating_mul(2 * 16), 16, 16), ); - batch.draw(ctx)?; + batch.draw(ctx)?; } MenuEntry::SaveData(save) | MenuEntry::SaveDataSingle(save) => { let name = &state.stages[save.current_map as usize].name; @@ -597,7 +616,7 @@ impl Menu { &mut self, controller: &mut CombinedMenuController, state: &mut SharedGameState, - ) -> MenuSelectionResult { + ) -> MenuSelectionResult { if controller.trigger_back() { state.sound_manager.play_sfx(5); return MenuSelectionResult::Canceled; @@ -605,21 +624,25 @@ impl Menu { if (controller.trigger_up() || controller.trigger_down()) && !self.entries.is_empty() { state.sound_manager.play_sfx(1); + + let mut selected = self.entries.iter().position(|(idx, _)| *idx == self.selected).ok_or(0).unwrap(); + loop { if controller.trigger_down() { - self.selected += 1; - if self.selected == self.entries.len() { - self.selected = 0; + selected += 1; + if selected == self.entries.len() { + selected = 0; } } else { - if self.selected == 0 { - self.selected = self.entries.len(); + if selected == 0 { + selected = self.entries.len(); } - self.selected -= 1; + selected -= 1; } - if let Some(entry) = self.entries.get(self.selected) { + if let Some((id, entry)) = self.entries.get(selected) { if entry.selectable() { + self.selected = *id; break; } } else { @@ -629,7 +652,8 @@ impl Menu { } let mut y = self.y as f32 + 8.0; - for (idx, entry) in self.entries.iter_mut().enumerate() { + for (id, entry) in self.entries.iter_mut() { + let idx = *id; let entry_bounds = Rect::new_size(self.x, y as isize, self.width as isize, entry.height() as isize); let right_entry_bounds = Rect::new_size(self.x + self.width as isize, y as isize, self.width as isize, entry.height() as isize); diff --git a/src/menu/pause_menu.rs b/src/menu/pause_menu.rs index 7f7f376..611dc80 100644 --- a/src/menu/pause_menu.rs +++ b/src/menu/pause_menu.rs @@ -19,13 +19,41 @@ enum CurrentMenu { ConfirmMenu, } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum PauseMenuEntry { + Resume, + Retry, + Options, + Title, + Quit, +} + +impl Default for PauseMenuEntry { + fn default() -> Self { + PauseMenuEntry::Resume + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum ConfirmMenuEntry { + Empty, + Yes, + No, +} + +impl Default for ConfirmMenuEntry { + fn default() -> Self { + ConfirmMenuEntry::Yes + } +} + pub struct PauseMenu { is_paused: bool, current_menu: CurrentMenu, settings_menu: SettingsMenu, controller: CombinedMenuController, - pause_menu: Menu, - confirm_menu: Menu, + pause_menu: Menu, + confirm_menu: Menu, tick: u32, } @@ -48,17 +76,17 @@ impl PauseMenu { self.controller.add(state.settings.create_player1_controller()); self.controller.add(state.settings.create_player2_controller()); - self.pause_menu.push_entry(MenuEntry::Active(state.t("menus.pause_menu.resume"))); - self.pause_menu.push_entry(MenuEntry::Active(state.t("menus.pause_menu.retry"))); - self.pause_menu.push_entry(MenuEntry::Active(state.t("menus.pause_menu.options"))); - self.pause_menu.push_entry(MenuEntry::Active(state.t("menus.pause_menu.title"))); - self.pause_menu.push_entry(MenuEntry::Active(state.t("menus.pause_menu.quit"))); + self.pause_menu.push_entry(PauseMenuEntry::Resume, MenuEntry::Active(state.t("menus.pause_menu.resume"))); + self.pause_menu.push_entry(PauseMenuEntry::Retry, MenuEntry::Active(state.t("menus.pause_menu.retry"))); + self.pause_menu.push_entry(PauseMenuEntry::Options, MenuEntry::Active(state.t("menus.pause_menu.options"))); + self.pause_menu.push_entry(PauseMenuEntry::Title, MenuEntry::Active(state.t("menus.pause_menu.title"))); + self.pause_menu.push_entry(PauseMenuEntry::Quit, MenuEntry::Active(state.t("menus.pause_menu.quit"))); - self.confirm_menu.push_entry(MenuEntry::Disabled("".to_owned())); - self.confirm_menu.push_entry(MenuEntry::Active(state.t("common.yes"))); - self.confirm_menu.push_entry(MenuEntry::Active(state.t("common.no"))); + self.confirm_menu.push_entry(ConfirmMenuEntry::Empty, MenuEntry::Disabled("".to_owned())); + self.confirm_menu.push_entry(ConfirmMenuEntry::Yes, MenuEntry::Active(state.t("common.yes"))); + self.confirm_menu.push_entry(ConfirmMenuEntry::No, MenuEntry::Active(state.t("common.no"))); - self.confirm_menu.selected = 1; + self.confirm_menu.selected = ConfirmMenuEntry::Yes; self.update_sizes(state); @@ -108,27 +136,33 @@ impl PauseMenu { match self.current_menu { CurrentMenu::PauseMenu => match self.pause_menu.tick(&mut self.controller, state) { - MenuSelectionResult::Selected(0, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(PauseMenuEntry::Resume, _) | MenuSelectionResult::Canceled => { // double tap prevention if self.tick >= 3 { self.tick = 0; self.is_paused = false; } } - MenuSelectionResult::Selected(1, _) => { + MenuSelectionResult::Selected(PauseMenuEntry::Retry, _) => { state.stop_noise(); state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?; state.load_or_start_game(ctx)?; } - MenuSelectionResult::Selected(2, _) => { + MenuSelectionResult::Selected(PauseMenuEntry::Options, _) => { self.current_menu = CurrentMenu::OptionsMenu; } - MenuSelectionResult::Selected(3, _) => { - self.confirm_menu.entries[0] = MenuEntry::Disabled(state.t("menus.pause_menu.title_confirm")); + MenuSelectionResult::Selected(PauseMenuEntry::Title, _) => { + self.confirm_menu.set_entry( + ConfirmMenuEntry::Empty, + MenuEntry::Disabled(state.t("menus.pause_menu.title_confirm")), + ); self.current_menu = CurrentMenu::ConfirmMenu; } - MenuSelectionResult::Selected(4, _) => { - self.confirm_menu.entries[0] = MenuEntry::Disabled(state.t("menus.pause_menu.quit_confirm")); + MenuSelectionResult::Selected(PauseMenuEntry::Quit, _) => { + self.confirm_menu.set_entry( + ConfirmMenuEntry::Empty, + MenuEntry::Disabled(state.t("menus.pause_menu.quit_confirm")), + ); self.current_menu = CurrentMenu::ConfirmMenu; } _ => (), @@ -145,18 +179,18 @@ impl PauseMenu { )?; } CurrentMenu::ConfirmMenu => match self.confirm_menu.tick(&mut self.controller, state) { - MenuSelectionResult::Selected(1, _) => match self.pause_menu.selected { - 3 => { + MenuSelectionResult::Selected(ConfirmMenuEntry::Yes, _) => match self.pause_menu.selected { + PauseMenuEntry::Title => { state.stop_noise(); state.textscript_vm.flags.set_cutscene_skip(false); state.next_scene = Some(Box::new(TitleScene::new())); } - 4 => { + PauseMenuEntry::Quit => { state.shutdown(); } _ => (), }, - MenuSelectionResult::Selected(2, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(ConfirmMenuEntry::No, _) | MenuSelectionResult::Canceled => { self.current_menu = CurrentMenu::PauseMenu; } _ => (), diff --git a/src/menu/save_select_menu.rs b/src/menu/save_select_menu.rs index 71fe5c4..ba4d47b 100644 --- a/src/menu/save_select_menu.rs +++ b/src/menu/save_select_menu.rs @@ -33,15 +33,68 @@ pub enum CurrentMenu { DeleteConfirm, LoadConfirm, } + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum SaveMenuEntry { + Load(usize), + New(usize), + Back, +} + +impl Default for SaveMenuEntry { + fn default() -> Self { + SaveMenuEntry::Load(0) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum DifficultyMenuEntry { + Title, + Difficulty(GameDifficulty), + Back, +} + +impl Default for DifficultyMenuEntry { + fn default() -> Self { + DifficultyMenuEntry::Difficulty(GameDifficulty::Normal) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum DeleteConfirmMenuEntry { + Title, + Yes, + No, +} + +impl Default for DeleteConfirmMenuEntry { + fn default() -> Self { + DeleteConfirmMenuEntry::No + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum LoadConfirmMenuEntry { + Start, + Delete, + Back, +} + +impl Default for LoadConfirmMenuEntry { + fn default() -> Self { + LoadConfirmMenuEntry::Start + } +} + pub struct SaveSelectMenu { pub saves: [MenuSaveInfo; 3], current_menu: CurrentMenu, - save_menu: Menu, - save_detailed: Menu, - difficulty_menu: Menu, + save_menu: Menu, + save_detailed: Menu, + difficulty_menu: Menu, coop_menu: PlayerCountMenu, - delete_confirm: Menu, - load_confirm: Menu, + delete_confirm: Menu, + load_confirm: Menu, skip_difficulty_menu: bool, } @@ -69,6 +122,8 @@ impl SaveSelectMenu { self.load_confirm = Menu::new(0, 0, 75, 0); self.skip_difficulty_menu = false; + let mut should_mutate_selection = true; + for (iter, save) in self.saves.iter_mut().enumerate() { if let Ok(data) = filesystem::user_open(ctx, state.get_save_filename(iter + 1).unwrap_or("".to_string())) { let loaded_save = GameProfile::load_from_save(data)?; @@ -80,38 +135,60 @@ impl SaveSelectMenu { save.weapon_id = loaded_save.weapon_data.map(|weapon| weapon.weapon_id); save.difficulty = loaded_save.difficulty; - self.save_menu.push_entry(MenuEntry::SaveData(*save)); + self.save_menu.push_entry(SaveMenuEntry::Load(iter), MenuEntry::SaveData(*save)); + + if should_mutate_selection { + should_mutate_selection = false; + self.save_menu.selected = SaveMenuEntry::Load(iter); + } } else { - self.save_menu.push_entry(MenuEntry::NewSave); + self.save_menu.push_entry(SaveMenuEntry::New(iter), MenuEntry::NewSave); + + if should_mutate_selection { + should_mutate_selection = false; + self.save_menu.selected = SaveMenuEntry::New(iter); + } } } - self.save_menu.push_entry(MenuEntry::Active(state.t("common.back"))); + self.save_menu.push_entry(SaveMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); - self.difficulty_menu.push_entry(MenuEntry::Disabled(state.t("menus.difficulty_menu.title"))); - self.difficulty_menu.push_entry(MenuEntry::Active(state.t("menus.difficulty_menu.easy"))); - self.difficulty_menu.push_entry(MenuEntry::Active(state.t("menus.difficulty_menu.normal"))); - self.difficulty_menu.push_entry(MenuEntry::Active(state.t("menus.difficulty_menu.hard"))); - self.difficulty_menu.push_entry(MenuEntry::Active(state.t("common.back"))); + self.difficulty_menu + .push_entry(DifficultyMenuEntry::Title, MenuEntry::Disabled(state.t("menus.difficulty_menu.title"))); + self.difficulty_menu.push_entry( + DifficultyMenuEntry::Difficulty(GameDifficulty::Easy), + MenuEntry::Active(state.t("menus.difficulty_menu.easy")), + ); + self.difficulty_menu.push_entry( + DifficultyMenuEntry::Difficulty(GameDifficulty::Normal), + MenuEntry::Active(state.t("menus.difficulty_menu.normal")), + ); + self.difficulty_menu.push_entry( + DifficultyMenuEntry::Difficulty(GameDifficulty::Hard), + MenuEntry::Active(state.t("menus.difficulty_menu.hard")), + ); + self.difficulty_menu.push_entry(DifficultyMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); - self.difficulty_menu.selected = 2; + self.difficulty_menu.selected = DifficultyMenuEntry::Difficulty(GameDifficulty::Normal); //self.coop_menu.init(state, ctx); - self.delete_confirm.push_entry(MenuEntry::Disabled(state.t("menus.save_menu.delete_confirm"))); - self.delete_confirm.push_entry(MenuEntry::Active(state.t("common.yes"))); - self.delete_confirm.push_entry(MenuEntry::Active(state.t("common.no"))); + self.delete_confirm + .push_entry(DeleteConfirmMenuEntry::Title, MenuEntry::Disabled(state.t("menus.save_menu.delete_confirm"))); + self.delete_confirm.push_entry(DeleteConfirmMenuEntry::Yes, MenuEntry::Active(state.t("common.yes"))); + self.delete_confirm.push_entry(DeleteConfirmMenuEntry::No, MenuEntry::Active(state.t("common.no"))); - self.delete_confirm.selected = 2; + self.delete_confirm.selected = DeleteConfirmMenuEntry::No; - self.load_confirm.push_entry(MenuEntry::Active(state.t("menus.main_menu.start"))); - self.load_confirm.push_entry(MenuEntry::Active(state.t("menus.save_menu.delete_confirm"))); - self.load_confirm.push_entry(MenuEntry::Active(state.t("common.back"))); + self.load_confirm.push_entry(LoadConfirmMenuEntry::Start, MenuEntry::Active(state.t("menus.main_menu.start"))); + self.load_confirm + .push_entry(LoadConfirmMenuEntry::Delete, MenuEntry::Active(state.t("menus.save_menu.delete_confirm"))); + self.load_confirm.push_entry(LoadConfirmMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); self.save_detailed.draw_cursor = false; - if let MenuEntry::SaveData(save) = self.save_menu.entries[0] { - self.save_detailed.push_entry(MenuEntry::SaveDataSingle(save)); + if let (_, MenuEntry::SaveData(save)) = self.save_menu.entries[0] { + self.save_detailed.push_entry(0, MenuEntry::SaveDataSingle(save)); } self.update_sizes(state); @@ -161,42 +238,40 @@ impl SaveSelectMenu { self.update_sizes(state); match self.current_menu { CurrentMenu::SaveMenu => match self.save_menu.tick(controller, state) { - MenuSelectionResult::Selected(3, _) | MenuSelectionResult::Canceled => exit_action(), - MenuSelectionResult::Selected(slot, _) => { + MenuSelectionResult::Selected(SaveMenuEntry::Back, _) | MenuSelectionResult::Canceled => exit_action(), + MenuSelectionResult::Selected(SaveMenuEntry::New(slot), _) => { + state.save_slot = slot + 1; + + if self.skip_difficulty_menu { + self.current_menu = CurrentMenu::PlayerCountMenu; + } else { + self.difficulty_menu.selected = DifficultyMenuEntry::Difficulty(GameDifficulty::Normal); + self.current_menu = CurrentMenu::DifficultyMenu; + } + } + MenuSelectionResult::Selected(SaveMenuEntry::Load(slot), _) => { state.save_slot = slot + 1; if let Ok(_) = filesystem::user_open(ctx, state.get_save_filename(state.save_slot).unwrap_or("".to_string())) { - if let MenuEntry::SaveData(save) = self.save_menu.entries[slot] { + if let (_, MenuEntry::SaveData(save)) = self.save_menu.entries[slot] { self.save_detailed.entries.clear(); - self.save_detailed.push_entry(MenuEntry::SaveDataSingle(save)); + self.save_detailed.push_entry(0, MenuEntry::SaveDataSingle(save)); } + self.current_menu = CurrentMenu::LoadConfirm; - self.load_confirm.selected = 0; - } else if self.skip_difficulty_menu { - self.current_menu = CurrentMenu::PlayerCountMenu; - } else { - self.difficulty_menu.selected = 2; - self.current_menu = CurrentMenu::DifficultyMenu; + self.load_confirm.selected = LoadConfirmMenuEntry::Start; } } _ => (), }, CurrentMenu::DifficultyMenu => match self.difficulty_menu.tick(controller, state) { - MenuSelectionResult::Selected(4, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(DifficultyMenuEntry::Back, _) | MenuSelectionResult::Canceled => { self.current_menu = CurrentMenu::SaveMenu; } - MenuSelectionResult::Selected(1, _) => { - state.difficulty = GameDifficulty::Easy; - self.current_menu = CurrentMenu::PlayerCountMenu; - } - MenuSelectionResult::Selected(2, _) => { - state.difficulty = GameDifficulty::Normal; - self.current_menu = CurrentMenu::PlayerCountMenu; - } - MenuSelectionResult::Selected(3, _) => { - state.difficulty = GameDifficulty::Hard; + MenuSelectionResult::Selected(DifficultyMenuEntry::Difficulty(difficulty), _) => { + state.difficulty = difficulty; self.current_menu = CurrentMenu::PlayerCountMenu; } _ => (), @@ -214,30 +289,33 @@ impl SaveSelectMenu { )?; } CurrentMenu::DeleteConfirm => match self.delete_confirm.tick(controller, state) { - MenuSelectionResult::Selected(1, _) => { - state.sound_manager.play_sfx(17); // Player Death sfx - filesystem::user_delete( - ctx, - state.get_save_filename(self.save_menu.selected + 1).unwrap_or("".to_string()), - )?; - self.save_menu.entries[self.save_menu.selected] = MenuEntry::NewSave; + MenuSelectionResult::Selected(DeleteConfirmMenuEntry::Yes, _) => { + match self.save_menu.selected { + SaveMenuEntry::Load(slot) => { + state.sound_manager.play_sfx(17); // Player Death sfx + filesystem::user_delete(ctx, state.get_save_filename(slot + 1).unwrap_or("".to_string()))?; + } + _ => (), + } + + self.save_menu.set_entry(self.save_menu.selected, MenuEntry::NewSave); self.current_menu = CurrentMenu::SaveMenu; } - MenuSelectionResult::Selected(2, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(DeleteConfirmMenuEntry::No, _) | MenuSelectionResult::Canceled => { self.current_menu = CurrentMenu::LoadConfirm; - self.load_confirm.selected = 0; + self.load_confirm.selected = LoadConfirmMenuEntry::Start; } _ => (), }, CurrentMenu::LoadConfirm => match self.load_confirm.tick(controller, state) { - MenuSelectionResult::Selected(0, _) => { + MenuSelectionResult::Selected(LoadConfirmMenuEntry::Start, _) => { self.current_menu = CurrentMenu::PlayerCountMenu; } - MenuSelectionResult::Selected(1, _) => { + MenuSelectionResult::Selected(LoadConfirmMenuEntry::Delete, _) => { self.current_menu = CurrentMenu::DeleteConfirm; - self.delete_confirm.selected = 2; + self.delete_confirm.selected = DeleteConfirmMenuEntry::No; } - MenuSelectionResult::Selected(2, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(LoadConfirmMenuEntry::Back, _) | MenuSelectionResult::Canceled => { self.current_menu = CurrentMenu::SaveMenu; } _ => (), diff --git a/src/menu/settings_menu.rs b/src/menu/settings_menu.rs index 594278a..c9a550b 100644 --- a/src/menu/settings_menu.rs +++ b/src/menu/settings_menu.rs @@ -24,13 +24,90 @@ enum CurrentMenu { LanguageMenu, } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum MainMenuEntry { + Graphics, + Sound, + Language, + GameTiming, + DiscordLink, + Back, +} + +impl Default for MainMenuEntry { + fn default() -> Self { + MainMenuEntry::Graphics + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum GraphicsMenuEntry { + WindowMode, + VSyncMode, + LightingEffects, + WeaponLightCone, + ScreenShake, + MotionInterpolation, + SubpixelScrolling, + OriginalTextures, + SeasonalTextures, + Renderer, + Back, +} + +impl Default for GraphicsMenuEntry { + fn default() -> Self { + GraphicsMenuEntry::WindowMode + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum SoundMenuEntry { + MusicVolume, + EffectsVolume, + BGMInterpolation, + Soundtrack, + Back, +} + +impl Default for SoundMenuEntry { + fn default() -> Self { + SoundMenuEntry::MusicVolume + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum SoundtrackMenuEntry { + Soundtrack(usize), + Back, +} + +impl Default for SoundtrackMenuEntry { + fn default() -> Self { + SoundtrackMenuEntry::Soundtrack(0) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum LanguageMenuEntry { + Title, + Language(Language), + Back, +} + +impl Default for LanguageMenuEntry { + fn default() -> Self { + LanguageMenuEntry::Language(Language::English) + } +} + pub struct SettingsMenu { current: CurrentMenu, - main: Menu, - graphics: Menu, - sound: Menu, - soundtrack: Menu, - language: Menu, + main: Menu, + graphics: Menu, + sound: Menu, + soundtrack: Menu, + language: Menu, pub on_title: bool, } @@ -49,155 +126,182 @@ impl SettingsMenu { pub fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { #[cfg(not(target_os = "android"))] - self.graphics.push_entry(MenuEntry::Options( - state.t("menus.options_menu.graphics_menu.window_mode.entry"), - state.settings.window_mode as usize, - vec![ - state.t("menus.options_menu.graphics_menu.window_mode.windowed"), - state.t("menus.options_menu.graphics_menu.window_mode.fullscreen"), - ], - )); + self.graphics.push_entry( + GraphicsMenuEntry::WindowMode, + MenuEntry::Options( + state.t("menus.options_menu.graphics_menu.window_mode.entry"), + state.settings.window_mode as usize, + vec![ + state.t("menus.options_menu.graphics_menu.window_mode.windowed"), + state.t("menus.options_menu.graphics_menu.window_mode.fullscreen"), + ], + ), + ); - #[cfg(target_os = "android")] - { - let entry_text = state.t("menus.options_menu.graphics_menu.window_mode.entry") + " N/A"; - self.graphics.push_entry(MenuEntry::Disabled(entry_text)); - self.graphics.selected += 1; - } - - self.graphics.push_entry(MenuEntry::DescriptiveOptions( - state.t("menus.options_menu.graphics_menu.vsync_mode.entry"), - state.settings.vsync_mode as usize, - vec![ - state.t("menus.options_menu.graphics_menu.vsync_mode.uncapped"), - state.t("menus.options_menu.graphics_menu.vsync_mode.vsync"), - state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x"), - state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x"), - state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x"), - ], - vec![ - state.t("menus.options_menu.graphics_menu.vsync_mode.uncapped_desc"), - state.t("menus.options_menu.graphics_menu.vsync_mode.vsync_desc"), - state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x_desc"), - state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x_desc"), - state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x_desc"), - ], - )); - self.graphics.push_entry(MenuEntry::Toggle( - state.t("menus.options_menu.graphics_menu.lighting_effects"), - state.settings.shader_effects, - )); - self.graphics.push_entry(MenuEntry::Toggle( - state.t("menus.options_menu.graphics_menu.weapon_light_cone"), - state.settings.light_cone, - )); - self.graphics.push_entry(MenuEntry::Options( - state.t("menus.options_menu.graphics_menu.screen_shake.entry"), - state.settings.screen_shake_intensity as usize, - vec![ - state.t("menus.options_menu.graphics_menu.screen_shake.full"), - state.t("menus.options_menu.graphics_menu.screen_shake.half"), - state.t("menus.options_menu.graphics_menu.screen_shake.off"), - ], - )); - self.graphics.push_entry(MenuEntry::Toggle( - state.t("menus.options_menu.graphics_menu.motion_interpolation"), - state.settings.motion_interpolation, - )); - self.graphics.push_entry(MenuEntry::Toggle( - state.t("menus.options_menu.graphics_menu.subpixel_scrolling"), - state.settings.subpixel_coords, - )); + self.graphics.push_entry( + GraphicsMenuEntry::VSyncMode, + MenuEntry::DescriptiveOptions( + state.t("menus.options_menu.graphics_menu.vsync_mode.entry"), + state.settings.vsync_mode as usize, + vec![ + state.t("menus.options_menu.graphics_menu.vsync_mode.uncapped"), + state.t("menus.options_menu.graphics_menu.vsync_mode.vsync"), + state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x"), + state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x"), + state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x"), + ], + vec![ + state.t("menus.options_menu.graphics_menu.vsync_mode.uncapped_desc"), + state.t("menus.options_menu.graphics_menu.vsync_mode.vsync_desc"), + state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x_desc"), + state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x_desc"), + state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x_desc"), + ], + ), + ); + self.graphics.push_entry( + GraphicsMenuEntry::LightingEffects, + MenuEntry::Toggle( + state.t("menus.options_menu.graphics_menu.lighting_effects"), + state.settings.shader_effects, + ), + ); + self.graphics.push_entry( + GraphicsMenuEntry::WeaponLightCone, + MenuEntry::Toggle(state.t("menus.options_menu.graphics_menu.weapon_light_cone"), state.settings.light_cone), + ); + self.graphics.push_entry( + GraphicsMenuEntry::ScreenShake, + MenuEntry::Options( + state.t("menus.options_menu.graphics_menu.screen_shake.entry"), + state.settings.screen_shake_intensity as usize, + vec![ + state.t("menus.options_menu.graphics_menu.screen_shake.full"), + state.t("menus.options_menu.graphics_menu.screen_shake.half"), + state.t("menus.options_menu.graphics_menu.screen_shake.off"), + ], + ), + ); + self.graphics.push_entry( + GraphicsMenuEntry::MotionInterpolation, + MenuEntry::Toggle( + state.t("menus.options_menu.graphics_menu.motion_interpolation"), + state.settings.motion_interpolation, + ), + ); + self.graphics.push_entry( + GraphicsMenuEntry::SubpixelScrolling, + MenuEntry::Toggle( + state.t("menus.options_menu.graphics_menu.subpixel_scrolling"), + state.settings.subpixel_coords, + ), + ); // NS version uses two different maps, therefore we can't dynamically switch between graphics presets. if state.constants.supports_og_textures { if !state.constants.is_switch || self.on_title { - self.graphics.push_entry(MenuEntry::Toggle( - state.t("menus.options_menu.graphics_menu.original_textures"), - state.settings.original_textures, - )); + self.graphics.push_entry( + GraphicsMenuEntry::OriginalTextures, + MenuEntry::Toggle( + state.t("menus.options_menu.graphics_menu.original_textures"), + state.settings.original_textures, + ), + ); } else { - self.graphics - .push_entry(MenuEntry::Disabled(state.t("menus.options_menu.graphics_menu.original_textures"))); + self.graphics.push_entry( + GraphicsMenuEntry::OriginalTextures, + MenuEntry::Disabled(state.t("menus.options_menu.graphics_menu.original_textures")), + ); } - } else { - self.graphics.push_entry(MenuEntry::Hidden); } if state.constants.is_cs_plus { - self.graphics.push_entry(MenuEntry::Toggle( - state.t("menus.options_menu.graphics_menu.seasonal_textures"), - state.settings.seasonal_textures, - )); - } else { - self.graphics.push_entry(MenuEntry::Hidden); + self.graphics.push_entry( + GraphicsMenuEntry::SeasonalTextures, + MenuEntry::Toggle( + state.t("menus.options_menu.graphics_menu.seasonal_textures"), + state.settings.seasonal_textures, + ), + ); } - self.graphics.push_entry(MenuEntry::Disabled(format!( - "{} {}", - state.t("menus.options_menu.graphics_menu.renderer"), - ctx.renderer.as_ref().unwrap().renderer_name() - ))); + self.graphics.push_entry( + GraphicsMenuEntry::Renderer, + MenuEntry::Disabled(format!( + "{} {}", + state.t("menus.options_menu.graphics_menu.renderer"), + ctx.renderer.as_ref().unwrap().renderer_name() + )), + ); - self.graphics.push_entry(MenuEntry::Active(state.t("common.back"))); + self.graphics.push_entry(GraphicsMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); - self.main.push_entry(MenuEntry::Active(state.t("menus.options_menu.graphics"))); - self.main.push_entry(MenuEntry::Active(state.t("menus.options_menu.sound"))); + 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.language.push_entry(LanguageMenuEntry::Title, MenuEntry::Disabled(state.t("menus.options_menu.language"))); - self.language.push_entry(MenuEntry::Disabled(state.t("menus.options_menu.language"))); for language in Language::values() { - self.language.push_entry(MenuEntry::Active(language.to_string())); + self.language.push_entry(LanguageMenuEntry::Language(language), MenuEntry::Active(language.to_string())); } - self.language.push_entry(MenuEntry::Active(state.t("common.back"))); + + self.language.push_entry(LanguageMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); if self.on_title { - self.main.push_entry(MenuEntry::Active(state.t("menus.options_menu.language"))); - } else { - self.main.push_entry(MenuEntry::Disabled(state.t("menus.options_menu.language"))); + self.main.push_entry(MainMenuEntry::Language, MenuEntry::Active(state.t("menus.options_menu.language"))); } - self.main.push_entry(MenuEntry::Options( - state.t("menus.options_menu.game_timing.entry"), - if state.settings.timing_mode == TimingMode::_50Hz { 0 } else { 1 }, - vec![state.t("menus.options_menu.game_timing.50tps"), state.t("menus.options_menu.game_timing.60tps")], - )); + self.main.push_entry( + MainMenuEntry::GameTiming, + MenuEntry::Options( + state.t("menus.options_menu.game_timing.entry"), + if state.settings.timing_mode == TimingMode::_50Hz { 0 } else { 1 }, + vec![state.t("menus.options_menu.game_timing.50tps"), state.t("menus.options_menu.game_timing.60tps")], + ), + ); - self.main.push_entry(MenuEntry::Active(DISCORD_LINK.to_owned())); + self.main.push_entry(MainMenuEntry::DiscordLink, MenuEntry::Active(DISCORD_LINK.to_owned())); - self.main.push_entry(MenuEntry::Active(state.t("common.back"))); + self.main.push_entry(MainMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); - self.sound.push_entry(MenuEntry::OptionsBar( - state.t("menus.options_menu.sound_menu.music_volume"), - state.settings.bgm_volume, - )); - self.sound.push_entry(MenuEntry::OptionsBar( - state.t("menus.options_menu.sound_menu.effects_volume"), - state.settings.sfx_volume, - )); + self.sound.push_entry( + SoundMenuEntry::MusicVolume, + MenuEntry::OptionsBar(state.t("menus.options_menu.sound_menu.music_volume"), state.settings.bgm_volume), + ); + self.sound.push_entry( + SoundMenuEntry::EffectsVolume, + MenuEntry::OptionsBar(state.t("menus.options_menu.sound_menu.effects_volume"), state.settings.sfx_volume), + ); - self.sound.push_entry(MenuEntry::DescriptiveOptions( - state.t("menus.options_menu.sound_menu.bgm_interpolation.entry"), - state.settings.organya_interpolation as usize, - vec![ - state.t("menus.options_menu.sound_menu.bgm_interpolation.nearest"), - state.t("menus.options_menu.sound_menu.bgm_interpolation.linear"), - state.t("menus.options_menu.sound_menu.bgm_interpolation.cosine"), - state.t("menus.options_menu.sound_menu.bgm_interpolation.cubic"), - state.t("menus.options_menu.sound_menu.bgm_interpolation.linear_lp"), - ], - vec![ - state.t("menus.options_menu.sound_menu.bgm_interpolation.nearest_desc"), - state.t("menus.options_menu.sound_menu.bgm_interpolation.linear_desc"), - state.t("menus.options_menu.sound_menu.bgm_interpolation.cosine_desc"), - state.t("menus.options_menu.sound_menu.bgm_interpolation.cubic_desc"), - state.t("menus.options_menu.sound_menu.bgm_interpolation.linear_lp_desc"), - ], - )); - self.sound.push_entry(MenuEntry::Active(state.tt( - "menus.options_menu.sound_menu.soundtrack", - HashMap::from([("soundtrack".to_owned(), state.settings.soundtrack.to_owned())]), - ))); - self.sound.push_entry(MenuEntry::Active(state.t("common.back"))); + self.sound.push_entry( + SoundMenuEntry::BGMInterpolation, + MenuEntry::DescriptiveOptions( + state.t("menus.options_menu.sound_menu.bgm_interpolation.entry"), + state.settings.organya_interpolation as usize, + vec![ + state.t("menus.options_menu.sound_menu.bgm_interpolation.nearest"), + state.t("menus.options_menu.sound_menu.bgm_interpolation.linear"), + state.t("menus.options_menu.sound_menu.bgm_interpolation.cosine"), + state.t("menus.options_menu.sound_menu.bgm_interpolation.cubic"), + state.t("menus.options_menu.sound_menu.bgm_interpolation.linear_lp"), + ], + vec![ + state.t("menus.options_menu.sound_menu.bgm_interpolation.nearest_desc"), + state.t("menus.options_menu.sound_menu.bgm_interpolation.linear_desc"), + state.t("menus.options_menu.sound_menu.bgm_interpolation.cosine_desc"), + state.t("menus.options_menu.sound_menu.bgm_interpolation.cubic_desc"), + state.t("menus.options_menu.sound_menu.bgm_interpolation.linear_lp_desc"), + ], + ), + ); + self.sound.push_entry( + SoundMenuEntry::Soundtrack, + MenuEntry::Active(state.tt( + "menus.options_menu.sound_menu.soundtrack", + HashMap::from([("soundtrack".to_owned(), state.settings.soundtrack.to_owned())]), + )), + ); + self.sound.push_entry(SoundMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); let mut soundtrack_entries = state.constants.soundtracks.iter().filter(|s| s.available).map(|s| s.name.to_owned()).collect_vec(); @@ -217,8 +321,8 @@ impl SettingsMenu { soundtrack_entries.sort(); - for soundtrack in &soundtrack_entries { - self.soundtrack.push_entry(MenuEntry::Active(soundtrack.to_string())); + for (idx, soundtrack) in soundtrack_entries.iter().enumerate() { + self.soundtrack.push_entry(SoundtrackMenuEntry::Soundtrack(idx), MenuEntry::Active(soundtrack.to_string())); } self.soundtrack.width = soundtrack_entries @@ -228,7 +332,7 @@ impl SettingsMenu { .unwrap_or(self.soundtrack.width as f32) as u16 + 32; - self.soundtrack.push_entry(MenuEntry::Active(state.t("common.back"))); + self.soundtrack.push_entry(SoundtrackMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); self.update_sizes(state); @@ -273,17 +377,17 @@ impl SettingsMenu { match self.current { CurrentMenu::MainMenu => match self.main.tick(controller, state) { - MenuSelectionResult::Selected(0, _) => { + MenuSelectionResult::Selected(MainMenuEntry::Graphics, _) => { self.current = CurrentMenu::GraphicsMenu; } - MenuSelectionResult::Selected(1, _) => { + MenuSelectionResult::Selected(MainMenuEntry::Sound, _) => { self.current = CurrentMenu::SoundMenu; } - MenuSelectionResult::Selected(2, _) => { - self.language.selected = (state.settings.locale as usize) + 1; + MenuSelectionResult::Selected(MainMenuEntry::Language, _) => { + self.language.selected = LanguageMenuEntry::Language(state.settings.locale); self.current = CurrentMenu::LanguageMenu; } - MenuSelectionResult::Selected(3, toggle) => { + MenuSelectionResult::Selected(MainMenuEntry::GameTiming, toggle) => { if let MenuEntry::Options(_, value, _) = toggle { match state.settings.timing_mode { TimingMode::_50Hz => { @@ -299,18 +403,18 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Selected(4, _) => { + MenuSelectionResult::Selected(MainMenuEntry::DiscordLink, _) => { if let Err(e) = webbrowser::open(DISCORD_LINK) { log::warn!("Error opening web browser: {}", e); } } - MenuSelectionResult::Selected(5, _) | MenuSelectionResult::Canceled => exit_action(), + MenuSelectionResult::Selected(MainMenuEntry::Back, _) | MenuSelectionResult::Canceled => exit_action(), _ => (), }, CurrentMenu::GraphicsMenu => match self.graphics.tick(controller, state) { - MenuSelectionResult::Selected(0, toggle) - | MenuSelectionResult::Right(0, toggle, _) - | MenuSelectionResult::Left(0, toggle, _) => { + MenuSelectionResult::Selected(GraphicsMenuEntry::WindowMode, toggle) + | MenuSelectionResult::Right(GraphicsMenuEntry::WindowMode, toggle, _) + | MenuSelectionResult::Left(GraphicsMenuEntry::WindowMode, toggle, _) => { if let MenuEntry::Options(_, value, _) = toggle { let (new_mode, new_value) = match *value { 0 => (WindowMode::Fullscreen, 1), @@ -324,7 +428,8 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Selected(1, toggle) | MenuSelectionResult::Right(1, toggle, _) => { + MenuSelectionResult::Selected(GraphicsMenuEntry::VSyncMode, toggle) + | MenuSelectionResult::Right(GraphicsMenuEntry::VSyncMode, toggle, _) => { if let MenuEntry::DescriptiveOptions(_, value, _, _) = toggle { let (new_mode, new_value) = match *value { 0 => (VSyncMode::VSync, 1), @@ -341,7 +446,7 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Left(1, toggle, _) => { + MenuSelectionResult::Left(GraphicsMenuEntry::VSyncMode, toggle, _) => { if let MenuEntry::DescriptiveOptions(_, value, _, _) = toggle { let (new_mode, new_value) = match *value { 0 => (VSyncMode::VRRTickSync3x, 4), @@ -358,7 +463,7 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Selected(2, toggle) => { + MenuSelectionResult::Selected(GraphicsMenuEntry::LightingEffects, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.shader_effects = !state.settings.shader_effects; let _ = state.settings.save(ctx); @@ -366,7 +471,7 @@ impl SettingsMenu { *value = state.settings.shader_effects; } } - MenuSelectionResult::Selected(3, toggle) => { + MenuSelectionResult::Selected(GraphicsMenuEntry::WeaponLightCone, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.light_cone = !state.settings.light_cone; let _ = state.settings.save(ctx); @@ -374,7 +479,8 @@ impl SettingsMenu { *value = state.settings.light_cone; } } - MenuSelectionResult::Selected(4, toggle) | MenuSelectionResult::Right(4, toggle, _) => { + MenuSelectionResult::Selected(GraphicsMenuEntry::ScreenShake, toggle) + | MenuSelectionResult::Right(GraphicsMenuEntry::ScreenShake, toggle, _) => { if let MenuEntry::Options(_, value, _) = toggle { let (new_intensity, new_value) = match *value { 0 => (ScreenShakeIntensity::Half, 1), @@ -388,7 +494,7 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Left(4, toggle, _) => { + MenuSelectionResult::Left(GraphicsMenuEntry::ScreenShake, toggle, _) => { if let MenuEntry::Options(_, value, _) = toggle { let (new_intensity, new_value) = match *value { 0 => (ScreenShakeIntensity::Off, 2), @@ -402,7 +508,7 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Selected(5, toggle) => { + MenuSelectionResult::Selected(GraphicsMenuEntry::MotionInterpolation, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.motion_interpolation = !state.settings.motion_interpolation; let _ = state.settings.save(ctx); @@ -410,7 +516,7 @@ impl SettingsMenu { *value = state.settings.motion_interpolation; } } - MenuSelectionResult::Selected(6, toggle) => { + MenuSelectionResult::Selected(GraphicsMenuEntry::SubpixelScrolling, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.subpixel_coords = !state.settings.subpixel_coords; let _ = state.settings.save(ctx); @@ -418,7 +524,7 @@ impl SettingsMenu { *value = state.settings.subpixel_coords; } } - MenuSelectionResult::Selected(7, toggle) => { + MenuSelectionResult::Selected(GraphicsMenuEntry::OriginalTextures, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.original_textures = !state.settings.original_textures; if self.on_title { @@ -431,7 +537,7 @@ impl SettingsMenu { *value = state.settings.original_textures; } } - MenuSelectionResult::Selected(8, toggle) => { + MenuSelectionResult::Selected(GraphicsMenuEntry::SeasonalTextures, toggle) => { if let MenuEntry::Toggle(_, value) = toggle { state.settings.seasonal_textures = !state.settings.seasonal_textures; state.reload_graphics(); @@ -440,13 +546,14 @@ impl SettingsMenu { *value = state.settings.seasonal_textures; } } - MenuSelectionResult::Selected(10, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(GraphicsMenuEntry::Back, _) | MenuSelectionResult::Canceled => { self.current = CurrentMenu::MainMenu } _ => (), }, CurrentMenu::SoundMenu => match self.sound.tick(controller, state) { - MenuSelectionResult::Left(0, bgm, direction) | MenuSelectionResult::Right(0, bgm, direction) => { + MenuSelectionResult::Left(SoundMenuEntry::MusicVolume, bgm, direction) + | MenuSelectionResult::Right(SoundMenuEntry::MusicVolume, bgm, direction) => { if let MenuEntry::OptionsBar(_, value) = bgm { *value = (*value * 10.0 + (direction as f32)).clamp(0.0, 10.0) / 10.0; state.settings.bgm_volume = *value; @@ -455,7 +562,8 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Left(1, sfx, direction) | MenuSelectionResult::Right(1, sfx, direction) => { + MenuSelectionResult::Left(SoundMenuEntry::EffectsVolume, sfx, direction) + | MenuSelectionResult::Right(SoundMenuEntry::EffectsVolume, sfx, direction) => { if let MenuEntry::OptionsBar(_, value) = sfx { *value = (*value * 10.0 + (direction as f32)).clamp(0.0, 10.0) / 10.0; state.settings.sfx_volume = *value; @@ -464,7 +572,7 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Selected(2, toggle) => { + MenuSelectionResult::Selected(SoundMenuEntry::BGMInterpolation, toggle) => { if let MenuEntry::DescriptiveOptions(_, value, _, _) = toggle { let (new_mode, new_value) = match *value { 0 => (InterpolationMode::Linear, 1), @@ -481,77 +589,72 @@ impl SettingsMenu { let _ = state.settings.save(ctx); } } - MenuSelectionResult::Selected(3, _) => { - let mut active_soundtrack_index = 0; + MenuSelectionResult::Selected(SoundMenuEntry::Soundtrack, _) => { + let mut active_soundtrack = SoundtrackMenuEntry::Soundtrack(0); - for (idx, entry) in self.soundtrack.entries.iter().enumerate() { + for (id, entry) in &self.soundtrack.entries { if let MenuEntry::Active(soundtrack) = entry { if soundtrack == &state.settings.soundtrack { - active_soundtrack_index = idx; + active_soundtrack = *id; let _ = state.settings.save(ctx); break; } } } - self.soundtrack.selected = active_soundtrack_index; + self.soundtrack.selected = active_soundtrack; self.current = CurrentMenu::SoundtrackMenu } - MenuSelectionResult::Selected(4, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(SoundMenuEntry::Back, _) | MenuSelectionResult::Canceled => { self.current = CurrentMenu::MainMenu } _ => (), }, - CurrentMenu::LanguageMenu => { - let last = self.language.entries.len() - 1; + CurrentMenu::LanguageMenu => match self.language.tick(controller, state) { + MenuSelectionResult::Selected(LanguageMenuEntry::Language(new_locale), entry) => { + if let MenuEntry::Active(_) = entry { + if new_locale == state.settings.locale { + self.current = CurrentMenu::MainMenu; + } else { + state.settings.locale = new_locale; + state.reload_fonts(ctx); - match self.language.tick(controller, state) { - MenuSelectionResult::Selected(idx, entry) => { - if let (true, MenuEntry::Active(_)) = (idx != last, entry) { - let new_locale = Language::from_primitive(idx.saturating_sub(1)); - if new_locale == state.settings.locale { - self.current = CurrentMenu::MainMenu; - } else { - state.settings.locale = new_locale; - state.reload_fonts(ctx); - - let _ = state.settings.save(ctx); - - let mut new_menu = TitleScene::new(); - new_menu.open_settings_menu()?; - state.next_scene = Some(Box::new(new_menu)); - } - } - - self.current = CurrentMenu::MainMenu; - } - MenuSelectionResult::Canceled => { - self.current = CurrentMenu::MainMenu; - } - _ => {} - } - } - CurrentMenu::SoundtrackMenu => { - let last = self.soundtrack.entries.len() - 1; - match self.soundtrack.tick(controller, state) { - MenuSelectionResult::Selected(idx, entry) => { - if let (true, MenuEntry::Active(name)) = (idx != last, entry) { - state.settings.soundtrack = name.to_owned(); let _ = state.settings.save(ctx); - self.sound.entries[3] = - MenuEntry::Active(format!("Soundtrack: {}", state.settings.soundtrack)); - state.sound_manager.reload_songs(&state.constants, &state.settings, ctx)?; - } - self.current = CurrentMenu::SoundMenu; + let mut new_menu = TitleScene::new(); + new_menu.open_settings_menu()?; + state.next_scene = Some(Box::new(new_menu)); + } } - MenuSelectionResult::Canceled => { - self.current = CurrentMenu::SoundMenu; - } - _ => (), + + self.current = CurrentMenu::MainMenu; } - } + MenuSelectionResult::Selected(LanguageMenuEntry::Back, _) | MenuSelectionResult::Canceled => { + self.current = CurrentMenu::MainMenu; + } + _ => {} + }, + CurrentMenu::SoundtrackMenu => match self.soundtrack.tick(controller, state) { + MenuSelectionResult::Selected(SoundtrackMenuEntry::Soundtrack(_), entry) => { + if let MenuEntry::Active(name) = entry { + state.settings.soundtrack = name.to_owned(); + let _ = state.settings.save(ctx); + + self.sound.set_entry( + SoundMenuEntry::Soundtrack, + MenuEntry::Active(format!("Soundtrack: {}", state.settings.soundtrack)), + ); + state.sound_manager.reload_songs(&state.constants, &state.settings, ctx)?; + } + + self.current = CurrentMenu::SoundMenu; + } + MenuSelectionResult::Selected(SoundtrackMenuEntry::Back, _) | MenuSelectionResult::Canceled => { + self.current = CurrentMenu::SoundMenu; + } + _ => (), + }, } Ok(()) } diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index 7bc19bb..f550818 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -29,14 +29,57 @@ enum CurrentMenu { PlayerCountMenu, } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum MainMenuEntry { + Start, + Challenges, + Options, + Editor, + Jukebox, + Quit, +} + +impl Default for MainMenuEntry { + fn default() -> Self { + MainMenuEntry::Start + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum ChallengesMenuEntry { + Back, + Challenge(usize), +} + +impl Default for ChallengesMenuEntry { + fn default() -> Self { + ChallengesMenuEntry::Back + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum ConfirmMenuEntry { + Title, + StartChallenge, + ReplayBest, + DeleteReplay, + Back, +} + +impl Default for ConfirmMenuEntry { + fn default() -> Self { + ConfirmMenuEntry::StartChallenge + } +} + pub struct TitleScene { tick: usize, controller: CombinedMenuController, current_menu: CurrentMenu, - main_menu: Menu, + main_menu: Menu, save_select_menu: SaveSelectMenu, - challenges_menu: Menu, - confirm_menu: Menu, + challenges_menu: Menu, + confirm_menu: Menu, coop_menu: PlayerCountMenu, settings_menu: SettingsMenu, background: Background, @@ -152,24 +195,24 @@ impl Scene for TitleScene { self.controller.add(state.settings.create_player1_controller()); self.controller.add(state.settings.create_player2_controller()); - self.main_menu.push_entry(MenuEntry::Active(state.t("menus.main_menu.start"))); + self.main_menu.push_entry(MainMenuEntry::Start, MenuEntry::Active(state.t("menus.main_menu.start"))); + if !state.mod_list.mods.is_empty() { - self.main_menu.push_entry(MenuEntry::Active(state.t("menus.main_menu.challenges"))); - } else { - self.main_menu.push_entry(MenuEntry::Hidden); + self.main_menu + .push_entry(MainMenuEntry::Challenges, MenuEntry::Active(state.t("menus.main_menu.challenges"))); } - self.main_menu.push_entry(MenuEntry::Active(state.t("menus.main_menu.options"))); + + self.main_menu.push_entry(MainMenuEntry::Options, MenuEntry::Active(state.t("menus.main_menu.options"))); + if cfg!(feature = "editor") { - self.main_menu.push_entry(MenuEntry::Active(state.t("menus.main_menu.editor"))); - } else { - self.main_menu.push_entry(MenuEntry::Hidden); + self.main_menu.push_entry(MainMenuEntry::Editor, MenuEntry::Active(state.t("menus.main_menu.editor"))); } + if state.constants.is_switch { - self.main_menu.push_entry(MenuEntry::Active(state.t("menus.main_menu.jukebox"))); - } else { - self.main_menu.push_entry(MenuEntry::Hidden); + self.main_menu.push_entry(MainMenuEntry::Jukebox, MenuEntry::Active(state.t("menus.main_menu.jukebox"))); } - self.main_menu.push_entry(MenuEntry::Active(state.t("menus.main_menu.quit"))); + + self.main_menu.push_entry(MainMenuEntry::Quit, MenuEntry::Active(state.t("menus.main_menu.quit"))); self.settings_menu.init(state, ctx)?; @@ -177,34 +220,39 @@ impl Scene for TitleScene { self.coop_menu.init(state)?; - let mut selected: usize = 0; + let mut selected = ChallengesMenuEntry::Back; let mut mutate_selection = true; - for mod_info in state.mod_list.mods.iter() { + for (idx, mod_info) in state.mod_list.mods.iter().enumerate() { if !mod_info.valid { - self.challenges_menu.push_entry(MenuEntry::Disabled(mod_info.path.clone())); + self.challenges_menu + .push_entry(ChallengesMenuEntry::Challenge(idx), MenuEntry::Disabled(mod_info.path.clone())); continue; } if mod_info.satisfies_requirement(&state.mod_requirements) { - self.challenges_menu.push_entry(MenuEntry::Active(mod_info.name.clone())); - mutate_selection = false; - } else { - self.challenges_menu.push_entry(MenuEntry::Disabled("???".to_owned())); + self.challenges_menu + .push_entry(ChallengesMenuEntry::Challenge(idx), MenuEntry::Active(mod_info.name.clone())); if mutate_selection { - selected += 1; + selected = ChallengesMenuEntry::Challenge(idx); + mutate_selection = false; } + } else { + self.challenges_menu + .push_entry(ChallengesMenuEntry::Challenge(idx), MenuEntry::Disabled("???".to_owned())); } } - self.challenges_menu.push_entry(MenuEntry::Active(state.t("common.back"))); + self.challenges_menu.push_entry(ChallengesMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); self.challenges_menu.selected = selected; - self.confirm_menu.push_entry(MenuEntry::Disabled("".to_owned())); - self.confirm_menu.push_entry(MenuEntry::Active(state.t("menus.challenge_menu.start"))); - self.confirm_menu.push_entry(MenuEntry::Disabled(state.t("menus.challenge_menu.no_replay"))); - self.confirm_menu.push_entry(MenuEntry::Hidden); - self.confirm_menu.push_entry(MenuEntry::Active(state.t("common.back"))); - self.confirm_menu.selected = 1; + self.confirm_menu.push_entry(ConfirmMenuEntry::Title, MenuEntry::Disabled("".to_owned())); + self.confirm_menu + .push_entry(ConfirmMenuEntry::StartChallenge, MenuEntry::Active(state.t("menus.challenge_menu.start"))); + self.confirm_menu + .push_entry(ConfirmMenuEntry::ReplayBest, MenuEntry::Disabled(state.t("menus.challenge_menu.no_replay"))); + self.confirm_menu.push_entry(ConfirmMenuEntry::DeleteReplay, MenuEntry::Hidden); + self.confirm_menu.push_entry(ConfirmMenuEntry::Back, MenuEntry::Active(state.t("common.back"))); + self.confirm_menu.selected = ConfirmMenuEntry::StartChallenge; self.controller.update(state, ctx)?; self.controller.update_trigger(); @@ -237,19 +285,19 @@ impl Scene for TitleScene { match self.current_menu { CurrentMenu::MainMenu => match self.main_menu.tick(&mut self.controller, state) { - MenuSelectionResult::Selected(0, _) => { + MenuSelectionResult::Selected(MainMenuEntry::Start, _) => { state.mod_path = None; self.save_select_menu.init(state, ctx)?; self.save_select_menu.set_skip_difficulty_menu(false); self.current_menu = CurrentMenu::SaveSelectMenu; } - MenuSelectionResult::Selected(1, _) => { + MenuSelectionResult::Selected(MainMenuEntry::Challenges, _) => { self.current_menu = CurrentMenu::ChallengesMenu; } - MenuSelectionResult::Selected(2, _) => { + MenuSelectionResult::Selected(MainMenuEntry::Options, _) => { self.current_menu = CurrentMenu::OptionMenu; } - MenuSelectionResult::Selected(3, _) => { + MenuSelectionResult::Selected(MainMenuEntry::Editor, _) => { // this comment is just there because rustfmt removes parenthesis around the match case and breaks compilation #[cfg(feature = "editor")] { @@ -257,10 +305,10 @@ impl Scene for TitleScene { state.next_scene = Some(Box::new(EditorScene::new())); } } - MenuSelectionResult::Selected(4, _) => { + MenuSelectionResult::Selected(MainMenuEntry::Jukebox, _) => { state.next_scene = Some(Box::new(JukeboxScene::new())); } - MenuSelectionResult::Selected(5, _) => { + MenuSelectionResult::Selected(MainMenuEntry::Quit, _) => { state.shutdown(); } _ => {} @@ -292,68 +340,70 @@ impl Scene for TitleScene { ctx, )?; } - CurrentMenu::ChallengesMenu => { - let last_idx = self.challenges_menu.entries.len() - 1; - match self.challenges_menu.tick(&mut self.controller, state) { - MenuSelectionResult::Selected(idx, _) => { - if last_idx == idx { - state.mod_path = None; + CurrentMenu::ChallengesMenu => match self.challenges_menu.tick(&mut self.controller, state) { + MenuSelectionResult::Selected(ChallengesMenuEntry::Challenge(idx), _) => { + if let Some(mod_info) = state.mod_list.mods.get(idx) { + state.mod_path = Some(mod_info.path.clone()); + if mod_info.save_slot >= 0 { + self.save_select_menu.init(state, ctx)?; + self.save_select_menu.set_skip_difficulty_menu(true); self.nikumaru_rec.load_counter(state, ctx)?; - self.current_menu = CurrentMenu::MainMenu; - } else if let Some(mod_info) = state.mod_list.mods.get(idx) { - state.mod_path = Some(mod_info.path.clone()); - if mod_info.save_slot >= 0 { - self.save_select_menu.init(state, ctx)?; - self.save_select_menu.set_skip_difficulty_menu(true); - self.nikumaru_rec.load_counter(state, ctx)?; - self.current_menu = CurrentMenu::SaveSelectMenu; + self.current_menu = CurrentMenu::SaveSelectMenu; + } else { + let mod_name = mod_info.name.clone(); + self.confirm_menu.width = + (state.font.text_width(mod_name.chars(), &state.constants).max(50.0) + 32.0) as u16; + + self.confirm_menu.set_entry(ConfirmMenuEntry::Title, MenuEntry::Disabled(mod_name)); + + if state.has_replay_data(ctx) { + self.confirm_menu.set_entry( + ConfirmMenuEntry::ReplayBest, + MenuEntry::Active(state.t("menus.challenge_menu.replay_best")), + ); + self.confirm_menu.set_entry( + ConfirmMenuEntry::DeleteReplay, + MenuEntry::Active(state.t("menus.challenge_menu.delete_replay")), + ); } else { - let mod_name = mod_info.name.clone(); - self.confirm_menu.width = - (state.font.text_width(mod_name.chars(), &state.constants).max(50.0) + 32.0) as u16; - self.confirm_menu.entries[0] = MenuEntry::Disabled(mod_name); - if state.has_replay_data(ctx) { - self.confirm_menu.entries[2] = - MenuEntry::Active(state.t("menus.challenge_menu.replay_best")); - self.confirm_menu.entries[3] = - MenuEntry::Active(state.t("menus.challenge_menu.delete_replay")); - } else { - self.confirm_menu.entries[2] = - MenuEntry::Disabled(state.t("menus.challenge_menu.no_replay")); - self.confirm_menu.entries[3] = MenuEntry::Hidden; - } - self.nikumaru_rec.load_counter(state, ctx)?; - self.current_menu = CurrentMenu::ChallengeConfirmMenu; + self.confirm_menu.set_entry( + ConfirmMenuEntry::ReplayBest, + MenuEntry::Disabled(state.t("menus.challenge_menu.no_replay")), + ); + self.confirm_menu.set_entry(ConfirmMenuEntry::DeleteReplay, MenuEntry::Hidden); } - state.reload_graphics(); + + self.nikumaru_rec.load_counter(state, ctx)?; + self.current_menu = CurrentMenu::ChallengeConfirmMenu; } - } - MenuSelectionResult::Canceled => { - state.mod_path = None; - self.nikumaru_rec.load_counter(state, ctx)?; - self.current_menu = CurrentMenu::MainMenu; state.reload_graphics(); } - _ => (), } - } + MenuSelectionResult::Selected(ChallengesMenuEntry::Back, _) | MenuSelectionResult::Canceled => { + state.mod_path = None; + self.nikumaru_rec.load_counter(state, ctx)?; + self.current_menu = CurrentMenu::MainMenu; + state.reload_graphics(); + } + _ => (), + }, CurrentMenu::ChallengeConfirmMenu => match self.confirm_menu.tick(&mut self.controller, state) { - MenuSelectionResult::Selected(1, _) => { + MenuSelectionResult::Selected(ConfirmMenuEntry::StartChallenge, _) => { state.difficulty = GameDifficulty::Normal; state.replay_state = ReplayState::Recording; self.current_menu = CurrentMenu::PlayerCountMenu; } - MenuSelectionResult::Selected(2, _) => { + MenuSelectionResult::Selected(ConfirmMenuEntry::ReplayBest, _) => { state.difficulty = GameDifficulty::Normal; state.replay_state = ReplayState::Playback; state.reload_resources(ctx)?; state.start_new_game(ctx)?; } - MenuSelectionResult::Selected(3, _) => { + MenuSelectionResult::Selected(ConfirmMenuEntry::DeleteReplay, _) => { state.delete_replay_data(ctx)?; self.current_menu = CurrentMenu::ChallengesMenu; } - MenuSelectionResult::Selected(4, _) | MenuSelectionResult::Canceled => { + MenuSelectionResult::Selected(ConfirmMenuEntry::Back, _) | MenuSelectionResult::Canceled => { self.current_menu = CurrentMenu::ChallengesMenu; } _ => (), diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 3254cae..33fd2e9 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -94,7 +94,7 @@ impl WindowMode { } } -#[derive(PartialEq, Eq, Copy, Clone, num_derive::FromPrimitive)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, num_derive::FromPrimitive)] pub enum GameDifficulty { Normal = 0, Easy = 2, @@ -113,7 +113,7 @@ impl GameDifficulty { } } -#[derive(PartialEq, Eq, Copy, Clone, Hash, num_derive::FromPrimitive, serde::Serialize, serde::Deserialize)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, num_derive::FromPrimitive, serde::Serialize, serde::Deserialize)] pub enum Language { English, Japanese,