diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index 0e16c8c..30b9cdc 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -207,7 +207,7 @@ pub struct AnimatedFace { #[derive(Debug, Clone)] pub struct ExtraSoundtrack { - pub name: String, + pub id: String, pub path: String, pub available: bool, } @@ -1613,10 +1613,10 @@ impl EngineConstants { font_path: "csfont.fnt".to_owned(), font_space_offset: 0.0, soundtracks: vec![ - ExtraSoundtrack { name: "Remastered".to_owned(), path: "/base/Ogg11/".to_owned(), available: false }, - ExtraSoundtrack { name: "New".to_owned(), path: "/base/Ogg/".to_owned(), available: false }, - ExtraSoundtrack { name: "Famitracks".to_owned(), path: "/base/ogg17/".to_owned(), available: false }, - ExtraSoundtrack { name: "Ridiculon".to_owned(), path: "/base/ogg_ridic/".to_owned(), available: false }, + ExtraSoundtrack { id: "remastered".to_owned(), path: "/base/Ogg11/".to_owned(), available: false }, + ExtraSoundtrack { id: "new".to_owned(), path: "/base/Ogg/".to_owned(), available: false }, + ExtraSoundtrack { id: "famitracks".to_owned(), path: "/base/ogg17/".to_owned(), available: false }, + ExtraSoundtrack { id: "ridiculon".to_owned(), path: "/base/ogg_ridic/".to_owned(), available: false }, ], music_table: vec![ "xxxx".to_owned(), diff --git a/src/game/settings.rs b/src/game/settings.rs index ff09105..e598415 100644 --- a/src/game/settings.rs +++ b/src/game/settings.rs @@ -95,7 +95,7 @@ fn default_true() -> bool { #[inline(always)] fn current_version() -> u32 { - 24 + 25 } #[inline(always)] @@ -347,6 +347,18 @@ impl Settings { self.allow_strafe = true; } + if self.version == 24 { + self.version = 25; + self.soundtrack = match self.soundtrack.as_str() { + "Organya" => "organya".to_owned(), + "Remastered" => "remastered".to_owned(), + "New" => "new".to_owned(), + "Famitracks" => "famitracks".to_owned(), + "Ridiculon" => "ridiculon".to_owned(), + _ => self.soundtrack.clone(), + } + } + if self.version != initial_version { log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version); } diff --git a/src/game/shared_game_state.rs b/src/game/shared_game_state.rs index ab17ec1..3d33d33 100644 --- a/src/game/shared_game_state.rs +++ b/src/game/shared_game_state.rs @@ -21,7 +21,9 @@ use crate::game::profile::GameProfile; #[cfg(feature = "scripting-lua")] use crate::game::scripting::lua::LuaScriptingState; use crate::game::scripting::tsc::credit_script::{CreditScript, CreditScriptVM}; -use crate::game::scripting::tsc::text_script::{ScriptMode, TextScript, TextScriptEncoding, TextScriptExecutionState, TextScriptVM}; +use crate::game::scripting::tsc::text_script::{ + ScriptMode, TextScript, TextScriptEncoding, TextScriptExecutionState, TextScriptVM, +}; use crate::game::settings::Settings; use crate::game::stage::StageData; use crate::graphics::bmfont::BMFont; @@ -408,7 +410,7 @@ impl SharedGameState { for soundtrack in constants.soundtracks.iter_mut() { if filesystem::exists(ctx, &soundtrack.path) { - log::info!("Enabling soundtrack {} from {}.", soundtrack.name, soundtrack.path); + log::info!("Enabling soundtrack {} from {}.", soundtrack.id, soundtrack.path); soundtrack.available = true; } } @@ -420,11 +422,11 @@ impl SharedGameState { let locale = SharedGameState::get_locale(&constants, &settings.locale).unwrap_or_default(); if (locale.code == "jp" || locale.code == "en") && constants.is_base() { - constants.textscript.encoding = TextScriptEncoding::ShiftJIS + constants.textscript.encoding = TextScriptEncoding::ShiftJIS } else { - constants.textscript.encoding = TextScriptEncoding::UTF8 + constants.textscript.encoding = TextScriptEncoding::UTF8 } - + let font = BMFont::load(&constants.base_paths, &locale.font.path, ctx, locale.font.scale).or_else(|e| { log::warn!("Failed to load font, using built-in: {}", e); BMFont::load(&vec!["/".to_owned()], "builtin/builtin_font.fnt", ctx, 1.0) @@ -573,9 +575,9 @@ impl SharedGameState { if let Some(locale) = SharedGameState::get_locale(&self.constants, &self.settings.locale) { self.loc = locale; if (self.loc.code == "jp" || self.loc.code == "en") && self.constants.is_base() { - self.constants.textscript.encoding = TextScriptEncoding::ShiftJIS + self.constants.textscript.encoding = TextScriptEncoding::ShiftJIS } else { - self.constants.textscript.encoding = TextScriptEncoding::UTF8 + self.constants.textscript.encoding = TextScriptEncoding::UTF8 } } @@ -647,7 +649,12 @@ impl SharedGameState { Ok(()) } - pub fn save_game(&mut self, game_scene: &mut GameScene, ctx: &mut Context, target_player: Option) -> GameResult { + pub fn save_game( + &mut self, + game_scene: &mut GameScene, + ctx: &mut Context, + target_player: Option, + ) -> GameResult { if let Some(save_path) = self.get_save_filename(self.save_slot) { if let Ok(data) = filesystem::open_options(ctx, save_path, OpenOptions::new().write(true).create(true)) { let profile = GameProfile::dump(self, game_scene, target_player); @@ -896,6 +903,18 @@ impl SharedGameState { out_locale } + pub fn get_localized_soundtrack_name(&self, id: &str) -> String { + if id == "organya" { + return self.loc.t("soundtrack.organya").to_owned(); + } + + self.constants + .soundtracks + .iter() + .find(|s| s.id == id) + .map_or_else(|| id.to_owned(), |s| self.loc.t(format!("soundtrack.{}", s.id).as_str()).to_owned()) + } + pub fn tt(&self, key: &str, args: &[(&str, &str)]) -> String { return self.loc.tt(key, args); } diff --git a/src/menu/settings_menu.rs b/src/menu/settings_menu.rs index 8c6efad..ad4fbaf 100644 --- a/src/menu/settings_menu.rs +++ b/src/menu/settings_menu.rs @@ -500,18 +500,21 @@ impl SettingsMenu { ); self.sound.push_entry( SoundMenuEntry::Soundtrack, - MenuEntry::Active( - state.tt( - "menus.options_menu.sound_menu.soundtrack", - &[("soundtrack", state.settings.soundtrack.as_str())], - ), - ), + MenuEntry::Active(state.loc.tt( + "menus.options_menu.sound_menu.soundtrack", + &[("soundtrack", state.get_localized_soundtrack_name(&state.settings.soundtrack).as_str())], + )), ); self.sound.push_entry(SoundMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned())); - let mut soundtrack_entries = - state.constants.soundtracks.iter().filter(|s| s.available).map(|s| s.name.to_owned()).collect_vec(); - soundtrack_entries.push("Organya".to_owned()); + let mut soundtrack_entries = state + .constants + .soundtracks + .iter() + .filter(|s| s.available) + .map(|s| state.loc.t(format!("soundtrack.{}", s.id).as_str()).to_owned()) + .collect_vec(); + soundtrack_entries.push(state.loc.t("soundtrack.organya").to_owned()); if let Ok(dir) = filesystem::read_dir(ctx, "/Soundtracks/") { for entry in dir { @@ -882,7 +885,7 @@ impl SettingsMenu { for (id, entry) in &self.soundtrack.entries { if let MenuEntry::Active(soundtrack) = entry { - if soundtrack == &state.settings.soundtrack { + if soundtrack == &state.get_localized_soundtrack_name(&state.settings.soundtrack) { active_soundtrack = *id; let _ = state.settings.save(ctx); break; @@ -937,12 +940,28 @@ impl SettingsMenu { 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(); + state.settings.soundtrack = state + .constants + .soundtracks + .iter() + .find(|s| state.loc.t(format!("soundtrack.{}", s.id).as_str()) == name) + .map_or_else(|| name.to_owned(), |s| s.id.clone()); + + if state.settings.soundtrack == "Organya" { + state.settings.soundtrack = "organya".to_owned() + } + let _ = state.settings.save(ctx); self.sound.set_entry( SoundMenuEntry::Soundtrack, - MenuEntry::Active(format!("Soundtrack: {}", state.settings.soundtrack)), + MenuEntry::Active(state.loc.tt( + "menus.options_menu.sound_menu.soundtrack", + &[( + "soundtrack", + state.get_localized_soundtrack_name(&state.settings.soundtrack).as_str(), + )], + )), ); state.sound_manager.reload_songs(&state.constants, &state.settings, ctx)?; } diff --git a/src/scene/jukebox_scene.rs b/src/scene/jukebox_scene.rs index b5d14e4..ff84873 100644 --- a/src/scene/jukebox_scene.rs +++ b/src/scene/jukebox_scene.rs @@ -3,6 +3,7 @@ use itertools::Itertools; use crate::common::Color; use crate::common::Rect; use crate::components::background::Background; +use crate::engine_constants::ExtraSoundtrack; use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::framework::filesystem; @@ -16,10 +17,43 @@ use crate::input::combined_menu_controller::CombinedMenuController; use crate::scene::title_scene::TitleScene; use crate::scene::Scene; +#[derive(Clone, Debug)] +pub enum JukeboxSoundtrackKind { + Organya, + Extra(ExtraSoundtrack), + Custom(String), +} + +impl JukeboxSoundtrackKind { + pub fn eq_str(&self, other: &str) -> bool { + match self { + JukeboxSoundtrackKind::Organya => other == "organya", + JukeboxSoundtrackKind::Extra(s) => other == s.id, + JukeboxSoundtrackKind::Custom(s) => other == s, + } + } + + pub fn to_localized_string(&self, state: &mut SharedGameState) -> String { + match self { + JukeboxSoundtrackKind::Organya => state.loc.t("soundtrack.organya").to_owned(), + JukeboxSoundtrackKind::Extra(s) => state.loc.t(format!("soundtrack.{}", s.id).as_str()).to_owned(), + JukeboxSoundtrackKind::Custom(s) => s.clone(), + } + } + + pub fn to_id(&self) -> String { + match self { + JukeboxSoundtrackKind::Organya => "organya".to_owned(), + JukeboxSoundtrackKind::Extra(s) => s.id.clone(), + JukeboxSoundtrackKind::Custom(s) => s.clone(), + } + } +} + pub struct JukeboxScene { selected_song: u16, song_list: Vec, - soundtracks: Vec, + soundtracks: Vec, selected_soundtrack: usize, controller: CombinedMenuController, background: Background, @@ -79,17 +113,26 @@ impl Scene for JukeboxScene { .cloned() .collect(); - let mut soundtrack_entries = - state.constants.soundtracks.iter().filter(|s| s.available).map(|s| s.name.to_owned()).collect_vec(); - soundtrack_entries.push("Organya".to_owned()); + let mut soundtrack_entries = state + .constants + .soundtracks + .iter() + .filter(|s| s.available) + .map(|s| JukeboxSoundtrackKind::Extra(s.clone())) + .collect_vec(); + soundtrack_entries.push(JukeboxSoundtrackKind::Organya); if let Ok(dir) = filesystem::read_dir(ctx, "/Soundtracks/") { for entry in dir { if filesystem::is_dir(ctx, &entry) { let filename = entry.file_name().unwrap().to_string_lossy().to_string(); - if !soundtrack_entries.contains(&filename) { - soundtrack_entries.push(filename); + if soundtrack_entries + .iter() + .find(|s| matches!(s, JukeboxSoundtrackKind::Custom(s) if s == &filename)) + .is_none() + { + soundtrack_entries.push(JukeboxSoundtrackKind::Custom(filename.clone())); } } } @@ -98,7 +141,7 @@ impl Scene for JukeboxScene { self.soundtracks = soundtrack_entries.clone(); let selected_soundtrack_index = - self.soundtracks.iter().position(|s| s == &state.settings.soundtrack).unwrap_or(0); + self.soundtracks.iter().position(|s| s.eq_str(&state.settings.soundtrack)).unwrap_or(0); self.selected_soundtrack = selected_soundtrack_index; self.previous_pause_on_focus_loss_setting = state.settings.pause_on_focus_loss; @@ -150,13 +193,13 @@ impl Scene for JukeboxScene { if self.controller.trigger_shift_left() { self.selected_soundtrack = self.selected_soundtrack.checked_sub(1).unwrap_or(self.soundtracks.len() - 1); - state.settings.soundtrack = self.soundtracks[self.selected_soundtrack].to_string(); + state.settings.soundtrack = self.soundtracks[self.selected_soundtrack].to_id(); state.sound_manager.reload_songs(&state.constants, &state.settings, ctx)?; } if self.controller.trigger_shift_right() { self.selected_soundtrack = (self.selected_soundtrack + 1) % self.soundtracks.len(); - state.settings.soundtrack = self.soundtracks[self.selected_soundtrack].to_string(); + state.settings.soundtrack = self.soundtracks[self.selected_soundtrack].to_id(); state.sound_manager.reload_songs(&state.constants, &state.settings, ctx)?; } @@ -284,9 +327,9 @@ impl Scene for JukeboxScene { // Write Soundtrack name - let text = &state.settings.soundtrack; + let text = self.soundtracks[self.selected_soundtrack].to_localized_string(state); state.font.builder().center(state.canvas_size.0).y(20.0).shadow(true).draw( - text, + text.as_str(), ctx, &state.constants, &mut state.texture_set, diff --git a/src/sound/mod.rs b/src/sound/mod.rs index 6d31ebc..946c9a3 100644 --- a/src/sound/mod.rs +++ b/src/sound/mod.rs @@ -11,8 +11,8 @@ use num_traits::clamp; use crate::engine_constants::EngineConstants; use crate::framework::context::Context; -use crate::framework::error::{GameError, GameResult}; use crate::framework::error::GameError::{AudioError, InvalidValue}; +use crate::framework::error::{GameError, GameResult}; use crate::framework::filesystem; use crate::framework::filesystem::File; use crate::game::settings::Settings; @@ -286,20 +286,19 @@ impl SoundManager { paths.insert(0, "/Soundtracks/".to_owned() + &settings.soundtrack + "/"); - if let Some(soundtrack) = - constants.soundtracks.iter().find(|s| s.available && s.name == settings.soundtrack) + if let Some(soundtrack) = constants.soundtracks.iter().find(|s| s.available && s.id == settings.soundtrack) { paths.insert(0, soundtrack.path.clone()); } let songs_paths = paths.iter().map(|prefix| { [ - #[cfg(feature = "ogg-playback")] + #[cfg(feature = "ogg-playback")] ( SongFormat::OggMultiPart, vec![format!("{}{}_intro.ogg", prefix, song_name), format!("{}{}_loop.ogg", prefix, song_name)], ), - #[cfg(feature = "ogg-playback")] + #[cfg(feature = "ogg-playback")] (SongFormat::OggSinglePart, vec![format!("{}{}.ogg", prefix, song_name)]), (SongFormat::Organya, vec![format!("{}{}.org", prefix, song_name)]), ] @@ -307,7 +306,7 @@ impl SoundManager { for songs in songs_paths { for (format, paths) in - songs.iter().filter(|(_, paths)| paths.iter().all(|path| filesystem::exists(ctx, path))) + songs.iter().filter(|(_, paths)| paths.iter().all(|path| filesystem::exists(ctx, path))) { match format { SongFormat::Organya => { @@ -385,7 +384,7 @@ impl SoundManager { Box::new(song_intro), Box::new(song_loop), )) - .unwrap(); + .unwrap(); return Ok(()); } @@ -597,8 +596,8 @@ fn run( device: cpal::Device, config: cpal::StreamConfig, ) -> GameResult - where - T: cpal::SizedSample + cpal::FromSample, +where + T: cpal::SizedSample + cpal::FromSample, { let sample_rate = config.sample_rate.0 as f32; let channels = config.channels as usize; @@ -732,7 +731,7 @@ fn run( assert!(new_speed > 0.0); speed = new_speed; #[cfg(feature = "ogg-playback")] - ogg_engine.set_sample_rate((sample_rate / new_speed) as usize); + ogg_engine.set_sample_rate((sample_rate / new_speed) as usize); org_engine.set_sample_rate((sample_rate / new_speed) as usize); } Ok(PlaybackMessage::SetSongVolume(new_volume)) => { @@ -895,7 +894,7 @@ fn run( } }, err_fn, - None + None, ); if stream_result.is_err() {