After releasing the demo for Half-Life: Element 64 we had some people asking about controller support so I took a look.
Joystick support is already included in the better forks of the HL1 code base, we use the op4 base which added support of using SDL2 for finding joysticks. It still requires some setup in a config as there's no proper settings for them but once setup properly then it the movement seems ok.
Triggers for modern controllers are they're own axis so they're not found as buttons for bindings, I added support for the left and right trigger axis to the code and spoofed them to trigger a button press if a threshold is met:
Increase JOY_MAX_AXIS to 8 and add the new axis
#define JOY_MAX_AXES 8
#define JOY_AXIS_TRIGGER_L 6
#define JOY_AXIS_TRIGGER_R 7
in the RawValuePointer method add the new axis:
case JOY_AXIS_TRIGGER_L:
return SDL_GameControllerGetAxis(s_pJoystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
case JOY_AXIS_TRIGGER_R:
return SDL_GameControllerGetAxis(s_pJoystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
in IN_JoyMove add the new axis:
// Turn trigger presses into key presses using a threshhold, use 31 and 32 due to not having engine access
if (i == JOY_AXIS_TRIGGER_R)
{
if (fabs(fAxisValue) > joy_triggerrthreshold->value)
{
gEngfuncs.Key_Event(K_AUX31, 1);
}
else
{
gEngfuncs.Key_Event(K_AUX31, 0);
}
}
else if (i == JOY_AXIS_TRIGGER_L)
{
if (fabs(fAxisValue) > joy_triggerlthreshold->value)
{
gEngfuncs.Key_Event(K_AUX32, 1);
}
else
{
gEngfuncs.Key_Event(K_AUX32, 0);
}
}
K_AUX31 and K_AUX32 are used because they're the bottom of the list of what we can use, I tried adding new keys but the engine doesn't allow us so I had to use them instead.
I added 2 new cvars for the threshold:
joy_triggerrthreshold = gEngfuncs.pfnRegisterVariable("joytriggerrthreshold", "0.15", 0);
joy_triggerlthreshold = gEngfuncs.pfnRegisterVariable("joytriggerlthreshold", "0.15", 0);
Then aux32 and aux33 can then be bound like normal buttons to +attack or whatever.
The engine menu is not supported as a binding on it's own so I couldn't bind it to the start button on the controller, in IN_Commands I added some code like this:
// Correct key is pressed for joymenukey
if (LookupJoyMenuKey(joy_menukey->string) == key_index + i)
{
gEngfuncs.Key_Event(K_ESCAPE, 1);
}
This gets the string from the joymenukey cvar and compares them and triggers the escape button on the engine side, this opens and closed the menu like normal. I originally added a normal concommand but the issue is the engine stops listening to button presses when the menu is open but I didn't want to have to put the controller down to close the menu.
LookupJoyMenuKey is just a mapping from text to the K_ enum.
I noticed that if my controller was off and I opened the game then the controller isn't picked up, it was annoying so I added a small snippet to IN_JoyMove:
if (!joy_avail || 0 == in_joystick->value)
{
// Check if the count has changed
int nJoysticks = SDL_NumJoysticks();
if (nJoysticks > 0)
{
IN_StartupJoystick();
}
return;
}