Options refactor part 2: input bindings.

This commit is contained in:
Muzychenko Andrey 2023-01-27 16:25:47 +03:00
parent 4192b12c29
commit d99fbb092e
5 changed files with 290 additions and 190 deletions

View File

@ -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<std::string, std::string> options::settings{};
ControlsStruct options::RebindControls{};
std::unordered_map<std::string, std::string> options::settings{};
bool options::ShowDialog = false;
GameInput* options::ControlWaitingForInput = nullptr;
std::vector<OptionBase*> 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<SDL_GameControllerButton>(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<GameBindings> options::MapGameInput(GameInput key)
{
std::vector<GameBindings> 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<std::map<std::string, std::string>*>(entry);
auto& keyValueStore = *static_cast<std::unordered_map<std::string, std::string>*>(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);

View File

@ -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<GameBindings>(static_cast<int>(value) + 1);
}
constexpr int operator~(const GameBindings& value)
{
return static_cast<int>(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<GameBindings> MapGameInput(GameInput key);
private:
static std::map<std::string, std::string> settings;
static ControlsStruct RebindControls;
static std::unordered_map<std::string, std::string> 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<bool>
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<InputTypes>(options::get_int((name + " type").c_str(),
static_cast<int>(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<int>(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;

View File

@ -424,29 +424,31 @@ 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<char>(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);

View File

@ -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);
};

View File

@ -29,6 +29,7 @@
#include <string>
#include <thread>
#include <map>
#include <unordered_map>
#include <initializer_list>
//#include <array>