From d87bbf2b4669741699ea21e6d77e2886f4c7733a Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Mon, 21 Nov 2022 15:12:45 +0100 Subject: [PATCH 1/5] abstract gamepad away from SDL --- src/framework/backend.rs | 6 ++ src/framework/backend_sdl2.rs | 50 ++++++++++++++-- src/framework/gamepad.rs | 105 ++++++++++++++++++++-------------- src/game/mod.rs | 6 +- 4 files changed, 115 insertions(+), 52 deletions(-) diff --git a/src/framework/backend.rs b/src/framework/backend.rs index f1b1886..cad83d5 100644 --- a/src/framework/backend.rs +++ b/src/framework/backend.rs @@ -95,6 +95,12 @@ pub trait BackendTexture { fn as_any(&self) -> &dyn Any; } +pub trait BackendGamepad { + fn set_rumble(&mut self, low_freq: u16, high_freq: u16, duration_ms: u32) -> GameResult; + + fn instance_id(&self) -> u32; +} + #[allow(unreachable_code)] pub fn init_backend(headless: bool, size_hint: (u16, u16)) -> GameResult> { if headless { diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index cdb8192..1d122c2 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -22,13 +22,11 @@ use sdl2::video::Window; use sdl2::video::WindowContext; use crate::common::{Color, Rect}; -use crate::framework::backend::{ - Backend, BackendEventLoop, BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData, -}; +use crate::framework::backend::{Backend, BackendEventLoop, BackendGamepad, BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData}; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; use crate::framework::filesystem; -use crate::framework::gamepad::{Axis, Button}; +use crate::framework::gamepad::{Axis, Button, GamepadType}; use crate::framework::graphics::BlendMode; use crate::framework::keyboard::ScanCode; use crate::framework::render_opengl::{GLContext, OpenGLRenderer}; @@ -320,10 +318,10 @@ impl BackendEventLoop for SDL2EventLoop { log::info!("Connected gamepad: {} (ID: {})", controller.name(), id); let axis_sensitivity = state.settings.get_gamepad_axis_sensitivity(which); - ctx.gamepad_context.add_gamepad(controller, axis_sensitivity); + ctx.gamepad_context.add_gamepad(SDL2Gamepad::new(controller), axis_sensitivity); unsafe { - let controller_type = sdl2_sys::SDL_GameControllerTypeForIndex(id as _); + let controller_type = get_game_controller_type(sdl2_sys::SDL_GameControllerTypeForIndex(id as _)); ctx.gamepad_context.set_gamepad_type(id, controller_type); } } @@ -471,6 +469,46 @@ impl BackendEventLoop for SDL2EventLoop { } } +fn get_game_controller_type(ctype: sdl2_sys::SDL_GameControllerType) -> GamepadType { + match ctype as i32 { + 1 => GamepadType::Xbox360, + 2 => GamepadType::XboxOne, + 3 => GamepadType::PS3, + 4 => GamepadType::PS4, + 5 => GamepadType::NintendoSwitchPro, + 6 => GamepadType::Virtual, + 7 => GamepadType::PS5, + 8 => GamepadType::AmazonLuma, + 9 => GamepadType::GoogleStadia, + 10 => GamepadType::NVIDIAShield, + 11 => GamepadType::NintendoSwitchJoyConLeft, + 12 => GamepadType::NintendoSwitchJoyConRight, + 13 => GamepadType::NintendoSwitchJoyConPair, + _ => GamepadType::Unknown, + } +} + +struct SDL2Gamepad { + inner: GameController, +} + +impl SDL2Gamepad { + pub fn new(inner: GameController) -> Box { + Box::new(SDL2Gamepad { inner }) + } +} + +impl BackendGamepad for SDL2Gamepad { + fn set_rumble(&mut self, low_freq: u16, high_freq: u16, duration_ms: u32) -> GameResult { + self.inner.set_rumble(low_freq, high_freq, duration_ms) + .map_err(|e| GameError::GamepadError(e.to_string())) + } + + fn instance_id(&self) -> u32 { + self.inner.instance_id() + } +} + struct SDL2Renderer { refs: Rc>, imgui: Rc>, diff --git a/src/framework/gamepad.rs b/src/framework/gamepad.rs index 384761e..ca0ca0e 100644 --- a/src/framework/gamepad.rs +++ b/src/framework/gamepad.rs @@ -1,17 +1,57 @@ use std::collections::{HashMap, HashSet}; -use sdl2::controller::GameController; use serde::{Deserialize, Serialize}; -use crate::{common::Rect, engine_constants::EngineConstants, framework::context::Context}; +use crate::framework::backend::BackendGamepad; use crate::framework::error::GameResult; use crate::game::shared_game_state::SharedGameState; +use crate::{common::Rect, engine_constants::EngineConstants, framework::context::Context}; const QUAKE_RUMBLE_LOW_FREQ: u16 = 0x3000; const QUAKE_RUMBLE_HI_FREQ: u16 = 0; const SUPER_QUAKE_RUMBLE_LOW_FREQ: u16 = 0x5000; const SUPER_QUAKE_RUMBLE_HI_FREQ: u16 = 0; +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +#[repr(u32)] +pub enum GamepadType { + Unknown, + Xbox360, + XboxOne, + PS3, + PS4, + NintendoSwitchPro, + Virtual, + PS5, + AmazonLuma, + GoogleStadia, + NVIDIAShield, + NintendoSwitchJoyConLeft, + NintendoSwitchJoyConRight, + NintendoSwitchJoyConPair, +} + +impl GamepadType { + pub fn get_name(&self) -> &str { + match self { + GamepadType::Unknown => "Unknown controller", + GamepadType::Xbox360 => "Xbox 360 controller", + GamepadType::XboxOne => "Xbox One controller", + GamepadType::PS3 => "PlayStation 3 controller", + GamepadType::PS4 => "PlayStation 4 controller", + GamepadType::NintendoSwitchPro => "Nintendo Switch Pro controller", + GamepadType::Virtual => "Virtual controller", + GamepadType::PS5 => "PlayStation 5 controller", + GamepadType::AmazonLuma => "Amazon Luma controller", + GamepadType::GoogleStadia => "Google Stadia controller", + GamepadType::NVIDIAShield => "NVIDIA Shield controller", + GamepadType::NintendoSwitchJoyConLeft => "Nintendo Switch Joy-Con (left)", + GamepadType::NintendoSwitchJoyConRight => "Nintendo Switch Joy-Con (right)", + GamepadType::NintendoSwitchJoyConPair => "Nintendo Switch Joy-Con (pair)", + } + } +} + #[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] #[repr(u32)] pub enum Axis { @@ -118,8 +158,8 @@ impl PlayerControllerInputType { } pub struct GamepadData { - controller: GameController, - controller_type: Option, + controller: Box, + controller_type: GamepadType, left_x: f64, left_y: f64, @@ -135,10 +175,10 @@ pub struct GamepadData { } impl GamepadData { - pub(crate) fn new(game_controller: GameController, axis_sensitivity: f64) -> Self { + pub(crate) fn new(game_controller: Box, axis_sensitivity: f64) -> Self { GamepadData { controller: game_controller, - controller_type: None, + controller_type: GamepadType::Unknown, left_x: 0.0, left_y: 0.0, @@ -154,50 +194,29 @@ impl GamepadData { } } - pub(crate) fn set_gamepad_type(&mut self, controller_type: sdl2_sys::SDL_GameControllerType) { - self.controller_type = Some(controller_type); + pub(crate) fn set_gamepad_type(&mut self, controller_type: GamepadType) { + self.controller_type = controller_type; } pub(crate) fn get_gamepad_sprite_offset(&self) -> usize { - if let Some(controller_type) = self.controller_type { - return match controller_type { - sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_PS3 - | sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_PS4 - | sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_PS5 => 0, - sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_XBOX360 - | sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_XBOXONE => 1, - sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO => 3, - _ => 1, - }; + match self.controller_type { + GamepadType::PS3 | GamepadType::PS4 | GamepadType::PS5 => 0, + GamepadType::Xbox360 | GamepadType::XboxOne => 1, + GamepadType::NintendoSwitchPro + | GamepadType::NintendoSwitchJoyConLeft + | GamepadType::NintendoSwitchJoyConRight + | GamepadType::NintendoSwitchJoyConPair => 3, + _ => 1, } - - 1 } pub fn get_gamepad_name(&self) -> String { - let name = if let Some(controller_type) = self.controller_type { - match controller_type { - sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_PS3 - | sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_PS4 - | sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_PS5 => "PlayStation Controller".to_string(), - sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_XBOX360 - | sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_XBOXONE => "Xbox Controller".to_string(), - sdl2_sys::SDL_GameControllerType::SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO => { - "Nintendo Switch Controller".to_string() - } - _ => "Unknown Controller".to_string(), - } - } else { - "Unknown controller".to_string() - }; - - name + self.controller_type.get_name().to_owned() } pub fn set_rumble(&mut self, state: &SharedGameState, low_freq: u16, hi_freq: u16, ticks: u32) -> GameResult { let duration_ms = (ticks as f32 / state.settings.timing_mode.get_tps() as f32 * 1000.0) as u32; - self.controller.set_rumble(low_freq, hi_freq, duration_ms); - Ok(()) + self.controller.set_rumble(low_freq, hi_freq, duration_ms) } } @@ -226,7 +245,7 @@ impl GamepadContext { self.gamepads.get_mut(gamepad_index) } - pub(crate) fn add_gamepad(&mut self, game_controller: GameController, axis_sensitivity: f64) { + pub(crate) fn add_gamepad(&mut self, game_controller: Box, axis_sensitivity: f64) { self.gamepads.push(GamepadData::new(game_controller, axis_sensitivity)); } @@ -234,7 +253,7 @@ impl GamepadContext { self.gamepads.retain(|data| data.controller.instance_id() != gamepad_id); } - pub(crate) fn set_gamepad_type(&mut self, gamepad_id: u32, controller_type: sdl2_sys::SDL_GameControllerType) { + pub(crate) fn set_gamepad_type(&mut self, gamepad_id: u32, controller_type: GamepadType) { if let Some(gamepad) = self.get_gamepad_mut(gamepad_id) { gamepad.set_gamepad_type(controller_type); } @@ -377,7 +396,7 @@ impl Default for GamepadContext { } } -pub fn add_gamepad(context: &mut Context, game_controller: GameController, axis_sensitivity: f64) { +pub fn add_gamepad(context: &mut Context, game_controller: Box, axis_sensitivity: f64) { context.gamepad_context.add_gamepad(game_controller, axis_sensitivity); } @@ -385,7 +404,7 @@ pub fn remove_gamepad(context: &mut Context, gamepad_id: u32) { context.gamepad_context.remove_gamepad(gamepad_id); } -pub fn set_gamepad_type(context: &mut Context, gamepad_id: u32, controller_type: sdl2_sys::SDL_GameControllerType) { +pub fn set_gamepad_type(context: &mut Context, gamepad_id: u32, controller_type: GamepadType) { context.gamepad_context.set_gamepad_type(gamepad_id, controller_type); } diff --git a/src/game/mod.rs b/src/game/mod.rs index 7b0254a..9a85a22 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -24,15 +24,15 @@ pub mod caret; pub mod frame; pub mod inventory; pub mod map; +pub mod npc; pub mod physics; +pub mod player; pub mod profile; +pub mod scripting; pub mod settings; pub mod shared_game_state; pub mod stage; -pub mod npc; -pub mod player; pub mod weapon; -pub mod scripting; pub struct LaunchOptions { pub server_mode: bool, From 5ed2d40e23b48af3b7ad1356fd7c710e76f058cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Sallai?= Date: Sun, 18 Dec 2022 19:07:37 +0200 Subject: [PATCH 2/5] rumble failure shouldn't crash game --- src/framework/backend_sdl2.rs | 44 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index 1d122c2..ab22187 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -7,10 +7,9 @@ use std::ptr::{null, null_mut}; use std::rc::Rc; use std::time::{Duration, Instant}; -use imgui::{ConfigFlags, DrawCmd, DrawData, DrawIdx, DrawVert, Key, MouseCursor, TextureId, Ui}; use imgui::internal::RawWrapper; use imgui::sys::{ImGuiKey_Backspace, ImGuiKey_Delete, ImGuiKey_Enter}; -use sdl2::{controller, EventPump, GameControllerSubsystem, keyboard, pixels, Sdl, VideoSubsystem}; +use imgui::{ConfigFlags, DrawCmd, DrawData, DrawIdx, DrawVert, Key, MouseCursor, TextureId, Ui}; use sdl2::controller::GameController; use sdl2::event::{Event, WindowEvent}; use sdl2::keyboard::Scancode; @@ -20,9 +19,13 @@ use sdl2::render::{Texture, TextureCreator, TextureQuery, WindowCanvas}; use sdl2::video::GLProfile; use sdl2::video::Window; use sdl2::video::WindowContext; +use sdl2::{controller, keyboard, pixels, EventPump, GameControllerSubsystem, Sdl, VideoSubsystem}; use crate::common::{Color, Rect}; -use crate::framework::backend::{Backend, BackendEventLoop, BackendGamepad, BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData}; +use crate::framework::backend::{ + Backend, BackendEventLoop, BackendGamepad, BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, + VertexData, +}; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; use crate::framework::filesystem; @@ -31,9 +34,9 @@ use crate::framework::graphics::BlendMode; use crate::framework::keyboard::ScanCode; use crate::framework::render_opengl::{GLContext, OpenGLRenderer}; use crate::framework::ui::init_imgui; +use crate::game::shared_game_state::WindowMode; use crate::game::Game; use crate::game::GAME_SUSPENDED; -use crate::game::shared_game_state::WindowMode; pub struct SDL2Backend { context: Sdl, @@ -165,7 +168,7 @@ impl SDL2EventLoop { window.resizable(); #[cfg(feature = "render-opengl")] - window.opengl(); + window.opengl(); let window = window.build().map_err(|e| GameError::WindowError(e.to_string()))?; let opengl_available = if let Ok(v) = std::env::var("CAVESTORY_NO_OPENGL") { v != "1" } else { true }; @@ -207,7 +210,7 @@ impl BackendEventLoop for SDL2EventLoop { loop { #[cfg(target_os = "macos")] - unsafe { + unsafe { use objc::*; // no UB: fields are initialized by SDL_GetWindowWMInfo @@ -321,7 +324,8 @@ impl BackendEventLoop for SDL2EventLoop { ctx.gamepad_context.add_gamepad(SDL2Gamepad::new(controller), axis_sensitivity); unsafe { - let controller_type = get_game_controller_type(sdl2_sys::SDL_GameControllerTypeForIndex(id as _)); + let controller_type = + get_game_controller_type(sdl2_sys::SDL_GameControllerTypeForIndex(id as _)); ctx.gamepad_context.set_gamepad_type(id, controller_type); } } @@ -404,19 +408,19 @@ impl BackendEventLoop for SDL2EventLoop { fn new_renderer(&self, ctx: *mut Context) -> GameResult> { #[cfg(feature = "render-opengl")] - { - let mut refs = self.refs.borrow_mut(); - match refs.window.window().gl_create_context() { - Ok(gl_ctx) => { - refs.window.window().gl_make_current(&gl_ctx).map_err(|e| GameError::RenderError(e.to_string()))?; - refs.gl_context = Some(gl_ctx); - } - Err(err) => { - *self.opengl_available.borrow_mut() = false; - log::error!("Failed to initialize OpenGL context, falling back to SDL2 renderer: {}", err); - } + { + let mut refs = self.refs.borrow_mut(); + match refs.window.window().gl_create_context() { + Ok(gl_ctx) => { + refs.window.window().gl_make_current(&gl_ctx).map_err(|e| GameError::RenderError(e.to_string()))?; + refs.gl_context = Some(gl_ctx); + } + Err(err) => { + *self.opengl_available.borrow_mut() = false; + log::error!("Failed to initialize OpenGL context, falling back to SDL2 renderer: {}", err); } } + } #[cfg(feature = "render-opengl")] if *self.opengl_available.borrow() { @@ -500,8 +504,8 @@ impl SDL2Gamepad { impl BackendGamepad for SDL2Gamepad { fn set_rumble(&mut self, low_freq: u16, high_freq: u16, duration_ms: u32) -> GameResult { - self.inner.set_rumble(low_freq, high_freq, duration_ms) - .map_err(|e| GameError::GamepadError(e.to_string())) + let _ = self.inner.set_rumble(low_freq, high_freq, duration_ms); + Ok(()) } fn instance_id(&self) -> u32 { From 4be3dd518bcec4d17559b31ac290de6836468ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Sallai?= Date: Thu, 22 Dec 2022 22:59:24 +0200 Subject: [PATCH 3/5] add basic support for switch P2 skins --- src/engine_constants/mod.rs | 1 + src/game/player/mod.rs | 28 ++++--- src/game/shared_game_state.rs | 2 + src/menu/coop_menu.rs | 31 ++++++-- src/menu/mod.rs | 139 +++++++++++++++++++++++----------- src/scene/game_scene.rs | 8 +- 6 files changed, 146 insertions(+), 63 deletions(-) diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index 0253898..5b17d18 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -1433,6 +1433,7 @@ impl EngineConstants { "ItemImage" => (256, 128), "Loading" => (64, 8), "MyChar" => (200, 64), + "mychar_p2" => (200, 384), // switch "Npc/Npc0" => (32, 32), "Npc/NpcAlmo1" => (320, 240), "Npc/NpcAlmo2" => (320, 240), diff --git a/src/game/player/mod.rs b/src/game/player/mod.rs index f3953d4..218e8a2 100644 --- a/src/game/player/mod.rs +++ b/src/game/player/mod.rs @@ -3,7 +3,7 @@ use std::clone::Clone; use num_derive::FromPrimitive; use num_traits::clamp; -use crate::common::{Condition, Direction, Equipment, Flag, interpolate_fix9_scale, Rect}; +use crate::common::{interpolate_fix9_scale, Condition, Direction, Equipment, Flag, Rect}; use crate::components::number_popup::NumberPopup; use crate::entity::GameEntity; use crate::framework::context::Context; @@ -12,8 +12,8 @@ use crate::game::caret::CaretType; use crate::game::frame::Frame; use crate::game::npc::list::NPCList; use crate::game::npc::NPC; -use crate::game::player::skin::{PlayerAnimationState, PlayerAppearanceState, PlayerSkin}; use crate::game::player::skin::basic::BasicPlayerSkin; +use crate::game::player::skin::{PlayerAnimationState, PlayerAppearanceState, PlayerSkin}; use crate::game::shared_game_state::SharedGameState; use crate::input::dummy_player_controller::DummyPlayerController; use crate::input::player_controller::PlayerController; @@ -189,6 +189,12 @@ impl Player { } } + pub fn load_skin(&mut self, texture_name: String, state: &mut SharedGameState, ctx: &mut Context) { + self.skin = Box::new(BasicPlayerSkin::new(texture_name, state, ctx)); + self.display_bounds = self.skin.get_display_bounds(); + self.hit_bounds = self.skin.get_hit_bounds(); + } + fn tick_normal(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult { if !state.control_flags.interactions_disabled() && state.control_flags.control_enabled() { if self.equip.has_air_tank() { @@ -402,10 +408,10 @@ impl Player { // stop interacting when moved if state.control_flags.control_enabled() && (self.controller.move_left() - || self.controller.move_right() - || self.controller.move_up() - || self.controller.jump() - || self.controller.shoot()) + || self.controller.move_right() + || self.controller.move_up() + || self.controller.jump() + || self.controller.shoot()) { self.cond.set_interacted(false); } @@ -524,8 +530,8 @@ impl Player { if (self.flags.hit_bottom_wall() && self.flags.hit_right_higher_half() && self.vel_x < 0) || (self.flags.hit_bottom_wall() && self.flags.hit_left_higher_half() && self.vel_x > 0) || (self.flags.hit_bottom_wall() - && self.flags.hit_left_lower_half() - && self.flags.hit_right_lower_half()) + && self.flags.hit_left_lower_half() + && self.flags.hit_right_lower_half()) { self.vel_y = 0x400; // 2.0fix9 } @@ -533,9 +539,9 @@ impl Player { let max_move = if self.flags.in_water() && !(self.flags.force_left() - || self.flags.force_up() - || self.flags.force_right() - || self.flags.force_down()) + || self.flags.force_up() + || self.flags.force_right() + || self.flags.force_down()) { state.constants.player.water_physics.max_move } else { diff --git a/src/game/shared_game_state.rs b/src/game/shared_game_state.rs index 64e8df2..13ab625 100644 --- a/src/game/shared_game_state.rs +++ b/src/game/shared_game_state.rs @@ -302,6 +302,7 @@ pub struct SharedGameState { pub player_count: PlayerCount, pub player_count_modified_in_game: bool, pub player2_skin: u16, + pub player2_uses_p2_skinsheet: bool, pub replay_state: ReplayState, pub mod_requirements: ModRequirements, pub loc: Locale, @@ -455,6 +456,7 @@ impl SharedGameState { player_count: PlayerCount::One, player_count_modified_in_game: false, player2_skin: 0, + player2_uses_p2_skinsheet: false, replay_state: ReplayState::None, mod_requirements, loc: locale, diff --git a/src/menu/coop_menu.rs b/src/menu/coop_menu.rs index 6643edd..26ad2aa 100644 --- a/src/menu/coop_menu.rs +++ b/src/menu/coop_menu.rs @@ -2,8 +2,8 @@ use crate::framework::context::Context; use crate::framework::error::GameResult; use crate::game::shared_game_state::{PlayerCount, SharedGameState}; use crate::input::combined_menu_controller::CombinedMenuController; -use crate::menu::{Menu, MenuSelectionResult}; use crate::menu::MenuEntry; +use crate::menu::{Menu, MenuSelectionResult}; pub enum CurrentMenu { CoopMenu, @@ -59,20 +59,26 @@ 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(CoopMenuEntry::Title, MenuEntry::Disabled(state.loc.t("menus.coop_menu.title").to_owned())); + self.coop_menu + .push_entry(CoopMenuEntry::Title, MenuEntry::Disabled(state.loc.t("menus.coop_menu.title").to_owned())); self.coop_menu.push_entry(CoopMenuEntry::One, MenuEntry::Active(state.loc.t("menus.coop_menu.one").to_owned())); self.coop_menu.push_entry(CoopMenuEntry::Two, MenuEntry::Active(state.loc.t("menus.coop_menu.two").to_owned())); self.coop_menu.push_entry(CoopMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned())); self.coop_menu.selected = CoopMenuEntry::One; - self.skin_menu.push_entry(SkinMenuEntry::Title, MenuEntry::Disabled(state.loc.t("menus.skin_menu.title").to_owned())); + self.skin_menu + .push_entry(SkinMenuEntry::Title, MenuEntry::Disabled(state.loc.t("menus.skin_menu.title").to_owned())); self.skin_menu.push_entry(SkinMenuEntry::Skin, MenuEntry::PlayerSkin); if self.on_title { - self.skin_menu.push_entry(SkinMenuEntry::Start, MenuEntry::Active(state.loc.t("menus.main_menu.start").to_owned())); + self.skin_menu + .push_entry(SkinMenuEntry::Start, MenuEntry::Active(state.loc.t("menus.main_menu.start").to_owned())); } else { - self.skin_menu.push_entry(SkinMenuEntry::Add, MenuEntry::Active(state.loc.t("menus.pause_menu.add_player2").to_owned())); + self.skin_menu.push_entry( + SkinMenuEntry::Add, + MenuEntry::Active(state.loc.t("menus.pause_menu.add_player2").to_owned()), + ); } self.skin_menu.push_entry(SkinMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned())); @@ -137,6 +143,21 @@ impl PlayerCountMenu { } MenuSelectionResult::Selected(SkinMenuEntry::Skin, _) => { state.player2_skin += 2; + + if state.player2_uses_p2_skinsheet && state.player2_skin == 2 { + state.player2_skin += 2; + } + + let current_skin_spritesheet = if state.player2_uses_p2_skinsheet { "mychar_p2" } else { "mychar" }; + + if let Some(tex_size) = state.constants.tex_sizes.get(current_skin_spritesheet) { + // TODO: should probably have a way to figure out the height from the spritesheet ahead of time + + if state.player2_skin * 2 * 16 >= tex_size.1 { + state.player2_skin = 0; + state.player2_uses_p2_skinsheet = !state.player2_uses_p2_skinsheet; + } + } } MenuSelectionResult::Selected(SkinMenuEntry::Start, _) => { state.player_count = PlayerCount::Two; diff --git a/src/menu/mod.rs b/src/menu/mod.rs index 14d0948..f5bf4ff 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -375,62 +375,90 @@ impl Menu { for (_, entry) in &self.entries { match entry { MenuEntry::Active(name) | MenuEntry::DisabledWhite(name) => { - state.font.builder() - .position(self.x as f32 + 20.0, y) - .draw(name, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y).draw( + name, + ctx, + &state.constants, + &mut state.texture_set, + )?; } MenuEntry::Disabled(name) => { - state.font.builder() - .position(self.x as f32 + 20.0, y) - .color((0xa0, 0xa0, 0xff, 0xff)) - .draw(name, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y).color((0xa0, 0xa0, 0xff, 0xff)).draw( + name, + ctx, + &state.constants, + &mut state.texture_set, + )?; } MenuEntry::Toggle(name, value) => { let value_text = if *value { "ON" } else { "OFF" }; let name_text_len = state.font.builder().compute_width(name); - state.font.builder() - .position(self.x as f32 + 20.0, y) - .draw(name, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y).draw( + name, + ctx, + &state.constants, + &mut state.texture_set, + )?; - state.font.builder() - .position(self.x as f32 + 25.0 + name_text_len, y) - .draw(value_text, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 25.0 + name_text_len, y).draw( + value_text, + ctx, + &state.constants, + &mut state.texture_set, + )?; } MenuEntry::Options(name, index, value) => { let value_text = if let Some(text) = value.get(*index) { text } else { "???" }; let name_text_len = state.font.builder().compute_width(name); - state.font.builder() - .position(self.x as f32 + 20.0, y) - .draw(name, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y).draw( + name, + ctx, + &state.constants, + &mut state.texture_set, + )?; - state.font.builder() - .position(self.x as f32 + 25.0 + name_text_len, y) - .draw(value_text, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 25.0 + name_text_len, y).draw( + value_text, + ctx, + &state.constants, + &mut state.texture_set, + )?; } MenuEntry::DescriptiveOptions(name, index, value, description) => { let value_text = if let Some(text) = value.get(*index) { text } else { "???" }; let description_text = if let Some(text) = description.get(*index) { text } else { "???" }; let name_text_len = state.font.builder().compute_width(name); - state.font.builder() - .position(self.x as f32 + 20.0, y) - .draw(name, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y).draw( + name, + ctx, + &state.constants, + &mut state.texture_set, + )?; - state.font.builder() - .position(self.x as f32 + 25.0 + name_text_len, y) - .draw(value_text, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 25.0 + name_text_len, y).draw( + value_text, + ctx, + &state.constants, + &mut state.texture_set, + )?; - state.font.builder() + state + .font + .builder() .position(self.x as f32 + 20.0, y + 16.0) .color((0xc0, 0xc0, 0xff, 0xff)) .draw(description_text, ctx, &state.constants, &mut state.texture_set)?; } MenuEntry::OptionsBar(name, percent) => { - state.font.builder() - .position(self.x as f32 + 20.0, y) - .draw(name, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y).draw( + name, + ctx, + &state.constants, + &mut state.texture_set, + )?; if state.constants.is_switch || state.constants.is_cs_plus { let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "ui")?; @@ -469,16 +497,24 @@ impl Menu { } } MenuEntry::NewSave => { - state.font.builder() - .position(self.x as f32 + 20.0, y) - .draw(state.loc.t("menus.save_menu.new"), ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y).draw( + state.loc.t("menus.save_menu.new"), + ctx, + &state.constants, + &mut state.texture_set, + )?; } MenuEntry::PlayerSkin => { - state.font.builder() - .position(self.x as f32 + 20.0, y) - .draw(state.loc.t("menus.skin_menu.label"), ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y).draw( + state.loc.t("menus.skin_menu.label"), + ctx, + &state.constants, + &mut state.texture_set, + )?; - let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, "MyChar")?; + let spritesheet_name = if state.player2_uses_p2_skinsheet { "mychar_p2" } else { "MyChar" }; + + let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, spritesheet_name)?; batch.add_rect( self.x as f32 + 88.0, y - 4.0, @@ -496,9 +532,12 @@ impl Menu { 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; - state.font.builder() - .position(self.x as f32 + 20.0, y) - .draw(name, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y).draw( + name, + ctx, + &state.constants, + &mut state.texture_set, + )?; if valid_save { // Lifebar @@ -529,9 +568,12 @@ impl Menu { _ => difficulty_name.push_str("(unknown)"), } - state.font.builder() - .position(self.x as f32 + 20.0, y + 10.0) - .draw(difficulty_name.as_str(), ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y + 10.0).draw( + difficulty_name.as_str(), + ctx, + &state.constants, + &mut state.texture_set, + )?; } // Weapons @@ -554,15 +596,20 @@ impl Menu { } } MenuEntry::Control(name, data) => { - state.font.builder() - .position(self.x as f32 + 20.0, y) - .draw(name, ctx, &state.constants, &mut state.texture_set)?; + state.font.builder().position(self.x as f32 + 20.0, y).draw( + name, + ctx, + &state.constants, + &mut state.texture_set, + )?; match data { ControlMenuData::String(value) => { let text_width = state.font.builder().compute_width(value); - state.font.builder() + state + .font + .builder() .position(self.x as f32 + self.width as f32 - 5.0 - text_width, y) .draw(value, ctx, &state.constants, &mut state.texture_set)?; } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 4cd5bdf..6831504 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -138,13 +138,19 @@ impl GameScene { Rc::new(RefCell::new(textures)) }; + let mut player2 = Player::new(state, ctx); + + if state.player2_uses_p2_skinsheet { + player2.load_skin("mychar_p2".to_owned(), state, ctx); + } + Ok(Self { tick: 0, stage, water_params, water_renderer, player1: Player::new(state, ctx), - player2: Player::new(state, ctx), + player2: player2, inventory_player1: Inventory::new(), inventory_player2: Inventory::new(), boss_life_bar: BossLifeBar::new(), From 356f4230b58d5492569cccb928d2a7860d544001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Sallai?= Date: Fri, 23 Dec 2022 00:26:46 +0200 Subject: [PATCH 4/5] refactor co-op skins --- src/engine_constants/mod.rs | 4 ++++ src/game/shared_game_state.rs | 24 ++++++++++++++++++++---- src/live_debugger/mod.rs | 21 +++++++++++---------- src/menu/coop_menu.rs | 22 ++++++++++++++-------- src/menu/mod.rs | 5 +++-- src/scene/game_scene.rs | 20 ++++++++++++++------ 6 files changed, 66 insertions(+), 30 deletions(-) diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index 5b17d18..517c04b 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -337,6 +337,7 @@ pub struct EngineConstants { pub music_table: Vec, pub organya_paths: Vec, pub credit_illustration_paths: Vec, + pub player_skin_paths: Vec, pub animated_face_table: Vec, pub string_table: HashMap, pub missile_flags: Vec, @@ -369,6 +370,7 @@ impl Clone for EngineConstants { music_table: self.music_table.clone(), organya_paths: self.organya_paths.clone(), credit_illustration_paths: self.credit_illustration_paths.clone(), + player_skin_paths: self.player_skin_paths.clone(), animated_face_table: self.animated_face_table.clone(), string_table: self.string_table.clone(), missile_flags: self.missile_flags.clone(), @@ -1666,6 +1668,7 @@ impl EngineConstants { "Resource/BITMAP/".to_owned(), // CSE2E "endpic/".to_owned(), // NXEngine ], + player_skin_paths: vec!["MyChar".to_owned()], animated_face_table: vec![AnimatedFace { face_id: 0, anim_id: 0, anim_frames: vec![(0, 0)] }], string_table: HashMap::new(), missile_flags: vec![200, 201, 202, 218, 550, 766, 880, 920, 1551], @@ -1770,6 +1773,7 @@ impl EngineConstants { self.textscript.fade_ticks = 21; self.game.tile_offset_x = 3; self.game.new_game_player_pos = (13, 8); + self.player_skin_paths.push("mychar_p2".to_owned()); } pub fn apply_csdemo_patches(&mut self) { diff --git a/src/game/shared_game_state.rs b/src/game/shared_game_state.rs index 13ab625..c146299 100644 --- a/src/game/shared_game_state.rs +++ b/src/game/shared_game_state.rs @@ -251,6 +251,24 @@ impl TileSize { } } +#[derive(PartialEq, Eq, Copy, Clone)] +pub struct PlayerSkinLocation { + pub texture_index: u16, + pub offset: u16, +} + +impl PlayerSkinLocation { + pub const fn new(texture_index: u16, offset: u16) -> PlayerSkinLocation { + PlayerSkinLocation { texture_index, offset } + } +} + +impl Default for PlayerSkinLocation { + fn default() -> PlayerSkinLocation { + PlayerSkinLocation::new(0, 0) + } +} + pub struct SharedGameState { pub control_flags: ControlFlags, pub game_flags: BitVec, @@ -301,8 +319,7 @@ pub struct SharedGameState { pub difficulty: GameDifficulty, pub player_count: PlayerCount, pub player_count_modified_in_game: bool, - pub player2_skin: u16, - pub player2_uses_p2_skinsheet: bool, + pub player2_skin_location: PlayerSkinLocation, pub replay_state: ReplayState, pub mod_requirements: ModRequirements, pub loc: Locale, @@ -455,8 +472,7 @@ impl SharedGameState { difficulty: GameDifficulty::Normal, player_count: PlayerCount::One, player_count_modified_in_game: false, - player2_skin: 0, - player2_uses_p2_skinsheet: false, + player2_skin_location: PlayerSkinLocation::default(), replay_state: ReplayState::None, mod_requirements, loc: locale, diff --git a/src/live_debugger/mod.rs b/src/live_debugger/mod.rs index fcc9268..b6c7595 100644 --- a/src/live_debugger/mod.rs +++ b/src/live_debugger/mod.rs @@ -3,9 +3,9 @@ use itertools::Itertools; use crate::framework::context::Context; use crate::framework::error::GameResult; +use crate::game::scripting::tsc::text_script::TextScriptExecutionState; use crate::game::shared_game_state::SharedGameState; use crate::scene::game_scene::GameScene; -use crate::game::scripting::tsc::text_script::TextScriptExecutionState; use self::command_line::CommandLineParser; @@ -187,22 +187,22 @@ impl LiveDebugger { } #[cfg(feature = "scripting-lua")] - { - ui.same_line(); - if ui.button("Reload Lua Scripts") { - if let Err(err) = state.lua.reload_scripts(ctx) { - log::error!("Error reloading scripts: {:?}", err); - self.error = Some(ImString::new(err.to_string())); - } + { + ui.same_line(); + if ui.button("Reload Lua Scripts") { + if let Err(err) = state.lua.reload_scripts(ctx) { + log::error!("Error reloading scripts: {:?}", err); + self.error = Some(ImString::new(err.to_string())); } } + } if game_scene.player2.cond.alive() { if ui.button("Drop Player 2") { game_scene.drop_player2(); } } else if ui.button("Add Player 2") { - game_scene.add_player2(state); + game_scene.add_player2(state, ctx); } ui.same_line(); @@ -216,7 +216,8 @@ impl LiveDebugger { let _ = state.save_game(game_scene, ctx); state.sound_manager.play_sfx(18); } - } else if ui.button("Busy") {} + } else if ui.button("Busy") { + } ui.same_line(); if ui.button("Hotkey List") { diff --git a/src/menu/coop_menu.rs b/src/menu/coop_menu.rs index 26ad2aa..0f15012 100644 --- a/src/menu/coop_menu.rs +++ b/src/menu/coop_menu.rs @@ -142,20 +142,26 @@ impl PlayerCountMenu { } } MenuSelectionResult::Selected(SkinMenuEntry::Skin, _) => { - state.player2_skin += 2; + state.player2_skin_location.offset += 2; - if state.player2_uses_p2_skinsheet && state.player2_skin == 2 { - state.player2_skin += 2; + if state.player2_skin_location.texture_index == 1 && state.player2_skin_location.offset == 2 { + state.player2_skin_location.offset += 2; } - let current_skin_spritesheet = if state.player2_uses_p2_skinsheet { "mychar_p2" } else { "mychar" }; + let current_skin_spritesheet_name = + state.constants.player_skin_paths[state.player2_skin_location.texture_index as usize].as_str(); - if let Some(tex_size) = state.constants.tex_sizes.get(current_skin_spritesheet) { + if let Some(tex_size) = state.constants.tex_sizes.get(current_skin_spritesheet_name) { // TODO: should probably have a way to figure out the height from the spritesheet ahead of time - if state.player2_skin * 2 * 16 >= tex_size.1 { - state.player2_skin = 0; - state.player2_uses_p2_skinsheet = !state.player2_uses_p2_skinsheet; + if state.player2_skin_location.offset * 2 * 16 >= tex_size.1 { + state.player2_skin_location.offset = 0; + + if state.player2_skin_location.texture_index == 1 { + state.player2_skin_location.texture_index = 0; + } else { + state.player2_skin_location.texture_index = 1; + } } } } diff --git a/src/menu/mod.rs b/src/menu/mod.rs index f5bf4ff..18264e4 100644 --- a/src/menu/mod.rs +++ b/src/menu/mod.rs @@ -512,13 +512,14 @@ impl Menu { &mut state.texture_set, )?; - let spritesheet_name = if state.player2_uses_p2_skinsheet { "mychar_p2" } else { "MyChar" }; + let spritesheet_name = + state.constants.player_skin_paths[state.player2_skin_location.texture_index as usize].as_str(); let batch = state.texture_set.get_or_load_batch(ctx, &state.constants, spritesheet_name)?; 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), + &Rect::new_size(0, (state.player2_skin_location.offset).saturating_mul(2 * 16), 16, 16), ); batch.draw(ctx)?; } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 6831504..2d0cbf6 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -140,8 +140,10 @@ impl GameScene { let mut player2 = Player::new(state, ctx); - if state.player2_uses_p2_skinsheet { - player2.load_skin("mychar_p2".to_owned(), state, ctx); + if state.player2_skin_location.texture_index != 0 { + let skinsheet_name = + state.constants.player_skin_paths[state.player2_skin_location.texture_index as usize].as_str(); + player2.load_skin(skinsheet_name.to_owned(), state, ctx); } Ok(Self { @@ -187,10 +189,16 @@ impl GameScene { pub fn display_map_name(&mut self, ticks: u16) { self.map_name_counter = ticks; } - pub fn add_player2(&mut self, state: &mut SharedGameState) { + + pub fn add_player2(&mut self, state: &mut SharedGameState, ctx: &mut Context) { self.player2.cond.set_alive(true); self.player2.cond.set_hidden(self.player1.cond.hidden()); - self.player2.skin.set_skinsheet_offset(state.player2_skin); + + let skinsheet_name = + state.constants.player_skin_paths[state.player2_skin_location.texture_index as usize].as_str(); + self.player2.load_skin(skinsheet_name.to_owned(), state, ctx); + self.player2.skin.set_skinsheet_offset(state.player2_skin_location.offset); + self.player2.x = self.player1.x; self.player2.y = self.player1.y; self.player2.vel_x = self.player1.vel_x; @@ -1637,7 +1645,7 @@ impl Scene for GameScene { self.replay.initialize_recording(state); } if state.player_count == PlayerCount::Two { - self.add_player2(state); + self.add_player2(state, ctx); } else { self.drop_player2(); } @@ -1743,7 +1751,7 @@ impl Scene for GameScene { if state.player_count_modified_in_game { if state.player_count == PlayerCount::Two { - self.add_player2(state); + self.add_player2(state, ctx); } else { self.drop_player2(); } From e975a75ec4665bbfa550494572cc1eaa7e740cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Sallai?= Date: Fri, 6 Jan 2023 10:01:45 +0200 Subject: [PATCH 5/5] sue --- src/menu/coop_menu.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/menu/coop_menu.rs b/src/menu/coop_menu.rs index 0f15012..cbe9f2c 100644 --- a/src/menu/coop_menu.rs +++ b/src/menu/coop_menu.rs @@ -144,10 +144,6 @@ impl PlayerCountMenu { MenuSelectionResult::Selected(SkinMenuEntry::Skin, _) => { state.player2_skin_location.offset += 2; - if state.player2_skin_location.texture_index == 1 && state.player2_skin_location.offset == 2 { - state.player2_skin_location.offset += 2; - } - let current_skin_spritesheet_name = state.constants.player_skin_paths[state.player2_skin_location.texture_index as usize].as_str();