Skip to content

Commit

Permalink
Allow controller axes to be used as buttons by unifying their logic.
Browse files Browse the repository at this point in the history
- Add "inp_fAxisPressThreshold" shell command for controlling the threshold after which held controller axes are considered "pressed".

Now any controller axis can be used as a button for player controls (although it's currently only useful for controller triggers).
  • Loading branch information
DreamyCecil committed Sep 14, 2024
1 parent 5466b8b commit bc60d9c
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 128 deletions.
152 changes: 87 additions & 65 deletions Sources/Engine/Base/Input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ static const KeyConversion _akcKeys[] = {
{ KID_2MOUSE3 , -1, -1, TRANAME("2nd Mouse Button 3")},
};

static const size_t _ctKeyArray = ARRAYCOUNT(_akcKeys);

// autogenerated fast conversion tables
static INDEX _aiScanToKid[SDL_NUM_SCANCODES];

Expand All @@ -228,7 +230,7 @@ static void MakeConversionTables(void) {
_aiScanToKid[i] = -1;
}

for (i = 0; i < ARRAYCOUNT(_akcKeys); i++) {
for (i = 0; i < _ctKeyArray; i++) {
const KeyConversion &kc = _akcKeys[i];

const INDEX iKID = kc.kc_iKID;
Expand Down Expand Up @@ -407,23 +409,23 @@ LRESULT CALLBACK SendMsgProc(int nCode, WPARAM wParam, LPARAM lParam) {
// pointer to global input object
CInput *_pInput = NULL;

// [Cecil]
bool InputDeviceAction::IsActive(DOUBLE fThreshold) const {
return Abs(ida_fReading) >= ClampUp(fThreshold, 1.0f);
};

// deafult constructor
CInput::CInput(void)
{
// disable control scaning
inp_bInputEnabled = FALSE;
inp_bPollJoysticks = FALSE;
inp_bLastPrescan = FALSE;
// clear key buffer
for( INDEX iButton=0; iButton<MAX_OVERALL_BUTTONS; iButton++)
{
inp_ubButtonsBuffer[ iButton] = 0;
}
// clear axis relative and absolute values
for( INDEX iAxis=0; iAxis<EIA_MAX_ALL; iAxis++)
{
inp_caiAllAxisInfo[ iAxis].cai_fReading = 0.0f;
inp_caiAllAxisInfo[ iAxis].cai_bExisting = FALSE;

// [Cecil] Clear all actions
for (INDEX iAction = 0; iAction < MAX_INPUT_ACTIONS; iAction++) {
inp_aInputActions[iAction].ida_fReading = 0;
inp_aInputActions[iAction].ida_bExists = (iAction < FIRST_AXIS_ACTION); // Axes are disabled by default
}

#if SE1_PREFER_SDL
Expand All @@ -445,42 +447,39 @@ CInput::~CInput() {
*/
void CInput::SetKeyNames( void)
{
// set name "None" for all keys, known keys will override this default name
{for (INDEX iKey = 0; iKey < ARRAYCOUNT(inp_strButtonNames); iKey++) {
inp_strButtonNames[iKey] = "None";
inp_strButtonNamesTra[iKey] = TRANS("None");
}}
// [Cecil] Reset names of all actions
for (INDEX iResetAction = 0; iResetAction < MAX_INPUT_ACTIONS; iResetAction++) {
inp_aInputActions[iResetAction].ida_strNameInt = "None";
inp_aInputActions[iResetAction].ida_strNameTra = TRANS("None");
}

// for each Key
{for (INDEX iKey=0; iKey<ARRAYCOUNT(_akcKeys); iKey++) {
// Set names of key IDs
for (INDEX iKey = 0; iKey < _ctKeyArray; iKey++) {
const KeyConversion &kc = _akcKeys[iKey];
if (kc.kc_strName == NULL) continue;

// set the name
if (kc.kc_strName!=NULL) {
inp_strButtonNames[kc.kc_iKID] = kc.kc_strName;
if (strlen(kc.kc_strNameTrans)==0) {
inp_strButtonNamesTra[kc.kc_iKID] = kc.kc_strName;
} else {
inp_strButtonNamesTra[kc.kc_iKID] = TranslateConst(kc.kc_strNameTrans, 4);
}
inp_aInputActions[kc.kc_iKID].ida_strNameInt = kc.kc_strName;
CTString &strTranslated = inp_aInputActions[kc.kc_iKID].ida_strNameTra;

if (strlen(kc.kc_strNameTrans) == 0) {
strTranslated = kc.kc_strName;
} else {
strTranslated = TranslateConst(kc.kc_strNameTrans, 4);
}
}}
}

// Set names of mouse axes
#define SET_AXIS_NAME(_Axis, _Name) \
inp_aInputActions[FIRST_AXIS_ACTION + _Axis].ida_strNameInt = _Name; \
inp_aInputActions[FIRST_AXIS_ACTION + _Axis].ida_strNameTra = TRANS(_Name);

// -------- Enumerate known axis -------------
// no axis as axis type 0
inp_caiAllAxisInfo[0].cai_strNameInt = "None";
inp_caiAllAxisInfo[0].cai_strNameTra = TRANS("None");
// mouse axis occupy types from 1 up to 3
inp_caiAllAxisInfo[1].cai_strNameInt = "mouse X";
inp_caiAllAxisInfo[1].cai_strNameTra = TRANS("mouse X");
inp_caiAllAxisInfo[2].cai_strNameInt = "mouse Y";
inp_caiAllAxisInfo[2].cai_strNameTra = TRANS("mouse Y");
inp_caiAllAxisInfo[3].cai_strNameInt = "mouse Z";
inp_caiAllAxisInfo[3].cai_strNameTra = TRANS("mouse Z");
inp_caiAllAxisInfo[4].cai_strNameInt = "2nd mouse X";
inp_caiAllAxisInfo[4].cai_strNameTra = TRANS("2nd mouse X");
inp_caiAllAxisInfo[5].cai_strNameInt = "2nd mouse Y";
inp_caiAllAxisInfo[5].cai_strNameTra = TRANS("2nd mouse Y");
SET_AXIS_NAME(EIA_MOUSE_X, "mouse X");
SET_AXIS_NAME(EIA_MOUSE_Y, "mouse Y");
SET_AXIS_NAME(EIA_MOUSE_Z, "mouse Z");
SET_AXIS_NAME(EIA_MOUSE2_X, "2nd mouse X");
SET_AXIS_NAME(EIA_MOUSE2_Y, "2nd mouse Y");

#undef SET_AXIS_NAME

// [Cecil] Set joystick names separately
SetJoystickNames();
Expand Down Expand Up @@ -546,7 +545,7 @@ void CInput::EnableInput(OS::Window hwnd)
SystemParametersInfo(SPI_GETMOUSE, 0, &inp_mscMouseSettings, 0);
// set new mouse speed
if (!inp_bAllowMouseAcceleration) {
MouseSpeedControl mscNewSetting = { 0, 0, 0};
MouseSpeedControl mscNewSetting = { 0, 0, 0 };
SystemParametersInfo(SPI_SETMOUSE, 0, &mscNewSetting, 0);
}
// set cursor position to screen center
Expand All @@ -569,7 +568,7 @@ void CInput::EnableInput(OS::Window hwnd)
// the entire thing is disabled because it caused last menu key to re-apply in game.
#if 0
// for each Key
{for (INDEX iKey=0; iKey<ARRAYCOUNT(_akcKeys); iKey++) {
{for (INDEX iKey = 0; iKey < _ctKeyArray; iKey++) {
struct KeyConversion &kc = _akcKeys[iKey];
// get codes
INDEX iKID = kc.kc_iKID;
Expand Down Expand Up @@ -655,8 +654,10 @@ void CInput::GetInput(BOOL bPreScan)

// if not pre-scanning
if (!bPreScan) {
// clear button's buffer
memset( inp_ubButtonsBuffer, 0, sizeof( inp_ubButtonsBuffer));
// [Cecil] Reset key readings
for (INDEX iResetAction = 0; iResetAction < MAX_OVERALL_BUTTONS; iResetAction++) {
inp_aInputActions[iResetAction].ida_fReading = 0;
}

#if SE1_PREFER_SDL
// [Cecil] SDL: Get current keyboard and mouse states just once
Expand All @@ -665,12 +666,14 @@ void CInput::GetInput(BOOL bPreScan)
#endif

// for each Key
for (INDEX iKey=0; iKey<ARRAYCOUNT(_akcKeys); iKey++) {
for (INDEX iKey = 0; iKey < _ctKeyArray; iKey++) {
const KeyConversion &kc = _akcKeys[iKey];
// get codes
INDEX iKID = kc.kc_iKID;
INDEX iVirt = kc.kc_iVirtKey;

InputDeviceAction &idaKey = inp_aInputActions[iKID];

// if reading async keystate
if (inp_iKeyboardReadingMethod == 0) {
// if there is a valid virtkey
Expand Down Expand Up @@ -701,14 +704,14 @@ void CInput::GetInput(BOOL bPreScan)
// is state is pressed
if (bKeyPressed) {
// mark it as pressed
inp_ubButtonsBuffer[iKID] = 0xFF;
idaKey.ida_fReading = 1;
}
}

// if snooping messages
} else if (_abKeysPressed[iKID]) {
// mark it as pressed
inp_ubButtonsBuffer[iKID] = 0xFF;
idaKey.ida_fReading = 1;
}
}
}
Expand Down Expand Up @@ -772,9 +775,9 @@ void CInput::GetInput(BOOL bPreScan)
FLOAT fMouseRelZ = _iMouseZ;

// just interpret values as normal
inp_caiAllAxisInfo[1].cai_fReading = fMouseRelX;
inp_caiAllAxisInfo[2].cai_fReading = fMouseRelY;
inp_caiAllAxisInfo[3].cai_fReading = fMouseRelZ;
inp_aInputActions[FIRST_AXIS_ACTION + EIA_MOUSE_X].ida_fReading = fMouseRelX;
inp_aInputActions[FIRST_AXIS_ACTION + EIA_MOUSE_Y].ida_fReading = fMouseRelY;
inp_aInputActions[FIRST_AXIS_ACTION + EIA_MOUSE_Z].ida_fReading = fMouseRelZ;
}

// if not pre-scanning
Expand All @@ -786,28 +789,32 @@ void CInput::GetInput(BOOL bPreScan)
// until the accumulated _iMouseZ reaches zero
_bWheelUp = _bWheelDn = FALSE; // [Cecil] TEMP: Reset states to prevent it from toggling each step

InputDeviceAction &idaUp = inp_aInputActions[KID_MOUSEWHEELUP];

// Detect wheel up/down movement
if (_iMouseZ > 0) {
if (_bWheelUp) {
inp_ubButtonsBuffer[KID_MOUSEWHEELUP] = 0x00;
idaUp.ida_fReading = 0;
} else {
inp_ubButtonsBuffer[KID_MOUSEWHEELUP] = 0xFF;
idaUp.ida_fReading = 1;
_iMouseZ = ClampDn(_iMouseZ - MOUSEWHEEL_SCROLL_INTERVAL, 0);
}
}

_bWheelUp = inp_ubButtonsBuffer[KID_MOUSEWHEELUP];
_bWheelUp = idaUp.IsActive();

InputDeviceAction &idaDn = inp_aInputActions[KID_MOUSEWHEELDOWN];

if (_iMouseZ < 0) {
if (_bWheelDn) {
inp_ubButtonsBuffer[KID_MOUSEWHEELDOWN] = 0x00;
idaDn.ida_fReading = 0;
} else {
inp_ubButtonsBuffer[KID_MOUSEWHEELDOWN] = 0xFF;
idaDn.ida_fReading = 1;
_iMouseZ = ClampUp(_iMouseZ + MOUSEWHEEL_SCROLL_INTERVAL, 0);
}
}

_bWheelDn = inp_ubButtonsBuffer[KID_MOUSEWHEELDOWN];
_bWheelDn = idaDn.IsActive();
}

inp_bLastPrescan = bPreScan;
Expand All @@ -826,12 +833,27 @@ void CInput::GetInput(BOOL bPreScan)
}

// Clear all input states (keys become not pressed, axes are reset to zero)
void CInput::ClearInput( void)
void CInput::ClearInput(void)
{
// clear button's buffer
memset( inp_ubButtonsBuffer, 0, sizeof( inp_ubButtonsBuffer));
// clear axis values
for (INDEX i=0; i<EIA_MAX_ALL; i++) {
inp_caiAllAxisInfo[i].cai_fReading = 0;
for (INDEX i = 0; i < MAX_INPUT_ACTIONS; i++) {
inp_aInputActions[i].ida_fReading = 0;
}
}
};

// Get given button's current state
BOOL CInput::GetButtonState(INDEX iButtonNo) const {
// [Cecil] Exclude mouse axes
if (iButtonNo >= FIRST_AXIS_ACTION && iButtonNo < FIRST_AXIS_ACTION + EIA_MAX_MOUSE) {
return FALSE;
}

// [Cecil] Set custom threshold for axes
FLOAT fThreshold = 0.5f;

if (iButtonNo >= FIRST_AXIS_ACTION && iButtonNo < FIRST_AXIS_ACTION + EIA_MAX_ALL) {
extern FLOAT inp_fAxisPressThreshold;
fThreshold = inp_fAxisPressThreshold;
}

return inp_aInputActions[iButtonNo].IsActive(fThreshold);
};
66 changes: 33 additions & 33 deletions Sources/Engine/Base/Input.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

#define FIRST_JOYBUTTON (KID_TOTALCOUNT)
#define MAX_OVERALL_BUTTONS (KID_TOTALCOUNT + MAX_JOYSTICKS * SDL_CONTROLLER_BUTTON_MAX)
#define FIRST_AXIS_ACTION (MAX_OVERALL_BUTTONS)

enum EInputAxis {
EIA_NONE = 0, // Invalid/no axis
Expand All @@ -47,23 +48,19 @@ enum EInputAxis {
EIA_MAX_ALL = (EIA_MAX_MOUSE + SDL_CONTROLLER_AXIS_MAX * MAX_JOYSTICKS),
};

/*
* Mouse speed control structure
*/
struct MouseSpeedControl
{
int msc_iThresholdX;
int msc_iThresholdY;
int msc_iSpeed;
};
// All possible input actions
#define MAX_INPUT_ACTIONS (MAX_OVERALL_BUTTONS + EIA_MAX_ALL)

// Information about a single axis
struct ControlAxisInfo {
CTString cai_strNameInt; // Internal name
CTString cai_strNameTra; // Translated display name
// Information about a single input action
struct InputDeviceAction {
CTString ida_strNameInt; // Internal name
CTString ida_strNameTra; // Translated display name

FLOAT cai_fReading; // Current reading of the axis
BOOL cai_bExisting; // Whether a controller has this axis
DOUBLE ida_fReading; // Current reading of the action (from -1 to +1)
BOOL ida_bExists; // Whether this action (controller axis) can be used

// Whether the action is active (button is held / controller stick is fully to the side)
bool IsActive(DOUBLE fThreshold = 0.5) const;
};

// [Cecil] Individual game controller
Expand All @@ -89,19 +86,22 @@ class ENGINE_API CInput {
BOOL inp_bLastPrescan;
BOOL inp_bInputEnabled;
BOOL inp_bPollJoysticks;
ControlAxisInfo inp_caiAllAxisInfo[EIA_MAX_ALL]; // info for all available axis
CTString inp_strButtonNames[ MAX_OVERALL_BUTTONS];// individual button names
CTString inp_strButtonNamesTra[ MAX_OVERALL_BUTTONS];// individual button names (translated)
UBYTE inp_ubButtonsBuffer[ MAX_OVERALL_BUTTONS]; // statuses for all buttons (KEY & 128 !=0)

// [Cecil] All possible actions that can be used as controls
InputDeviceAction inp_aInputActions[MAX_INPUT_ACTIONS];

// [Cecil] Game controllers
CStaticArray<GameController_t> inp_aControllers;

#if !SE1_PREFER_SDL
SLONG inp_slScreenCenterX; // screen center X in pixels
SLONG inp_slScreenCenterY; // screen center Y in pixels
int inp_aOldMousePos[2]; // old mouse position
struct MouseSpeedControl inp_mscMouseSettings; // system mouse settings
SLONG inp_slScreenCenterX; // Screen center X in pixels
SLONG inp_slScreenCenterY; // Screen center Y in pixels
int inp_aOldMousePos[2]; // Old mouse position

// System mouse settings
struct MouseSpeedControl {
int iThresholdX, iThresholdY, iSpeed;
} inp_mscMouseSettings;
#endif

public:
Expand Down Expand Up @@ -186,38 +186,38 @@ class ENGINE_API CInput {

// Get count of available buttons
inline const INDEX GetAvailableButtonsCount(void) const {
return MAX_OVERALL_BUTTONS;
// [Cecil] Include axes with buttons
return MAX_INPUT_ACTIONS;
};

// Get name of given axis
inline const CTString &GetAxisName(INDEX iAxisNo) const {
return inp_caiAllAxisInfo[iAxisNo].cai_strNameInt;
// [Cecil] Start past the button actions for compatibility
return inp_aInputActions[FIRST_AXIS_ACTION + iAxisNo].ida_strNameInt;
};

// Get translated name of given axis
const CTString &GetAxisTransName(INDEX iAxisNo) const {
return inp_caiAllAxisInfo[iAxisNo].cai_strNameTra;
inline const CTString &GetAxisTransName(INDEX iAxisNo) const {
return inp_aInputActions[FIRST_AXIS_ACTION + iAxisNo].ida_strNameTra;
};

// Get current position of given axis
inline FLOAT GetAxisValue(INDEX iAxisNo) const {
return inp_caiAllAxisInfo[iAxisNo].cai_fReading;
return inp_aInputActions[FIRST_AXIS_ACTION + iAxisNo].ida_fReading;
};

// Get given button's name
inline const CTString &GetButtonName(INDEX iButtonNo) const {
return inp_strButtonNames[iButtonNo];
return inp_aInputActions[iButtonNo].ida_strNameInt;
};

// Get given button's name translated
inline const CTString &GetButtonTransName(INDEX iButtonNo) const {
return inp_strButtonNamesTra[iButtonNo];
return inp_aInputActions[iButtonNo].ida_strNameTra;
};

// Get given button's current state
inline BOOL GetButtonState(INDEX iButtonNo) const {
return (inp_ubButtonsBuffer[iButtonNo] & 128) != 0;
};
BOOL GetButtonState(INDEX iButtonNo) const;
};

// pointer to global input object
Expand Down
Loading

0 comments on commit bc60d9c

Please sign in to comment.