diff --git a/game/graphics/display.cpp b/game/graphics/display.cpp index dbbc4da5b4..5e75124175 100644 --- a/game/graphics/display.cpp +++ b/game/graphics/display.cpp @@ -92,7 +92,6 @@ int InitMainDisplay(int width, lg::error("Failed to make main display."); return 1; } - display->set_imgui_visible(true); set_main_display(display); return 0; } diff --git a/game/graphics/gfx.cpp b/game/graphics/gfx.cpp index b98beec433..896d6b1bd1 100644 --- a/game/graphics/gfx.cpp +++ b/game/graphics/gfx.cpp @@ -14,6 +14,7 @@ #include "common/log/log.h" #include "common/symbols.h" #include "common/util/FileUtil.h" +#include "common/util/json_util.h" #include "game/common/file_paths.h" #include "game/kernel/common/kscheme.h" @@ -42,8 +43,8 @@ void InitSettings(GfxSettings& settings) { settings.pad_mapping_info.buffer_mode = true; // debug input settings settings.pad_mapping_info.debug = true; - // use a default mapping - Pad::DefaultMapping(settings.pad_mapping_info); + + Pad::DefaultMapping(Gfx::g_settings.pad_mapping_info); } } // namespace @@ -53,36 +54,180 @@ namespace Gfx { std::function vsync_callback; GfxGlobalSettings g_global_settings; GfxSettings g_settings; + +Pad::MappingInfo& get_button_mapping() { + return g_settings.pad_mapping_info; +} + // const std::vector renderers = {&moduleOpenGL}; -// TODO serialize -void LoadSettings() { - const auto filename = file_util::get_file_path({GAME_CONFIG_DIR_NAME, SETTINGS_GFX_FILE_NAME}); - if (fs::exists(filename)) { - // this is just wrong LOL - FILE* fp = file_util::open_file(filename.c_str(), "rb"); - lg::info("Found graphics configuration file. Checking version."); - u64 version; - fread(&version, sizeof(u64), 1, fp); - if (version == GfxSettings::CURRENT_VERSION) { - fseek(fp, 0, SEEK_SET); - fread(&g_settings, sizeof(GfxSettings), 1, fp); - lg::info("Loaded graphics configuration file."); - } else { - // TODO upgrade func - lg::info("Detected graphics configuration file from old version. Ignoring."); +// Not crazy about this declaration +const std::pair gamepad_map[] = {{"Select", Pad::Button::Select}, + {"L3", Pad::Button::L3}, + {"R3", Pad::Button::R3}, + {"Start", Pad::Button::Start}, + {"Up", Pad::Button::Up}, + {"Right", Pad::Button::Right}, + {"Down", Pad::Button::Down}, + {"Left", Pad::Button::Left}, + {"L1", Pad::Button::L1}, + {"R1", Pad::Button::R1}, + {"Triangle", Pad::Button::Triangle}, + {"Circle", Pad::Button::Circle}, + {"X", Pad::Button::X}, + {"Square", Pad::Button::Square}}; + +const std::pair analog_map[] = { + {"Left X Axis", Pad::Analog::Left_X}, + {"Left Y Axis", Pad::Analog::Left_Y}, + {"Right X Axis", Pad::Analog::Right_X}, + {"Right Y Axis", Pad::Analog::Right_Y}, +}; + +bool g_is_debug_menu_visible_on_startup = false; + +bool get_debug_menu_visible_on_startup() { + return g_is_debug_menu_visible_on_startup; +} + +void DumpToJson(ghc::filesystem::path& filename) { + nlohmann::json json; + json["Debug Menu Visibility"] = false; // Assume start up debug display is disabled + auto& peripherals_json = json["Peripherals"]; + + for (uint32_t i = 0; i < Pad::CONTROLLER_COUNT; ++i) { + nlohmann::json peripheral_json; + peripheral_json["ID"] = i + 1; + + auto& controller_json = peripheral_json["Controller"]; + auto& controller_buttons_json = controller_json["Buttons"]; + for (const auto& [name, value] : gamepad_map) { + controller_buttons_json[name] = + g_settings.pad_mapping_info.controller_button_mapping[i][(int)value]; } - fclose(fp); + + auto& keyboard_json = peripheral_json["Keyboard+Mouse"]; + auto& keyboard_buttons_json = keyboard_json["Buttons"]; + for (const auto& [name, value] : gamepad_map) { + keyboard_buttons_json[name] = + g_settings.pad_mapping_info.keyboard_button_mapping[i][(int)value]; + } + + auto& keyboard_analogs_json = keyboard_json["Analog"]; + for (const auto& [name, value] : analog_map) { + if (g_settings.pad_mapping_info.keyboard_analog_mapping[i][(int)value].mode == + Pad::AnalogMappingMode::AnalogInput) { + keyboard_analogs_json[name]["Axis Id"] = + g_settings.pad_mapping_info.keyboard_analog_mapping[i][(int)value].axis_id; + } else { + keyboard_analogs_json[name]["Positive Key"] = + g_settings.pad_mapping_info.keyboard_analog_mapping[i][(int)value].positive_key; + keyboard_analogs_json[name]["Negative Key"] = + g_settings.pad_mapping_info.keyboard_analog_mapping[i][(int)value].negative_key; + } + } + peripheral_json["X-Axis Mouse Sensitivity"] = + g_settings.pad_mapping_info.mouse_x_axis_sensitivities[i]; + peripheral_json["Y-Axis Mouse Sensitivity"] = + g_settings.pad_mapping_info.mouse_y_axis_sensitivities[i]; + peripherals_json.emplace_back(peripheral_json); + } + + file_util::write_text_file(filename, json.dump(4)); +} + +void SavePeripheralSettings() { + auto filename = (file_util::get_user_config_dir() / "controller" / "controller-settings.json"); + file_util::create_dir_if_needed_for_file(filename); + + DumpToJson(filename); + lg::info("Saved graphics configuration file."); +} + +void LoadPeripheralSettings(const ghc::filesystem::path& filepath) { + Pad::DefaultMapping(g_settings.pad_mapping_info); + + auto file_txt = file_util::read_text_file(filepath); + auto configuration = parse_commented_json(file_txt, filepath.string()); + + if (configuration.find("Debug Menu Visibility") != configuration.end()) { + g_is_debug_menu_visible_on_startup = configuration["Debug Menu Visibility"].get(); + } + + int controller_index = 0; + for (const auto& peripheral : configuration["Peripherals"]) { + auto& controller_buttons_json = peripheral["Controller"]["Buttons"]; + auto& keyboard_buttons_json = peripheral["Keyboard+Mouse"]["Buttons"]; + + for (const auto& [name, button] : gamepad_map) { + if (controller_buttons_json.find(name) != controller_buttons_json.end()) { + g_settings.pad_mapping_info.controller_button_mapping[controller_index][(int)button] = + controller_buttons_json[name].get(); + } else { + lg::warn( + "Controller button override not found for {}. Using controller default value: {}", name, + g_settings.pad_mapping_info.controller_button_mapping[controller_index][(int)button]); + } + + if (keyboard_buttons_json.find(name) != keyboard_buttons_json.end()) { + g_settings.pad_mapping_info.keyboard_button_mapping[controller_index][(int)button] = + keyboard_buttons_json[name].get(); + } else { + lg::warn( + "Keyboard button override not found for {}. Using keyboard default value: {}", name, + g_settings.pad_mapping_info.keyboard_button_mapping[controller_index][(int)button]); + } + } + + auto& keyboard_analogs_json = peripheral["Keyboard+Mouse"]["Analog"]; + for (const auto& [name, value] : analog_map) { + Pad::AnalogMappingInfo analog_mapping; + if (keyboard_analogs_json[name].contains("Axis Id") == true) { + analog_mapping.mode = Pad::AnalogMappingMode::AnalogInput; + analog_mapping.axis_id = keyboard_analogs_json[name]["Axis Id"].get(); + g_settings.pad_mapping_info.keyboard_analog_mapping[controller_index][(int)value] = + analog_mapping; + continue; + } + + if (keyboard_analogs_json[name].contains("Positive Key") == true) { + analog_mapping.positive_key = keyboard_analogs_json[name]["Positive Key"].get(); + } else { + lg::warn("Keyboard analog override not found for {}. Using keyboard default value: {}", + name, + g_settings.pad_mapping_info.keyboard_analog_mapping[controller_index][(int)value] + .positive_key); + } + + if (keyboard_analogs_json[name].contains("Negative Key") == true) { + analog_mapping.negative_key = keyboard_analogs_json[name]["Negative Key"].get(); + } else { + lg::warn("Keyboard analog override not found for {}. Using keyboard default value: {}", + name, + g_settings.pad_mapping_info.keyboard_analog_mapping[controller_index][(int)value] + .negative_key); + } + g_settings.pad_mapping_info.keyboard_analog_mapping[controller_index][(int)value] = + analog_mapping; + } + g_settings.pad_mapping_info.mouse_x_axis_sensitivities[controller_index] = + peripheral["X-Axis Mouse Sensitivity"].get(); + g_settings.pad_mapping_info.mouse_y_axis_sensitivities[controller_index] = + peripheral["Y-Axis Mouse Sensitivity"].get(); + controller_index++; } } -void SaveSettings() { - const auto filename = file_util::get_file_path({GAME_CONFIG_DIR_NAME, SETTINGS_GFX_FILE_NAME}); - file_util::create_dir_if_needed(file_util::get_file_path({GAME_CONFIG_DIR_NAME})); - FILE* fp = file_util::open_file(filename.c_str(), "wb"); - fwrite(&g_settings, sizeof(GfxSettings), 1, fp); - fclose(fp); - lg::info("Saved graphics configuration file."); +void LoadSettings() { + auto filename = (file_util::get_user_config_dir() / "controller" / "controller-settings.json"); + if (fs::exists(filename)) { + LoadPeripheralSettings(filename); + lg::info("Loaded graphics configuration file."); + return; + } else { + SavePeripheralSettings(); + lg::info("Couldn't find controller-settings.json creating new controller settings file."); + } } const GfxRendererModule* GetRenderer(GfxPipeline pipeline) { @@ -319,7 +464,7 @@ void input_mode_save() { g_settings.pad_mapping_info_backup = g_settings.pad_mapping_info; // copy to backup g_settings.pad_mapping_info = Pad::g_input_mode_mapping; // set current mapping - SaveSettings(); + SavePeripheralSettings(); } } @@ -328,15 +473,18 @@ s64 get_mapped_button(s64 pad, s64 button) { lg::error("Invalid parameters to get_mapped_button({}, {})", pad, button); return -1; } - return (s64)g_settings.pad_mapping_info.pad_mapping[pad][button]; + + return (Pad::GetGamepadState(pad) > -1) + ? (s64)g_settings.pad_mapping_info.controller_button_mapping[pad][button] + : (s64)g_settings.pad_mapping_info.keyboard_button_mapping[pad][button]; } int PadIsPressed(Pad::Button button, int port) { return Pad::IsPressed(g_settings.pad_mapping_info, button, port); } -int PadAnalogValue(Pad::Analog analog, int port) { - return Pad::AnalogValue(g_settings.pad_mapping_info, analog, port); +int PadGetAnalogValue(Pad::Analog analog, int port) { + return Pad::GetAnalogValue(g_settings.pad_mapping_info, analog, port); } void SetLod(RendererTreeType tree, int lod) { diff --git a/game/graphics/gfx.h b/game/graphics/gfx.h index 941ac790fc..5d61f78060 100644 --- a/game/graphics/gfx.h +++ b/game/graphics/gfx.h @@ -124,6 +124,8 @@ u32 Init(GameVersion version); void Loop(std::function f); u32 Exit(); +Pad::MappingInfo& get_button_mapping(); + u32 vsync(); void register_vsync_callback(std::function f); void clear_vsync_callback(); @@ -152,9 +154,10 @@ void set_msaa(int samples); void input_mode_set(u32 enable); void input_mode_save(); s64 get_mapped_button(s64 pad, s64 button); +bool get_debug_menu_visible_on_startup(); int PadIsPressed(Pad::Button button, int port); -int PadAnalogValue(Pad::Analog analog, int port); +int PadGetAnalogValue(Pad::Analog analog, int port); // matching enum in kernel-defs.gc !! enum class RendererTreeType { NONE = 0, TFRAG3 = 1, TIE3 = 2, INVALID }; diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 8602f83d8c..b89bf5d22e 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -78,6 +78,11 @@ struct GraphicsData { std::unique_ptr g_gfx_data; +std::atomic g_cursor_input_mode = GLFW_CURSOR_DISABLED; +bool is_cursor_position_valid = false; +double last_cursor_x_position = 0; +double last_cursor_y_position = 0; + struct { bool callbacks_registered = false; GLFWmonitor** monitors; @@ -209,6 +214,7 @@ static std::shared_ptr gl_make_display(int width, } auto display = std::make_shared(window, is_main); + // lg::debug("init display #x{:x}", (uintptr_t)display); // setup imgui @@ -255,6 +261,16 @@ GLDisplay::GLDisplay(GLFWwindow* window, bool is_main) : m_window(window) { display->on_key(window, key, scancode, action, mods); }); + glfwSetMouseButtonCallback(window, [](GLFWwindow* window, int button, int action, int mode) { + GLDisplay* display = reinterpret_cast(glfwGetWindowUserPointer(window)); + display->on_mouse_key(window, button, action, mode); + }); + + glfwSetCursorPosCallback(window, [](GLFWwindow* window, double xposition, double yposition) { + GLDisplay* display = reinterpret_cast(glfwGetWindowUserPointer(window)); + display->on_cursor_position(window, xposition, yposition); + }); + glfwSetWindowPosCallback(window, [](GLFWwindow* window, int xpos, int ypos) { GLDisplay* display = reinterpret_cast(glfwGetWindowUserPointer(window)); display->on_window_pos(window, xpos, ypos); @@ -289,6 +305,11 @@ GLDisplay::~GLDisplay() { } } +void GLDisplay::update_cursor_visibility(GLFWwindow* window, bool is_visible) { + g_cursor_input_mode = (is_visible) ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED; + glfwSetInputMode(window, GLFW_CURSOR, g_cursor_input_mode); +} + void GLDisplay::on_key(GLFWwindow* window, int key, int /*scancode*/, int action, int /*mods*/) { if (action == GlfwKeyAction::Press) { // lg::debug("KEY PRESS: key: {} scancode: {} mods: {:X}", key, scancode, mods); @@ -296,13 +317,63 @@ void GLDisplay::on_key(GLFWwindow* window, int key, int /*scancode*/, int action } else if (action == GlfwKeyAction::Release) { // lg::debug("KEY RELEASE: key: {} scancode: {} mods: {:X}", key, scancode, mods); Pad::OnKeyRelease(key); - if ((key == GLFW_KEY_LEFT_ALT || key == GLFW_KEY_RIGHT_ALT) && - glfwGetWindowAttrib(window, GLFW_FOCUSED)) { - set_imgui_visible(!is_imgui_visible()); + GLDisplay* display = reinterpret_cast(glfwGetWindowUserPointer(window)); + if (display != NULL) { // toggle ImGui when pressing Alt + if ((key == GLFW_KEY_LEFT_ALT || key == GLFW_KEY_RIGHT_ALT) && + glfwGetWindowAttrib(window, GLFW_FOCUSED)) { + display->set_imgui_visible(!display->is_imgui_visible()); + update_cursor_visibility(window, display->is_imgui_visible()); + } } } } +void GLDisplay::on_mouse_key(GLFWwindow* window, int button, int action, int mode) { + int key = + button + GLFW_KEY_LAST; // Mouse button index are appended after initial GLFW keys in newpad + + if (button == GLFW_MOUSE_BUTTON_LEFT && + g_cursor_input_mode == + GLFW_CURSOR_NORMAL) { // Are there any other mouse buttons we don't want to use? + Pad::ClearKey(key); + return; + } + + if (action == GlfwKeyAction::Press) { + Pad::OnKeyPress(key); + } else if (action == GlfwKeyAction::Release) { + Pad::OnKeyRelease(key); + } +} + +void GLDisplay::on_cursor_position(GLFWwindow* window, double xposition, double yposition) { + Pad::MappingInfo mapping_info = Gfx::get_button_mapping(); + if (g_cursor_input_mode == GLFW_CURSOR_NORMAL) { + if (is_cursor_position_valid == true) { + Pad::ClearAnalogAxisValue(mapping_info, GlfwKeyCustomAxis::CURSOR_X_AXIS); + Pad::ClearAnalogAxisValue(mapping_info, GlfwKeyCustomAxis::CURSOR_Y_AXIS); + is_cursor_position_valid = false; + } + return; + } + + if (is_cursor_position_valid == false) { + last_cursor_x_position = xposition; + last_cursor_y_position = yposition; + is_cursor_position_valid = true; + return; + } + + double xoffset = xposition - last_cursor_x_position; + double yoffset = yposition - last_cursor_y_position; + + Pad::SetAnalogAxisValue(mapping_info, GlfwKeyCustomAxis::CURSOR_X_AXIS, xoffset); + Pad::SetAnalogAxisValue(mapping_info, GlfwKeyCustomAxis::CURSOR_Y_AXIS, yoffset); + + last_cursor_x_position = xposition; + last_cursor_y_position = yposition; +} + void GLDisplay::on_window_pos(GLFWwindow* /*window*/, int xpos, int ypos) { if (m_fullscreen_target_mode == GfxDisplayMode::Windowed) { m_last_windowed_xpos = xpos; @@ -451,7 +522,8 @@ void GLDisplay::update_fullscreen(GfxDisplayMode mode, int screen) { x = m_last_windowed_xpos; y = m_last_windowed_ypos; } else { - // fullscreen -> windowed, use last windowed size but on the monitor previously fullscreened + // fullscreen -> windowed, use last windowed size but on the monitor previously + // fullscreened int monitorX, monitorY, monitorWidth, monitorHeight; glfwGetMonitorWorkarea(monitor, &monitorX, &monitorY, &monitorWidth, &monitorHeight); @@ -464,8 +536,8 @@ void GLDisplay::update_fullscreen(GfxDisplayMode mode, int screen) { glfwSetWindowAttrib(m_window, GLFW_DECORATED, GLFW_TRUE); glfwSetWindowFocusCallback(m_window, NULL); glfwSetWindowAttrib(m_window, GLFW_FLOATING, GLFW_FALSE); + glfwSetWindowMonitor(m_window, NULL, x, y, width, height, GLFW_DONT_CARE); - set_imgui_visible(true); // these might have changed m_last_windowed_width = width; @@ -480,7 +552,6 @@ void GLDisplay::update_fullscreen(GfxDisplayMode mode, int screen) { glfwSetWindowFocusCallback(m_window, NULL); glfwSetWindowAttrib(m_window, GLFW_FLOATING, GLFW_FALSE); glfwSetWindowMonitor(m_window, monitor, 0, 0, vmode->width, vmode->height, GLFW_DONT_CARE); - set_imgui_visible(false); } break; case GfxDisplayMode::Borderless: { // borderless fullscreen @@ -495,7 +566,6 @@ void GLDisplay::update_fullscreen(GfxDisplayMode mode, int screen) { #else glfwSetWindowMonitor(m_window, NULL, x, y, vmode->width, vmode->height, GLFW_DONT_CARE); #endif - set_imgui_visible(false); } break; } } @@ -602,7 +672,9 @@ void GLDisplay::render() { auto p = scoped_prof("poll-gamepads"); glfwPollEvents(); glfwMakeContextCurrent(m_window); - Pad::update_gamepads(); + + auto& mapping_info = Gfx::get_button_mapping(); + Pad::update_gamepads(mapping_info); } // imgui start of frame @@ -761,13 +833,15 @@ void gl_send_chain(const void* data, u32 offset) { // we copy the dma data and give a copy of it to the render. // the copy has a few advantages: // - if the game code has a bug and corrupts the DMA buffer, the renderer won't see it. - // - the copied DMA is much smaller than the entire game memory, so it can be dumped to a file + // - the copied DMA is much smaller than the entire game memory, so it can be dumped to a + // file // separate of the entire RAM. // - it verifies the DMA data is valid early on. - // but it may also be pretty expensive. Both the renderer and the game wait on this to complete. + // but it may also be pretty expensive. Both the renderer and the game wait on this to + // complete. - // The renderers should just operate on DMA chains, so eliminating this step in the future may - // be easy. + // The renderers should just operate on DMA chains, so eliminating this step in the future + // may be easy. g_gfx_data->dma_copier.set_input_data(data, offset, run_dma_copy); diff --git a/game/graphics/pipelines/opengl.h b/game/graphics/pipelines/opengl.h index ffe4a1b0be..40d53be35f 100644 --- a/game/graphics/pipelines/opengl.h +++ b/game/graphics/pipelines/opengl.h @@ -18,6 +18,11 @@ enum GlfwKeyAction { Repeat = GLFW_REPEAT // repeated input on hold e.g. when typing something }; +enum GlfwKeyCustomAxis { + CURSOR_X_AXIS = GLFW_GAMEPAD_AXIS_LAST + 1, + CURSOR_Y_AXIS = GLFW_GAMEPAD_AXIS_LAST + 2 +}; + class GLDisplay : public GfxDisplay { public: GLDisplay(GLFWwindow* window, bool is_main); @@ -42,6 +47,9 @@ class GLDisplay : public GfxDisplay { void on_window_pos(GLFWwindow* window, int xpos, int ypos); void on_window_size(GLFWwindow* window, int width, int height); void on_iconify(GLFWwindow* window, int iconified); + void on_mouse_key(GLFWwindow* window, int button, int action, int mode); + void on_cursor_position(GLFWwindow* window, double xposition, double yposition); + void update_cursor_visibility(GLFWwindow* window, bool is_visible); private: GLFWwindow* m_window; @@ -52,3 +60,6 @@ class GLDisplay : public GfxDisplay { }; extern const GfxRendererModule gRendererOpenGL; +namespace glfw { +static const int NUM_KEYS = GLFW_KEY_LAST + GLFW_MOUSE_BUTTON_LAST + 1; +} diff --git a/game/sce/libpad.cpp b/game/sce/libpad.cpp index d92410a3c8..823cf7884c 100644 --- a/game/sce/libpad.cpp +++ b/game/sce/libpad.cpp @@ -70,10 +70,10 @@ int scePadRead(int port, int /*slot*/, u8* rdata) { cpad->status = 0x70 /* (dualshock2) */ | (20 / 2); /* (dualshock2 data size) */ - cpad->rightx = Gfx::PadAnalogValue(Pad::Analog::Right_X, port); - cpad->righty = Gfx::PadAnalogValue(Pad::Analog::Right_Y, port); - cpad->leftx = Gfx::PadAnalogValue(Pad::Analog::Left_X, port); - cpad->lefty = Gfx::PadAnalogValue(Pad::Analog::Left_Y, port); + cpad->rightx = Gfx::PadGetAnalogValue(Pad::Analog::Right_X, port); + cpad->righty = Gfx::PadGetAnalogValue(Pad::Analog::Right_Y, port); + cpad->leftx = Gfx::PadGetAnalogValue(Pad::Analog::Left_X, port); + cpad->lefty = Gfx::PadGetAnalogValue(Pad::Analog::Left_Y, port); // pressure sensitivity. ignore for now. for (int i = 0; i < 12; ++i) { diff --git a/game/system/newpad.cpp b/game/system/newpad.cpp index 7931b15624..cde31c0dd0 100644 --- a/game/system/newpad.cpp +++ b/game/system/newpad.cpp @@ -6,6 +6,9 @@ #include "newpad.h" +#include +#include + #include "common/log/log.h" #include "common/util/Assert.h" #include "common/util/FileUtil.h" @@ -22,11 +25,12 @@ namespace Pad { ******************************** */ -constexpr int NUM_KEYS = GLFW_KEY_LAST + 1; // key-down status of any detected key. -bool g_key_status[NUM_KEYS] = {0}; +bool g_key_status[glfw::NUM_KEYS] = {0}; // key-down status of any detected key. this is buffered for the remainder of a frame. -bool g_buffered_key_status[NUM_KEYS] = {0}; +bool g_buffered_key_status[glfw::NUM_KEYS] = {0}; + +float g_key_analogs[CONTROLLER_COUNT][(int)Analog::Max] = {{0}}; bool g_gamepad_buttons[CONTROLLER_COUNT][(int)Button::Max] = {{0}}; float g_gamepad_analogs[CONTROLLER_COUNT][(int)Analog::Max] = {{0}}; @@ -44,6 +48,36 @@ u64 input_mode_mod = 0; u64 input_mode_index = 0; MappingInfo g_input_mode_mapping; +void ClearKey(int key) { + if (key < 0 || key > glfw::NUM_KEYS) { + lg::warn("ClearKey failed: Attempted to clear invalid key {}", key); + return; + } + + g_key_status[key] = false; + g_buffered_key_status[key] = false; +} + +void ClearAnalogAxisValue(MappingInfo& mapping_info, int axis) { + for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) { + for (int analog = 0; analog < (int)Analog::Max; ++analog) { + if (mapping_info.keyboard_analog_mapping[pad][analog].axis_id == axis && + mapping_info.keyboard_analog_mapping[pad][analog].mode == + AnalogMappingMode::AnalogInput) { + g_key_analogs[pad][analog] = 0.0f; + } + } + } +} + +void ForceClearAnalogValue() { + for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) { + for (int analog = 0; analog < (int)Analog::Max; ++analog) { + g_key_analogs[pad][analog] = 0.0f; + } + } +} + void ForceClearKeys() { for (auto& key : g_key_status) { key = false; @@ -54,7 +88,7 @@ void ForceClearKeys() { } void ClearKeys() { - for (int key = 0; key < NUM_KEYS; key++) { + for (int key = 0; key < glfw::NUM_KEYS; key++) { g_buffered_key_status[key] = g_key_status[key]; } } @@ -77,7 +111,7 @@ void OnKeyPress(int key) { return; } // set absolute key status - ASSERT(key < NUM_KEYS); + ASSERT(key < glfw::NUM_KEYS); g_key_status[key] = true; // set buffered key status g_buffered_key_status[key] = true; @@ -87,7 +121,7 @@ void OnKeyRelease(int key) { if (input_mode == InputModeStatus::Enabled) { return; } - ASSERT(key < NUM_KEYS); + ASSERT(key < glfw::NUM_KEYS); g_key_status[key] = false; } @@ -98,14 +132,15 @@ void OnKeyRelease(int key) { */ static int CheckPadIdx(int pad) { - if (pad < 0 || pad > CONTROLLER_COUNT) { + if (pad < 0 || pad >= CONTROLLER_COUNT) { lg::error("Invalid pad {}", pad); return -1; } return pad; } -// returns 1 if button is pressed. returns 0 if invalid or not pressed. +// returns 1 if either keyboard or controller button is pressed. Controller button has priority. +// returns 0 if invalid or not pressed. int IsPressed(MappingInfo& mapping, Button button, int pad = 0) { if (CheckPadIdx(pad) == -1) { return 0; @@ -114,67 +149,119 @@ int IsPressed(MappingInfo& mapping, Button button, int pad = 0) { if (g_gamepad_buttons[pad][(int)button]) { return 1; } - auto key = mapping.pad_mapping[pad][(int)button]; + + int key = mapping.keyboard_button_mapping[pad][(int)button]; if (key == -1) return 0; auto& keymap = mapping.buffer_mode ? g_buffered_key_status : g_key_status; - ASSERT(key < NUM_KEYS); + ASSERT(key < glfw::NUM_KEYS); return keymap[key]; } +void SetAnalogAxisValue(MappingInfo& mapping_info, int axis, double value) { + const double sensitivity_numerator = Gfx::g_global_settings.target_fps; + const double minimum_sensitivity = 1e-4; + + for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) { + for (int analog = 0; analog < (int)Analog::Max; ++analog) { + if (mapping_info.keyboard_analog_mapping[pad][analog].axis_id == axis) { + double newValue = value; + if (axis == GlfwKeyCustomAxis::CURSOR_X_AXIS) { + if (mapping_info.mouse_x_axis_sensitivities[pad] < minimum_sensitivity) { + mapping_info.mouse_x_axis_sensitivities[pad] = minimum_sensitivity; + } + newValue /= (sensitivity_numerator / mapping_info.mouse_x_axis_sensitivities[pad]); + } else if (axis == GlfwKeyCustomAxis::CURSOR_Y_AXIS) { + if (mapping_info.mouse_y_axis_sensitivities[pad] < minimum_sensitivity) { + mapping_info.mouse_y_axis_sensitivities[pad] = minimum_sensitivity; + } + newValue /= (sensitivity_numerator / mapping_info.mouse_y_axis_sensitivities[pad]); + } + + if (newValue > 1.0) { + g_key_analogs[pad][analog] = 1.0; + } else if (newValue < -1.0) { + g_key_analogs[pad][analog] = -1.0; + } else if (std::isnan(newValue)) { + g_key_analogs[pad][analog] = 0.0; + } else { + g_key_analogs[pad][analog] = newValue; + } + + // Invert logic used here. Left Y axis movement is based on towrds the camera. + // In game forward is treated as going away from the camera and backwards is headed towards + // the camera. + if (axis == GlfwKeyCustomAxis::CURSOR_Y_AXIS) { + g_key_analogs[pad][analog] *= -1; + } + } + } + } +} + +void UpdateAxisValue(MappingInfo& mapping_info) { + for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) { + for (int analog = 0; analog < (int)Analog::Max; ++analog) { + if (mapping_info.keyboard_analog_mapping[pad][analog].mode == + AnalogMappingMode::AnalogInput) { + continue; // Assumed Set Axis set value already + } + + // Invert logic used here. Left Y axis movement is based on towrds the camera. + // In game forward is treated as going away from the camera and backwards is headed towards + // the camera. + double input = 0.0f; + if (mapping_info.keyboard_analog_mapping[pad][analog].positive_key > -1 && + mapping_info.keyboard_analog_mapping[pad][analog].positive_key < glfw::NUM_KEYS) { + if (analog == static_cast(Analog::Left_Y) || + analog == static_cast(Analog::Right_Y)) { + input -= + g_buffered_key_status[mapping_info.keyboard_analog_mapping[pad][analog].positive_key]; + } else { + input += + g_buffered_key_status[mapping_info.keyboard_analog_mapping[pad][analog].positive_key]; + } + } + if (mapping_info.keyboard_analog_mapping[pad][analog].negative_key > -1 && + mapping_info.keyboard_analog_mapping[pad][analog].negative_key < glfw::NUM_KEYS) { + if (analog == static_cast(Analog::Left_Y) || + analog == static_cast(Analog::Right_Y)) { + input += + g_buffered_key_status[mapping_info.keyboard_analog_mapping[pad][analog].negative_key]; + } else { + input -= + g_buffered_key_status[mapping_info.keyboard_analog_mapping[pad][analog].negative_key]; + } + } + g_key_analogs[pad][analog] = input; + } + } +} + // returns the value of the analog axis (in the future, likely pressure sensitive if we support it?) // if invalid or otherwise -- returns 127 (analog stick neutral position) -int AnalogValue(MappingInfo& /*mapping*/, Analog analog, int pad = 0) { +int GetAnalogValue(MappingInfo& /*mapping*/, Analog analog, int pad = 0) { + float input = 0.0f; if (CheckPadIdx(pad) == -1) { // Pad out of range, return a stable value return 127; } - float input = 0.0f; - if (pad == 0) { - // Movement controls mapped to WASD keys - if (g_buffered_key_status[GLFW_KEY_W] && analog == Analog::Left_Y) - input += -1.0f; - if (g_buffered_key_status[GLFW_KEY_S] && analog == Analog::Left_Y) - input += 1.0f; - if (g_buffered_key_status[GLFW_KEY_A] && analog == Analog::Left_X) - input += -1.0f; - if (g_buffered_key_status[GLFW_KEY_D] && analog == Analog::Left_X) - input += 1.0f; - - // Camera controls mapped to IJKL keys - if (g_buffered_key_status[GLFW_KEY_I] && analog == Analog::Right_Y) - input += -1.0f; - if (g_buffered_key_status[GLFW_KEY_K] && analog == Analog::Right_Y) - input += 1.0f; - if (g_buffered_key_status[GLFW_KEY_J] && analog == Analog::Right_X) - input += -1.0f; - if (g_buffered_key_status[GLFW_KEY_L] && analog == Analog::Right_X) - input += 1.0f; - } else if (pad == 1) { - // these bindings are not sane - if (g_buffered_key_status[GLFW_KEY_KP_5] && analog == Analog::Left_Y) - input += -1.0f; - if (g_buffered_key_status[GLFW_KEY_KP_2] && analog == Analog::Left_Y) - input += 1.0f; - if (g_buffered_key_status[GLFW_KEY_KP_1] && analog == Analog::Left_X) - input += -1.0f; - if (g_buffered_key_status[GLFW_KEY_KP_3] && analog == Analog::Left_X) - input += 1.0f; - - // these bindings are not sane - if (g_buffered_key_status[GLFW_KEY_KP_DIVIDE] && analog == Analog::Right_Y) - input += -1.0f; - if (g_buffered_key_status[GLFW_KEY_KP_8] && analog == Analog::Right_Y) - input += 1.0f; - if (g_buffered_key_status[GLFW_KEY_KP_7] && analog == Analog::Right_X) - input += -1.0f; - if (g_buffered_key_status[GLFW_KEY_KP_9] && analog == Analog::Right_X) - input += 1.0f; + float controller_input = 0.0f; + if (g_gamepads.gamepad_idx[pad] > -1) { + controller_input = g_gamepad_analogs[pad][(int)analog]; } - if (input == 0) { - input = g_gamepad_analogs[pad][(int)analog]; + float keyboard_input = g_key_analogs[pad][(int)analog]; + // Hack. Clearing the buffer immediately can lead to inconsistencies on analog input. + // If a mouse is disconnected or can't calculate a new delta it would stay stuck at 1. + // Decreasing the values gradually seems like a good comprise. + g_key_analogs[pad][(int)analog] *= 0.95; + + if (fabs(controller_input) > fabs(keyboard_input)) { + input = controller_input; + } else { + input = keyboard_input; } // GLFW provides float in range -1 to 1, caller expects 0-255 @@ -195,15 +282,62 @@ void MapButton(MappingInfo& mapping, Button button, int pad, int key) { return; } - mapping.pad_mapping[pad][(int)button] = key; + if (g_gamepads.gamepad_idx[pad] == -1) { + // TODO: Check if other pad is keyboard and if key is already bound + mapping.keyboard_button_mapping[pad][(int)button] = key; + } else { + mapping.controller_button_mapping[pad][(int)button] = key; + } +} + +void MapAnalog(MappingInfo& mapping, Analog button, int pad, AnalogMappingInfo& analomapping_info) { + // check if pad is valid. dont map buttons with invalid pads. + if (CheckPadIdx(pad) == -1) { + return; + } + + if (g_gamepads.gamepad_idx[pad] == -1) { + // TODO: Check if other pad is keyboard and if key is already bound + mapping.keyboard_analog_mapping[pad][(int)button] = analomapping_info; + } else { + mapping.controller_analog_mapping[pad][(int)button] = analomapping_info; + } } // reset button mappings void DefaultMapping(MappingInfo& mapping) { // make every button invalid - for (int p = 0; p < CONTROLLER_COUNT; ++p) { - for (int i = 0; i < (int)Button::Max; ++i) { - MapButton(mapping, (Button)i, p, -1); + for (int32_t pad = 0; pad < CONTROLLER_COUNT; ++pad) { + for (int32_t button = 0; button < (int)Pad::Button::Max; ++button) { + mapping.controller_button_mapping[pad][button] = -1; + mapping.keyboard_button_mapping[pad][button] = -1; + } + + for (int32_t analog = 0; analog < (int)Pad::Analog::Max; ++analog) { + mapping.controller_analog_mapping[pad][analog] = AnalogMappingInfo(); + mapping.keyboard_analog_mapping[pad][analog] = AnalogMappingInfo(); + } + } + + constexpr std::pair gamepad_map[] = { + {Button::Select, GLFW_GAMEPAD_BUTTON_BACK}, + {Button::L3, GLFW_GAMEPAD_BUTTON_LEFT_THUMB}, + {Button::R3, GLFW_GAMEPAD_BUTTON_RIGHT_THUMB}, + {Button::Start, GLFW_GAMEPAD_BUTTON_START}, + {Button::Up, GLFW_GAMEPAD_BUTTON_DPAD_UP}, + {Button::Right, GLFW_GAMEPAD_BUTTON_DPAD_RIGHT}, + {Button::Down, GLFW_GAMEPAD_BUTTON_DPAD_DOWN}, + {Button::Left, GLFW_GAMEPAD_BUTTON_DPAD_LEFT}, + {Button::L1, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER}, + {Button::R1, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER}, + {Button::Triangle, GLFW_GAMEPAD_BUTTON_TRIANGLE}, + {Button::Circle, GLFW_GAMEPAD_BUTTON_CIRCLE}, + {Button::X, GLFW_GAMEPAD_BUTTON_CROSS}, + {Button::Square, GLFW_GAMEPAD_BUTTON_SQUARE}}; + + for (int32_t pad = 0; pad < CONTROLLER_COUNT; ++pad) { + for (const auto& [button, value] : gamepad_map) { + mapping.controller_button_mapping[pad][(int)button] = value; } } @@ -212,6 +346,8 @@ void DefaultMapping(MappingInfo& mapping) { // // Need someway to toggle off -- where do we have access to the game's settings? + // TODO - What should the second pc default controls be? + // R1 / L1 MapButton(mapping, Button::L1, 0, GLFW_KEY_Q); MapButton(mapping, Button::R1, 0, GLFW_KEY_O); @@ -238,6 +374,31 @@ void DefaultMapping(MappingInfo& mapping) { // l3/r3 for menu MapButton(mapping, Button::L3, 0, GLFW_KEY_COMMA); MapButton(mapping, Button::R3, 0, GLFW_KEY_PERIOD); + + AnalogMappingInfo analomapping_info; + + analomapping_info.positive_key = GLFW_KEY_D; + analomapping_info.negative_key = GLFW_KEY_A; + MapAnalog(mapping, Analog::Left_X, 0, analomapping_info); + + analomapping_info.positive_key = GLFW_KEY_W; + analomapping_info.negative_key = GLFW_KEY_S; + MapAnalog(mapping, Analog::Left_Y, 0, analomapping_info); + + analomapping_info.mode = AnalogMappingMode::AnalogInput; + analomapping_info.axis_id = GlfwKeyCustomAxis::CURSOR_X_AXIS; + MapAnalog(mapping, Analog::Right_X, 0, analomapping_info); + + analomapping_info.axis_id = GlfwKeyCustomAxis::CURSOR_Y_AXIS; + MapAnalog(mapping, Analog::Right_Y, 0, analomapping_info); + + const double default_mouse_x_sensitivity = 5.0f; + const double default_mouse_y_sensitivity = 2.0f; + + for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) { + mapping.mouse_x_axis_sensitivities[pad] = default_mouse_x_sensitivity; + mapping.mouse_y_axis_sensitivities[pad] = default_mouse_y_sensitivity; + } } void EnterInputMode() { @@ -315,29 +476,21 @@ void clear_pad(int pad) { for (int i = 0; i < (int)Button::Max; ++i) { g_gamepad_buttons[pad][i] = false; } - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < (int)Analog::Max; ++i) { g_gamepad_analogs[pad][i] = 0; } } -void update_gamepads() { +void update_gamepads(MappingInfo& mapping_info) { check_gamepads(); + UpdateAxisValue(mapping_info); - constexpr std::pair gamepad_map[] = { - {Button::Select, GLFW_GAMEPAD_BUTTON_BACK}, - {Button::L3, GLFW_GAMEPAD_BUTTON_LEFT_THUMB}, - {Button::R3, GLFW_GAMEPAD_BUTTON_RIGHT_THUMB}, - {Button::Start, GLFW_GAMEPAD_BUTTON_START}, - {Button::Up, GLFW_GAMEPAD_BUTTON_DPAD_UP}, - {Button::Right, GLFW_GAMEPAD_BUTTON_DPAD_RIGHT}, - {Button::Down, GLFW_GAMEPAD_BUTTON_DPAD_DOWN}, - {Button::Left, GLFW_GAMEPAD_BUTTON_DPAD_LEFT}, - {Button::L1, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER}, - {Button::R1, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER}, - {Button::Triangle, GLFW_GAMEPAD_BUTTON_TRIANGLE}, - {Button::Circle, GLFW_GAMEPAD_BUTTON_CIRCLE}, - {Button::X, GLFW_GAMEPAD_BUTTON_CROSS}, - {Button::Square, GLFW_GAMEPAD_BUTTON_SQUARE}}; + if (g_gamepads.gamepad_idx[0] == -1) { + for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) { + clear_pad(pad); + } + return; + } constexpr std::pair gamepad_analog_map[] = { {Analog::Left_X, GLFW_GAMEPAD_AXIS_LEFT_X}, @@ -345,12 +498,13 @@ void update_gamepads() { {Analog::Right_X, GLFW_GAMEPAD_AXIS_RIGHT_X}, {Analog::Right_Y, GLFW_GAMEPAD_AXIS_RIGHT_Y}}; - auto read_pad_state = [gamepad_map, gamepad_analog_map](int pad) { + auto read_pad_state = [gamepad_analog_map, mapping_info](int pad) { GLFWgamepadstate state; glfwGetGamepadState(g_gamepads.gamepad_idx[pad], &state); - for (const auto& [button, idx] : gamepad_map) { - g_gamepad_buttons[pad][(int)button] = state.buttons[idx]; + for (int32_t button = 0; button < (int)Pad::Button::Max; ++button) { + int key = mapping_info.controller_button_mapping[pad][button]; + g_gamepad_buttons[pad][(int)button] = state.buttons[key]; } g_gamepad_buttons[pad][(int)Button::L2] = state.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER] > 0; @@ -377,4 +531,37 @@ int rumble(int pad, float slow_motor, float fast_motor) { return 0; } +int GetGamepadState(int pad) { + return g_gamepads.gamepad_idx[pad]; +} + +// The following setters/getters are mainly used for unit tests +void SetGamepadState(int pad, int pad_index) { + if (CheckPadIdx(pad) != -1) { + if (pad_index <= GLFW_JOYSTICK_LAST) { + g_gamepads.gamepad_idx[pad] = pad_index; + } + } +} + +bool* GetKeyboardInputBuffer() { + return g_key_status; +} +// key-down status of any detected key. this is buffered for the remainder of a frame. +bool* GetKeyboardBufferedInputBuffer() { + return g_buffered_key_status; +} + +float* GetKeyboardInputAnalogBuffer(int pad) { + return g_key_analogs[pad]; +} + +bool* GetControllerInputBuffer(int pad) { + return g_gamepad_buttons[pad]; +} + +float* GetControllerAnalogInputBuffer(int pad) { + return g_gamepad_analogs[pad]; +} + }; // namespace Pad diff --git a/game/system/newpad.h b/game/system/newpad.h index 0d5d994e54..0eb2e3ed30 100644 --- a/game/system/newpad.h +++ b/game/system/newpad.h @@ -60,23 +60,48 @@ enum class Button { O = Circle }; +enum class AnalogMappingMode { DigitalInput = 0, AnalogInput = 1 }; + +// AnalogMappingInfo allows either button or axes to control analog input(s). +// * In Digital Input Mode, uses both positive_key and negative_key as button indices. +// * In Analog Input mode, only the positive_key is used. +// - The positive_key in Analog Input mode represents an analog axis (i.e +// GLFW_GAMEPAD_AXIS_RIGHT_Y) +struct AnalogMappingInfo { + AnalogMappingMode mode = AnalogMappingMode::DigitalInput; + int axis_id = -1; + int positive_key = -1; + int negative_key = -1; +}; + struct MappingInfo { bool debug = true; // debug mode bool buffer_mode = true; // use buffered inputs - int pad_mapping[CONTROLLER_COUNT][(int)Pad::Button::Max]; // controller button mapping + int controller_button_mapping[CONTROLLER_COUNT][(int)Pad::Button::Max]; + AnalogMappingInfo controller_analog_mapping[CONTROLLER_COUNT][(int)Pad::Analog::Max]; + + int keyboard_button_mapping[CONTROLLER_COUNT][( + int)Pad::Button::Max]; // Back up in case controller gets disconnected + AnalogMappingInfo keyboard_analog_mapping[CONTROLLER_COUNT][(int)Pad::Analog::Max]; + double mouse_x_axis_sensitivities[CONTROLLER_COUNT]; + double mouse_y_axis_sensitivities[CONTROLLER_COUNT]; // TODO complex button mapping & key macros (e.g. shift+x for l2+r2 press etc.) }; void OnKeyPress(int key); void OnKeyRelease(int key); +void ClearKey(int key); void ForceClearKeys(); void ClearKeys(); void DefaultMapping(MappingInfo& mapping); int IsPressed(MappingInfo& mapping, Button button, int pad); -int AnalogValue(MappingInfo& mapping, Analog analog, int pad); +int GetAnalogValue(MappingInfo& mapping, Analog analog, int pad); void MapButton(MappingInfo& mapping, Button button, int pad, int key); +void MapAnalog(MappingInfo& mapping, Button button, int pad, AnalogMappingInfo& analogMapping); +void SetAnalogAxisValue(MappingInfo& mapping, int axis, double value); +void ClearAnalogAxisValue(MappingInfo& mapping, int axis); // this enum is also in pc-pad-utils.gc enum class InputModeStatus { Disabled, Enabled, Canceled }; @@ -90,7 +115,18 @@ u64 input_mode_get_index(); void input_mode_pad_set(s64); void initialize(); -void update_gamepads(); +void update_gamepads(MappingInfo& mapping_info); int rumble(int pad, float slow_motor, float fast_motor); +int GetGamepadState(int pad); +void ForceClearAnalogValue(); +void clear_pad(int pad); + +void UpdateAxisValue(MappingInfo& mapping_info); +void SetGamepadState(int pad, int pad_index); +bool* GetKeyboardInputBuffer(); +bool* GetKeyboardBufferedInputBuffer(); +float* GetKeyboardInputAnalogBuffer(int pad); +bool* GetControllerInputBuffer(int pad); +float* GetControllerAnalogInputBuffer(int pad); } // namespace Pad diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1e76c35927..949435a2b9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -34,6 +34,7 @@ add_executable(goalc-test ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_DataParser.cpp ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_DisasmVifDecompile.cpp ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_VuDisasm.cpp + ${CMAKE_CURRENT_LIST_DIR}/game/test_newpad.cpp ${GOALC_TEST_FRAMEWORK_SOURCES} ${GOALC_TEST_CASES}) diff --git a/test/game/test_newpad.cpp b/test/game/test_newpad.cpp new file mode 100644 index 0000000000..6308324d3e --- /dev/null +++ b/test/game/test_newpad.cpp @@ -0,0 +1,311 @@ + +#include +#include + +#include "game/graphics/pipelines/opengl.h" +#include "game/system/newpad.h" +#include "gtest/gtest.h" +//#include "gmock/gmock.h" + +class PeripheralTest : public ::testing::Test { + public: + PeripheralTest() { + Pad::ForceClearKeys(); + Pad::ForceClearAnalogValue(); + + for (int i = 0; i < Pad::CONTROLLER_COUNT; ++i) { + Pad::clear_pad(i); + Pad::SetGamepadState(i, -1); + } + Pad::DefaultMapping(mapping_info_); + }; + ~PeripheralTest(){}; + + protected: + Pad::MappingInfo mapping_info_; +}; + +TEST_F(PeripheralTest, UpdatePad_KeyboardPad_ClearsControllerInputBuffers) { + // Arrange + bool expected_controller_status = false; + + bool* controller_input_buffer = Pad::GetControllerInputBuffer(0); + ::memset(controller_input_buffer, true, + sizeof(controller_input_buffer[0]) * (int)Pad::Button::Max); + + // Act + Pad::update_gamepads(mapping_info_); + + // Assert + for (int i = 0; i < (int)Pad::Button::Max; ++i) { + EXPECT_EQ(expected_controller_status, controller_input_buffer[i]); + } +} +TEST_F(PeripheralTest, ClearKey_ValidKey_UpdateKeyboardBuffer) { + // Arrange + int input_key = 127; + bool expected_buffer_key_status = false; + + bool* actual_keyboard_buffer = Pad::GetKeyboardBufferedInputBuffer(); + ::memset(actual_keyboard_buffer, true, sizeof(*actual_keyboard_buffer) * glfw::NUM_KEYS); + + // Act + Pad::ClearKey(input_key); + + // Assert + bool actual_buffer_key_status = actual_keyboard_buffer[input_key]; + EXPECT_EQ(expected_buffer_key_status, actual_buffer_key_status); +} +TEST_F(PeripheralTest, ClearKey_InvalidKey_DoNothing) { + // Arrange + int input_key = glfw::NUM_KEYS + 1; + bool expected_buffer_key_status = true; + + bool* actual_keyboard_buffer = Pad::GetKeyboardBufferedInputBuffer(); + ::memset(actual_keyboard_buffer, true, sizeof(*actual_keyboard_buffer) * glfw::NUM_KEYS); + + // Act + Pad::ClearKey(input_key); + + // Assert + for (int i = 0; i < glfw::NUM_KEYS; ++i) { + EXPECT_EQ(expected_buffer_key_status, actual_keyboard_buffer[i]); + } +} +TEST_F(PeripheralTest, SetAnalogAxisValue_NominalAnalogAxisX_SetConvertedValue) { + // Arrange + float expected_analog_value = 0; + std::swap(mapping_info_.controller_analog_mapping[0][(int)Pad::Analog::Left_X], + mapping_info_.controller_analog_mapping[0][(int)Pad::Analog::Right_X]); + + // Act + Pad::SetAnalogAxisValue(mapping_info_, static_cast(GlfwKeyCustomAxis::CURSOR_X_AXIS), 100.0); + + // Assert + float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(0); + EXPECT_FLOAT_EQ(expected_analog_value, keyboard_analog_buffer[(int)Pad::Analog::Left_X]); +} +TEST_F(PeripheralTest, SetAnalogAxisValue_NominalAnalogAxisY_SetConvertedValue) { // Arrange + float expected_analog_value = 0; + std::swap(mapping_info_.controller_analog_mapping[0][(int)Pad::Analog::Left_Y], + mapping_info_.controller_analog_mapping[0][(int)Pad::Analog::Right_Y]); + + // Act + Pad::SetAnalogAxisValue(mapping_info_, static_cast(GlfwKeyCustomAxis::CURSOR_X_AXIS), 100.0); + + // Assert + float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(0); + EXPECT_FLOAT_EQ(expected_analog_value, keyboard_analog_buffer[(int)Pad::Analog::Left_Y]); +} +TEST_F(PeripheralTest, SetAnalogAxisValue_InputLargerThanMaxValue_SetMaxValue) { + // Arrange + float expected_analog_value = 1; + + // Act + Pad::SetAnalogAxisValue(mapping_info_, static_cast(GlfwKeyCustomAxis::CURSOR_X_AXIS), 100.0); + + // Assert + float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(0); + EXPECT_FLOAT_EQ(expected_analog_value, keyboard_analog_buffer[(int)Pad::Analog::Right_X]); +} +TEST_F(PeripheralTest, SetAnalogAxisValue_InputSmallerThanMinValue_SetMinValue) { + // Arrange + float expected_analog_value = -1; + + // Act + Pad::SetAnalogAxisValue(mapping_info_, static_cast(GlfwKeyCustomAxis::CURSOR_X_AXIS), + -100.0); + + // Assert + float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(0); + EXPECT_FLOAT_EQ(expected_analog_value, keyboard_analog_buffer[(int)Pad::Analog::Right_X]); +} +TEST_F(PeripheralTest, SetAnalogAxisValue_InputIsNAN_SetZero) { + // Arrange + float expected_analog_value = 0; + + // Act + Pad::SetAnalogAxisValue(mapping_info_, static_cast(GlfwKeyCustomAxis::CURSOR_X_AXIS), + std::nan("1")); + + // Assert + float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(0); + EXPECT_FLOAT_EQ(expected_analog_value, keyboard_analog_buffer[(int)Pad::Analog::Right_X]); +} +TEST_F( + PeripheralTest, + SetAnalogAxisValue_MouseXAxisSensitivityLowerThanMinimumSensitivty_SetMouseSensitivityToMinimum) { + // Arrange + float expected_x_axis_sensitivity = 1e-4; + mapping_info_.mouse_x_axis_sensitivities[0] = 0; + + // Act + Pad::SetAnalogAxisValue(mapping_info_, static_cast(GlfwKeyCustomAxis::CURSOR_X_AXIS), 100); + + // Assert + EXPECT_FLOAT_EQ(expected_x_axis_sensitivity, mapping_info_.mouse_x_axis_sensitivities[0]); +} +TEST_F( + PeripheralTest, + SetAnalogAxisValue_MouseYAxisSensitivityLowerThanMinimumSensitivty_SetMouseSensitivityToMinimum) { + // Arrange + float expected_y_axis_sensitivity = 1e-4; + mapping_info_.mouse_y_axis_sensitivities[0] = 0; + + // Act + Pad::SetAnalogAxisValue(mapping_info_, static_cast(GlfwKeyCustomAxis::CURSOR_Y_AXIS), 100); + + // Assert + EXPECT_FLOAT_EQ(expected_y_axis_sensitivity, mapping_info_.mouse_y_axis_sensitivities[0]); +} + +TEST_F(PeripheralTest, UpdateAxisValue_XAxisPositiveKey_IncrementValue) { + // Arrange + float expected_analog_value = 1.0f; + int pad_index = 0; + int key = GLFW_KEY_D; + bool* keyboard_buffered_key_buffer = Pad::GetKeyboardBufferedInputBuffer(); + keyboard_buffered_key_buffer[key] = true; + + // Act + Pad::UpdateAxisValue(mapping_info_); + + // Arrange + float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(pad_index); + float actual_analog_value = keyboard_analog_buffer[(int)Pad::Analog::Left_X]; + EXPECT_FLOAT_EQ(expected_analog_value, actual_analog_value); +} +TEST_F(PeripheralTest, UpdateAxisValue_YAxisPositiveKey_DecrementValue) { + // Arrange + float expected_analog_value = -1.0f; + int pad_index = 0; + int key = GLFW_KEY_W; + bool* keyboard_buffered_key_buffer = Pad::GetKeyboardBufferedInputBuffer(); + keyboard_buffered_key_buffer[key] = true; + + // Act + Pad::UpdateAxisValue(mapping_info_); + + // Arrange + float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(pad_index); + float actual_analog_value = keyboard_analog_buffer[(int)Pad::Analog::Left_Y]; + EXPECT_FLOAT_EQ(expected_analog_value, actual_analog_value); +} +TEST_F(PeripheralTest, UpdateAxisValue_XAxisNegativeKey_DecrementValue) { + // Arrange + int pad_index = 0; + float expected_analog_value = -1.0f; + + int key = GLFW_KEY_A; + bool* keyboard_buffered_key_buffer = Pad::GetKeyboardBufferedInputBuffer(); + keyboard_buffered_key_buffer[key] = true; + + // Act + Pad::UpdateAxisValue(mapping_info_); + + // Arrange + float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(pad_index); + float actual_analog_value = keyboard_analog_buffer[(int)Pad::Analog::Left_X]; + EXPECT_FLOAT_EQ(expected_analog_value, actual_analog_value); +} + +TEST_F(PeripheralTest, UpdateAxisValue_RightYAxisPositiveKey_IncrementValue) { + // Arrange + float expected_analog_value = 1.0f; + int pad_index = 0; + int key = GLFW_KEY_S; + bool* keyboard_buffered_key_buffer = Pad::GetKeyboardBufferedInputBuffer(); + keyboard_buffered_key_buffer[key] = true; + + // Act + Pad::UpdateAxisValue(mapping_info_); + + // Arrange + float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(pad_index); + float actual_analog_value = keyboard_analog_buffer[(int)Pad::Analog::Left_Y]; + EXPECT_FLOAT_EQ(expected_analog_value, actual_analog_value); +} + +TEST_F(PeripheralTest, GetAnalogValue_InvalidPadId_ReturnsNeutralPosition) { + // Arrange + int expected_analog_status = 127; + + // Act + int actual_analog_status = Pad::GetAnalogValue(mapping_info_, Pad::Analog::Left_X, 2); + + // Assert + EXPECT_EQ(expected_analog_status, actual_analog_status); +} +TEST_F(PeripheralTest, GetAnalogValue_ControllerPad_ReturnsAnalogValue) { + // Arrange + int expected_analog_status = 0; + + int pad_index = 0; + int controller_index = 0; + Pad::SetGamepadState(pad_index, controller_index); + + float* controller_analog_buffer = Pad::GetControllerAnalogInputBuffer(pad_index); + controller_analog_buffer[(int)Pad::Analog::Left_X] = -1.0f; + + // Act + int actual_analog_status = Pad::GetAnalogValue(mapping_info_, Pad::Analog::Left_X, 0); + + // Assert + EXPECT_EQ(expected_analog_status, actual_analog_status); +} +TEST_F(PeripheralTest, GetAnalogValue_KeyboardPad_ReturnsAnalogValue) { + // Arrange + int expected_analog_status = 255; + int pad_index = 0; + + float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(pad_index); + keyboard_analog_buffer[(int)Pad::Analog::Left_X] = 1.0f; + + // Act + int actual_analog_status = Pad::GetAnalogValue(mapping_info_, Pad::Analog::Left_X, 0); + + // Assert + EXPECT_EQ(expected_analog_status, actual_analog_status); +} + +TEST_F(PeripheralTest, IsPressed_InvalidPadId_ReturnsFalse) { + // Arrange + int expected_button_status = 0; + + // Act + int actual_button_status = Pad::IsPressed(mapping_info_, Pad::Button::X, 2); + + // Assert + EXPECT_EQ(expected_button_status, actual_button_status); +} +TEST_F(PeripheralTest, IsPressed_ControllerPad_ReturnsControllerBufferValue) { + // Arrange + int expected_button_status = 1; + + int pad_index = 0; + int controller_index = 0; + Pad::SetGamepadState(pad_index, controller_index); + + bool* controller_button_status_buffer = Pad::GetControllerInputBuffer(pad_index); + controller_button_status_buffer[(int)Pad::Button::X] = expected_button_status; + + // Act + int actual_button_status = Pad::IsPressed(mapping_info_, Pad::Button::X, pad_index); + + // Assert + EXPECT_EQ(expected_button_status, actual_button_status); +} +TEST_F(PeripheralTest, IsPressed_KeyboardPad_ReturnsKeyboardBufferValue) { + // Arrange + int expected_button_status = 1; + bool* keyboard_buffered_key_status_buffer = Pad::GetKeyboardBufferedInputBuffer(); + keyboard_buffered_key_status_buffer[GLFW_KEY_SPACE] = expected_button_status; + + // Act + int actual_button_status = Pad::IsPressed(mapping_info_, Pad::Button::X, 0); + + // Assert + EXPECT_EQ(expected_button_status, actual_button_status); +} + +// TODO: InputModeStatus Tests