#include "KInput.h" #include "Constants.h" #include #include namespace KapitanGame { KInput::KInput() : GamePadsCount(0), Initialized(false) { } void KInput::Init() { if (Initialized) return; if (SDL_WasInit(SDL_INIT_GAMECONTROLLER) != 1) SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); const int nJoysticks = SDL_NumJoysticks(); GamePadsCount = 0; // Count how many controllers there are for (int i = 0; i < nJoysticks; i++) if (SDL_IsGameController(i)) GamePadsCount++; // If we have some controllers attached if (GamePadsCount > 0) { for (int i = 0; i < GamePadsCount; i++) { // Open the controller and add it to our list SDL_GameController* pad = SDL_GameControllerOpen(i); if (SDL_GameControllerGetAttached(pad) == 1) ConnectedControllers.push_back(pad); else printf("SDL_GetError() = %s\n", SDL_GetError()); } SDL_GameControllerEventState(SDL_ENABLE); } // Vectors are empty to begin with, this sets their size ControllerInputs.resize(GamePadsCount); LastControllerInputs.resize(GamePadsCount); // Set the status of the controllers to "nothing is happening" for (int i = 0; i < GamePadsCount; i++) { for (int a = 0; a < SDL_CONTROLLER_AXIS_MAX; a++) { ControllerInputs[i].Axis[a] = 0; LastControllerInputs[i].Axis[a] = 0; } for (int b = 0; b < SDL_CONTROLLER_BUTTON_MAX; b++) { ControllerInputs[i].Buttons[b] = false; LastControllerInputs[i].Buttons[b] = false; } } Initialized = true; } void KInput::Free() { for (const auto& pad : ConnectedControllers) { SDL_GameControllerClose(pad); } ConnectedControllers.clear(); Actions.clear(); } void KInput::HandleInputPreEvents() { for (int i = 0; i < GamePadsCount; i++) { for (int a = 0; a < SDL_CONTROLLER_AXIS_MAX; a++) { LastControllerInputs[i].Axis[a] = ControllerInputs[i].Axis[a]; } for (int b = 0; b < SDL_CONTROLLER_BUTTON_MAX; b++) { LastControllerInputs[i].Buttons[b] = ControllerInputs[i].Buttons[b]; } } LastKeyboardInputs = KeyboardInputs; } void KInput::HandleInputPostEvents(const bool processInput) { int size = 0; const Uint8* currentKeyStates = SDL_GetKeyboardState(&size); KeyboardInputs = std::vector(currentKeyStates, currentKeyStates + size); if (processInput) { ProcessActions(); ProcessAxes(); } } void KInput::HandleEvent(const SDL_Event& event) { switch (event.type) { // This happens when a device is added // A future improvement is to cope with new controllers being plugged in // when the game is running case SDL_CONTROLLERDEVICEADDED: printf("DEVICEADDED cdevice.which = %d\n", event.cdevice.which); break; // If a controller button is pressed case SDL_CONTROLLERBUTTONDOWN: // Cycle through the controllers for (int i = 0; i < GamePadsCount; i++) { // Looking for the button that was pressed if (event.cbutton.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ConnectedControllers[i]))) { // So the relevant state can be updated ControllerInputs[i].Buttons[event.cbutton.button] = true; } } break; // Do the same for releasing a button case SDL_CONTROLLERBUTTONUP: for (int i = 0; i < GamePadsCount; i++) { if (event.cbutton.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ConnectedControllers[i]))) { ControllerInputs[i].Buttons[event.cbutton.button] = false; } } break; // And something similar for axis motion case SDL_CONTROLLERAXISMOTION: for (int i = 0; i < GamePadsCount; i++) { if (event.cbutton.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ConnectedControllers[i]))) { ControllerInputs[i].Axis[event.caxis.axis] = event.caxis.value; } } break; default: break; } } void KInput::BindAction(const std::string& name, InputState expectedInputState, const std::shared_ptr& controllerObject, KPlayerCommand command, KPlayer player) { Actions.emplace_back(name, expectedInputState, controllerObject, command, player); } void KInput::BindAxis(const std::string& name, const std::shared_ptr& controllerObject, KPlayerAxisCommand command, KPlayer player) { Axes.emplace_back(name, controllerObject, command, player); } bool KInput::CheckAction(const std::string& name, const InputState expectedInputState, KPlayer player) { const auto keyBindRange = KeyBinds[static_cast(player)].equal_range(name); for (auto keyBind = keyBindRange.first; keyBind != keyBindRange.second; ++keyBind) { switch (expectedInputState) { case InputState::Pressed: if (IsKeyboardButtonPressed(keyBind->second)) { return true; } break; case InputState::Released: if (IsKeyboardButtonReleased(keyBind->second)) { return true; } break; case InputState::Hold: if (IsKeyboardButtonHeld(keyBind->second)) { return true; } break; } } const auto buttonBindRange = ButtonBinds[static_cast(player)].equal_range(name); for (auto buttonBind = buttonBindRange.first; buttonBind != buttonBindRange.second; ++buttonBind) { switch (expectedInputState) { case InputState::Pressed: if (IsControllerButtonPressed(buttonBind->second.first, buttonBind->second.second)) { return true; } break; case InputState::Released: if (IsControllerButtonReleased(buttonBind->second.first, buttonBind->second.second)) { return true; } break; case InputState::Hold: if (IsControllerButtonHeld(buttonBind->second.first, buttonBind->second.second)) { return true; } break; } } return false; } void KInput::ProcessActions() { for (auto& action : Actions) { if (CheckAction(action.Name, action.ExpectedInputState, action.Player)) { if (auto obj = action.ControllerObject.lock()) std::invoke(action.Command, obj); } } } void KInput::ProcessAxes() { for (auto& axis : Axes) { if (auto obj = axis.ControllerObject.lock()) std::invoke(axis.AxisCommand, obj, GetAxisValue(axis.Name, axis.Player)); } } void KInput::AddAxisMapping(const std::string& name, const float& scale, const std::string& key, const KPlayer& player) { if (key.rfind("Gamepad", 0) == 0) { const auto delimiterPosition = key.find('_', 7); const auto controllerIndex = static_cast(std::stoi(key.substr(7, delimiterPosition))); auto axis = key.substr(delimiterPosition + 1); std::for_each(axis.begin(), axis.end(), [](char& c) { c = ::tolower(c); }); ControllerAxisBinds[static_cast(player)].emplace(name, KControllerAxisMapping(controllerIndex, SDL_GameControllerGetAxisFromString(axis.c_str()), scale)); } else { SDL_Scancode scanCode = SDL_GetScancodeFromName(key.c_str()); if (scanCode != SDL_SCANCODE_UNKNOWN) { KeyAxisBinds[static_cast(player)].emplace(name, std::make_pair(scanCode, scale)); } } } void KInput::AddActionMapping(const std::string& name, const std::string& key, const KPlayer& player) { if (key.rfind("Gamepad", 0) == 0) { const auto delimiterPosition = key.find('_', 7); const auto controllerIndex = static_cast(std::stoi(key.substr(7, delimiterPosition))); auto button = key.substr(delimiterPosition + 1); std::for_each(button.begin(), button.end(), [](char& c) { c = ::tolower(c); }); ButtonBinds[static_cast(player)].emplace(name, std::make_pair(controllerIndex, SDL_GameControllerGetButtonFromString(button.c_str()))); } else { SDL_Scancode scanCode = SDL_GetScancodeFromName(key.c_str()); if (scanCode != SDL_SCANCODE_UNKNOWN) { KeyBinds[static_cast(player)].emplace(name, scanCode); } } } bool KInput::IsControllerButtonPressed(const Controllers controllerId, const SDL_GameControllerButton button) const { if (controllerId < Controllers::Controller1 || controllerId > static_cast(GamePadsCount - 1)) return false; return ControllerInputs[static_cast(controllerId)].Buttons[button] && !LastControllerInputs[static_cast(controllerId)].Buttons[button]; } bool KInput::IsControllerButtonReleased(const Controllers controllerId, const SDL_GameControllerButton button) const { if (controllerId < Controllers::Controller1 || controllerId > static_cast(GamePadsCount - 1)) return false; return !ControllerInputs[static_cast(controllerId)].Buttons[button] && LastControllerInputs[static_cast(controllerId)].Buttons[button]; } bool KInput::IsControllerButtonHeld(const Controllers controllerId, const SDL_GameControllerButton button) const { if (controllerId < Controllers::Controller1 || controllerId > static_cast(GamePadsCount - 1)) return false; return ControllerInputs[static_cast(controllerId)].Buttons[button] && LastControllerInputs[static_cast(controllerId)].Buttons[button]; } float KInput::GetControllerAxis(const Controllers controllerId, const SDL_GameControllerAxis axis) const { if (controllerId static_cast(GamePadsCount - 1)) return 0.0; const int value = ControllerInputs[static_cast(controllerId)].Axis[axis]; if (std::abs(value) < Constants::JOYSTICK_DEAD_ZONE) return 0.0; return static_cast(value) / 32768.0f; } bool KInput::IsKeyboardButtonPressed(const Uint8 scanCode) const { if (scanCode > KeyboardInputs.size() || scanCode > LastKeyboardInputs.size()) return false; return KeyboardInputs[scanCode] && KeyboardInputs[scanCode] != LastKeyboardInputs[scanCode]; } bool KInput::IsKeyboardButtonReleased(const Uint8 scanCode) const { if (scanCode > KeyboardInputs.size() || scanCode > LastKeyboardInputs.size()) return false; return !KeyboardInputs[scanCode] && KeyboardInputs[scanCode] != LastKeyboardInputs[scanCode]; } float KInput::GetAxisValue(const std::string& name, const KPlayer& player) const { float value = 0.f; const auto keyAxisBindRange = KeyAxisBinds[static_cast(player)].equal_range(name); for (auto keyAxisBind = keyAxisBindRange.first; keyAxisBind != keyAxisBindRange.second; ++keyAxisBind) { if (IsKeyboardButtonHeld(keyAxisBind->second.first)) { value += 1.0f * keyAxisBind->second.second; } } const auto controllerAxisBindRange = ControllerAxisBinds[static_cast(player)].equal_range(name); for (auto controllerAxisBind = controllerAxisBindRange.first; controllerAxisBind != controllerAxisBindRange.second; ++controllerAxisBind) { value += GetControllerAxis(controllerAxisBind->second.Controller, controllerAxisBind->second.Axis) * controllerAxisBind->second.Scale; } return value; } bool KInput::IsKeyboardButtonHeld(const Uint8 scanCode) const { if (scanCode > KeyboardInputs.size()) return false; return KeyboardInputs[scanCode]; } }