mirror of https://github.com/AxioDL/metaforce.git
Initial controller configuration support
This commit is contained in:
parent
834bc8d183
commit
d1b4a71110
|
@ -218,6 +218,7 @@ endif ()
|
||||||
|
|
||||||
add_executable(metaforce CMain.cpp ${PLAT_SRCS}
|
add_executable(metaforce CMain.cpp ${PLAT_SRCS}
|
||||||
ImGuiConsole.hpp ImGuiConsole.cpp
|
ImGuiConsole.hpp ImGuiConsole.cpp
|
||||||
|
ImGuiControllerConfig.hpp ImGuiControllerConfig.cpp
|
||||||
ImGuiEntitySupport.hpp ImGuiEntitySupport.cpp)
|
ImGuiEntitySupport.hpp ImGuiEntitySupport.cpp)
|
||||||
# RUNTIME_LIBRARIES repeated here for link ordering
|
# RUNTIME_LIBRARIES repeated here for link ordering
|
||||||
target_link_libraries(metaforce PUBLIC RuntimeCommon RuntimeCommonB ${RUNTIME_LIBRARIES} ${PLAT_LIBS})
|
target_link_libraries(metaforce PUBLIC RuntimeCommon RuntimeCommonB ${RUNTIME_LIBRARIES} ${PLAT_LIBS})
|
||||||
|
|
|
@ -1158,6 +1158,7 @@ void ImGuiConsole::ShowAppMainMenuBar(bool canInspect) {
|
||||||
ImGui::MenuItem("Items", nullptr, &m_showItemsWindow, canInspect && m_developer && m_cheats);
|
ImGui::MenuItem("Items", nullptr, &m_showItemsWindow, canInspect && m_developer && m_cheats);
|
||||||
ImGui::MenuItem("Layers", nullptr, &m_showLayersWindow, canInspect && m_developer);
|
ImGui::MenuItem("Layers", nullptr, &m_showLayersWindow, canInspect && m_developer);
|
||||||
ImGui::MenuItem("Console Variables", nullptr, &m_showConsoleVariablesWindow);
|
ImGui::MenuItem("Console Variables", nullptr, &m_showConsoleVariablesWindow);
|
||||||
|
ImGui::MenuItem("Controller Config", nullptr, &m_controllerConfigVisible);
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
if (ImGui::BeginMenu("Debug")) {
|
if (ImGui::BeginMenu("Debug")) {
|
||||||
|
@ -1292,6 +1293,7 @@ void ImGuiConsole::PreUpdate() {
|
||||||
ShowInputViewer();
|
ShowInputViewer();
|
||||||
ShowPlayerTransformEditor();
|
ShowPlayerTransformEditor();
|
||||||
ShowPipelineProgress();
|
ShowPipelineProgress();
|
||||||
|
m_controllerConfig.show(m_controllerConfigVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiConsole::PostUpdate() {
|
void ImGuiConsole::PostUpdate() {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "Runtime/World/CActor.hpp"
|
#include "Runtime/World/CActor.hpp"
|
||||||
#include "Runtime/World/CEntity.hpp"
|
#include "Runtime/World/CEntity.hpp"
|
||||||
#include "Runtime/ImGuiPlayerLoadouts.hpp"
|
#include "Runtime/ImGuiPlayerLoadouts.hpp"
|
||||||
|
#include "Runtime/ImGuiControllerConfig.hpp"
|
||||||
|
|
||||||
#include "Runtime/ConsoleVariables/CVarCommons.hpp"
|
#include "Runtime/ConsoleVariables/CVarCommons.hpp"
|
||||||
#include "Runtime/ConsoleVariables/CVarManager.hpp"
|
#include "Runtime/ConsoleVariables/CVarManager.hpp"
|
||||||
|
@ -100,6 +101,9 @@ private:
|
||||||
std::string m_controllerName;
|
std::string m_controllerName;
|
||||||
u32 m_whichController = -1;
|
u32 m_whichController = -1;
|
||||||
|
|
||||||
|
bool m_controllerConfigVisible = false;
|
||||||
|
ImGuiControllerConfig m_controllerConfig;
|
||||||
|
|
||||||
void ShowAppMainMenuBar(bool canInspect);
|
void ShowAppMainMenuBar(bool canInspect);
|
||||||
void ShowMenuGame();
|
void ShowMenuGame();
|
||||||
bool ShowEntityInfoWindow(TUniqueId uid);
|
bool ShowEntityInfoWindow(TUniqueId uid);
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
#include "Runtime/ImGuiControllerConfig.hpp"
|
||||||
|
|
||||||
|
#include "aurora/pad.hpp"
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
namespace metaforce {
|
||||||
|
|
||||||
|
void ImGuiControllerConfig::show(bool& visible) {
|
||||||
|
/** TODO:
|
||||||
|
* - Implement multiple controllers
|
||||||
|
* - Implement setting controller ports
|
||||||
|
* - Implement fancy graphical UI
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pendingMapping != nullptr) {
|
||||||
|
s32 nativeButton = PADGetNativeButtonPressed(0);
|
||||||
|
if (nativeButton != -1) {
|
||||||
|
m_pendingMapping->nativeButton = nativeButton;
|
||||||
|
m_pendingMapping = nullptr;
|
||||||
|
PADBlockInput(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Begin("Controller Config", &visible)) {
|
||||||
|
ImGui::Text("%s", PADGetName(0));
|
||||||
|
u32 buttonCount = 0;
|
||||||
|
PADButtonMapping* mapping = PADGetButtonMappings(0, &buttonCount);
|
||||||
|
if (mapping != nullptr) {
|
||||||
|
for (u32 i = 0; i < buttonCount; ++i) {
|
||||||
|
bool pressed = ImGui::Button(PADGetButtonName(mapping[i].padButton));
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text("%s", PADGetNativeButtonName(mapping[i].nativeButton));
|
||||||
|
|
||||||
|
if (pressed && m_pendingMapping == nullptr) {
|
||||||
|
m_pendingMapping = &mapping[i];
|
||||||
|
PADBlockInput(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pendingMapping == &mapping[i]) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text(" - Waiting for button...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::CollapsingHeader("Dead-zones")) {
|
||||||
|
PADDeadZones* deadZones = PADGetDeadZones(0);
|
||||||
|
ImGui::Checkbox("Use Dead-zones", &deadZones->useDeadzones);
|
||||||
|
s32 tmp = deadZones->stickDeadZone;
|
||||||
|
if (ImGui::DragInt("Left Stick", &tmp)) {
|
||||||
|
deadZones->stickDeadZone = tmp;
|
||||||
|
}
|
||||||
|
tmp = deadZones->substickDeadZone;
|
||||||
|
if (ImGui::DragInt("Right Stick", &tmp)) {
|
||||||
|
deadZones->substickDeadZone = tmp;
|
||||||
|
}
|
||||||
|
ImGui::Checkbox("Emulate Triggers", &deadZones->emulateTriggers);
|
||||||
|
tmp = deadZones->leftTriggerActivationZone;
|
||||||
|
if (ImGui::DragInt("Left Trigger Activation", &tmp)) {
|
||||||
|
deadZones->leftTriggerActivationZone = tmp;
|
||||||
|
}
|
||||||
|
tmp = deadZones->rightTriggerActivationZone;
|
||||||
|
if (ImGui::DragInt("Right Trigger Activation", &tmp)) {
|
||||||
|
deadZones->rightTriggerActivationZone = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::Button("Display Editor")) {
|
||||||
|
m_editorVisible = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Save Mappings")) {
|
||||||
|
PADSerializeMappings();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Restore Defaults")) {
|
||||||
|
for (u32 i = 0; i < 4; ++i) {
|
||||||
|
PADRestoreDefaultMapping(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
showEditor(m_editorVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImGuiControllerConfig::showEditor(bool& visible) {
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Begin("Controller Atlas Editor", &visible)) {
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace metaforce
|
|
@ -0,0 +1,42 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Runtime/GCNTypes.hpp"
|
||||||
|
#include "aurora/pad.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace metaforce {
|
||||||
|
class ImGuiControllerConfig {
|
||||||
|
struct Button {
|
||||||
|
u32 button; // the SDL button this entry corresponds to
|
||||||
|
u32 uvX; // Offset if icon image in atlas from left (in pixels)
|
||||||
|
u32 uvY; // Offset if icon image in atlas from top (in pixels)
|
||||||
|
u32 width; // Width of button image (in pixels)
|
||||||
|
u32 height; // Height of button image (in pixels)
|
||||||
|
float offX; // Offset from left of config window
|
||||||
|
float offY; // Offset from top of config window
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ControllerMapping {
|
||||||
|
std::string name;
|
||||||
|
std::pair<u32, u32> vidPid;
|
||||||
|
std::string atlasFile; // Path to atlas relative to controller definition
|
||||||
|
std::vector<Button> buttons;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
void show(bool& visible);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void showEditor(bool& visible);
|
||||||
|
|
||||||
|
PADButtonMapping* m_pendingMapping = nullptr;
|
||||||
|
bool m_editorVisible = false;
|
||||||
|
|
||||||
|
std::array<ControllerMapping*, 4> m_controllers;
|
||||||
|
std::vector<ControllerMapping> m_mappings;
|
||||||
|
};
|
||||||
|
} // namespace metaforce
|
|
@ -256,5 +256,7 @@ void set_controller_player_index(uint32_t which, int32_t index) noexcept;
|
||||||
[[nodiscard]] bool controller_has_rumble(uint32_t which) noexcept;
|
[[nodiscard]] bool controller_has_rumble(uint32_t which) noexcept;
|
||||||
void controller_rumble(uint32_t which, uint16_t low_freq_intensity, uint16_t high_freq_intensity,
|
void controller_rumble(uint32_t which, uint16_t low_freq_intensity, uint16_t high_freq_intensity,
|
||||||
uint32_t duration_ms = 0) noexcept;
|
uint32_t duration_ms = 0) noexcept;
|
||||||
|
|
||||||
|
uint32_t get_controller_count() noexcept;
|
||||||
[[nodiscard]] std::string get_controller_name(uint32_t which) noexcept;
|
[[nodiscard]] std::string get_controller_name(uint32_t which) noexcept;
|
||||||
} // namespace aurora
|
} // namespace aurora
|
||||||
|
|
|
@ -81,3 +81,33 @@ void PADSetSamplingCallback(PADSamplingCallback callback);
|
||||||
void PADControlAllMotors(const u32* commands);
|
void PADControlAllMotors(const u32* commands);
|
||||||
|
|
||||||
u32 SIProbe(s32 chan);
|
u32 SIProbe(s32 chan);
|
||||||
|
|
||||||
|
/* New API to facilitate controller interactions */
|
||||||
|
struct PADDeadZones {
|
||||||
|
bool emulateTriggers = true;
|
||||||
|
bool useDeadzones = true;
|
||||||
|
u16 stickDeadZone = 8000;
|
||||||
|
u16 substickDeadZone = 8000;
|
||||||
|
u16 leftTriggerActivationZone = 31150;
|
||||||
|
u16 rightTriggerActivationZone = 31150;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PADButtonMapping {
|
||||||
|
u32 nativeButton;
|
||||||
|
PAD::BUTTON padButton;
|
||||||
|
};
|
||||||
|
|
||||||
|
s32 PADGetCount();
|
||||||
|
void PADGetVidPid(u32 idx, u32* vid, u32* pid);
|
||||||
|
const char* PADGetName(u32 idx);
|
||||||
|
void PADSetButtonMapping(u32 port, PADButtonMapping mapping);
|
||||||
|
void PADSetAllButtonMappings(u32 port, PADButtonMapping buttons[12]);
|
||||||
|
PADButtonMapping* PADGetButtonMappings(u32 port, u32* buttonCount);
|
||||||
|
void PADSerializeMappings();
|
||||||
|
PADDeadZones* PADGetDeadZones(u32 port);
|
||||||
|
const char* PADGetButtonName(PAD::BUTTON);
|
||||||
|
const char* PADGetNativeButtonName(u32 button);
|
||||||
|
/* Returns any pressed native button */
|
||||||
|
s32 PADGetNativeButtonPressed(u32 port);
|
||||||
|
void PADRestoreDefaultMapping(u32 port);
|
||||||
|
void PADBlockInput(bool block);
|
||||||
|
|
|
@ -437,5 +437,6 @@ void controller_rumble(uint32_t instance, uint16_t low_freq_intensity, uint16_t
|
||||||
input::controller_rumble(instance, low_freq_intensity, high_freq_intensity, duration_ms);
|
input::controller_rumble(instance, low_freq_intensity, high_freq_intensity, duration_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t get_controller_count() noexcept { return input::controller_count(); }
|
||||||
std::string get_controller_name(uint32_t instance) noexcept { return input::controller_name(instance); }
|
std::string get_controller_name(uint32_t instance) noexcept { return input::controller_name(instance); }
|
||||||
} // namespace aurora
|
} // namespace aurora
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
#include "input.hpp"
|
#include "input.hpp"
|
||||||
#include "aurora/pad.hpp"
|
#include "aurora/pad.hpp"
|
||||||
|
#include "Runtime/ConsoleVariables/FileStoreManager.hpp"
|
||||||
|
|
||||||
|
#include "magic_enum.hpp"
|
||||||
|
|
||||||
#include <SDL_haptic.h>
|
#include <SDL_haptic.h>
|
||||||
|
|
||||||
|
@ -16,18 +19,39 @@ struct GameController {
|
||||||
bool m_isGameCube = false;
|
bool m_isGameCube = false;
|
||||||
Sint32 m_index = -1;
|
Sint32 m_index = -1;
|
||||||
bool m_hasRumble = false;
|
bool m_hasRumble = false;
|
||||||
constexpr bool operator==(const GameController&) const = default;
|
PADDeadZones m_deadZones;
|
||||||
|
u16 m_vid = 0;
|
||||||
|
u16 m_pid = 0;
|
||||||
|
std::array<PADButtonMapping, 12> m_mapping;
|
||||||
|
bool m_mappingLoaded = false;
|
||||||
|
constexpr bool operator==(const GameController& other) const {
|
||||||
|
return m_controller == other.m_controller && m_index == other.m_index;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
absl::flat_hash_map<Uint32, GameController> g_GameControllers;
|
absl::flat_hash_map<Uint32, GameController> g_GameControllers;
|
||||||
|
|
||||||
GameController get_controller_for_player(u32 player) noexcept {
|
GameController* get_controller_for_player(u32 player) noexcept {
|
||||||
for (const auto& [which, controller] : g_GameControllers) {
|
for (auto& [which, controller] : g_GameControllers) {
|
||||||
if (player_index(which) == player) {
|
if (player_index(which) == player) {
|
||||||
return controller;
|
return &controller;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
/* If we don't have a controller assigned to this port use the first unassigned controller */
|
||||||
|
if (!g_GameControllers.empty()) {
|
||||||
|
int32_t availIndex = -1;
|
||||||
|
for (const auto& controller : g_GameControllers) {
|
||||||
|
if (player_index(controller.second.m_index) == -1) {
|
||||||
|
availIndex = controller.second.m_index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (availIndex != -1) {
|
||||||
|
set_player_index(g_GameControllers.begin()->second.m_index, player);
|
||||||
|
return get_controller_for_player(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sint32 get_instance_for_player(u32 player) noexcept {
|
Sint32 get_instance_for_player(u32 player) noexcept {
|
||||||
|
@ -104,8 +128,9 @@ Sint32 add_controller(Sint32 which) noexcept {
|
||||||
GameController controller;
|
GameController controller;
|
||||||
controller.m_controller = ctrl;
|
controller.m_controller = ctrl;
|
||||||
controller.m_index = which;
|
controller.m_index = which;
|
||||||
controller.m_isGameCube =
|
controller.m_vid = SDL_GameControllerGetVendor(ctrl);
|
||||||
SDL_GameControllerGetVendor(ctrl) == 0x057E && SDL_GameControllerGetProduct(ctrl) == 0x0337;
|
controller.m_pid = SDL_GameControllerGetProduct(ctrl);
|
||||||
|
controller.m_isGameCube = controller.m_vid == 0x057E && controller.m_pid == 0x0337;
|
||||||
controller.m_hasRumble = (SDL_GameControllerHasRumble(ctrl) != 0u);
|
controller.m_hasRumble = (SDL_GameControllerHasRumble(ctrl) != 0u);
|
||||||
Sint32 instance = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ctrl));
|
Sint32 instance = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(ctrl));
|
||||||
g_GameControllers[instance] = controller;
|
g_GameControllers[instance] = controller;
|
||||||
|
@ -342,12 +367,11 @@ MouseButton translate_mouse_button_state(Uint8 state) noexcept {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t controller_count() noexcept { return g_GameControllers.size(); }
|
||||||
|
|
||||||
} // namespace aurora::input
|
} // namespace aurora::input
|
||||||
|
|
||||||
void PADSetSpec(s32 spec) {}
|
static const std::array<PADButtonMapping, 12> mDefaultButtons{{
|
||||||
void PADInit() {}
|
|
||||||
|
|
||||||
static const std::array<std::pair<SDL_GameControllerButton, PAD::BUTTON>, 12> mMapping{{
|
|
||||||
{SDL_CONTROLLER_BUTTON_A, PAD::BUTTON_A},
|
{SDL_CONTROLLER_BUTTON_A, PAD::BUTTON_A},
|
||||||
{SDL_CONTROLLER_BUTTON_B, PAD::BUTTON_B},
|
{SDL_CONTROLLER_BUTTON_B, PAD::BUTTON_B},
|
||||||
{SDL_CONTROLLER_BUTTON_X, PAD::BUTTON_X},
|
{SDL_CONTROLLER_BUTTON_X, PAD::BUTTON_X},
|
||||||
|
@ -362,63 +386,138 @@ static const std::array<std::pair<SDL_GameControllerButton, PAD::BUTTON>, 12> mM
|
||||||
{SDL_CONTROLLER_BUTTON_DPAD_RIGHT, PAD::BUTTON_RIGHT},
|
{SDL_CONTROLLER_BUTTON_DPAD_RIGHT, PAD::BUTTON_RIGHT},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
void PADSetSpec(s32 spec) {}
|
||||||
|
void PADInit() {}
|
||||||
|
|
||||||
|
void __PADLoadMapping(aurora::input::GameController* controller) {
|
||||||
|
s32 playerIndex = SDL_GameControllerGetPlayerIndex(controller->m_controller);
|
||||||
|
if (playerIndex == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string basePath = std::string(metaforce::FileStoreManager::instance()->getStoreRoot());
|
||||||
|
if (!controller->m_mappingLoaded) {
|
||||||
|
controller->m_mapping = mDefaultButtons;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller->m_mappingLoaded = true;
|
||||||
|
|
||||||
|
FILE* file =
|
||||||
|
fopen(fmt::format(FMT_STRING("{}/{}_{:04X}_{:04X}.controller"), basePath,
|
||||||
|
aurora::input::controller_name(controller->m_index), controller->m_vid, controller->m_pid)
|
||||||
|
.c_str(),
|
||||||
|
"rbe");
|
||||||
|
if (file == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 magic = 0;
|
||||||
|
fread(&magic, 1, sizeof(u32), file);
|
||||||
|
if (magic != SBIG('CTRL')) {
|
||||||
|
fmt::print(FMT_STRING("Invalid controller mapping magic!\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 version = 0;
|
||||||
|
fread(&version, 1, sizeof(u32), file);
|
||||||
|
if (version != 1) {
|
||||||
|
fmt::print(FMT_STRING("Invalid controller mapping version!\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isGameCube = false;
|
||||||
|
fread(&isGameCube, 1, 1, file);
|
||||||
|
fseek(file, (ftell(file) + 31) & ~31, SEEK_SET);
|
||||||
|
u32 dataStart = ftell(file);
|
||||||
|
if (isGameCube) {
|
||||||
|
fseek(file, dataStart + ((sizeof(PADDeadZones) + sizeof(PADButtonMapping)) * playerIndex), SEEK_SET);
|
||||||
|
}
|
||||||
|
|
||||||
|
fread(&controller->m_deadZones, 1, sizeof(PADDeadZones), file);
|
||||||
|
fread(&controller->m_mapping, 1, sizeof(PADButtonMapping), file);
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gBlockPAD = false;
|
||||||
u32 PADRead(PAD::Status* status) {
|
u32 PADRead(PAD::Status* status) {
|
||||||
|
if (gBlockPAD) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
u32 rumbleSupport = 0;
|
u32 rumbleSupport = 0;
|
||||||
for (u32 i = 0; i < 4; ++i) {
|
for (u32 i = 0; i < 4; ++i) {
|
||||||
memset(&status[i], 0, sizeof(PAD::Status));
|
memset(&status[i], 0, sizeof(PAD::Status));
|
||||||
auto controller = aurora::input::get_controller_for_player(i);
|
auto controller = aurora::input::get_controller_for_player(i);
|
||||||
if (controller == aurora::input::GameController{}) {
|
if (controller == nullptr) {
|
||||||
status[i].xa_err = PAD::ERR_NO_CONTROLLER;
|
status[i].xa_err = PAD::ERR_NO_CONTROLLER;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!controller->m_mappingLoaded) {
|
||||||
|
__PADLoadMapping(controller);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
PADSerializeMappings();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
status[i].xa_err = PAD::ERR_NONE;
|
status[i].xa_err = PAD::ERR_NONE;
|
||||||
std::for_each(mMapping.begin(), mMapping.end(), [&controller, &i, &status](const auto& pair) {
|
std::for_each(controller->m_mapping.begin(), controller->m_mapping.end(),
|
||||||
if (SDL_GameControllerGetButton(controller.m_controller, pair.first)) {
|
[&controller, &i, &status](const auto& mapping) {
|
||||||
status[i].x0_buttons |= pair.second;
|
if (SDL_GameControllerGetButton(controller->m_controller,
|
||||||
|
static_cast<SDL_GameControllerButton>(mapping.nativeButton))) {
|
||||||
|
status[i].x0_buttons |= mapping.padButton;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Sint16 x = SDL_GameControllerGetAxis(controller.m_controller, SDL_CONTROLLER_AXIS_LEFTX);
|
Sint16 x = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_LEFTX);
|
||||||
Sint16 y = SDL_GameControllerGetAxis(controller.m_controller, SDL_CONTROLLER_AXIS_LEFTY);
|
Sint16 y = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_LEFTY);
|
||||||
if (std::abs(x) > 8000) {
|
if (controller->m_deadZones.useDeadzones) {
|
||||||
|
if (std::abs(x) > controller->m_deadZones.stickDeadZone) {
|
||||||
x /= 256;
|
x /= 256;
|
||||||
} else {
|
} else {
|
||||||
x = 0;
|
x = 0;
|
||||||
}
|
}
|
||||||
|
if (std::abs(y) > controller->m_deadZones.stickDeadZone) {
|
||||||
if (std::abs(y) > 8000) {
|
|
||||||
y = (-(y + 1u)) / 256u;
|
y = (-(y + 1u)) / 256u;
|
||||||
} else {
|
} else {
|
||||||
y = 0;
|
y = 0;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
x /= 256;
|
||||||
|
y = (-(y + 1u)) / 256u;
|
||||||
|
}
|
||||||
|
|
||||||
status[i].x2_stickX = static_cast<s8>(x);
|
status[i].x2_stickX = static_cast<s8>(x);
|
||||||
status[i].x3_stickY = static_cast<s8>(y);
|
status[i].x3_stickY = static_cast<s8>(y);
|
||||||
|
|
||||||
x = SDL_GameControllerGetAxis(controller.m_controller, SDL_CONTROLLER_AXIS_RIGHTX);
|
x = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_RIGHTX);
|
||||||
y = SDL_GameControllerGetAxis(controller.m_controller, SDL_CONTROLLER_AXIS_RIGHTY);
|
y = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_RIGHTY);
|
||||||
if (std::abs(x) > 8000) {
|
if (controller->m_deadZones.useDeadzones) {
|
||||||
|
if (std::abs(x) > controller->m_deadZones.substickDeadZone) {
|
||||||
x /= 256;
|
x /= 256;
|
||||||
} else {
|
} else {
|
||||||
x = 0;
|
x = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std::abs(y) > 8000) {
|
if (std::abs(y) > controller->m_deadZones.substickDeadZone) {
|
||||||
y = (-(y + 1u)) / 256u;
|
y = (-(y + 1u)) / 256u;
|
||||||
} else {
|
} else {
|
||||||
y = 0;
|
y = 0;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
x /= 256;
|
||||||
|
y = (-(y + 1u)) / 256u;
|
||||||
|
}
|
||||||
|
|
||||||
status[i].x4_substickX = static_cast<s8>(x);
|
status[i].x4_substickX = static_cast<s8>(x);
|
||||||
status[i].x5_substickY = static_cast<s8>(y);
|
status[i].x5_substickY = static_cast<s8>(y);
|
||||||
|
|
||||||
x = SDL_GameControllerGetAxis(controller.m_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
|
x = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
|
||||||
y = SDL_GameControllerGetAxis(controller.m_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
|
y = SDL_GameControllerGetAxis(controller->m_controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
|
||||||
if (!controller.m_isGameCube) {
|
if (/*!controller->m_isGameCube && */ controller->m_deadZones.emulateTriggers) {
|
||||||
if (x > 31150) {
|
if (x > controller->m_deadZones.leftTriggerActivationZone) {
|
||||||
status[i].x0_buttons |= PAD::TRIGGER_L;
|
status[i].x0_buttons |= PAD::TRIGGER_L;
|
||||||
}
|
}
|
||||||
if (y > 31150) {
|
if (y > controller->m_deadZones.rightTriggerActivationZone) {
|
||||||
status[i].x0_buttons |= PAD::TRIGGER_R;
|
status[i].x0_buttons |= PAD::TRIGGER_R;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -428,7 +527,7 @@ u32 PADRead(PAD::Status* status) {
|
||||||
status[i].x6_triggerL = static_cast<s8>(x);
|
status[i].x6_triggerL = static_cast<s8>(x);
|
||||||
status[i].x7_triggerR = static_cast<s8>(y);
|
status[i].x7_triggerR = static_cast<s8>(y);
|
||||||
|
|
||||||
if (controller.m_hasRumble) {
|
if (controller->m_hasRumble) {
|
||||||
rumbleSupport |= PAD::CHAN0_BIT >> i;
|
rumbleSupport |= PAD::CHAN0_BIT >> i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -439,11 +538,11 @@ void PADControlAllMotors(const u32* commands) {
|
||||||
for (u32 i = 0; i < 4; ++i) {
|
for (u32 i = 0; i < 4; ++i) {
|
||||||
auto controller = aurora::input::get_controller_for_player(i);
|
auto controller = aurora::input::get_controller_for_player(i);
|
||||||
auto instance = aurora::input::get_instance_for_player(i);
|
auto instance = aurora::input::get_instance_for_player(i);
|
||||||
if (controller == aurora::input::GameController{}) {
|
if (controller == nullptr) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controller.m_isGameCube) {
|
if (controller->m_isGameCube) {
|
||||||
if (commands[i] == PAD::MOTOR_STOP) {
|
if (commands[i] == PAD::MOTOR_STOP) {
|
||||||
aurora::input::controller_rumble(instance, 0, 1, 0);
|
aurora::input::controller_rumble(instance, 0, 1, 0);
|
||||||
} else if (commands[i] == PAD::MOTOR_RUMBLE) {
|
} else if (commands[i] == PAD::MOTOR_RUMBLE) {
|
||||||
|
@ -464,13 +563,13 @@ void PADControlAllMotors(const u32* commands) {
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 SIProbe(s32 chan) {
|
u32 SIProbe(s32 chan) {
|
||||||
const auto controller = aurora::input::get_controller_for_player(chan);
|
auto *const controller = aurora::input::get_controller_for_player(chan);
|
||||||
if (controller == aurora::input::GameController{}) {
|
if (controller == nullptr) {
|
||||||
return SI::ERROR_NO_RESPONSE;
|
return SI::ERROR_NO_RESPONSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controller.m_isGameCube) {
|
if (controller->m_isGameCube) {
|
||||||
auto level = SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(controller.m_controller));
|
auto level = SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(controller->m_controller));
|
||||||
if (level == SDL_JOYSTICK_POWER_UNKNOWN) {
|
if (level == SDL_JOYSTICK_POWER_UNKNOWN) {
|
||||||
return SI::GC_WAVEBIRD;
|
return SI::GC_WAVEBIRD;
|
||||||
}
|
}
|
||||||
|
@ -635,3 +734,188 @@ void PADClampCircle(PAD::Status* status) {
|
||||||
ClampTrigger(&status[i].x7_triggerR, ClampRegion.minTrigger, ClampRegion.maxTrigger);
|
ClampTrigger(&status[i].x7_triggerR, ClampRegion.minTrigger, ClampRegion.maxTrigger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s32 PADGetCount() { return aurora::input::g_GameControllers.size(); }
|
||||||
|
|
||||||
|
aurora::input::GameController* __PADGetController(s32 idx) {
|
||||||
|
auto iter = aurora::input::g_GameControllers.begin();
|
||||||
|
s32 i = 0;
|
||||||
|
for (; aurora::input::g_GameControllers.begin() != aurora::input::g_GameControllers.end() && i < idx; ++iter, ++i) {}
|
||||||
|
if (iter == aurora::input::g_GameControllers.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PADGetVidPid(u32 idx, u32* vid, u32* pid) {
|
||||||
|
*vid = 0;
|
||||||
|
*pid = 0;
|
||||||
|
if (idx < 0 || idx >= aurora::input::g_GameControllers.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* controller = __PADGetController(idx);
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*vid = controller->m_vid;
|
||||||
|
*pid = controller->m_pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* PADGetName(u32 idx) {
|
||||||
|
auto* controller = __PADGetController(idx);
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDL_GameControllerName(controller->m_controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PADSetButtonMapping(u32 port, PADButtonMapping mapping) {
|
||||||
|
auto* controller = aurora::input::get_controller_for_player(port);
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* iter = std::find_if(controller->m_mapping.begin(), controller->m_mapping.end(),
|
||||||
|
[mapping](const auto& pair) { return mapping.padButton == pair.padButton; });
|
||||||
|
if (iter == controller->m_mapping.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*iter = mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PADSetAllButtonMappings(u32 port, PADButtonMapping buttons[12]) {
|
||||||
|
for (u32 i = 0; i < 12; ++i) {
|
||||||
|
PADSetButtonMapping(port, buttons[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PADButtonMapping* PADGetButtonMappings(u32 port, u32* buttonCount) {
|
||||||
|
auto* controller = aurora::input::get_controller_for_player(port);
|
||||||
|
if (controller == nullptr) {
|
||||||
|
*buttonCount = 0;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
*buttonCount = controller->m_mapping.size();
|
||||||
|
return controller->m_mapping.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void __PADWriteDeadZones(FILE* file, aurora::input::GameController& controller) {
|
||||||
|
fwrite(&controller.m_deadZones, 1, sizeof(PADDeadZones), file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PADSerializeMappings() {
|
||||||
|
std::string basePath = std::string(metaforce::FileStoreManager::instance()->getStoreRoot());
|
||||||
|
|
||||||
|
bool wroteGameCubeAlready = false;
|
||||||
|
for (auto& controller : aurora::input::g_GameControllers) {
|
||||||
|
if (!controller.second.m_mappingLoaded) {
|
||||||
|
__PADLoadMapping(&controller.second);
|
||||||
|
}
|
||||||
|
FILE* file = fopen(fmt::format(FMT_STRING("{}/{}_{:04X}_{:04X}.controller"), basePath,
|
||||||
|
aurora::input::controller_name(controller.second.m_index), controller.second.m_vid,
|
||||||
|
controller.second.m_pid)
|
||||||
|
.c_str(),
|
||||||
|
"wbe");
|
||||||
|
if (file == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t magic = SBIG('CTRL');
|
||||||
|
uint32_t version = 1;
|
||||||
|
fwrite(&magic, 1, sizeof(magic), file);
|
||||||
|
fwrite(&version, 1, sizeof(magic), file);
|
||||||
|
fwrite(&controller.second.m_isGameCube, 1, 1, file);
|
||||||
|
fseek(file, (ftell(file) + 31) & ~31, SEEK_SET);
|
||||||
|
int32_t dataStart = ftell(file);
|
||||||
|
if (!controller.second.m_isGameCube) {
|
||||||
|
__PADWriteDeadZones(file, controller.second);
|
||||||
|
fwrite(controller.second.m_mapping.data(), 1, sizeof(PADButtonMapping) * controller.second.m_mapping.size(),
|
||||||
|
file);
|
||||||
|
} else {
|
||||||
|
if (!wroteGameCubeAlready) {
|
||||||
|
for (u32 i = 0; i < 4; ++i) {
|
||||||
|
/* Just use the current controller's configs for this */
|
||||||
|
__PADWriteDeadZones(file, controller.second);
|
||||||
|
fwrite(mDefaultButtons.data(), 1, sizeof(PADButtonMapping) * mDefaultButtons.size(), file);
|
||||||
|
}
|
||||||
|
fflush(file);
|
||||||
|
wroteGameCubeAlready = true;
|
||||||
|
}
|
||||||
|
uint32_t port = aurora::input::player_index(controller.second.m_index);
|
||||||
|
fseek(file, dataStart + ((sizeof(PADDeadZones) + sizeof(PADButtonMapping)) * port), SEEK_SET);
|
||||||
|
__PADWriteDeadZones(file, controller.second);
|
||||||
|
fwrite(controller.second.m_mapping.data(), 1, sizeof(PADButtonMapping) * controller.second.m_mapping.size(),
|
||||||
|
file);
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PADDeadZones* PADGetDeadZones(u32 port) {
|
||||||
|
auto* controller = aurora::input::get_controller_for_player(port);
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return &controller->m_deadZones;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::array<std::pair<PAD::BUTTON, std::string_view>, 12> skButtonNames = {{
|
||||||
|
{PAD::BUTTON_LEFT, "Left"sv},
|
||||||
|
{PAD::BUTTON_RIGHT, "Right"sv},
|
||||||
|
{PAD::BUTTON_DOWN, "Down"sv},
|
||||||
|
{PAD::BUTTON_UP, "Up"sv},
|
||||||
|
{PAD::TRIGGER_Z, "Z"sv},
|
||||||
|
{PAD::TRIGGER_R, "R"sv},
|
||||||
|
{PAD::TRIGGER_L, "L"sv},
|
||||||
|
{PAD::BUTTON_A, "A"sv},
|
||||||
|
{PAD::BUTTON_B, "B"sv},
|
||||||
|
{PAD::BUTTON_X, "X"sv},
|
||||||
|
{PAD::BUTTON_Y, "Y"sv},
|
||||||
|
{PAD::BUTTON_START, "Start"sv},
|
||||||
|
}};
|
||||||
|
|
||||||
|
const char* PADGetButtonName(PAD::BUTTON button) {
|
||||||
|
auto it = std::find_if(skButtonNames.begin(), skButtonNames.end(),
|
||||||
|
[&button](const auto& pair) { return button == pair.first; });
|
||||||
|
|
||||||
|
if (it != skButtonNames.end()) {
|
||||||
|
return it->second.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* PADGetNativeButtonName(u32 button) {
|
||||||
|
return SDL_GameControllerGetStringForButton(static_cast<SDL_GameControllerButton>(button));
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 PADGetNativeButtonPressed(u32 port) {
|
||||||
|
auto* controller = aurora::input::get_controller_for_player(port);
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < SDL_CONTROLLER_BUTTON_MAX; ++i) {
|
||||||
|
if (SDL_GameControllerGetButton(controller->m_controller, static_cast<SDL_GameControllerButton>(i)) != 0u) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PADRestoreDefaultMapping(u32 port) {
|
||||||
|
auto* controller = aurora::input::get_controller_for_player(port);
|
||||||
|
if (controller == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controller->m_mapping = mDefaultButtons;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PADBlockInput(bool block) {
|
||||||
|
gBlockPAD = block;
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ bool is_gamecube(Uint32 instance) noexcept;
|
||||||
bool controller_has_rumble(Uint32 instance) noexcept;
|
bool controller_has_rumble(Uint32 instance) noexcept;
|
||||||
void controller_rumble(uint32_t instance, uint16_t low_freq_intensity, uint16_t high_freq_intensity,
|
void controller_rumble(uint32_t instance, uint16_t low_freq_intensity, uint16_t high_freq_intensity,
|
||||||
uint16_t duration_ms) noexcept;
|
uint16_t duration_ms) noexcept;
|
||||||
|
uint32_t controller_count() noexcept;
|
||||||
ControllerButton translate_controller_button(SDL_GameControllerButton button) noexcept;
|
ControllerButton translate_controller_button(SDL_GameControllerButton button) noexcept;
|
||||||
ControllerAxis translate_controller_axis(SDL_GameControllerAxis axis) noexcept;
|
ControllerAxis translate_controller_axis(SDL_GameControllerAxis axis) noexcept;
|
||||||
char translate_key(SDL_Keysym sym, SpecialKey& specialSym, ModifierKey& modifierSym) noexcept;
|
char translate_key(SDL_Keysym sym, SpecialKey& specialSym, ModifierKey& modifierSym) noexcept;
|
||||||
|
|
Loading…
Reference in New Issue