388 lines
12 KiB
C++
388 lines
12 KiB
C++
#include "STDInclude.hpp"
|
|
|
|
namespace Components
|
|
{
|
|
XINPUT_STATE XInput::xiStates[XUSER_MAX_COUNT];
|
|
XINPUT_STATE XInput::lastXiState = { 0 };
|
|
int XInput::xiPlayerNum = -1;
|
|
std::chrono::milliseconds XInput::timeAtFirstHeldMaxLookX = 0ms; // "For how much time in miliseconds has the player been holding a horizontal direction on their stick, fully" (-1.0 or 1.0)
|
|
bool XInput::isHoldingMaxLookX = false;
|
|
|
|
float XInput::lockedSensitivityMultiplier = 0.45f;
|
|
float XInput::generalXSensitivityMultiplier = 1.5f;
|
|
float XInput::generalYSensitivityMultiplier = 0.8f;
|
|
|
|
float XInput::lastMenuNavigationDirection = .0f;
|
|
std::chrono::milliseconds XInput::lastNavigationTime = 0ms;
|
|
std::chrono::milliseconds XInput::msBetweenNavigations = 220ms;
|
|
|
|
std::chrono::milliseconds XInput::msBeforeUnlockingSensitivity = 350ms;
|
|
|
|
std::vector<XInput::ActionMapping> mappings = {
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_A, "gostand"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_B, "stance"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_X, "usereload"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_Y, "weapnext", false),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_LEFT_SHOULDER, "smoke"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_RIGHT_SHOULDER, "frag"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_LEFT_THUMB, "breath_sprint"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_RIGHT_THUMB, "melee"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_START, "togglemenu", false),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_BACK, "scores"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_DPAD_RIGHT, "actionslot 3"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_DPAD_LEFT, "actionslot 2"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_DPAD_UP, "actionslot 1"),
|
|
XInput::ActionMapping(XINPUT_GAMEPAD_DPAD_DOWN, "actionslot 4"),
|
|
};
|
|
|
|
std::vector<XInput::MenuMapping> menuMappings = {
|
|
XInput::MenuMapping(XINPUT_GAMEPAD_A, Game::keyNum_t::K_KP_ENTER),
|
|
XInput::MenuMapping(XINPUT_GAMEPAD_B, Game::keyNum_t::K_ESCAPE),
|
|
XInput::MenuMapping(XINPUT_GAMEPAD_DPAD_RIGHT, Game::keyNum_t::K_KP_RIGHTARROW),
|
|
XInput::MenuMapping(XINPUT_GAMEPAD_DPAD_LEFT, Game::keyNum_t::K_KP_LEFTARROW),
|
|
XInput::MenuMapping(XINPUT_GAMEPAD_DPAD_UP, Game::keyNum_t::K_KP_UPARROW),
|
|
XInput::MenuMapping(XINPUT_GAMEPAD_DPAD_DOWN, Game::keyNum_t::K_KP_DOWNARROW)
|
|
};
|
|
|
|
void XInput::Vibrate(int leftVal, int rightVal)
|
|
{
|
|
// Create a Vibraton State
|
|
XINPUT_VIBRATION Vibration;
|
|
|
|
// Zeroise the Vibration
|
|
ZeroMemory(&Vibration, sizeof(XINPUT_VIBRATION));
|
|
|
|
// Set the Vibration Values
|
|
Vibration.wLeftMotorSpeed = leftVal;
|
|
Vibration.wRightMotorSpeed = rightVal;
|
|
|
|
// Vibrate the controller
|
|
XInputSetState(xiPlayerNum, &Vibration);
|
|
}
|
|
|
|
|
|
void XInput::PollXInputDevices()
|
|
{
|
|
XInput::xiPlayerNum = -1;
|
|
|
|
for (int i = XUSER_MAX_COUNT - 1; i >= 0; i--)
|
|
{
|
|
if (XInputGetState(i, &xiStates[i]) == ERROR_SUCCESS)
|
|
XInput::xiPlayerNum = i;
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void XInput::CL_FrameStub()
|
|
{
|
|
__asm
|
|
{
|
|
// poll the xinput devices on every client frame
|
|
call XInput::PollXInputDevices
|
|
|
|
// execute the code we patched over
|
|
sub esp, 0Ch
|
|
push ebx
|
|
push ebp
|
|
push esi
|
|
|
|
// return back to original code
|
|
push 0x486976
|
|
retn
|
|
}
|
|
}
|
|
|
|
void XInput::CL_GamepadMove(int, Game::usercmd_s* cmd)
|
|
{
|
|
if (XInput::xiPlayerNum != -1)
|
|
{
|
|
XINPUT_STATE* xiState = &xiStates[xiPlayerNum];
|
|
|
|
// Deadzones
|
|
float moveStickX = abs(xiState->Gamepad.sThumbLX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE ? xiState->Gamepad.sThumbLX / (float)std::numeric_limits<SHORT>().max() : .0f;
|
|
float moveStickY = abs(xiState->Gamepad.sThumbLY) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE ? xiState->Gamepad.sThumbLY / (float)std::numeric_limits<SHORT>().max() : .0f;
|
|
|
|
float viewStickX = abs(xiState->Gamepad.sThumbRX) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE ? xiState->Gamepad.sThumbRX / (float)std::numeric_limits<SHORT>().max() : .0f;
|
|
float viewStickY = abs(xiState->Gamepad.sThumbRY) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE ? xiState->Gamepad.sThumbRY / (float)std::numeric_limits<SHORT>().max() : .0f;
|
|
|
|
if (moveStickX != 0 || moveStickY != 0) {
|
|
// We check for 0:0 again so we don't overwrite keyboard input in case the user doesn't feel like using their gamepad, even though its plugged in
|
|
cmd->rightmove = moveStickX * std::numeric_limits<char>().max();
|
|
cmd->forwardmove = moveStickY * std::numeric_limits<char>().max();
|
|
}
|
|
|
|
// Gamepad horizontal acceleration on view
|
|
if (abs(viewStickX) > 0.9f) {
|
|
if (!XInput::isHoldingMaxLookX) {
|
|
XInput::isHoldingMaxLookX = true;
|
|
XInput::timeAtFirstHeldMaxLookX = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
|
|
}
|
|
else {
|
|
std::chrono::milliseconds hasBeenHoldingLeftXForMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()) - XInput::timeAtFirstHeldMaxLookX;
|
|
#ifdef STEP_SENSITIVITY
|
|
if (hasBeenHoldingLeftXForMs < XInput::msBeforeUnlockingSensitivity) {
|
|
viewStickX *= XInput::lockedSensitivityMultiplier;
|
|
}
|
|
#else
|
|
float coeff = std::clamp(hasBeenHoldingLeftXForMs.count() / (float)XInput::msBeforeUnlockingSensitivity.count(), 0.0F, 1.0F);
|
|
viewStickX *= XInput::lockedSensitivityMultiplier + coeff * (1.0f - XInput::lockedSensitivityMultiplier);
|
|
#endif
|
|
}
|
|
}
|
|
else {
|
|
XInput::isHoldingMaxLookX = false;
|
|
XInput::timeAtFirstHeldMaxLookX = 0ms;
|
|
}
|
|
|
|
|
|
Game::cl_angles[0] -= viewStickY * generalYSensitivityMultiplier;
|
|
Game::cl_angles[1] -= viewStickX * generalXSensitivityMultiplier;
|
|
|
|
bool pressingLeftTrigger = xiState->Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD ? true : false;
|
|
if (pressingLeftTrigger != XInput::lastXiState.Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
|
|
{
|
|
if (pressingLeftTrigger)
|
|
Command::Execute("+speed");
|
|
else
|
|
Command::Execute("-speed");
|
|
}
|
|
|
|
bool pressingRightTrigger = xiState->Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD ? true : false;
|
|
if (pressingRightTrigger != XInput::lastXiState.Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
|
|
{
|
|
if (pressingRightTrigger)
|
|
Command::Execute("+attack");
|
|
else
|
|
Command::Execute("-attack");
|
|
}
|
|
|
|
// Buttons (on/off) mappings
|
|
for (size_t i = 0; i < mappings.size(); i++)
|
|
{
|
|
auto mapping = mappings[i];
|
|
auto action = mapping.action;
|
|
auto antiAction = mapping.action;
|
|
|
|
if (mapping.isReversible) {
|
|
action = "+" + mapping.action;
|
|
antiAction = "-" + mapping.action;
|
|
}
|
|
else if (mapping.wasPressed) {
|
|
if (xiState->Gamepad.wButtons & mapping.input) {
|
|
// Button still pressed, do not send info
|
|
}
|
|
else {
|
|
mappings[i].wasPressed = false;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (xiState->Gamepad.wButtons & mapping.input) {
|
|
if (mapping.spamWhenHeld || !mappings[i].wasPressed) {
|
|
Command::Execute(action.c_str());
|
|
}
|
|
mappings[i].wasPressed = true;
|
|
}
|
|
else if (mapping.isReversible && mapping.wasPressed) {
|
|
mappings[i].wasPressed = false;
|
|
Command::Execute(antiAction.c_str());
|
|
}
|
|
}
|
|
|
|
memcpy(&XInput::lastXiState, xiState, sizeof XINPUT_STATE);
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void XInput::CL_CreateCmdStub()
|
|
{
|
|
__asm
|
|
{
|
|
// do xinput!
|
|
push esi
|
|
push ebp
|
|
call XInput::CL_GamepadMove
|
|
add esp, 8h
|
|
|
|
// execute code we patched over
|
|
add esp, 4
|
|
fld st
|
|
pop ebx
|
|
|
|
// return back
|
|
push 0x5A6DBF
|
|
retn
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void XInput::MSG_WriteDeltaUsercmdKeyStub()
|
|
{
|
|
__asm
|
|
{
|
|
// fix stack pointer
|
|
add esp, 0Ch
|
|
|
|
// put both forward move and rightmove values in the movement button
|
|
mov dl, byte ptr[edi + 1Ah] // to_forwardMove
|
|
mov dh, byte ptr[edi + 1Bh] // to_rightMove
|
|
|
|
mov[esp + 30h], dx // to_buttons
|
|
|
|
mov dl, byte ptr[ebp + 1Ah] // from_forwardMove
|
|
mov dh, byte ptr[ebp + 1Bh] // from_rightMove
|
|
|
|
mov[esp + 2Ch], dx // from_buttons
|
|
|
|
// return back
|
|
push 0x60E40E
|
|
retn
|
|
}
|
|
}
|
|
|
|
void XInput::ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to)
|
|
{
|
|
char forward;
|
|
char right;
|
|
|
|
if (Game::MSG_ReadBit(msg))
|
|
{
|
|
short movementBits = static_cast<short>(key ^ Game::MSG_ReadBits(msg, 16));
|
|
|
|
forward = static_cast<char>(movementBits);
|
|
right = static_cast<char>(movementBits >> 8);
|
|
}
|
|
else
|
|
{
|
|
forward = from->forwardmove;
|
|
right = from->rightmove;
|
|
}
|
|
|
|
to->forwardmove = forward;
|
|
to->rightmove = right;
|
|
}
|
|
|
|
__declspec(naked) void XInput::MSG_ReadDeltaUsercmdKeyStub()
|
|
{
|
|
__asm
|
|
{
|
|
push ebx // to
|
|
push ebp // from
|
|
push edi // key
|
|
push esi // msg
|
|
call XInput::ApplyMovement
|
|
add esp, 10h
|
|
|
|
// return back
|
|
push 0x4921BF
|
|
ret
|
|
}
|
|
}
|
|
|
|
__declspec(naked) void XInput::MSG_ReadDeltaUsercmdKeyStub2()
|
|
{
|
|
__asm
|
|
{
|
|
push ebx // to
|
|
push ebp // from
|
|
push edi // key
|
|
push esi // msg
|
|
call XInput::ApplyMovement
|
|
add esp, 10h
|
|
|
|
// return back
|
|
push 3
|
|
push esi
|
|
push 0x492085
|
|
ret
|
|
}
|
|
}
|
|
|
|
void XInput::MenuNavigate() {
|
|
|
|
Game::menuDef_t* menuDef = Game::Menu_GetFocused(Game::uiContext);
|
|
|
|
#define SIGN(d) ((d > 0) - (d < 0))
|
|
|
|
if (menuDef) {
|
|
PollXInputDevices();
|
|
|
|
if (XInput::xiPlayerNum != -1)
|
|
{
|
|
XINPUT_STATE* xiState = &xiStates[xiPlayerNum];
|
|
|
|
// Up/Down
|
|
float moveStickX = abs(xiState->Gamepad.sThumbLX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE ? xiState->Gamepad.sThumbLX / (float)std::numeric_limits<SHORT>().max() : .0f;
|
|
float moveStickY = abs(xiState->Gamepad.sThumbLY) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE ? xiState->Gamepad.sThumbLY / (float)std::numeric_limits<SHORT>().max() : .0f;
|
|
|
|
std::chrono::milliseconds now = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
|
|
std::chrono::milliseconds timeSinceLastNavigation = now - lastNavigationTime;
|
|
bool canNavigate = timeSinceLastNavigation > msBetweenNavigations;
|
|
|
|
if (moveStickY > .0f) {
|
|
if (canNavigate || SIGN(moveStickY) != SIGN(lastMenuNavigationDirection)) {
|
|
Game::Menu_SetPrevCursorItem(Game::uiContext, menuDef, 1);
|
|
lastMenuNavigationDirection = moveStickY;
|
|
lastNavigationTime = now;
|
|
}
|
|
}
|
|
else if (moveStickY < .0f) {
|
|
if (canNavigate || SIGN(moveStickY) != SIGN(lastMenuNavigationDirection)) {
|
|
Game::Menu_SetNextCursorItem(Game::uiContext, menuDef, 1);
|
|
lastMenuNavigationDirection = moveStickY;
|
|
lastNavigationTime = now;
|
|
}
|
|
}
|
|
else {
|
|
lastMenuNavigationDirection = .0f;
|
|
}
|
|
|
|
for (size_t i = 0; i < menuMappings.size(); i++)
|
|
{
|
|
MenuMapping mapping = menuMappings[i];
|
|
auto action = mapping.keystroke;
|
|
|
|
if (mapping.wasPressed) {
|
|
if (xiState->Gamepad.wButtons & mapping.input) {
|
|
// Button still pressed, do not send info
|
|
}
|
|
else {
|
|
menuMappings[i].wasPressed = false;
|
|
}
|
|
}
|
|
else if(xiState->Gamepad.wButtons & mapping.input){
|
|
Game::UI_KeyEvent(0, mapping.keystroke, 1);
|
|
menuMappings[i].wasPressed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
XInput::XInput()
|
|
{
|
|
// poll xinput devices every client frame
|
|
Utils::Hook(0x486970, XInput::CL_FrameStub, HOOK_JUMP).install()->quick();
|
|
|
|
// use the xinput state when creating a usercmd
|
|
Utils::Hook(0x5A6DB9, XInput::CL_CreateCmdStub, HOOK_JUMP).install()->quick();
|
|
|
|
// package the forward and right move components in the move buttons
|
|
Utils::Hook(0x60E38D, XInput::MSG_WriteDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick();
|
|
|
|
// send two bytes for sending movement data
|
|
Utils::Hook::Set<BYTE>(0x60E501, 16);
|
|
Utils::Hook::Set<BYTE>(0x60E5CD, 16);
|
|
|
|
// make sure to parse the movement data properly and apply it
|
|
Utils::Hook(0x492127, XInput::MSG_ReadDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick();
|
|
Utils::Hook(0x492009, XInput::MSG_ReadDeltaUsercmdKeyStub2, HOOK_JUMP).install()->quick();
|
|
|
|
PollXInputDevices();
|
|
|
|
if (xiPlayerNum >= 0) {
|
|
Vibrate(3000, 3000);
|
|
}
|
|
|
|
Scheduler::OnFrame(MenuNavigate);
|
|
}
|
|
}
|