segatools/fgoio/xi.c

133 lines
3.5 KiB
C

#include <windows.h>
#include <xinput.h>
#include <math.h>
#include <assert.h>
#include <stdint.h>
#include "fgoio/backend.h"
#include "fgoio/config.h"
#include "fgoio/fgoio.h"
#include "fgoio/xi.h"
#include "util/dprintf.h"
static void fgo_xi_get_gamebtns(uint8_t* gamebtn_out);
static void fgo_xi_get_analogs(int16_t* x, int16_t* y);
static HRESULT fgo_xi_config_apply(const struct fgo_xi_config* cfg);
static const struct fgo_io_backend fgo_xi_backend = {
.get_gamebtns = fgo_xi_get_gamebtns,
.get_analogs = fgo_xi_get_analogs
};
static float fgo_xi_stick_deadzone;
const uint16_t max_stick_value = 32767;
HRESULT fgo_xi_init(const struct fgo_xi_config* cfg, const struct fgo_io_backend** backend) {
assert(cfg != NULL);
assert(backend != NULL);
HRESULT hr = fgo_xi_config_apply(cfg);
if (FAILED(hr)) {
return hr;
}
dprintf("XInput: Using XInput controller\n");
*backend = &fgo_xi_backend;
return S_OK;
}
static HRESULT fgo_xi_config_apply(const struct fgo_xi_config* cfg) {
/* Deadzone check */
if (cfg->stick_deadzone > 32767 || cfg->stick_deadzone < 0) {
dprintf("XInput: Stick deadzone is too large or negative\n");
return E_INVALIDARG;
}
dprintf("XInput: --- Begin configuration ---\n");
dprintf("XInput: Left Deadzone . . . . : %i\n", cfg->stick_deadzone);
dprintf("XInput: --- End configuration ---\n");
fgo_xi_stick_deadzone = cfg->stick_deadzone;
return S_OK;
}
static void fgo_xi_get_gamebtns(uint8_t* gamebtn_out) {
assert(gamebtn_out != NULL);
XINPUT_STATE xi;
memset(&xi, 0, sizeof(xi));
XInputGetState(0, &xi);
uint8_t gamebtn = 0;
WORD xb = xi.Gamepad.wButtons;
if (xi.Gamepad.bLeftTrigger > 64) {
gamebtn |= FGO_IO_GAMEBTN_SPEED_UP;
}
if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) {
gamebtn |= FGO_IO_GAMEBTN_TARGET;
}
if (xb & XINPUT_GAMEPAD_A || xb & XINPUT_GAMEPAD_B) {
gamebtn |= FGO_IO_GAMEBTN_ATTACK;
}
if (xb & XINPUT_GAMEPAD_Y || xb & XINPUT_GAMEPAD_X) {
gamebtn |= FGO_IO_GAMEBTN_NOBLE_PHANTASM;
}
if (xb & XINPUT_GAMEPAD_LEFT_THUMB) {
gamebtn |= FGO_IO_GAMEBTN_CAMERA;
}
*gamebtn_out = gamebtn;
}
static void fgo_xi_get_analogs(int16_t* x, int16_t* y) {
assert(x != NULL);
assert(y != NULL);
XINPUT_STATE xi;
memset(&xi, 0, sizeof(xi));
XInputGetState(0, &xi);
float LX = xi.Gamepad.sThumbLX;
float LY = xi.Gamepad.sThumbLY;
// determine how far the controller is pushed
float magnitude = sqrt(LX * LX + LY * LY);
// determine the direction the controller is pushed
float normalizedLX = LX / magnitude;
float normalizedLY = LY / magnitude;
float normalizedMagnitude = 0;
// check if the controller is outside a circular dead zone
if (magnitude > fgo_xi_stick_deadzone) {
// clip the magnitude at its expected maximum value
if (magnitude > 32767) magnitude = 32767;
// adjust magnitude relative to the end of the dead zone
magnitude -= fgo_xi_stick_deadzone;
// optionally normalize the magnitude with respect to its expected range
// giving a magnitude value of 0.0 to 1.0
normalizedMagnitude = magnitude / (32767 - fgo_xi_stick_deadzone);
} else // if the controller is in the deadzone zero out the magnitude
{
normalizedMagnitude = 0;
}
*x = (int16_t) (normalizedLX * normalizedMagnitude * 32767);
*y = (int16_t) (normalizedLY * normalizedMagnitude * 32767);
}