mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2024-11-26 23:33:01 +00:00
Added save select menu (#58)
This commit is contained in:
parent
2223358991
commit
693155ca6a
|
@ -1,17 +1,18 @@
|
|||
use std::cell::Cell;
|
||||
|
||||
use crate::common::{Color, Rect};
|
||||
use crate::components::draw_common::{draw_number, Alignment};
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics;
|
||||
use crate::input::combined_menu_controller::CombinedMenuController;
|
||||
use crate::menu::save_select_menu::MenuSaveInfo;
|
||||
use crate::shared_game_state::{MenuCharacter, SharedGameState};
|
||||
|
||||
pub mod pause_menu;
|
||||
pub mod save_select_menu;
|
||||
pub mod settings_menu;
|
||||
|
||||
pub struct MenuSaveInfo {}
|
||||
|
||||
pub enum MenuEntry {
|
||||
Hidden,
|
||||
Active(String),
|
||||
|
@ -402,6 +403,55 @@ impl Menu {
|
|||
graphics::draw_rect(ctx, bar_rect, Color::new(1.0, 1.0, 1.0, 1.0))?;
|
||||
}
|
||||
}
|
||||
MenuEntry::NewSave => {
|
||||
state.font.draw_text(
|
||||
"New Save".chars(),
|
||||
self.x as f32 + 20.0,
|
||||
y,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
MenuEntry::SaveData(save) => {
|
||||
let name = &state.stages[save.current_map as usize].name;
|
||||
let bar_width = (save.life as f32 / save.max_life as f32 * 39.0) as u16;
|
||||
let right_edge = self.x as f32 + self.width as f32 - 4.0;
|
||||
|
||||
// Lifebar
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "TextBox")?;
|
||||
|
||||
batch.add_rect(right_edge - 60.0, y, &Rect::new_size(0, 40, 24, 8));
|
||||
batch.add_rect(right_edge - 36.0, y, &Rect::new_size(24, 40, 40, 8));
|
||||
batch.add_rect(right_edge - 36.0, y, &Rect::new_size(0, 24, bar_width, 8));
|
||||
|
||||
state.font.draw_text(
|
||||
name.chars(),
|
||||
self.x as f32 + 20.0,
|
||||
y,
|
||||
&state.constants,
|
||||
&mut state.texture_set,
|
||||
ctx,
|
||||
)?;
|
||||
|
||||
// Weapons
|
||||
let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ArmsImage")?;
|
||||
|
||||
for weapon_slot in 0..save.weapon_count {
|
||||
let wtype = save.weapon_id[weapon_slot];
|
||||
let pos_x = weapon_slot as f32 * 16.0 - (16 * save.weapon_count.saturating_sub(4)) as f32;
|
||||
let mut rect = Rect::new(0, 0, 0, 16);
|
||||
if wtype != 0 {
|
||||
rect.left = wtype as u16 * 16;
|
||||
rect.right = rect.left + 16;
|
||||
batch.add_rect(right_edge + pos_x - 60.0, y + 8.0, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
batch.draw(ctx)?;
|
||||
|
||||
draw_number(right_edge - 36.0, y, save.life as usize, Alignment::Right, state, ctx)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -453,6 +503,8 @@ impl Menu {
|
|||
let mut y = self.y as f32 + 8.0;
|
||||
for (idx, entry) in self.entries.iter_mut().enumerate() {
|
||||
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);
|
||||
y += entry.height() as f32;
|
||||
|
||||
match entry {
|
||||
|
@ -460,6 +512,8 @@ impl Menu {
|
|||
| MenuEntry::Toggle(_, _)
|
||||
| MenuEntry::Options(_, _, _)
|
||||
| MenuEntry::DescriptiveOptions(_, _, _, _)
|
||||
| MenuEntry::SaveData(_)
|
||||
| MenuEntry::NewSave
|
||||
if (self.selected == idx && controller.trigger_ok())
|
||||
|| state.touch_controls.consume_click_in(entry_bounds) =>
|
||||
{
|
||||
|
@ -478,11 +532,16 @@ impl Menu {
|
|||
state.sound_manager.play_sfx(1);
|
||||
return MenuSelectionResult::Right(self.selected, entry, 1);
|
||||
}
|
||||
MenuEntry::DescriptiveOptions(_, _, _, _) if self.selected == idx && controller.trigger_left() => {
|
||||
MenuEntry::DescriptiveOptions(_, _, _, _)
|
||||
if (self.selected == idx && controller.trigger_left())
|
||||
|| state.touch_controls.consume_click_in(right_entry_bounds) =>
|
||||
{
|
||||
state.sound_manager.play_sfx(1);
|
||||
return MenuSelectionResult::Left(self.selected, entry, -1);
|
||||
}
|
||||
MenuEntry::DescriptiveOptions(_, _, _, _) if self.selected == idx && controller.trigger_right() => {
|
||||
MenuEntry::DescriptiveOptions(_, _, _, _) | MenuEntry::SaveData(_)
|
||||
if self.selected == idx && controller.trigger_right() =>
|
||||
{
|
||||
state.sound_manager.play_sfx(1);
|
||||
return MenuSelectionResult::Right(self.selected, entry, 1);
|
||||
}
|
||||
|
|
144
src/menu/save_select_menu.rs
Normal file
144
src/menu/save_select_menu.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem;
|
||||
use crate::input::combined_menu_controller::CombinedMenuController;
|
||||
use crate::menu::MenuEntry;
|
||||
use crate::menu::{Menu, MenuSelectionResult};
|
||||
use crate::profile::GameProfile;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MenuSaveInfo {
|
||||
pub current_map: u32,
|
||||
pub max_life: u16,
|
||||
pub life: u16,
|
||||
pub weapon_count: usize,
|
||||
pub weapon_id: [u32; 8],
|
||||
}
|
||||
|
||||
impl Default for MenuSaveInfo {
|
||||
fn default() -> Self {
|
||||
MenuSaveInfo { current_map: 0, max_life: 0, life: 0, weapon_count: 0, weapon_id: [0; 8] }
|
||||
}
|
||||
}
|
||||
pub enum CurrentMenu {
|
||||
SaveMenu,
|
||||
DeleteConfirm,
|
||||
}
|
||||
pub struct SaveSelectMenu {
|
||||
pub saves: [MenuSaveInfo; 3],
|
||||
current_menu: CurrentMenu,
|
||||
save_menu: Menu,
|
||||
delete_confirm: Menu,
|
||||
}
|
||||
|
||||
impl SaveSelectMenu {
|
||||
pub fn new() -> SaveSelectMenu {
|
||||
SaveSelectMenu {
|
||||
saves: [MenuSaveInfo::default(); 3],
|
||||
current_menu: CurrentMenu::SaveMenu,
|
||||
save_menu: Menu::new(0, 0, 200, 0),
|
||||
delete_confirm: Menu::new(0, 0, 75, 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self, state: &mut SharedGameState, ctx: &Context) -> GameResult {
|
||||
for (iter, save) in self.saves.iter_mut().enumerate() {
|
||||
if let Ok(data) = filesystem::user_open(ctx, state.get_save_filename(iter + 1)) {
|
||||
let loaded_save = GameProfile::load_from_save(data)?;
|
||||
|
||||
save.current_map = loaded_save.current_map;
|
||||
save.max_life = loaded_save.max_life;
|
||||
save.life = loaded_save.life;
|
||||
save.weapon_count = loaded_save.weapon_data.iter().filter(|weapon| weapon.weapon_id != 0).count();
|
||||
save.weapon_id = loaded_save.weapon_data.map(|weapon| weapon.weapon_id);
|
||||
|
||||
self.save_menu.push_entry(MenuEntry::SaveData(*save));
|
||||
} else {
|
||||
self.save_menu.push_entry(MenuEntry::NewSave);
|
||||
}
|
||||
}
|
||||
|
||||
self.save_menu.push_entry(MenuEntry::Active("< Back".to_owned()));
|
||||
self.save_menu.push_entry(MenuEntry::Disabled("Press Right to Delete".to_owned()));
|
||||
|
||||
self.delete_confirm.push_entry(MenuEntry::Disabled("Delete?".to_owned()));
|
||||
self.delete_confirm.push_entry(MenuEntry::Active("Yes".to_owned()));
|
||||
self.delete_confirm.push_entry(MenuEntry::Active("No".to_owned()));
|
||||
|
||||
self.delete_confirm.selected = 2;
|
||||
|
||||
self.update_sizes(state);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_sizes(&mut self, state: &SharedGameState) {
|
||||
self.save_menu.update_height();
|
||||
self.save_menu.x = ((state.canvas_size.0 - self.save_menu.width as f32) / 2.0).floor() as isize;
|
||||
self.save_menu.y = 30 + ((state.canvas_size.1 - self.save_menu.height as f32) / 2.0).floor() as isize;
|
||||
self.delete_confirm.update_height();
|
||||
self.delete_confirm.x = ((state.canvas_size.0 - self.delete_confirm.width as f32) / 2.0).floor() as isize;
|
||||
self.delete_confirm.y = 30 + ((state.canvas_size.1 - self.delete_confirm.height as f32) / 2.0).floor() as isize
|
||||
}
|
||||
|
||||
pub fn tick(
|
||||
&mut self,
|
||||
exit_action: &mut dyn FnMut(),
|
||||
controller: &mut CombinedMenuController,
|
||||
state: &mut SharedGameState,
|
||||
ctx: &mut Context,
|
||||
) -> GameResult {
|
||||
self.update_sizes(state);
|
||||
match self.current_menu {
|
||||
CurrentMenu::SaveMenu => match self.save_menu.tick(controller, state) {
|
||||
MenuSelectionResult::Selected(0, _) => {
|
||||
state.save_slot = 1;
|
||||
state.load_or_start_game(ctx)?;
|
||||
}
|
||||
MenuSelectionResult::Selected(1, _) => {
|
||||
state.save_slot = 2;
|
||||
state.load_or_start_game(ctx)?;
|
||||
}
|
||||
MenuSelectionResult::Selected(2, _) => {
|
||||
state.save_slot = 3;
|
||||
state.load_or_start_game(ctx)?;
|
||||
}
|
||||
MenuSelectionResult::Selected(3, _) | MenuSelectionResult::Canceled => exit_action(),
|
||||
MenuSelectionResult::Right(slot, _, _) => {
|
||||
if slot <= 2 {
|
||||
self.current_menu = CurrentMenu::DeleteConfirm;
|
||||
self.delete_confirm.selected = 2;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
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))?;
|
||||
self.save_menu.entries[self.save_menu.selected] = MenuEntry::NewSave;
|
||||
self.current_menu = CurrentMenu::SaveMenu;
|
||||
}
|
||||
MenuSelectionResult::Selected(2, _) | MenuSelectionResult::Canceled => {
|
||||
self.current_menu = CurrentMenu::SaveMenu;
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||
match self.current_menu {
|
||||
CurrentMenu::SaveMenu => {
|
||||
self.save_menu.draw(state, ctx)?;
|
||||
}
|
||||
CurrentMenu::DeleteConfirm => {
|
||||
self.delete_confirm.draw(state, ctx)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ use crate::framework::graphics;
|
|||
use crate::input::combined_menu_controller::CombinedMenuController;
|
||||
use crate::input::touch_controls::TouchControlType;
|
||||
use crate::map::Map;
|
||||
use crate::menu::save_select_menu::SaveSelectMenu;
|
||||
use crate::menu::settings_menu::SettingsMenu;
|
||||
use crate::menu::{Menu, MenuEntry, MenuSelectionResult};
|
||||
use crate::scene::Scene;
|
||||
|
@ -33,7 +34,7 @@ pub struct TitleScene {
|
|||
current_menu: CurrentMenu,
|
||||
main_menu: Menu,
|
||||
option_menu: SettingsMenu,
|
||||
save_select_menu: Menu,
|
||||
save_select_menu: SaveSelectMenu,
|
||||
background: Background,
|
||||
frame: Frame,
|
||||
nikumaru_rec: NikumaruCounter,
|
||||
|
@ -67,7 +68,7 @@ impl TitleScene {
|
|||
current_menu: CurrentMenu::MainMenu,
|
||||
main_menu: Menu::new(0, 0, 100, 0),
|
||||
option_menu: SettingsMenu::new(),
|
||||
save_select_menu: Menu::new(0, 0, 200, 0),
|
||||
save_select_menu: SaveSelectMenu::new(),
|
||||
background: Background::new(),
|
||||
frame: Frame::new(),
|
||||
nikumaru_rec: NikumaruCounter::new(),
|
||||
|
@ -126,8 +127,7 @@ 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("New game".to_string()));
|
||||
self.main_menu.push_entry(MenuEntry::Active("Load game".to_string()));
|
||||
self.main_menu.push_entry(MenuEntry::Active("Start Game".to_string()));
|
||||
self.main_menu.push_entry(MenuEntry::Active("Options".to_string()));
|
||||
if cfg!(feature = "editor") {
|
||||
self.main_menu.push_entry(MenuEntry::Active("Editor".to_string()));
|
||||
|
@ -138,11 +138,7 @@ impl Scene for TitleScene {
|
|||
|
||||
self.option_menu.init(state, ctx)?;
|
||||
|
||||
self.save_select_menu.push_entry(MenuEntry::NewSave);
|
||||
self.save_select_menu.push_entry(MenuEntry::NewSave);
|
||||
self.save_select_menu.push_entry(MenuEntry::NewSave);
|
||||
self.save_select_menu.push_entry(MenuEntry::Active("Delete a save".to_string()));
|
||||
self.save_select_menu.push_entry(MenuEntry::Active("Back".to_string()));
|
||||
self.save_select_menu.init(state, ctx)?;
|
||||
|
||||
self.controller.update(state, ctx)?;
|
||||
self.controller.update_trigger();
|
||||
|
@ -166,20 +162,12 @@ impl Scene for TitleScene {
|
|||
match self.current_menu {
|
||||
CurrentMenu::MainMenu => match self.main_menu.tick(&mut self.controller, state) {
|
||||
MenuSelectionResult::Selected(0, _) => {
|
||||
state.reset();
|
||||
state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?;
|
||||
self.tick = 1;
|
||||
self.current_menu = CurrentMenu::StartGame;
|
||||
self.current_menu = CurrentMenu::SaveSelectMenu;
|
||||
}
|
||||
MenuSelectionResult::Selected(1, _) => {
|
||||
state.sound_manager.play_song(0, &state.constants, &state.settings, ctx)?;
|
||||
self.tick = 1;
|
||||
self.current_menu = CurrentMenu::LoadGame;
|
||||
}
|
||||
MenuSelectionResult::Selected(2, _) => {
|
||||
self.current_menu = CurrentMenu::OptionMenu;
|
||||
}
|
||||
MenuSelectionResult::Selected(3, _) => {
|
||||
MenuSelectionResult::Selected(2, _) => {
|
||||
// this comment is just there because rustfmt removes parenthesis around the match case and breaks compilation
|
||||
#[cfg(feature = "editor")]
|
||||
{
|
||||
|
@ -187,7 +175,7 @@ impl Scene for TitleScene {
|
|||
state.next_scene = Some(Box::new(EditorScene::new()));
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(4, _) => {
|
||||
MenuSelectionResult::Selected(3, _) => {
|
||||
state.shutdown();
|
||||
}
|
||||
_ => {}
|
||||
|
@ -207,6 +195,17 @@ impl Scene for TitleScene {
|
|||
self.update_menu_cursor(state, ctx)?;
|
||||
}
|
||||
}
|
||||
CurrentMenu::SaveSelectMenu => {
|
||||
let cm = &mut self.current_menu;
|
||||
self.save_select_menu.tick(
|
||||
&mut || {
|
||||
*cm = CurrentMenu::MainMenu;
|
||||
},
|
||||
&mut self.controller,
|
||||
state,
|
||||
ctx,
|
||||
)?;
|
||||
}
|
||||
CurrentMenu::StartGame => {
|
||||
if self.tick == 10 {
|
||||
state.reset_skip_flags();
|
||||
|
@ -253,6 +252,7 @@ impl Scene for TitleScene {
|
|||
match self.current_menu {
|
||||
CurrentMenu::MainMenu => self.main_menu.draw(state, ctx)?,
|
||||
CurrentMenu::OptionMenu => self.option_menu.draw(state, ctx)?,
|
||||
CurrentMenu::SaveSelectMenu => self.save_select_menu.draw(state, ctx)?,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
|
@ -184,6 +184,7 @@ pub struct SharedGameState {
|
|||
pub lua: LuaScriptingState,
|
||||
pub sound_manager: SoundManager,
|
||||
pub settings: Settings,
|
||||
pub save_slot: usize,
|
||||
pub shutdown: bool,
|
||||
}
|
||||
|
||||
|
@ -293,6 +294,7 @@ impl SharedGameState {
|
|||
lua: LuaScriptingState::new(),
|
||||
sound_manager,
|
||||
settings,
|
||||
save_slot: 1,
|
||||
shutdown: false,
|
||||
})
|
||||
}
|
||||
|
@ -384,7 +386,11 @@ impl SharedGameState {
|
|||
}
|
||||
|
||||
pub fn save_game(&mut self, game_scene: &mut GameScene, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(data) = filesystem::open_options(ctx, "/Profile.dat", OpenOptions::new().write(true).create(true)) {
|
||||
if let Ok(data) = filesystem::open_options(
|
||||
ctx,
|
||||
self.get_save_filename(self.save_slot),
|
||||
OpenOptions::new().write(true).create(true),
|
||||
) {
|
||||
let profile = GameProfile::dump(self, game_scene);
|
||||
profile.write_save(data)?;
|
||||
} else {
|
||||
|
@ -395,7 +401,7 @@ impl SharedGameState {
|
|||
}
|
||||
|
||||
pub fn load_or_start_game(&mut self, ctx: &mut Context) -> GameResult {
|
||||
if let Ok(data) = filesystem::user_open(ctx, "/Profile.dat") {
|
||||
if let Ok(data) = filesystem::user_open(ctx, self.get_save_filename(self.save_slot)) {
|
||||
match GameProfile::load_from_save(data) {
|
||||
Ok(profile) => {
|
||||
self.reset();
|
||||
|
@ -529,4 +535,12 @@ impl SharedGameState {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_save_filename(&self, slot: usize) -> String {
|
||||
if slot == 1 {
|
||||
"/Profile.dat".to_owned()
|
||||
} else {
|
||||
format!("/Profile{}.dat", slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue