From 18a767024880f0600b645b383c95afef0ac79d8e Mon Sep 17 00:00:00 2001 From: IruzzArcana <89710975+IruzzArcana@users.noreply.github.com> Date: Wed, 22 Jun 2022 23:08:36 +0100 Subject: [PATCH] add coop menu (#141) --- src/builtin/locale/en.json | 10 +++ src/builtin/locale/jp.json | 9 +++ src/live_debugger.rs | 2 +- src/menu/coop_menu.rs | 120 +++++++++++++++++++++++++++++++++++ src/menu/mod.rs | 24 +++++++ src/menu/save_select_menu.rs | 43 +++++++++---- src/player/skin/basic.rs | 3 + src/player/skin/mod.rs | 3 + src/scene/game_scene.rs | 11 +++- src/scene/title_scene.rs | 25 +++++++- src/shared_game_state.rs | 10 +++ 11 files changed, 243 insertions(+), 17 deletions(-) create mode 100644 src/menu/coop_menu.rs diff --git a/src/builtin/locale/en.json b/src/builtin/locale/en.json index e7c8363..7368598 100644 --- a/src/builtin/locale/en.json +++ b/src/builtin/locale/en.json @@ -41,6 +41,16 @@ "hard": "Hard" }, + "coop_menu": { + "title": "Select Number of Players", + "one": "Single Player", + "two": "Two Players" + }, + "skin_menu": { + "title": "Select Player 2's appearance", + "label": "Appearance:" + }, + "challenge_menu": { "start": "Start", "no_replay": "No Replay", diff --git a/src/builtin/locale/jp.json b/src/builtin/locale/jp.json index 409bb9c..2779d25 100644 --- a/src/builtin/locale/jp.json +++ b/src/builtin/locale/jp.json @@ -36,6 +36,15 @@ "normal": "普通", "hard": "難しい" }, + "coop_menu": { + "title": "プレイヤー数を選択", + "one":"1人プレイ", + "two": "2人プレイ" + }, + "skin_menu": { + "title": "プレーヤー2の外観を選択します", + "label": "外観:" + }, "challenge_menu": { "start": "スタート", "no_replay": "ノーリプレイ", diff --git a/src/live_debugger.rs b/src/live_debugger.rs index 81cc693..5d8c916 100644 --- a/src/live_debugger.rs +++ b/src/live_debugger.rs @@ -142,7 +142,7 @@ impl LiveDebugger { game_scene.drop_player2(); } } else if ui.button("Add Player 2") { - game_scene.add_player2(); + game_scene.add_player2(state); } ui.same_line(); diff --git a/src/menu/coop_menu.rs b/src/menu/coop_menu.rs new file mode 100644 index 0000000..f57f0b9 --- /dev/null +++ b/src/menu/coop_menu.rs @@ -0,0 +1,120 @@ +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::{PlayerCount, SharedGameState}; + +pub enum CurrentMenu { + CoopMenu, + PlayerSkin, +} + +pub struct PlayerCountMenu { + current_menu: CurrentMenu, + coop_menu: Menu, + skin_menu: Menu, +} + +impl PlayerCountMenu { + pub fn new() -> PlayerCountMenu { + PlayerCountMenu { + coop_menu: Menu::new(0, 0, 130, 0), + skin_menu: Menu::new(0, 0, 130, 0), + current_menu: CurrentMenu::CoopMenu, + } + } + pub fn init(&mut self, state: &mut SharedGameState, ctx: &Context) -> GameResult { + 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.selected = 1; + + 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.update_sizes(state); + + Ok(()) + } + + fn update_sizes(&mut self, state: &SharedGameState) { + self.coop_menu.update_width(state); + self.coop_menu.update_height(); + self.coop_menu.x = ((state.canvas_size.0 - self.coop_menu.width as f32) / 2.3).floor() as isize; + self.coop_menu.y = + 30 + ((state.canvas_size.1 - self.coop_menu.height as f32) / 2.0).floor() as isize; + + self.skin_menu.update_width(state); + self.skin_menu.update_height(); + self.skin_menu.x = ((state.canvas_size.0 - self.coop_menu.width as f32) / 2.3).floor() as isize; + self.skin_menu.y = + 30 + ((state.canvas_size.1 - self.coop_menu.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::CoopMenu => match self.coop_menu.tick(controller, state) { + MenuSelectionResult::Selected(3, _) | MenuSelectionResult::Canceled => exit_action(), + MenuSelectionResult::Selected(1, _) => { + state.player_count = PlayerCount::One; + state.reload_resources(ctx)?; + state.load_or_start_game(ctx)?; + } + MenuSelectionResult::Selected(2, _) => { + if state.constants.is_cs_plus { + self.current_menu = CurrentMenu::PlayerSkin; + } else { + state.player_count = PlayerCount::Two; + state.reload_resources(ctx)?; + state.load_or_start_game(ctx)?; + } + } + _ => (), + }, + CurrentMenu::PlayerSkin => match self.skin_menu.tick(controller, state) { + MenuSelectionResult::Selected(3, _) | MenuSelectionResult::Canceled => { + self.current_menu = CurrentMenu::CoopMenu; + } + MenuSelectionResult::Selected(1, _) =>{ + state.player2_skin += 2; + } + MenuSelectionResult::Selected(2, _) =>{ + state.player_count = PlayerCount::Two; + state.reload_resources(ctx)?; + state.load_or_start_game(ctx)?; + } + _ => (), + }, + } + Ok(()) + } + pub fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult { + match self.current_menu { + CurrentMenu::CoopMenu => { + self.coop_menu.draw(state, ctx)?; + } + CurrentMenu::PlayerSkin => { + self.skin_menu.draw(state, ctx)?; + } + } + Ok(()) + } +} \ No newline at end of file diff --git a/src/menu/mod.rs b/src/menu/mod.rs index ec997ae..e6c53f3 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -12,6 +12,7 @@ use crate::shared_game_state::{GameDifficulty, MenuCharacter, SharedGameState}; pub mod pause_menu; pub mod save_select_menu; pub mod settings_menu; +pub mod coop_menu; #[allow(dead_code)] #[derive(Clone)] @@ -27,6 +28,7 @@ pub enum MenuEntry { SaveData(MenuSaveInfo), SaveDataSingle(MenuSaveInfo), NewSave, + PlayerSkin, } impl MenuEntry { @@ -43,6 +45,7 @@ impl MenuEntry { MenuEntry::SaveData(_) => 32.0, MenuEntry::SaveDataSingle(_) => 32.0, MenuEntry::NewSave => 32.0, + MenuEntry::PlayerSkin => 24.0, } } @@ -59,6 +62,7 @@ impl MenuEntry { MenuEntry::SaveData(_) => true, MenuEntry::SaveDataSingle(_) => true, MenuEntry::NewSave => true, + MenuEntry::PlayerSkin=> true, } } } @@ -161,6 +165,7 @@ impl Menu { MenuEntry::SaveData(_) => {} MenuEntry::SaveDataSingle(_) => {} MenuEntry::NewSave => {} + MenuEntry::PlayerSkin => {} } } @@ -491,6 +496,24 @@ impl Menu { ctx, )?; } + MenuEntry::PlayerSkin => { + state.font.draw_text( + state.t("menus.skin_menu.label").chars(), + self.x as f32 + 20.0, + y, + &state.constants, + &mut state.texture_set, + ctx, + )?; + + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "MyChar")?; + batch.add_rect( + self.x as f32 + 88.0, + y - 4.0, + &Rect::new_size(0, (state.player2_skin).saturating_mul(2 * 16), 16, 16), + ); + batch.draw(ctx)?; + } MenuEntry::SaveData(save) | MenuEntry::SaveDataSingle(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; @@ -621,6 +644,7 @@ impl Menu { | MenuEntry::DescriptiveOptions(_, _, _, _) | MenuEntry::SaveData(_) | MenuEntry::NewSave + | MenuEntry::PlayerSkin if (self.selected == idx && controller.trigger_ok()) || state.touch_controls.consume_click_in(entry_bounds) => { diff --git a/src/menu/save_select_menu.rs b/src/menu/save_select_menu.rs index 40c11ba..42eb0a0 100644 --- a/src/menu/save_select_menu.rs +++ b/src/menu/save_select_menu.rs @@ -3,9 +3,10 @@ use crate::framework::error::GameResult; use crate::framework::filesystem; use crate::input::combined_menu_controller::CombinedMenuController; use crate::menu::MenuEntry; +use crate::menu::coop_menu::PlayerCountMenu; use crate::menu::{Menu, MenuSelectionResult}; use crate::profile::GameProfile; -use crate::shared_game_state::{GameDifficulty, SharedGameState}; +use crate::shared_game_state::{GameDifficulty, PlayerCount, SharedGameState}; #[derive(Clone, Copy)] pub struct MenuSaveInfo { @@ -22,9 +23,13 @@ impl Default for MenuSaveInfo { MenuSaveInfo { current_map: 0, max_life: 0, life: 0, weapon_count: 0, weapon_id: [0; 8], difficulty: 0 } } } +#[derive(PartialEq, Eq, Copy, Clone)] +#[repr(u8)] +#[allow(unused)] pub enum CurrentMenu { SaveMenu, DifficultyMenu, + PlayerCountMenu, DeleteConfirm, LoadConfirm, } @@ -34,6 +39,7 @@ pub struct SaveSelectMenu { save_menu: Menu, save_detailed: Menu, difficulty_menu: Menu, + coop_menu: PlayerCountMenu, delete_confirm: Menu, load_confirm: Menu, skip_difficulty_menu: bool, @@ -45,6 +51,7 @@ impl SaveSelectMenu { saves: [MenuSaveInfo::default(); 3], current_menu: CurrentMenu::SaveMenu, save_menu: Menu::new(0, 0, 230, 0), + coop_menu: PlayerCountMenu::new(), save_detailed: Menu::new(0, 0, 230, 0), difficulty_menu: Menu::new(0, 0, 130, 0), delete_confirm: Menu::new(0, 0, 75, 0), @@ -56,6 +63,7 @@ impl SaveSelectMenu { pub fn init(&mut self, state: &mut SharedGameState, ctx: &Context) -> GameResult { self.save_menu = Menu::new(0, 0, 230, 0); self.save_detailed = Menu::new(0, 0, 230, 0); + self.coop_menu.init(state, ctx)?; self.difficulty_menu = Menu::new(0, 0, 130, 0); self.delete_confirm = Menu::new(0, 0, 75, 0); self.load_confirm = Menu::new(0, 0, 75, 0); @@ -88,6 +96,8 @@ impl SaveSelectMenu { self.difficulty_menu.selected = 2; + //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"))); @@ -99,6 +109,7 @@ impl SaveSelectMenu { self.load_confirm.push_entry(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)); } @@ -164,8 +175,7 @@ impl SaveSelectMenu { self.current_menu = CurrentMenu::LoadConfirm; self.load_confirm.selected = 0; } else if self.skip_difficulty_menu { - state.reload_resources(ctx)?; - state.load_or_start_game(ctx)?; + self.current_menu = CurrentMenu::PlayerCountMenu; } else { self.difficulty_menu.selected = 2; self.current_menu = CurrentMenu::DifficultyMenu; @@ -179,21 +189,30 @@ impl SaveSelectMenu { } MenuSelectionResult::Selected(1, _) => { state.difficulty = GameDifficulty::Easy; - state.reload_resources(ctx)?; - state.load_or_start_game(ctx)?; + self.current_menu = CurrentMenu::PlayerCountMenu; } MenuSelectionResult::Selected(2, _) => { state.difficulty = GameDifficulty::Normal; - state.reload_resources(ctx)?; - state.load_or_start_game(ctx)?; + self.current_menu = CurrentMenu::PlayerCountMenu; } MenuSelectionResult::Selected(3, _) => { state.difficulty = GameDifficulty::Hard; - state.reload_resources(ctx)?; - state.load_or_start_game(ctx)?; + self.current_menu = CurrentMenu::PlayerCountMenu; } _ => (), }, + CurrentMenu::PlayerCountMenu => { + let cm = &mut self.current_menu; + let rm = CurrentMenu::SaveMenu; + self.coop_menu.tick( + &mut || { + *cm = rm; + }, + controller, + state, + ctx, + )?; + }, CurrentMenu::DeleteConfirm => match self.delete_confirm.tick(controller, state) { MenuSelectionResult::Selected(1, _) => { state.sound_manager.play_sfx(17); // Player Death sfx @@ -212,8 +231,7 @@ impl SaveSelectMenu { }, CurrentMenu::LoadConfirm => match self.load_confirm.tick(controller, state) { MenuSelectionResult::Selected(0, _) => { - state.reload_resources(ctx)?; - state.load_or_start_game(ctx)?; + self.current_menu = CurrentMenu::PlayerCountMenu; } MenuSelectionResult::Selected(1, _) => { self.current_menu = CurrentMenu::DeleteConfirm; @@ -237,6 +255,9 @@ impl SaveSelectMenu { CurrentMenu::DifficultyMenu => { self.difficulty_menu.draw(state, ctx)?; } + CurrentMenu::PlayerCountMenu => { + self.coop_menu.draw(state, ctx)?; + } CurrentMenu::DeleteConfirm => { self.save_detailed.draw(state, ctx)?; self.delete_confirm.draw(state, ctx)?; diff --git a/src/player/skin/basic.rs b/src/player/skin/basic.rs index df892fb..7f9014d 100644 --- a/src/player/skin/basic.rs +++ b/src/player/skin/basic.rs @@ -258,4 +258,7 @@ impl PlayerSkin for BasicPlayerSkin { fn apply_gamestate(&mut self, state: &SharedGameState) { self.skinsheet_offset = state.get_skinsheet_offset(); } + fn set_skinsheet_offset(&mut self, offset: u16) { + self.skinsheet_offset = offset; + } } diff --git a/src/player/skin/mod.rs b/src/player/skin/mod.rs index 73e019b..9e85dfd 100644 --- a/src/player/skin/mod.rs +++ b/src/player/skin/mod.rs @@ -96,6 +96,9 @@ pub trait PlayerSkin: PlayerSkinClone { /// Applies modifications on the skin based on current state fn apply_gamestate(&mut self, state: &SharedGameState); + + /// Sets the spritesheet's offset + fn set_skinsheet_offset(&mut self, offset: u16); } pub trait PlayerSkinClone { diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 73e5382..e31b642 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -46,7 +46,7 @@ use crate::scene::title_scene::TitleScene; use crate::scene::Scene; use crate::scripting::tsc::credit_script::CreditScriptVM; use crate::scripting::tsc::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM}; -use crate::shared_game_state::{Language, ReplayState, SharedGameState, TileSize}; +use crate::shared_game_state::{Language, ReplayState, PlayerCount, SharedGameState, TileSize}; use crate::stage::{BackgroundType, Stage, StageTexturePaths}; use crate::texture_set::SpriteBatch; use crate::weapon::bullet::BulletManager; @@ -180,10 +180,10 @@ impl GameScene { pub fn display_map_name(&mut self, ticks: u16) { self.map_name_counter = ticks; } - - pub fn add_player2(&mut self) { + pub fn add_player2(&mut self, state: &mut SharedGameState) { self.player2.cond.set_alive(true); self.player2.cond.set_hidden(self.player1.cond.hidden()); + self.player2.skin.set_skinsheet_offset(state.player2_skin); self.player2.x = self.player1.x; self.player2.y = self.player1.y; self.player2.vel_x = self.player1.vel_x; @@ -1598,6 +1598,11 @@ impl Scene for GameScene { if state.mod_path.is_some() && state.replay_state == ReplayState::Recording { self.replay.initialize_recording(state); } + if state.player_count == PlayerCount::Two { + self.add_player2(state); + } else { + self.drop_player2(); + } if state.mod_path.is_some() && state.replay_state == ReplayState::Playback { self.replay.initialize_playback(state, ctx)?; diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index 2324462..fe7eea4 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -9,6 +9,7 @@ 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::coop_menu::PlayerCountMenu; use crate::menu::settings_menu::SettingsMenu; use crate::menu::{Menu, MenuEntry, MenuSelectionResult}; use crate::scene::jukebox_scene::JukeboxScene; @@ -25,6 +26,7 @@ enum CurrentMenu { SaveSelectMenu, ChallengesMenu, ChallengeConfirmMenu, + PlayerCountMenu, } pub struct TitleScene { @@ -35,6 +37,7 @@ pub struct TitleScene { save_select_menu: SaveSelectMenu, challenges_menu: Menu, confirm_menu: Menu, + coop_menu: PlayerCountMenu, settings_menu: SettingsMenu, background: Background, frame: Frame, @@ -75,6 +78,7 @@ impl TitleScene { 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(), @@ -171,6 +175,8 @@ impl Scene for TitleScene { self.save_select_menu.init(state, ctx)?; + self.coop_menu.init(state, ctx)?; + let mut selected: usize = 0; let mut mutate_selection = true; @@ -319,12 +325,14 @@ impl Scene for TitleScene { self.nikumaru_rec.load_counter(state, ctx)?; self.current_menu = CurrentMenu::ChallengeConfirmMenu; } + state.reload_graphics(); } } MenuSelectionResult::Canceled => { state.mod_path = None; self.nikumaru_rec.load_counter(state, ctx)?; self.current_menu = CurrentMenu::MainMenu; + state.reload_graphics(); } _ => (), } @@ -333,8 +341,7 @@ impl Scene for TitleScene { MenuSelectionResult::Selected(1, _) => { state.difficulty = GameDifficulty::Normal; state.replay_state = ReplayState::Recording; - state.reload_resources(ctx)?; - state.start_new_game(ctx)?; + self.current_menu = CurrentMenu::PlayerCountMenu; } MenuSelectionResult::Selected(2, _) => { state.difficulty = GameDifficulty::Normal; @@ -350,6 +357,18 @@ impl Scene for TitleScene { 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, + )?; }, } @@ -381,6 +400,7 @@ impl Scene for TitleScene { CurrentMenu::ChallengeConfirmMenu | CurrentMenu::SaveSelectMenu => (state.t("menus.main_menu.start")), CurrentMenu::OptionMenu => (state.t("menus.main_menu.options")), CurrentMenu::MainMenu => unreachable!(), + CurrentMenu::PlayerCountMenu => (state.t("menus.main_menu.start")), }; state.font.draw_colored_text_with_shadow_scaled( window_title.chars(), @@ -410,6 +430,7 @@ impl Scene for TitleScene { 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(()) diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index 7b32a9d..f53df8e 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -77,6 +77,12 @@ pub enum GameDifficulty { Hard = 4, } +#[derive(PartialEq, Eq, Copy, Clone, num_derive::FromPrimitive)] +pub enum PlayerCount { + One, + Two, +} + impl GameDifficulty { pub fn from_primitive(val: u8) -> GameDifficulty { return num_traits::FromPrimitive::from_u8(val).unwrap_or(GameDifficulty::Normal); @@ -270,6 +276,8 @@ pub struct SharedGameState { pub settings: Settings, pub save_slot: usize, pub difficulty: GameDifficulty, + pub player_count: PlayerCount, + pub player2_skin: u16, pub replay_state: ReplayState, pub mod_requirements: ModRequirements, pub tutorial_counter: u16, @@ -397,6 +405,8 @@ impl SharedGameState { settings, save_slot: 1, difficulty: GameDifficulty::Normal, + player_count: PlayerCount::One, + player2_skin: 0, replay_state: ReplayState::None, mod_requirements, tutorial_counter: 0,