diff --git a/SpaceCadetPinball/options.cpp b/SpaceCadetPinball/options.cpp index bad2845..7cec04a 100644 --- a/SpaceCadetPinball/options.cpp +++ b/SpaceCadetPinball/options.cpp @@ -13,62 +13,55 @@ constexpr int options::MaxUps, options::MaxFps, options::MinUps, options::MinFps constexpr int options::MaxSoundChannels, options::MinSoundChannels, options::DefSoundChannels; constexpr int options::MaxVolume, options::MinVolume, options::DefVolume; -std::map options::settings{}; -ControlsStruct options::RebindControls{}; +std::unordered_map options::settings{}; bool options::ShowDialog = false; GameInput* options::ControlWaitingForInput = nullptr; std::vector options::AllOptions{}; -const ControlRef options::Controls[6] -{ - {Msg::KEYMAPPER_FlipperL, RebindControls.LeftFlipper}, - {Msg::KEYMAPPER_FlipperR, RebindControls.RightFlipper}, - {Msg::KEYMAPPER_BumpLeft, RebindControls.LeftTableBump}, - {Msg::KEYMAPPER_BumpRight, RebindControls.RightTableBump}, - {Msg::KEYMAPPER_BumpBottom, RebindControls.BottomTableBump}, - {Msg::KEYMAPPER_Plunger, RebindControls.Plunger}, -}; -const ControlsStruct options::KeyDft = -{ - { - {InputTypes::Keyboard, SDLK_z}, - {InputTypes::Mouse, SDL_BUTTON_LEFT}, - {InputTypes::GameController, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - }, - { - {InputTypes::Keyboard, SDLK_SLASH}, - {InputTypes::Mouse, SDL_BUTTON_RIGHT}, - {InputTypes::GameController, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - }, - { - {InputTypes::Keyboard, SDLK_SPACE}, - {InputTypes::Mouse, SDL_BUTTON_MIDDLE}, - {InputTypes::GameController, SDL_CONTROLLER_BUTTON_A}, - }, - { - {InputTypes::Keyboard, SDLK_x}, - {InputTypes::Mouse, SDL_BUTTON_X1}, - {InputTypes::GameController, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - }, - { - {InputTypes::Keyboard, SDLK_PERIOD}, - {InputTypes::Mouse, SDL_BUTTON_X2}, - {InputTypes::GameController, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - }, - { - {InputTypes::Keyboard, SDLK_UP}, - {InputTypes::Mouse, SDL_BUTTON_X2 + 1}, - {InputTypes::GameController, SDL_CONTROLLER_BUTTON_DPAD_UP}, - }, -}; - optionsStruct options::Options { - KeyDft, + { + { + "Left Flipper key", + {InputTypes::Keyboard, SDLK_z}, + {InputTypes::Mouse, SDL_BUTTON_LEFT}, + {InputTypes::GameController, SDL_CONTROLLER_BUTTON_LEFTSHOULDER} + }, + { + "Right Flipper key", + {InputTypes::Keyboard, SDLK_SLASH}, + {InputTypes::Mouse,SDL_BUTTON_RIGHT}, + {InputTypes::GameController, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER} + }, + { + "Plunger key", + {InputTypes::Keyboard, SDLK_SPACE}, + {InputTypes::Mouse,SDL_BUTTON_MIDDLE}, + {InputTypes::GameController, SDL_CONTROLLER_BUTTON_A} + }, + { + "Left Table Bump key", + {InputTypes::Keyboard, SDLK_x}, + {InputTypes::Mouse,SDL_BUTTON_X1}, + {InputTypes::GameController, SDL_CONTROLLER_BUTTON_DPAD_LEFT} + }, + { + "Right Table Bump key", + {InputTypes::Keyboard, SDLK_PERIOD}, + {InputTypes::Mouse,SDL_BUTTON_X2}, + {InputTypes::GameController, SDL_CONTROLLER_BUTTON_DPAD_RIGHT} + }, + { + "Bottom Table Bump key", + {InputTypes::Keyboard, SDLK_UP}, + {InputTypes::Mouse,SDL_BUTTON_X2 + 1}, + {InputTypes::GameController, SDL_CONTROLLER_BUTTON_DPAD_UP} + }, + }, {"Sounds", true}, - {"Music", false }, - {"FullScreen", false }, - {"Players", 1 }, + {"Music", false}, + {"FullScreen", false}, + {"Players", 1}, {"Screen Resolution", -1 }, {"UI Scale", 1.0f}, {"Uniform scaling", true}, @@ -116,13 +109,6 @@ void options::InitPrimary() imContext->SettingsLoaded = true; } - GetInput("Left Flipper key", Options.Key.LeftFlipper); - GetInput("Right Flipper key", Options.Key.RightFlipper); - GetInput("Plunger key", Options.Key.Plunger); - GetInput("Left Table Bump key", Options.Key.LeftTableBump); - GetInput("Right Table Bump key", Options.Key.RightTableBump); - GetInput("Bottom Table Bump key", Options.Key.BottomTableBump); - for(const auto opt : AllOptions) { opt->Load(); @@ -150,13 +136,7 @@ void options::InitSecondary() void options::uninit() { - SetInput("Left Flipper key", Options.Key.LeftFlipper); - SetInput("Right Flipper key", Options.Key.RightFlipper); - SetInput("Plunger key", Options.Key.Plunger); - SetInput("Left Table Bump key", Options.Key.LeftTableBump); - SetInput("Right Table Bump key", Options.Key.RightTableBump); - SetInput("Bottom Table Bump key", Options.Key.BottomTableBump); - + Options.Language.V = translations::GetCurrentLanguage()->ShortName; for (const auto opt : AllOptions) { opt->Save(); @@ -308,27 +288,31 @@ void options::ShowControlDialog() if (!ShowDialog) { ControlWaitingForInput = nullptr; - RebindControls = Options.Key; ShowDialog = true; + // Save previous controls in KVP storage. + for(const auto& control: Options.Key) + { + control.Save(); + } } } void options::RenderControlDialog() { - static const char* mouseButtons[] + static const Msg controlDescriptions[6] { - nullptr, - "Mouse Left", - "Mouse Middle", - "Mouse Right", - "Mouse X1", - "Mouse X2", + Msg::KEYMAPPER_FlipperL, + Msg::KEYMAPPER_FlipperR, + Msg::KEYMAPPER_BumpLeft, + Msg::KEYMAPPER_BumpRight, + Msg::KEYMAPPER_BumpBottom, + Msg::KEYMAPPER_Plunger, }; if (!ShowDialog) return; - ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2{550, 450}); + ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2{550, 550}); if (ImGui::Begin(pb::get_rc_string(Msg::KEYMAPPER_Caption), &ShowDialog)) { ImGui::TextUnformatted(pb::get_rc_string(Msg::KEYMAPPER_Groupbox2)); @@ -341,7 +325,7 @@ void options::RenderControlDialog() ImGui::TextUnformatted(pb::get_rc_string(Msg::KEYMAPPER_Groupbox1)); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{5, 10}); - if (ImGui::BeginTable("Controls", 4, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders)) + if (ImGui::BeginTable("Controls", 4, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_Borders| ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("Control"); ImGui::TableSetupColumn("Binding 1"); @@ -349,55 +333,37 @@ void options::RenderControlDialog() ImGui::TableSetupColumn("Binding 3"); ImGui::TableHeadersRow(); - int index = 0; - for (auto& row : Controls) + int rowHash = 0; + for (auto inputId = GameBindings::Min; inputId < GameBindings::Max; inputId++) { + auto& option = Options.Key[~inputId]; + ImGui::TableNextColumn(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.5, 0, 0, 1}); - if (ImGui::Button(pb::get_rc_string(row.NameStringId))) + if (ImGui::Button(pb::get_rc_string(controlDescriptions[~inputId]))) { - for (auto i = 0u; i <= 2; i++) - row.Option[i] = {}; + for (auto& input : option.Inputs) + input = {}; } ImGui::PopStyleColor(1); - for (auto i = 0u; i <= 2; i++) + for (auto& input : option.Inputs) { - auto& ctrl = row.Option[i]; ImGui::TableNextColumn(); - if (ControlWaitingForInput == &ctrl) + if (ControlWaitingForInput == &input) { - ImGui::Button("Press the key", ImVec2(-1, 0)); + if(ImGui::Button("Press the key", ImVec2(-1, 0))) + { + ControlWaitingForInput = &input; + } } else { - std::string tmp; - const char* keyName; - switch (ctrl.Type) - { - case InputTypes::Keyboard: - keyName = SDL_GetKeyName(ctrl.Value); - break; - case InputTypes::Mouse: - if (ctrl.Value >= SDL_BUTTON_LEFT && ctrl.Value <= SDL_BUTTON_X2) - keyName = mouseButtons[ctrl.Value]; - else - keyName = (tmp += "Mouse " + std::to_string(ctrl.Value)).c_str(); - break; - case InputTypes::GameController: - keyName = SDL_GameControllerGetStringForButton( - static_cast(ctrl.Value)); - break; - case InputTypes::None: - default: - keyName = "Unused"; - } - if (!keyName || !keyName[0]) - keyName = "Unknown key"; - if (ImGui::Button((std::string{keyName} + "##" + std::to_string(index++)).c_str(), + auto inputDescription = input.GetInputDescription(); + if (ImGui::Button((inputDescription + "##" + std::to_string(rowHash++)).c_str(), ImVec2(-1, 0))) { - ControlWaitingForInput = &ctrl; + ControlWaitingForInput = &input; } } } @@ -409,20 +375,26 @@ void options::RenderControlDialog() if (ImGui::Button(pb::get_rc_string(Msg::KEYMAPPER_Ok))) { - Options.Key = RebindControls; ShowDialog = false; } ImGui::SameLine(); if (ImGui::Button(pb::get_rc_string(Msg::KEYMAPPER_Cancel))) { + for (auto& control : Options.Key) + { + control.Load(); + } ShowDialog = false; } ImGui::SameLine(); if (ImGui::Button(pb::get_rc_string(Msg::KEYMAPPER_Default))) { - RebindControls = KeyDft; + for (auto& control : Options.Key) + { + control.Reset(); + } ControlWaitingForInput = nullptr; } } @@ -433,9 +405,26 @@ void options::RenderControlDialog() ControlWaitingForInput = nullptr; } +std::vector options::MapGameInput(GameInput key) +{ + std::vector result; + for (auto inputId = GameBindings::Min; inputId < GameBindings::Max; inputId++) + { + for (auto& inputValue : Options.Key[~inputId].Inputs) + { + if (key == inputValue) + { + result.push_back(inputId); + break; + } + } + } + return result; +} + void options::MyUserData_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line) { - auto& keyValueStore = *static_cast*>(entry); + auto& keyValueStore = *static_cast*>(entry); std::string keyValue = line; auto separatorPos = keyValue.find('='); if (separatorPos != std::string::npos) @@ -462,6 +451,72 @@ void options::MyUserData_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handl buf->append("\n"); } +std::string GameInput::GetInputDescription() const +{ + static LPCSTR mouseButtons[] + { + nullptr, + "Left", + "Middle", + "Right", + "X1", + "X2", + }; + + static LPCSTR controllerButtons[] = + { + "A", + "B", + "X", + "Y", + "Back", + "Guide", + "Start", + "LeftStick", + "RightStick", + "LeftShoulder", + "RightShoulder", + "DpUp", + "DpDown", + "DpLeft", + "DpRight", + "Misc1", + "Paddle1", + "Paddle2", + "Paddle3", + "Paddle4", + "Touchpad", + }; + + std::string keyName; + switch (Type) + { + case InputTypes::Keyboard: + keyName = "Keyboard\n"; + keyName += SDL_GetKeyName(Value); + break; + case InputTypes::Mouse: + keyName = "Mouse\n"; + if (Value >= SDL_BUTTON_LEFT && Value <= SDL_BUTTON_X2) + keyName += mouseButtons[Value]; + else + keyName += std::to_string(Value); + break; + case InputTypes::GameController: + keyName = "Controller\n"; + if (Value >= SDL_CONTROLLER_BUTTON_A && Value <= SDL_CONTROLLER_BUTTON_TOUCHPAD) + keyName += controllerButtons[Value]; + else + keyName += std::to_string(Value); + break; + case InputTypes::None: + default: + keyName = "Unused"; + } + + return keyName; +} + const std::string& options::GetSetting(const std::string& key, const std::string& defaultValue) { auto setting = settings.find(key); diff --git a/SpaceCadetPinball/options.h b/SpaceCadetPinball/options.h index 251dbe1..c032c6e 100644 --- a/SpaceCadetPinball/options.h +++ b/SpaceCadetPinball/options.h @@ -33,12 +33,12 @@ enum class Menu1:int Prefer3DPBGameData = 700, }; -enum class InputTypes: unsigned +enum class InputTypes { None = 0, - Keyboard = 1, - Mouse = 2, - GameController = 3, + Keyboard, + Mouse, + GameController, }; struct GameInput @@ -50,23 +50,31 @@ struct GameInput { return Type == other.Type && Value == other.Value; } + + std::string GetInputDescription() const; }; -struct ControlsStruct +enum class GameBindings { - GameInput LeftFlipper[3]; - GameInput RightFlipper[3]; - GameInput Plunger[3]; - GameInput LeftTableBump[3]; - GameInput RightTableBump[3]; - GameInput BottomTableBump[3]; + Min = 0, + LeftFlipper = 0, + RightFlipper, + Plunger, + LeftTableBump, + RightTableBump, + BottomTableBump, + Max }; -struct ControlRef +inline GameBindings& operator++(GameBindings& value, int) { - Msg NameStringId; - GameInput (&Option)[3]; -}; + return value = static_cast(static_cast(value) + 1); +} + +constexpr int operator~(const GameBindings& value) +{ + return static_cast(value); +} class options { @@ -95,14 +103,12 @@ public: static void InputDown(GameInput input); static void ShowControlDialog(); static void RenderControlDialog(); - static bool WaitingForInput() { return ControlWaitingForInput != nullptr; } + static bool WaitingForInput() { return ControlWaitingForInput; } + static std::vector MapGameInput(GameInput key); private: - static std::map settings; - static ControlsStruct RebindControls; + static std::unordered_map settings; static bool ShowDialog; - static const ControlRef Controls[6]; static GameInput* ControlWaitingForInput; - static const ControlsStruct KeyDft; static void MyUserData_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* entry, const char* line); static void* MyUserData_ReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler, const char* name); @@ -183,9 +189,53 @@ struct BoolOption : OptionBaseT using OptionBaseT::operator=; }; +struct ControlOption : OptionBase +{ + GameInput Defaults[3]; + GameInput Inputs[3]; + + ControlOption(LPCSTR name, GameInput defaultKeyboard, GameInput defaultMouse, GameInput defaultController) : + OptionBase(name), + Defaults{defaultKeyboard, defaultMouse, defaultController}, + Inputs{ + {InputTypes::Keyboard, -1}, + {InputTypes::Mouse, -1}, + {InputTypes::GameController, -1} + } + { + } + + void Load() override + { + for (auto i = 0u; i <= 2; i++) + { + auto name = std::string{ Name } + " " + std::to_string(i); + Inputs[i].Type = static_cast(options::get_int((name + " type").c_str(), + static_cast(Defaults[i].Type))); + Inputs[i].Value = options::get_int((name + " input").c_str(), Defaults[i].Value); + } + } + + void Save() const override + { + for (auto i = 0u; i <= 2; i++) + { + auto name = std::string{ Name } + " " + std::to_string(i); + options::set_int((name + " type").c_str(), static_cast(Inputs[i].Type)); + options::set_int((name + " input").c_str(), Inputs[i].Value); + } + } + + + void Reset() override + { + std::copy(std::begin(Defaults), std::end(Defaults), std::begin(Inputs)); + } +}; + struct optionsStruct { - ControlsStruct Key; + ControlOption Key[~GameBindings::Max]; BoolOption Sounds; BoolOption Music; BoolOption FullScreen; diff --git a/SpaceCadetPinball/pb.cpp b/SpaceCadetPinball/pb.cpp index 6c45be3..9051823 100644 --- a/SpaceCadetPinball/pb.cpp +++ b/SpaceCadetPinball/pb.cpp @@ -423,30 +423,32 @@ void pb::InputUp(GameInput input) { if (game_mode != GameModes::InGame || winmain::single_step || demo_mode) return; - - if (AnyBindingMatchesInput(options::Options.Key.LeftFlipper, input)) + + const auto bindings = options::MapGameInput(input); + for (const auto binding : bindings) { - MainTable->Message(MessageCode::LeftFlipperInputReleased, time_now); - } - if (AnyBindingMatchesInput(options::Options.Key.RightFlipper, input)) - { - MainTable->Message(MessageCode::RightFlipperInputReleased, time_now); - } - if (AnyBindingMatchesInput(options::Options.Key.Plunger, input)) - { - MainTable->Message(MessageCode::PlungerInputReleased, time_now); - } - if (AnyBindingMatchesInput(options::Options.Key.LeftTableBump, input)) - { - nudge::un_nudge_right(0, nullptr); - } - if (AnyBindingMatchesInput(options::Options.Key.RightTableBump, input)) - { - nudge::un_nudge_left(0, nullptr); - } - if (AnyBindingMatchesInput(options::Options.Key.BottomTableBump, input)) - { - nudge::un_nudge_up(0, nullptr); + switch (binding) + { + case GameBindings::LeftFlipper: + MainTable->Message(MessageCode::LeftFlipperInputReleased, time_now); + break; + case GameBindings::RightFlipper: + MainTable->Message(MessageCode::RightFlipperInputReleased, time_now); + break; + case GameBindings::Plunger: + MainTable->Message(MessageCode::PlungerInputReleased, time_now); + break; + case GameBindings::LeftTableBump: + nudge::un_nudge_right(0, nullptr); + break; + case GameBindings::RightTableBump: + nudge::un_nudge_left(0, nullptr); + break; + case GameBindings::BottomTableBump: + nudge::un_nudge_up(0, nullptr); + break; + default: break; + } } } @@ -459,33 +461,35 @@ void pb::InputDown(GameInput input) if (input.Type == InputTypes::Keyboard) control::pbctrl_bdoor_controller(static_cast(input.Value)); - if (AnyBindingMatchesInput(options::Options.Key.LeftFlipper, input)) + const auto bindings = options::MapGameInput(input); + for (const auto binding : bindings) { - MainTable->Message(MessageCode::LeftFlipperInputPressed, time_now); - } - if (AnyBindingMatchesInput(options::Options.Key.RightFlipper, input)) - { - MainTable->Message(MessageCode::RightFlipperInputPressed, time_now); - } - if (AnyBindingMatchesInput(options::Options.Key.Plunger, input)) - { - MainTable->Message(MessageCode::PlungerInputPressed, time_now); - } - if (AnyBindingMatchesInput(options::Options.Key.LeftTableBump, input)) - { - if (!MainTable->TiltLockFlag) - nudge::nudge_right(); - } - if (AnyBindingMatchesInput(options::Options.Key.RightTableBump, input)) - { - if (!MainTable->TiltLockFlag) - nudge::nudge_left(); - } - if (AnyBindingMatchesInput(options::Options.Key.BottomTableBump, input)) - { - if (!MainTable->TiltLockFlag) - nudge::nudge_up(); - } + switch (binding) + { + case GameBindings::LeftFlipper: + MainTable->Message(MessageCode::LeftFlipperInputPressed, time_now); + break; + case GameBindings::RightFlipper: + MainTable->Message(MessageCode::RightFlipperInputPressed, time_now); + break; + case GameBindings::Plunger: + MainTable->Message(MessageCode::PlungerInputPressed, time_now); + break; + case GameBindings::LeftTableBump: + if (!MainTable->TiltLockFlag) + nudge::nudge_right(); + break; + case GameBindings::RightTableBump: + if (!MainTable->TiltLockFlag) + nudge::nudge_left(); + break; + case GameBindings::BottomTableBump: + if (!MainTable->TiltLockFlag) + nudge::nudge_up(); + break; + default: break; + } + } if (cheat_mode && input.Type == InputTypes::Keyboard) { @@ -660,14 +664,6 @@ void pb::PushCheat(const std::string& cheat) control::pbctrl_bdoor_controller(ch); } -bool pb::AnyBindingMatchesInput(GameInput (&options)[3], GameInput key) -{ - for (auto& option : options) - if (key == option) - return true; - return false; -} - LPCSTR pb::get_rc_string(Msg uID) { return translations::GetTranslation(uID); diff --git a/SpaceCadetPinball/pb.h b/SpaceCadetPinball/pb.h index 88e0fe3..c1bc8ff 100644 --- a/SpaceCadetPinball/pb.h +++ b/SpaceCadetPinball/pb.h @@ -82,6 +82,4 @@ public: static void ShowMessageBox(Uint32 flags, LPCSTR title, LPCSTR message); private: static bool demo_mode; - - static bool AnyBindingMatchesInput(GameInput (&options)[3], GameInput key); }; diff --git a/SpaceCadetPinball/pch.h b/SpaceCadetPinball/pch.h index f4819d0..a2cbcfe 100644 --- a/SpaceCadetPinball/pch.h +++ b/SpaceCadetPinball/pch.h @@ -29,6 +29,7 @@ #include #include #include +#include #include //#include