doukutsu-rs/src/scene/title_scene.rs

522 lines
21 KiB
Rust

use crate::common::{Color, VERSION_BANNER};
use crate::components::background::Background;
use crate::components::nikumaru::NikumaruCounter;
use crate::entity::GameEntity;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::game::frame::Frame;
use crate::game::map::Map;
use crate::game::shared_game_state::{
GameDifficulty, MenuCharacter, ReplayKind, ReplayState, Season, SharedGameState, TileSize,
};
use crate::game::stage::{BackgroundType, NpcType, Stage, StageData, StageTexturePaths, Tileset};
use crate::graphics::font::Font;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::input::touch_controls::TouchControlType;
use crate::menu::coop_menu::PlayerCountMenu;
use crate::menu::save_select_menu::SaveSelectMenu;
use crate::menu::settings_menu::SettingsMenu;
use crate::menu::{Menu, MenuEntry, MenuSelectionResult};
use crate::scene::jukebox_scene::JukeboxScene;
use crate::scene::Scene;
#[derive(PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
#[allow(unused)]
enum CurrentMenu {
MainMenu,
OptionMenu,
SaveSelectMenu,
ChallengesMenu,
ChallengeConfirmMenu,
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,
Replay(ReplayKind),
DeleteReplay,
Back,
}
impl Default for ConfirmMenuEntry {
fn default() -> Self {
ConfirmMenuEntry::StartChallenge
}
}
pub struct TitleScene {
tick: usize,
controller: CombinedMenuController,
current_menu: CurrentMenu,
main_menu: Menu<MainMenuEntry>,
save_select_menu: SaveSelectMenu,
challenges_menu: Menu<ChallengesMenuEntry>,
confirm_menu: Menu<ConfirmMenuEntry>,
coop_menu: PlayerCountMenu,
settings_menu: SettingsMenu,
background: Background,
frame: Frame,
nikumaru_rec: NikumaruCounter,
stage: Stage,
textures: StageTexturePaths,
}
impl TitleScene {
pub fn new() -> Self {
let fake_stage = Stage {
map: Map { width: 0, height: 0, tiles: vec![], attrib: [0; 0x100], tile_size: TileSize::Tile16x16 },
data: StageData {
name: String::new(),
name_jp: String::new(),
map: String::new(),
boss_no: 0,
tileset: Tileset { name: "0".to_string() },
pxpack_data: None,
background: crate::game::stage::Background::new("bkMoon"),
background_type: BackgroundType::Outside,
background_color: Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 },
npc1: NpcType::new("0"),
npc2: NpcType::new("0"),
},
};
let mut textures = StageTexturePaths::new();
textures.update(&fake_stage);
let mut settings_menu = SettingsMenu::new();
settings_menu.on_title = true;
Self {
tick: 0,
controller: CombinedMenuController::new(),
current_menu: CurrentMenu::MainMenu,
main_menu: Menu::new(0, 0, 100, 0),
save_select_menu: SaveSelectMenu::new(),
challenges_menu: Menu::new(0, 0, 150, 0),
confirm_menu: Menu::new(0, 0, 150, 0),
coop_menu: PlayerCountMenu::new(),
settings_menu,
background: Background::new(),
frame: Frame::new(),
nikumaru_rec: NikumaruCounter::new(),
stage: fake_stage,
textures,
}
}
fn draw_text_centered(&self, text: &str, y: f32, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
state.font.builder().center(state.canvas_size.0).y(y).shadow(true).draw(
text,
ctx,
&state.constants,
&mut state.texture_set,
)?;
Ok(())
}
pub fn update_menu_cursor(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
let minutes = self.nikumaru_rec.tick / (60 * state.settings.timing_mode.get_tps());
let mut song_id: usize;
if self.nikumaru_rec.shown && minutes < 3 {
state.menu_character = MenuCharacter::Sue;
song_id = 2;
} else if self.nikumaru_rec.shown && minutes < 4 {
state.menu_character = MenuCharacter::King;
song_id = 41;
} else if self.nikumaru_rec.shown && minutes < 5 {
state.menu_character = MenuCharacter::Toroko;
song_id = 40;
} else if self.nikumaru_rec.shown && minutes < 6 {
state.menu_character = MenuCharacter::Curly;
song_id = 36;
} else {
state.menu_character = MenuCharacter::Quote;
song_id = 24;
}
if state.settings.soundtrack == "New" && Season::current() == Season::PixelBirthday {
song_id = 43;
}
if song_id != state.sound_manager.current_song() {
state.sound_manager.play_song(song_id, &state.constants, &state.settings, ctx, false)?;
}
Ok(())
}
pub fn open_settings_menu(&mut self) -> GameResult {
self.current_menu = CurrentMenu::OptionMenu;
Ok(())
}
}
static COPYRIGHT_PIXEL: &str = "2004.12 Studio Pixel";
// Freeware
static COPYRIGHT_NICALIS: &str = "@2022 NICALIS INC."; // Nicalis font uses @ for copyright
impl Scene for TitleScene {
fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
if !state.mod_path.is_none() {
state.mod_path = None;
state.reload_resources(ctx)?;
}
self.controller.add(state.settings.create_player1_controller());
self.controller.add(state.settings.create_player2_controller());
self.main_menu
.push_entry(MainMenuEntry::Start, MenuEntry::Active(state.loc.t("menus.main_menu.start").to_owned()));
if !state.mod_list.mods.is_empty() {
self.main_menu.push_entry(
MainMenuEntry::Challenges,
MenuEntry::Active(state.loc.t("menus.main_menu.challenges").to_owned()),
);
}
self.main_menu
.push_entry(MainMenuEntry::Options, MenuEntry::Active(state.loc.t("menus.main_menu.options").to_owned()));
if cfg!(feature = "editor") {
self.main_menu
.push_entry(MainMenuEntry::Editor, MenuEntry::Active(state.loc.t("menus.main_menu.editor").to_owned()));
}
if state.constants.is_switch {
self.main_menu.push_entry(
MainMenuEntry::Jukebox,
MenuEntry::Active(state.loc.t("menus.main_menu.jukebox").to_owned()),
);
}
self.main_menu
.push_entry(MainMenuEntry::Quit, MenuEntry::Active(state.loc.t("menus.main_menu.quit").to_owned()));
self.settings_menu.init(state, ctx)?;
self.save_select_menu.init(state, ctx)?;
self.coop_menu.on_title = true;
self.coop_menu.init(state)?;
let mut selected = ChallengesMenuEntry::Back;
let mut mutate_selection = true;
for (idx, mod_info) in state.mod_list.mods.iter().enumerate() {
if !mod_info.valid {
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(ChallengesMenuEntry::Challenge(idx), MenuEntry::Active(mod_info.name.clone()));
if mutate_selection {
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(ChallengesMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.challenges_menu.selected = selected;
self.confirm_menu.push_entry(ConfirmMenuEntry::Title, MenuEntry::Disabled(String::new()));
self.confirm_menu.push_entry(
ConfirmMenuEntry::StartChallenge,
MenuEntry::Active(state.loc.t("menus.challenge_menu.start").to_owned()),
);
self.confirm_menu.push_entry(ConfirmMenuEntry::Replay(ReplayKind::Best), MenuEntry::Hidden);
self.confirm_menu.push_entry(ConfirmMenuEntry::Replay(ReplayKind::Last), MenuEntry::Hidden);
self.confirm_menu.push_entry(ConfirmMenuEntry::DeleteReplay, MenuEntry::Hidden);
self.confirm_menu.push_entry(ConfirmMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.confirm_menu.selected = ConfirmMenuEntry::StartChallenge;
self.controller.update(state, ctx)?;
self.controller.update_trigger();
self.nikumaru_rec.load_counter(state, ctx)?;
self.update_menu_cursor(state, ctx)?;
state.replay_state = ReplayState::None;
state.textscript_vm.flags.set_cutscene_skip(false);
state.difficulty = GameDifficulty::Normal;
#[cfg(feature = "discord-rpc")]
state.discord_rpc.set_idling()?;
Ok(())
}
fn tick(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
state.touch_controls.control_type = TouchControlType::None;
self.background.tick()?;
self.controller.update(state, ctx)?;
self.controller.update_trigger();
self.main_menu.update_width(state);
self.main_menu.update_height(state);
self.main_menu.x = ((state.canvas_size.0 - self.main_menu.width as f32) / 2.0).floor() as isize;
self.main_menu.y = ((state.canvas_size.1 + 70.0 - self.main_menu.height as f32) / 2.0).floor() as isize;
self.challenges_menu.update_width(state);
self.challenges_menu.update_height(state);
self.challenges_menu.x = ((state.canvas_size.0 - self.challenges_menu.width as f32) / 2.0).floor() as isize;
self.challenges_menu.y =
((state.canvas_size.1 + 30.0 - self.challenges_menu.height as f32) / 2.0).floor() as isize;
match self.current_menu {
CurrentMenu::MainMenu => match self.main_menu.tick(&mut self.controller, state) {
MenuSelectionResult::Selected(MainMenuEntry::Start, _) => {
state.mod_path = None;
self.save_select_menu.init(state, ctx)?;
self.save_select_menu.set_skip_difficulty_menu(!state.constants.has_difficulty_menu);
self.current_menu = CurrentMenu::SaveSelectMenu;
}
MenuSelectionResult::Selected(MainMenuEntry::Challenges, _) => {
self.current_menu = CurrentMenu::ChallengesMenu;
}
MenuSelectionResult::Selected(MainMenuEntry::Options, _) => {
self.current_menu = CurrentMenu::OptionMenu;
}
MenuSelectionResult::Selected(MainMenuEntry::Editor, _) => {
// this comment is just there because rustfmt removes parenthesis around the match case and breaks compilation
#[cfg(feature = "editor")]
{
use crate::scene::editor_scene::EditorScene;
state.next_scene = Some(Box::new(EditorScene::new()));
}
}
MenuSelectionResult::Selected(MainMenuEntry::Jukebox, _) => {
state.next_scene = Some(Box::new(JukeboxScene::new()));
}
MenuSelectionResult::Selected(MainMenuEntry::Quit, _) => {
state.shutdown();
}
_ => {}
},
CurrentMenu::OptionMenu => {
let timing_mode = state.settings.timing_mode;
let cm = &mut self.current_menu;
self.settings_menu.tick(
&mut || {
*cm = CurrentMenu::MainMenu;
},
&mut self.controller,
state,
ctx,
)?;
if timing_mode != state.settings.timing_mode {
self.update_menu_cursor(state, ctx)?;
}
}
CurrentMenu::SaveSelectMenu => {
let cm = &mut self.current_menu;
let rm = if state.mod_path.is_none() { CurrentMenu::MainMenu } else { CurrentMenu::ChallengesMenu };
self.save_select_menu.tick(
&mut || {
*cm = rm;
},
&mut self.controller,
state,
ctx,
)?;
}
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::SaveSelectMenu;
} else {
let mod_name = mod_info.name.clone();
self.confirm_menu.width =
(state.font.builder().compute_width(&mod_name).max(50.0) + 32.0) as u16;
self.confirm_menu.set_entry(ConfirmMenuEntry::Title, MenuEntry::Disabled(mod_name));
if state.has_replay_data(ctx, ReplayKind::Best) {
self.confirm_menu.set_entry(
ConfirmMenuEntry::Replay(ReplayKind::Best),
MenuEntry::Active(state.loc.t("menus.challenge_menu.replay_best").to_owned()),
);
self.confirm_menu.set_entry(
ConfirmMenuEntry::DeleteReplay,
MenuEntry::Active(state.loc.t("menus.challenge_menu.delete_replay").to_owned()),
);
} else {
self.confirm_menu
.set_entry(ConfirmMenuEntry::Replay(ReplayKind::Best), MenuEntry::Hidden);
self.confirm_menu.set_entry(ConfirmMenuEntry::DeleteReplay, MenuEntry::Hidden);
}
if state.has_replay_data(ctx, ReplayKind::Last) {
self.confirm_menu.set_entry(
ConfirmMenuEntry::Replay(ReplayKind::Last),
MenuEntry::Active(state.loc.t("menus.challenge_menu.replay_last").to_owned()),
);
} else {
self.confirm_menu
.set_entry(ConfirmMenuEntry::Replay(ReplayKind::Last), MenuEntry::Hidden);
}
self.nikumaru_rec.load_counter(state, ctx)?;
self.current_menu = CurrentMenu::ChallengeConfirmMenu;
}
}
}
MenuSelectionResult::Selected(ChallengesMenuEntry::Back, _) | MenuSelectionResult::Canceled => {
state.mod_path = None;
self.nikumaru_rec.load_counter(state, ctx)?;
self.current_menu = CurrentMenu::MainMenu;
}
_ => (),
},
CurrentMenu::ChallengeConfirmMenu => match self.confirm_menu.tick(&mut self.controller, state) {
MenuSelectionResult::Selected(ConfirmMenuEntry::StartChallenge, _) => {
state.difficulty = GameDifficulty::Normal;
state.replay_state = ReplayState::Recording;
self.current_menu = CurrentMenu::PlayerCountMenu;
}
MenuSelectionResult::Selected(ConfirmMenuEntry::Replay(kind), _) => {
state.difficulty = GameDifficulty::Normal;
state.replay_state = ReplayState::Playback(kind);
state.reload_resources(ctx)?;
state.start_new_game(ctx)?;
}
MenuSelectionResult::Selected(ConfirmMenuEntry::DeleteReplay, _) => {
state.delete_replay_data(ctx, ReplayKind::Best)?;
self.current_menu = CurrentMenu::ChallengesMenu;
}
MenuSelectionResult::Selected(ConfirmMenuEntry::Back, _) | MenuSelectionResult::Canceled => {
self.current_menu = CurrentMenu::ChallengesMenu;
}
_ => (),
},
CurrentMenu::PlayerCountMenu => {
let cm = &mut self.current_menu;
let rm = CurrentMenu::ChallengeConfirmMenu;
self.coop_menu.tick(
&mut || {
*cm = rm;
},
&mut self.controller,
state,
ctx,
)?;
}
}
self.confirm_menu.update_width(state);
self.confirm_menu.update_height(state);
self.confirm_menu.x = ((state.canvas_size.0 - self.confirm_menu.width as f32) / 2.0).floor() as isize;
self.confirm_menu.y = ((state.canvas_size.1 + 30.0 - self.confirm_menu.height as f32) / 2.0).floor() as isize;
self.tick += 1;
Ok(())
}
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
self.background.draw(state, ctx, &self.frame, &self.textures, &self.stage)?;
if self.current_menu == CurrentMenu::MainMenu {
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "Title")?;
let logo_x_offset = if state.settings.original_textures && state.constants.supports_og_textures {
20.0
} else {
0.0
};
batch.add_rect(
((state.canvas_size.0 - state.constants.title.logo_rect.width() as f32) / 2.0).floor() + logo_x_offset,
40.0,
&state.constants.title.logo_rect,
);
batch.add_rect(
((state.canvas_size.0 - state.constants.title.logo_splash_rect.width() as f32) / 2.0).floor() + 72.0,
88.0,
&state.constants.title.logo_splash_rect,
);
batch.draw(ctx)?;
} else {
let window_title = match self.current_menu {
CurrentMenu::ChallengesMenu => state.loc.t("menus.main_menu.challenges"),
CurrentMenu::ChallengeConfirmMenu | CurrentMenu::SaveSelectMenu => state.loc.t("menus.main_menu.start"),
CurrentMenu::OptionMenu => state.loc.t("menus.main_menu.options"),
CurrentMenu::MainMenu => unreachable!(),
CurrentMenu::PlayerCountMenu => state.loc.t("menus.main_menu.start"),
};
state
.font
.builder()
.shadow(true)
.position(0.0, state.font.line_height())
.center(state.canvas_size.0)
.draw(&window_title, ctx, &state.constants, &mut state.texture_set)?;
}
if self.current_menu == CurrentMenu::MainMenu {
self.draw_text_centered(&VERSION_BANNER, state.canvas_size.1 - 15.0, state, ctx)?;
if state.constants.is_cs_plus {
self.draw_text_centered(COPYRIGHT_NICALIS, state.canvas_size.1 - 30.0, state, ctx)?;
} else {
self.draw_text_centered(COPYRIGHT_PIXEL, state.canvas_size.1 - 30.0, state, ctx)?;
}
}
self.nikumaru_rec.draw(state, ctx, &self.frame)?;
match self.current_menu {
CurrentMenu::MainMenu => self.main_menu.draw(state, ctx)?,
CurrentMenu::ChallengesMenu => self.challenges_menu.draw(state, ctx)?,
CurrentMenu::ChallengeConfirmMenu => self.confirm_menu.draw(state, ctx)?,
CurrentMenu::OptionMenu => self.settings_menu.draw(state, ctx)?,
CurrentMenu::SaveSelectMenu => self.save_select_menu.draw(state, ctx)?,
CurrentMenu::PlayerCountMenu => self.coop_menu.draw(state, ctx)?,
}
Ok(())
}
}