refactor menus to use enums instead of indices

This commit is contained in:
Sallai József 2022-07-24 17:52:51 +03:00
parent 4a6b2c4400
commit ef040a393c
7 changed files with 729 additions and 411 deletions

View File

@ -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<CoopMenuEntry>,
skin_menu: Menu<SkinMenuEntry>,
}
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)?;

View File

@ -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<T: std::cmp::PartialEq> {
pub x: isize,
pub y: isize,
pub width: u16,
pub height: u16,
pub selected: usize,
pub entries: Vec<MenuEntry>,
pub selected: T,
pub entries: Vec<(T, MenuEntry)>,
anim_num: u16,
anim_wait: u16,
custom_cursor: Cell<bool>,
pub draw_cursor: bool,
}
impl Menu {
pub fn new(x: isize, y: isize, width: u16, height: u16) -> Menu {
impl<T: std::cmp::PartialEq + std::default::Default + Copy> Menu<T> {
pub fn new(x: isize, y: isize, width: u16, height: u16) -> Menu<T> {
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::<f64>().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<T> {
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);

View File

@ -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<PauseMenuEntry>,
confirm_menu: Menu<ConfirmMenuEntry>,
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;
}
_ => (),

View File

@ -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<SaveMenuEntry>,
save_detailed: Menu<usize>,
difficulty_menu: Menu<DifficultyMenuEntry>,
coop_menu: PlayerCountMenu,
delete_confirm: Menu,
load_confirm: Menu,
delete_confirm: Menu<DeleteConfirmMenuEntry>,
load_confirm: Menu<LoadConfirmMenuEntry>,
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;
}
_ => (),

View File

@ -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<MainMenuEntry>,
graphics: Menu<GraphicsMenuEntry>,
sound: Menu<SoundMenuEntry>,
soundtrack: Menu<SoundtrackMenuEntry>,
language: Menu<LanguageMenuEntry>,
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(())
}

View File

@ -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<MainMenuEntry>,
save_select_menu: SaveSelectMenu,
challenges_menu: Menu,
confirm_menu: Menu,
challenges_menu: Menu<ChallengesMenuEntry>,
confirm_menu: Menu<ConfirmMenuEntry>,
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;
}
_ => (),

View File

@ -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,