Files
jak-project/game/system/hid/devices/game_controller.cpp
Tyler Wilding 60db0e5ef9 deps: update fmt to latest version (#3403)
This updates `fmt` to the latest version and moves to just being a copy
of their repo to make updating easier (no editing their cmake / figuring
out which files to minimally include).

The motivation for this is now that we switched to C++ 20, there were a
ton of deprecated function usages that is going away in future compiler
versions. This gets rid of all those warnings.
2024-03-05 22:11:52 -05:00

191 lines
7.3 KiB
C++

#include "game_controller.h"
#include "game/system/hid/sdl_util.h"
#include "fmt/core.h"
GameController::GameController(int sdl_device_id,
std::shared_ptr<game_settings::InputSettings> settings)
: m_sdl_instance_id(sdl_device_id) {
m_settings = settings;
m_loaded = false;
m_device_handle = SDL_GameControllerOpen(sdl_device_id);
if (!m_device_handle) {
sdl_util::log_error(
fmt::format("Could not open game controller with device id: {}", sdl_device_id));
return;
}
auto joystick = SDL_GameControllerGetJoystick(m_device_handle);
if (!joystick) {
sdl_util::log_error(
fmt::format("Could not get underlying joystick for gamecontroller: id {}", sdl_device_id));
return;
}
m_sdl_instance_id = SDL_JoystickInstanceID(joystick);
if (m_sdl_instance_id < 0) {
sdl_util::log_error(
fmt::format("Could not determine instance id for gamecontroller: id {}", sdl_device_id));
return;
}
const auto controller_guid = SDL_JoystickGetGUID(joystick);
if (sdl_util::is_SDL_GUID_zero(controller_guid)) {
sdl_util::log_error(fmt::format("Could not determine controller guid: id {}", sdl_device_id));
return;
}
char guidStr[33];
SDL_JoystickGetGUIDString(controller_guid, guidStr, sizeof(guidStr));
m_guid = guidStr;
// NOTE - it has been observed that this function will return `NULL` which indicates a bad
// controller this seems to happen if you disconnect the controller while it's in the process of
// connecting
//
// However, SDL_GameControllerGetAttached returns true, and `NULL` here is not an outright failure
// there could be controllers that just have no name.
//
// So I'm letting this one go through, in most cases it had an invalid guid as well (so caught
// above).
auto name = SDL_GameControllerName(m_device_handle);
if (!name) {
sdl_util::log_error(fmt::format("Could not get device name with id: {}", sdl_device_id));
m_device_name = "";
} else {
m_device_name = name;
}
m_has_led = sdl_util::from_sdl_bool(SDL_GameControllerHasLED(m_device_handle));
m_has_rumble = sdl_util::from_sdl_bool(SDL_GameControllerHasRumble(m_device_handle));
if (m_settings->controller_binds.find(m_guid) == m_settings->controller_binds.end()) {
m_settings->controller_binds[m_guid] = DEFAULT_CONTROLLER_BINDS;
}
m_loaded = true;
}
void GameController::process_event(const SDL_Event& event,
const CommandBindingGroups& commands,
std::shared_ptr<PadData> data,
std::optional<InputBindAssignmentMeta>& bind_assignment) {
if (event.type == SDL_CONTROLLERAXISMOTION && event.caxis.which == m_sdl_instance_id) {
// https://wiki.libsdl.org/SDL2/SDL_GameControllerAxis
if ((int)event.caxis.axis <= SDL_CONTROLLER_AXIS_INVALID ||
event.caxis.axis >= SDL_CONTROLLER_AXIS_MAX) {
return;
}
auto& binds = m_settings->controller_binds.at(m_guid);
// Handle analog stick binds
if (event.caxis.axis >= SDL_CONTROLLER_AXIS_LEFTX &&
event.caxis.axis <= SDL_CONTROLLER_AXIS_RIGHTY && !data->analogs_being_simulated() &&
binds.analog_axii.find(event.caxis.axis) != binds.analog_axii.end()) {
for (const auto& bind : binds.analog_axii.at(event.caxis.axis)) {
// Adjust the value range to 0-255 (127 being neutral)
// Values come out of SDL as -32,768 + 32,767
int axis_val = ((event.caxis.value + 32768) * 256) / 65536;
data->analog_data.at(bind.pad_data_index) = axis_val;
}
} else if (event.caxis.axis >= SDL_CONTROLLER_AXIS_TRIGGERLEFT &&
event.caxis.axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT &&
binds.button_axii.find(event.caxis.axis) != binds.button_axii.end()) {
// Binding re-assignment
if (bind_assignment) {
// In the event that the user binds an analog input to the confirm binds
// (ie. a trigger on X) we wait until it hits a neutral position (0)
// before proceeding to rebind.
//
// TODO - this still isn't perfect as the data mutation below will trigger it after the bind
// assignment has been set but atleast it's in a mostly working state right now
if (!bind_assignment->seen_controller_confirm_neutral) {
for (const auto& confirm_bind : bind_assignment->controller_confirmation_binds) {
if (confirm_bind.sdl_idx == event.caxis.axis) {
if (event.caxis.value <= 0) {
bind_assignment->seen_controller_confirm_neutral = true;
}
return;
}
}
}
if (bind_assignment->device_type == InputDeviceType::CONTROLLER &&
!bind_assignment->for_analog) {
binds.assign_button_bind(event.caxis.axis, bind_assignment.value(), true);
}
return;
}
// and analog triggers
for (const auto& bind : binds.button_axii.at(event.caxis.axis)) {
data->button_data.at(bind.pad_data_index) = event.caxis.value > 0;
}
}
} else if ((event.type == SDL_CONTROLLERBUTTONDOWN || event.type == SDL_CONTROLLERBUTTONUP) &&
event.cbutton.which == m_sdl_instance_id) {
auto& binds = m_settings->controller_binds.at(m_guid);
// https://wiki.libsdl.org/SDL2/SDL_GameControllerButton
if ((int)event.cbutton.button <= SDL_CONTROLLER_BUTTON_INVALID ||
event.cbutton.button >= SDL_CONTROLLER_BUTTON_MAX) {
return;
}
// Binding re-assignment
if (bind_assignment && event.type == SDL_CONTROLLERBUTTONDOWN) {
if (bind_assignment->device_type == InputDeviceType::CONTROLLER &&
!bind_assignment->for_analog) {
binds.assign_button_bind(event.cbutton.button, bind_assignment.value());
}
return;
}
if (binds.buttons.find(event.cbutton.button) == binds.buttons.end()) {
return;
}
// Iterate the binds, and apply all of them
for (const auto& bind : binds.buttons.at(event.cbutton.button)) {
data->button_data.at(bind.pad_data_index) = event.type == SDL_CONTROLLERBUTTONDOWN;
}
// Check for commands
if (event.type == SDL_CONTROLLERBUTTONDOWN &&
commands.controller_binds.find(event.cbutton.button) != commands.controller_binds.end()) {
for (const auto& command : commands.controller_binds.at(event.cbutton.button)) {
command.command();
}
}
}
}
void GameController::close_device() {
if (m_device_handle) {
SDL_GameControllerClose(m_device_handle);
}
}
int GameController::update_rumble(const u8 low_rumble, const u8 high_rumble) {
if (m_has_rumble && m_device_handle) {
// https://wiki.libsdl.org/SDL2/SDL_GameControllerRumble
// Arbitrary duration, since it's called every frame anyway, just so the vibration doesn't last
// forever. SDL expects a value in the range of 0-0xFFFF, so the `257` is just normalizing the
// 0-255 we get to that range
if (SDL_GameControllerRumble(m_device_handle, low_rumble * 257, high_rumble * 257, 100) == 0) {
return 1;
}
}
return 0;
}
void GameController::set_led(const u8 red, const u8 green, const u8 blue) {
if (!m_has_led) {
return;
}
if (m_device_handle) {
auto ok = SDL_GameControllerSetLED(m_device_handle, red, green, blue);
if (ok != 0) {
m_has_led = false;
}
}
}