forked from TeamTofuShop/segatools
FGO: add keyboard input (#61)
Probably self-explanatory :p Reviewed-on: TeamTofuShop/segatools#61 Co-authored-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com> Co-committed-by: kyoubate-haruka <46010460+kyoubate-haruka@users.noreply.github.com>
This commit is contained in:
29
dist/fgo/segatools.ini
vendored
29
dist/fgo/segatools.ini
vendored
@ -158,8 +158,11 @@ coin=0x72
|
|||||||
; : : AIME. : :
|
; : : AIME. : :
|
||||||
; '·:..............................................:·'
|
; '·:..............................................:·'
|
||||||
;
|
;
|
||||||
; Only XInput is currently supported.
|
|
||||||
|
|
||||||
|
; Select the input mode. "xinput" for controller, "keyboard" for keyboard.
|
||||||
|
mode=xinput
|
||||||
|
|
||||||
|
[xinput]
|
||||||
; XInput bindings
|
; XInput bindings
|
||||||
;
|
;
|
||||||
; Left Stick Joystick
|
; Left Stick Joystick
|
||||||
@ -168,3 +171,27 @@ coin=0x72
|
|||||||
; Left Shoulder Switch Target
|
; Left Shoulder Switch Target
|
||||||
; A/B Attack
|
; A/B Attack
|
||||||
; X/Y Noble Phantasm
|
; X/Y Noble Phantasm
|
||||||
|
|
||||||
|
; Configure deadzones for the left thumbsticks.
|
||||||
|
; The default value for the left stick is 7849, max value is 32767.
|
||||||
|
stickDeadzone=7849
|
||||||
|
|
||||||
|
[keyboard]
|
||||||
|
; Keyboard bindings:
|
||||||
|
|
||||||
|
; Stick controls (default: WASD)
|
||||||
|
up=0x57
|
||||||
|
left=0x41
|
||||||
|
down=0x53
|
||||||
|
right=0x44
|
||||||
|
|
||||||
|
; Attack (default: Space)
|
||||||
|
attack=0x20
|
||||||
|
; Dash (default: LSHIFT)
|
||||||
|
dash=0xa0
|
||||||
|
; Change Target (default: J)
|
||||||
|
target=0x4A
|
||||||
|
; Re-center camera (default: K)
|
||||||
|
camera=0x4B
|
||||||
|
; Noble Phantasm (default: L)
|
||||||
|
np=0x4C
|
||||||
|
@ -87,7 +87,7 @@ static HRESULT fgo_io4_poll(void *ctx, struct io4_state *state)
|
|||||||
state->buttons[0] |= 1 << 1;
|
state->buttons[0] |= 1 << 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gamebtn & FGO_IO_GAMEBTN_NOBLE_PHANTASHM) {
|
if (gamebtn & FGO_IO_GAMEBTN_NOBLE_PHANTASM) {
|
||||||
state->buttons[0] |= 1 << 0;
|
state->buttons[0] |= 1 << 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
fgoio/backend.h
Normal file
10
fgoio/backend.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "fgoio/fgoio.h"
|
||||||
|
|
||||||
|
struct fgo_io_backend {
|
||||||
|
void (*get_gamebtns)(uint8_t *gamebtn);
|
||||||
|
void (*get_analogs)(int16_t *x, int16_t *y);
|
||||||
|
};
|
@ -1,11 +1,38 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stddef.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "fgoio/config.h"
|
#include "fgoio/config.h"
|
||||||
|
|
||||||
|
#include <xinput.h>
|
||||||
|
|
||||||
|
|
||||||
|
void fgo_kb_config_load(
|
||||||
|
struct fgo_kb_config *cfg,
|
||||||
|
const wchar_t *filename) {
|
||||||
|
|
||||||
|
cfg->vk_attack = GetPrivateProfileIntW(L"keyboard", L"attack", ' ', filename);
|
||||||
|
cfg->vk_dash = GetPrivateProfileIntW(L"keyboard", L"dash", VK_LSHIFT, filename);
|
||||||
|
cfg->vk_target = GetPrivateProfileIntW(L"keyboard", L"target", 'J', filename);
|
||||||
|
cfg->vk_camera = GetPrivateProfileIntW(L"keyboard", L"camera", 'K', filename);
|
||||||
|
cfg->vk_np = GetPrivateProfileIntW(L"keyboard", L"np", 'L', filename);
|
||||||
|
|
||||||
|
// Standard WASD
|
||||||
|
cfg->vk_right = GetPrivateProfileIntW(L"keyboard", L"right", 'D', filename);
|
||||||
|
cfg->vk_left = GetPrivateProfileIntW(L"keyboard", L"left", 'A', filename);
|
||||||
|
cfg->vk_down = GetPrivateProfileIntW(L"keyboard", L"down", 'S', filename);
|
||||||
|
cfg->vk_up = GetPrivateProfileIntW(L"keyboard", L"up", 'W', filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fgo_xi_config_load(
|
||||||
|
struct fgo_xi_config *cfg,
|
||||||
|
const wchar_t *filename) {
|
||||||
|
|
||||||
|
cfg->stick_deadzone = GetPrivateProfileIntW(L"xinput", L"stickDeadzone", XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE, filename);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void fgo_io_config_load(
|
void fgo_io_config_load(
|
||||||
struct fgo_io_config *cfg,
|
struct fgo_io_config *cfg,
|
||||||
@ -17,4 +44,15 @@ void fgo_io_config_load(
|
|||||||
cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename);
|
cfg->vk_test = GetPrivateProfileIntW(L"io4", L"test", VK_F1, filename);
|
||||||
cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename);
|
cfg->vk_service = GetPrivateProfileIntW(L"io4", L"service", VK_F2, filename);
|
||||||
cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename);
|
cfg->vk_coin = GetPrivateProfileIntW(L"io4", L"coin", VK_F3, filename);
|
||||||
|
|
||||||
|
GetPrivateProfileStringW(
|
||||||
|
L"io4",
|
||||||
|
L"mode",
|
||||||
|
L"xinput",
|
||||||
|
cfg->mode,
|
||||||
|
_countof(cfg->mode),
|
||||||
|
filename);
|
||||||
|
|
||||||
|
fgo_xi_config_load(&cfg->xi, filename);
|
||||||
|
fgo_kb_config_load(&cfg->kb, filename);
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,34 @@
|
|||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
struct fgo_kb_config {
|
||||||
|
uint8_t vk_np;
|
||||||
|
uint8_t vk_target;
|
||||||
|
uint8_t vk_dash;
|
||||||
|
uint8_t vk_attack;
|
||||||
|
uint8_t vk_camera;
|
||||||
|
uint8_t vk_right;
|
||||||
|
uint8_t vk_left;
|
||||||
|
uint8_t vk_down;
|
||||||
|
uint8_t vk_up;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fgo_xi_config {
|
||||||
|
uint16_t stick_deadzone;
|
||||||
|
};
|
||||||
|
|
||||||
struct fgo_io_config {
|
struct fgo_io_config {
|
||||||
uint8_t vk_test;
|
uint8_t vk_test;
|
||||||
uint8_t vk_service;
|
uint8_t vk_service;
|
||||||
uint8_t vk_coin;
|
uint8_t vk_coin;
|
||||||
|
|
||||||
|
wchar_t mode[12];
|
||||||
|
struct fgo_kb_config kb;
|
||||||
|
struct fgo_xi_config xi;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void fgo_kb_config_load(struct fgo_kb_config *cfg, const wchar_t *filename);
|
||||||
|
void fgo_xi_config_load(struct fgo_xi_config *cfg, const wchar_t *filename);
|
||||||
void fgo_io_config_load(
|
void fgo_io_config_load(
|
||||||
struct fgo_io_config *cfg,
|
struct fgo_io_config *cfg,
|
||||||
const wchar_t *filename);
|
const wchar_t *filename);
|
||||||
|
123
fgoio/fgoio.c
123
fgoio/fgoio.c
@ -2,37 +2,51 @@
|
|||||||
#include <xinput.h>
|
#include <xinput.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "fgoio/fgoio.h"
|
#include "fgoio/fgoio.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "keyboard.h"
|
||||||
|
#include "xi.h"
|
||||||
#include "fgoio/config.h"
|
#include "fgoio/config.h"
|
||||||
#include "util/dprintf.h"
|
#include "util/dprintf.h"
|
||||||
#include "util/env.h"
|
#include "util/env.h"
|
||||||
|
#include "util/str.h"
|
||||||
|
|
||||||
static uint8_t fgo_opbtn;
|
static uint8_t fgo_opbtn;
|
||||||
static uint8_t fgo_gamebtn;
|
static uint8_t fgo_gamebtn;
|
||||||
static int16_t fgo_stick_x;
|
static int16_t fgo_stick_x;
|
||||||
static int16_t fgo_stick_y;
|
static int16_t fgo_stick_y;
|
||||||
static struct fgo_io_config fgo_io_cfg;
|
static struct fgo_io_config fgo_io_cfg;
|
||||||
|
static const struct fgo_io_backend* fgo_io_backend;
|
||||||
static bool fgo_io_coin;
|
static bool fgo_io_coin;
|
||||||
|
|
||||||
uint16_t fgo_io_get_api_version(void)
|
uint16_t fgo_io_get_api_version(void) {
|
||||||
{
|
|
||||||
return 0x0100;
|
return 0x0100;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT fgo_io_init(void)
|
HRESULT fgo_io_init(void) {
|
||||||
{
|
|
||||||
fgo_io_config_load(&fgo_io_cfg, get_config_path());
|
fgo_io_config_load(&fgo_io_cfg, get_config_path());
|
||||||
|
|
||||||
return S_OK;
|
HRESULT hr;
|
||||||
|
|
||||||
|
if (wstr_ieq(fgo_io_cfg.mode, L"keyboard")) {
|
||||||
|
hr = fgo_kb_init(&fgo_io_cfg.kb, &fgo_io_backend);
|
||||||
|
} else if (wstr_ieq(fgo_io_cfg.mode, L"xinput")) {
|
||||||
|
hr = fgo_xi_init(&fgo_io_cfg.xi, &fgo_io_backend);
|
||||||
|
} else {
|
||||||
|
hr = E_INVALIDARG;
|
||||||
|
dprintf("FGO IO: Invalid IO mode \"%S\", use keyboard or xinput\n",
|
||||||
|
fgo_io_cfg.mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hr;
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT fgo_io_poll(void)
|
HRESULT fgo_io_poll(void) {
|
||||||
{
|
assert(fgo_io_backend != NULL);
|
||||||
XINPUT_STATE xi;
|
|
||||||
WORD xb;
|
|
||||||
|
|
||||||
fgo_opbtn = 0;
|
fgo_opbtn = 0;
|
||||||
fgo_gamebtn = 0;
|
fgo_gamebtn = 0;
|
||||||
@ -56,97 +70,34 @@ HRESULT fgo_io_poll(void)
|
|||||||
fgo_io_coin = false;
|
fgo_io_coin = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&xi, 0, sizeof(xi));
|
|
||||||
XInputGetState(0, &xi);
|
|
||||||
xb = xi.Gamepad.wButtons;
|
|
||||||
|
|
||||||
if (xi.Gamepad.bLeftTrigger > 64) {
|
|
||||||
fgo_gamebtn |= FGO_IO_GAMEBTN_SPEED_UP;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xb & XINPUT_GAMEPAD_LEFT_SHOULDER) {
|
|
||||||
fgo_gamebtn |= FGO_IO_GAMEBTN_TARGET;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xb & XINPUT_GAMEPAD_A || xb & XINPUT_GAMEPAD_B) {
|
|
||||||
fgo_gamebtn |= FGO_IO_GAMEBTN_ATTACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xb & XINPUT_GAMEPAD_Y || xb & XINPUT_GAMEPAD_X) {
|
|
||||||
fgo_gamebtn |= FGO_IO_GAMEBTN_NOBLE_PHANTASHM;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xb & XINPUT_GAMEPAD_LEFT_THUMB) {
|
|
||||||
fgo_gamebtn |= FGO_IO_GAMEBTN_CAMERA;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 > XINPUT_GAMEPAD_LEFT_THUMB_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 -= XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
|
|
||||||
|
|
||||||
// optionally normalize the magnitude with respect to its expected range
|
|
||||||
// giving a magnitude value of 0.0 to 1.0
|
|
||||||
normalizedMagnitude = magnitude / (32767 - XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
|
|
||||||
} else // if the controller is in the deadzone zero out the magnitude
|
|
||||||
{
|
|
||||||
magnitude = 0.0;
|
|
||||||
normalizedMagnitude = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fgo_stick_x = (int16_t)(normalizedLX * normalizedMagnitude * 32767);
|
|
||||||
fgo_stick_y = (int16_t)(normalizedLY * normalizedMagnitude * 32767);
|
|
||||||
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fgo_io_get_opbtns(uint8_t *opbtn)
|
void fgo_io_get_opbtns(uint8_t* opbtn) {
|
||||||
{
|
|
||||||
if (opbtn != NULL) {
|
if (opbtn != NULL) {
|
||||||
*opbtn = fgo_opbtn;
|
*opbtn = fgo_opbtn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fgo_io_get_gamebtns(uint8_t *btn)
|
void fgo_io_get_gamebtns(uint8_t* btn) {
|
||||||
{
|
assert(fgo_io_backend != NULL);
|
||||||
if (btn != NULL) {
|
assert(btn != NULL);
|
||||||
*btn = fgo_gamebtn;
|
|
||||||
}
|
fgo_io_backend->get_gamebtns(btn);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fgo_io_get_analogs(int16_t *stick_x, int16_t *stick_y)
|
void fgo_io_get_analogs(int16_t* stick_x, int16_t* stick_y) {
|
||||||
{
|
assert(fgo_io_backend != NULL);
|
||||||
if (stick_x != NULL) {
|
assert(stick_x != NULL);
|
||||||
*stick_x = fgo_stick_x;
|
assert(stick_y != NULL);
|
||||||
}
|
|
||||||
|
|
||||||
if (stick_y != NULL) {
|
fgo_io_backend->get_analogs(stick_x, stick_y);
|
||||||
*stick_y = fgo_stick_y;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT fgo_io_led_init(void)
|
HRESULT fgo_io_led_init(void) {
|
||||||
{
|
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void fgo_io_led_set_colors(uint8_t board, uint8_t *rgb)
|
void fgo_io_led_set_colors(uint8_t board, uint8_t* rgb) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ enum {
|
|||||||
FGO_IO_GAMEBTN_SPEED_UP = 0x01,
|
FGO_IO_GAMEBTN_SPEED_UP = 0x01,
|
||||||
FGO_IO_GAMEBTN_TARGET = 0x02,
|
FGO_IO_GAMEBTN_TARGET = 0x02,
|
||||||
FGO_IO_GAMEBTN_ATTACK = 0x04,
|
FGO_IO_GAMEBTN_ATTACK = 0x04,
|
||||||
FGO_IO_GAMEBTN_NOBLE_PHANTASHM = 0x08,
|
FGO_IO_GAMEBTN_NOBLE_PHANTASM = 0x08,
|
||||||
FGO_IO_GAMEBTN_CAMERA = 0x10,
|
FGO_IO_GAMEBTN_CAMERA = 0x10,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
82
fgoio/keyboard.c
Normal file
82
fgoio/keyboard.c
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include "fgoio/backend.h"
|
||||||
|
#include "fgoio/config.h"
|
||||||
|
#include "fgoio/fgoio.h"
|
||||||
|
#include "fgoio/keyboard.h"
|
||||||
|
|
||||||
|
#include "util/dprintf.h"
|
||||||
|
|
||||||
|
static void fgo_kb_get_gamebtns(uint8_t* gamebtn_out);
|
||||||
|
static void fgo_kb_get_analogs(int16_t* x, int16_t* y);
|
||||||
|
|
||||||
|
static const struct fgo_io_backend fgo_kb_backend = {
|
||||||
|
.get_gamebtns = fgo_kb_get_gamebtns,
|
||||||
|
.get_analogs = fgo_kb_get_analogs
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct fgo_kb_config config;
|
||||||
|
|
||||||
|
HRESULT fgo_kb_init(const struct fgo_kb_config* cfg, const struct fgo_io_backend** backend) {
|
||||||
|
assert(cfg != NULL);
|
||||||
|
assert(backend != NULL);
|
||||||
|
|
||||||
|
dprintf("Keyboard: Using keyboard input\n");
|
||||||
|
*backend = &fgo_kb_backend;
|
||||||
|
config = *cfg;
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fgo_kb_get_gamebtns(uint8_t* gamebtn_out) {
|
||||||
|
assert(gamebtn_out != NULL);
|
||||||
|
|
||||||
|
uint8_t gamebtn = 0;
|
||||||
|
|
||||||
|
if (GetAsyncKeyState(config.vk_np) & 0x8000) {
|
||||||
|
gamebtn |= FGO_IO_GAMEBTN_NOBLE_PHANTASM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetAsyncKeyState(config.vk_target) & 0x8000) {
|
||||||
|
gamebtn |= FGO_IO_GAMEBTN_TARGET;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetAsyncKeyState(config.vk_dash) & 0x8000) {
|
||||||
|
gamebtn |= FGO_IO_GAMEBTN_SPEED_UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetAsyncKeyState(config.vk_attack) & 0x8000) {
|
||||||
|
gamebtn |= FGO_IO_GAMEBTN_ATTACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetAsyncKeyState(config.vk_camera) & 0x8000) {
|
||||||
|
gamebtn |= FGO_IO_GAMEBTN_CAMERA;
|
||||||
|
}
|
||||||
|
|
||||||
|
*gamebtn_out = gamebtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fgo_kb_get_analogs(int16_t* x, int16_t* y) {
|
||||||
|
assert(x != NULL);
|
||||||
|
assert(y != NULL);
|
||||||
|
|
||||||
|
if (GetAsyncKeyState(config.vk_left) & 0x8000) {
|
||||||
|
*x = SHRT_MIN + 1;
|
||||||
|
} else if (GetAsyncKeyState(config.vk_right) & 0x8000) {
|
||||||
|
*x = SHRT_MAX - 1;
|
||||||
|
} else {
|
||||||
|
*x = 0;
|
||||||
|
}
|
||||||
|
if (GetAsyncKeyState(config.vk_down) & 0x8000) {
|
||||||
|
*y = SHRT_MIN + 1;
|
||||||
|
} else if (GetAsyncKeyState(config.vk_up) & 0x8000) {
|
||||||
|
*y = SHRT_MAX - 1;
|
||||||
|
} else {
|
||||||
|
*y = 0;
|
||||||
|
}
|
||||||
|
}
|
8
fgoio/keyboard.h
Normal file
8
fgoio/keyboard.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "fgoio/backend.h"
|
||||||
|
#include "fgoio/config.h"
|
||||||
|
|
||||||
|
HRESULT fgo_kb_init(const struct fgo_kb_config *cfg, const struct fgo_io_backend **backend);
|
@ -11,5 +11,10 @@ fgoio_lib = static_library(
|
|||||||
'fgoio.h',
|
'fgoio.h',
|
||||||
'config.c',
|
'config.c',
|
||||||
'config.h',
|
'config.h',
|
||||||
|
'backend.h',
|
||||||
|
'keyboard.c',
|
||||||
|
'keyboard.h',
|
||||||
|
'xi.c',
|
||||||
|
'xi.h',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
132
fgoio/xi.c
Normal file
132
fgoio/xi.c
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
#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);
|
||||||
|
}
|
10
fgoio/xi.h
Normal file
10
fgoio/xi.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* Can't call this xinput.h or it will conflict with <xinput.h> */
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "fgoio/backend.h"
|
||||||
|
#include "fgoio/config.h"
|
||||||
|
|
||||||
|
HRESULT fgo_xi_init(const struct fgo_xi_config *cfg, const struct fgo_io_backend **backend);
|
Reference in New Issue
Block a user