Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xinput controller support prototype #118

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ endif ()

# Link Windows libraries
if (WIN32)
target_link_libraries(clonk dbghelp dwmapi iphlpapi winmm ws2_32)
target_link_libraries(clonk dbghelp dwmapi iphlpapi winmm XInput ws2_32)

if (USE_SDL_MIXER)
target_link_libraries(clonk imm32.lib setupapi.lib version.lib)
Expand Down
Binary file added XinputButtons.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
662 changes: 662 additions & 0 deletions XinputButtons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/C4Components.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
#define C4FLS_Graphics "Loader*.bmp|Loader*.png|Loader*.jpeg|Loader*.jpg|FontEndeavour12.png|FontEndeavour24.png|FontEndeavour16.png|FontEndeavour10.png|Font*.png" \
"|*.pal|Control.png|Fire.png|Background.png|Flag.png|Crew.png|Score.png|Wealth.png|Player.png|Rank.png|Entry.png|Captain.png|Cursor.png|CursorSmall.png|CursorMedium.png|CursorLarge.png|CursorXLarge.png|CursorXXLarge.png|CursorXXXLarge.png|CursorXXXXLarge.png|CursorXXXXXLarge.png|SelectMark.png|MenuSymbol.png|Menu.png|Logo.png|Construction.png|Energy.png|Magic.png|Options.png|UpperBoard.png|Arrow.png|Exit.png|Hand.png|Gamepad.png|Build.png|EnergyBars.png|Liquid.png" \
"|GUICaption.png|GUIButton.png|GUIButtonDown.png|GUIButtonHighlight.png|GUIIcons.png|GUIIcons2.png|GUIScroll.png|GUIContext.png|GUISubmenu.png|GUICheckBox.png|GUIBigArrows.png|GUIProgress.png" \
"|StartupScenSelBG.*|StartupPlrSelBG.*|StartupPlrPropBG.*|StartupNetworkBG.*|StartupAboutBG.*|StartupBigButton.png|StartupBigButtonDown.png|StartupBookScroll.png|StartupContext.png|StartupScenSelIcons.png|StartupScenSelTitleOv.png|StartupPlrCtrlType.png|StartupDlgPaper.png|StartupOptionIcons.png|StartupTabClip.png|StartupNetGetRef.png"
"|StartupScenSelBG.*|StartupPlrSelBG.*|StartupPlrPropBG.*|StartupNetworkBG.*|StartupAboutBG.*|StartupBigButton.png|StartupBigButtonDown.png|StartupBookScroll.png|StartupContext.png|StartupScenSelIcons.png|StartupScenSelTitleOv.png|StartupPlrCtrlType.png|StartupDlgPaper.png|StartupOptionIcons.png|StartupTabClip.png|StartupNetGetRef.png|XinputButtons.png"
#define C4FLS_Mouse "*.txt|*.rtf|Title.bmp|Title.png|Icon.bmp|Tutorial01.c4s|Tutorial02.c4s|Tutorial03.c4s|Objects.c4d"
#define C4FLS_Keyboard "*.txt|*.rtf|Title.bmp|Title.png|Icon.bmp|Tutorial01.c4s|Tutorial02.c4s|Tutorial03.c4s|Tutorial04.c4s|Tutorial05.c4s|Tutorial06.c4s|Tutorial07.c4s|Tutorial08.c4s|Tutorial09.c4s|Tutorial10.c4s"
#define C4FLS_Easy "*.txt|*.rtf|Title.bmp|Title.png|Icon.bmp|Goldmine.c4s|Monsterkill.c4s|Economy.c4s|Melee.c4s|Lake.c4s|Castle.c4s"
Expand Down
80 changes: 58 additions & 22 deletions src/C4Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,28 +275,64 @@ void C4ConfigIRC::CompileFunc(StdCompiler *pComp)

void C4ConfigGamepad::CompileFunc(StdCompiler *pComp, bool fButtonsOnly)
{
/* The defaults here are for a Logitech Dual Action under Linux-SDL. Better than nothing, I guess. */
if (!fButtonsOnly)
{
for (int i = 0; i < 6; ++i)
{
pComp->Value(mkNamingAdapt(AxisMin[i], FormatString("Axis%dMin", i).getData(), 0u));
pComp->Value(mkNamingAdapt(AxisMax[i], FormatString("Axis%dMax", i).getData(), 0u));
pComp->Value(mkNamingAdapt(AxisCalibrated[i], FormatString("Axis%dCalibrated", i).getData(), false));
}
}
pComp->Value(mkNamingAdapt(Button[0], "Button1", -1));
pComp->Value(mkNamingAdapt(Button[1], "Button2", -1));
pComp->Value(mkNamingAdapt(Button[2], "Button3", -1));
pComp->Value(mkNamingAdapt(Button[3], "Button4", -1));
pComp->Value(mkNamingAdapt(Button[4], "Button5", -1));
pComp->Value(mkNamingAdapt(Button[5], "Button6", -1));
pComp->Value(mkNamingAdapt(Button[6], "Button7", -1));
pComp->Value(mkNamingAdapt(Button[7], "Button8", -1));
pComp->Value(mkNamingAdapt(Button[8], "Button9", -1));
pComp->Value(mkNamingAdapt(Button[9], "Button10", -1));
pComp->Value(mkNamingAdapt(Button[10], "Button11", -1));
pComp->Value(mkNamingAdapt(Button[11], "Button12", -1));
// Left stick
pComp->Value(mkNamingAdapt(AxisMinCommand[XINPUT_AXIS_LS_X], "Axis0MinCommand", COM_Left));
pComp->Value(mkNamingAdapt(AxisMaxCommand[XINPUT_AXIS_LS_X], "Axis0MaxCommand", COM_Right));
pComp->Value(mkNamingAdapt(AxisMinCommand[XINPUT_AXIS_LS_Y], "Axis1MinCommand", COM_Down));
pComp->Value(mkNamingAdapt(AxisMaxCommand[XINPUT_AXIS_LS_Y], "Axis1MaxCommand", COM_Up));
// Right stick
pComp->Value(mkNamingAdapt(AxisMinCommand[XINPUT_AXIS_RS_X], "Axis2MinCommand", COM_Left));
pComp->Value(mkNamingAdapt(AxisMaxCommand[XINPUT_AXIS_RS_X], "Axis2MaxCommand", COM_Right));
pComp->Value(mkNamingAdapt(AxisMinCommand[XINPUT_AXIS_RS_Y], "Axis3MinCommand", COM_Up));
pComp->Value(mkNamingAdapt(AxisMaxCommand[XINPUT_AXIS_RS_Y], "Axis3MaxCommand", COM_Down));
// Triggers
pComp->Value(mkNamingAdapt(AxisMinCommand[XINPUT_AXIS_LT], "Axis4MinCommand", COM_None));
pComp->Value(mkNamingAdapt(AxisMaxCommand[XINPUT_AXIS_LT], "Axis4MaxCommand", COM_CursorLeft));
pComp->Value(mkNamingAdapt(AxisMinCommand[XINPUT_AXIS_RT], "Axis5MinCommand", COM_None));
pComp->Value(mkNamingAdapt(AxisMaxCommand[XINPUT_AXIS_RT], "Axis5MaxCommand", COM_CursorRight));

// Primary function for each button, used for normal control
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_UP], "ButtonCommand0", COM_Up));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_DOWN], "ButtonCommand1", COM_Down));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_LEFT], "ButtonCommand2", COM_Left));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_RIGHT], "ButtonCommand3", COM_Right));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_START], "ButtonCommand4", COM_PlayerMenu));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_BACK], "ButtonCommand5", COM_Special));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_LS], "ButtonCommand6", COM_None));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_RS], "ButtonCommand7", COM_None));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_LB], "ButtonCommand8", COM_CursorToggle));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_RB], "ButtonCommand9", COM_CursorToggle));
pComp->Value(mkNamingAdapt(ButtonCommand[10], "ButtonCommand10", COM_None));
pComp->Value(mkNamingAdapt(ButtonCommand[11], "ButtonCommand11", COM_None));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_A], "ButtonCommand12", COM_Jump));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_B], "ButtonCommand13", COM_Dig));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_X], "ButtonCommand14", COM_Throw));
pComp->Value(mkNamingAdapt(ButtonCommand[XINPUT_BUTTON_Y], "ButtonCommand15", COM_Special2));

// Possible secondary function for each button when in a menu
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_UP], "ButtonMenuCommand0", COM_MenuUp));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_DOWN], "ButtonMenuCommand1", COM_MenuDown));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_LEFT], "ButtonMenuCommand2", COM_MenuLeft));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_RIGHT], "ButtonMenuCommand3", COM_MenuRight));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_START], "ButtonMenuCommand4", COM_MenuClose));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_BACK], "ButtonMenuCommand5", COM_MenuClose));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_LS], "ButtonMenuCommand6", COM_None));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_RS], "ButtonMenuCommand7", COM_None));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_LB], "ButtonMenuCommand8", COM_CursorToggle));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_RB], "ButtonMenuCommand9", COM_CursorToggle));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[10], "ButtonMenuCommand10", COM_None));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[11], "ButtonMenuCommand11", COM_None));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_A], "ButtonMenuCommand12", COM_MenuEnter));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_B], "ButtonMenuCommand13", COM_MenuClose));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_X], "ButtonMenuCommand14", COM_Throw));
pComp->Value(mkNamingAdapt(ButtonMenuCommand[XINPUT_BUTTON_Y], "ButtonMenuCommand15", COM_MenuEnterAll));
}

bool C4ConfigGamepad::HasExplicitJumpButton() {
for (auto& command : this->AxisMaxCommand) if (command == COM_Jump) return true;
for (auto& command : this->AxisMinCommand) if (command == COM_Jump) return true;
for (auto& command : this->ButtonCommand) if (command == COM_Jump) return true;
return false;
}

void C4ConfigGamepad::Reset()
Expand Down
7 changes: 4 additions & 3 deletions src/C4Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,12 @@ const int C4ConfigMaxGamepads = 4;
class C4ConfigGamepad
{
public:
int32_t Button[C4MaxKey];
uint32_t AxisMin[6], AxisMax[6];
bool AxisCalibrated[6];
int32_t ButtonCommand[C4MaxGamePadButtons];
int32_t ButtonMenuCommand[C4MaxGamePadButtons];
uint32_t AxisMinCommand[C4MaxGamePadAxis], AxisMaxCommand[C4MaxGamePadAxis];
void CompileFunc(StdCompiler *pComp, bool fButtonsOnly = false);
void Reset(); // reset all buttons and axis calibration to default
bool HasExplicitJumpButton();
};

class C4ConfigControls
Expand Down
10 changes: 9 additions & 1 deletion src/C4Constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ const int C4P_Control_None = -1,
C4P_Control_GamePad4 = 7,
C4P_Control_GamePadMax = C4P_Control_GamePad4;

constexpr int C4MaxGamePadButtons = 16,
C4MaxGamePadAxis = 6;

const int C4ViewportScrollBorder = 40; // scrolling past landscape allowed at range of this border

// Engine Return Values
Expand Down Expand Up @@ -195,7 +198,9 @@ const uint8_t COM_Left = 1,
COM_CursorToggle = 14,
COM_CursorFirst = COM_CursorLeft,
COM_CursorLast = COM_CursorToggle,
COM_Jump = 15,

COM_Release = 16, // TODO: Change all "+16" to "|COM_Release"
COM_Left_R = COM_Left + 16,
COM_Right_R = COM_Right + 16,
COM_Up_R = COM_Up + 16,
Expand All @@ -207,15 +212,17 @@ const uint8_t COM_Left = 1,
COM_CursorLeft_R = COM_CursorLeft + 16,
COM_CursorToggle_R = COM_CursorToggle + 16,
COM_CursorRight_R = COM_CursorRight + 16,
COM_Jump_R = COM_Jump + 16,
COM_ReleaseFirst = COM_Left_R,
COM_ReleaseLast = COM_CursorToggle_R,
COM_ReleaseLast = COM_Jump_R,

COM_Left_S = COM_Left | COM_Single,
COM_Right_S = COM_Right | COM_Single,
COM_Up_S = COM_Up | COM_Single,
COM_Down_S = COM_Down | COM_Single,
COM_Throw_S = COM_Throw | COM_Single,
COM_Dig_S = COM_Dig | COM_Single,
COM_Jump_S = COM_Jump | COM_Single,
COM_Special_S = COM_Special | COM_Single,
COM_Special2_S = COM_Special2 | COM_Single,
COM_CursorLeft_S = COM_CursorLeft | COM_Single,
Expand All @@ -228,6 +235,7 @@ const uint8_t COM_Left = 1,
COM_Down_D = COM_Down | COM_Double,
COM_Throw_D = COM_Throw | COM_Double,
COM_Dig_D = COM_Dig | COM_Double,
COM_Jump_D = COM_Jump | COM_Double,
COM_Special_D = COM_Special | COM_Double,
COM_Special2_D = COM_Special2 | COM_Double,
COM_CursorLeft_D = COM_CursorLeft | COM_Double,
Expand Down
75 changes: 67 additions & 8 deletions src/C4Game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3429,18 +3429,30 @@ bool C4Game::InitKeyboard()
}

// Map player gamepad controls
int32_t iGamepad;
for (iGamepad = C4P_Control_GamePad1; iGamepad < C4P_Control_GamePad1 + C4ConfigMaxGamepads; iGamepad++)
for (int32_t iGamepad = 0; iGamepad < C4ConfigMaxGamepads; iGamepad++)
{
C4ConfigGamepad &cfg = Config.Gamepads[iGamepad - C4P_Control_GamePad1];
for (iCtrl = 0; iCtrl < C4MaxKey; iCtrl++)
int32_t iControlSet = iGamepad + C4P_Control_GamePad1;
C4ConfigGamepad &cfg = Config.Gamepads[iGamepad];

std::map<C4KeyCodeEx, uint32_t> keyToCommand;
for (int iButton = 0; iButton < C4MaxGamePadButtons; iButton++) {
C4KeyCodeEx keyCode = C4KeyCodeEx(KEY_Gamepad(iGamepad, KEY_JOY_Button(iButton)));
keyToCommand.insert_or_assign(keyCode, cfg.ButtonMenuCommand[iButton]); // Ensure that buttons with no normal command and only a menu-command are also registered
keyToCommand.insert_or_assign(keyCode, cfg.ButtonCommand[iButton]); // Normal command overrides menu command, and is converted back to menu command by C4Menu::ConvertCom
}
for (int iAxis = 0; iAxis < C4MaxGamePadAxis; iAxis++) {
keyToCommand.insert_or_assign(C4KeyCodeEx(KEY_Gamepad(iGamepad, KEY_JOY_Axis(iAxis, true))), cfg.AxisMaxCommand[iAxis]);
keyToCommand.insert_or_assign(C4KeyCodeEx(KEY_Gamepad(iGamepad, KEY_JOY_Axis(iAxis, false))), cfg.AxisMinCommand[iAxis]);
}

for (auto& [keyCodeEx, iCommand] : keyToCommand)
{
if (cfg.Button[iCtrl] == -1) continue;
sPlrCtrlName.Format("Joy%dBtn%d", iGamepad - C4P_Control_GamePad1 + 1, iCtrl + 1);
if (iCommand == COM_None) continue;
sPlrCtrlName = C4KeyCodeEx::KeyCode2String(keyCodeEx.Key, false, false);
KeyboardInput.RegisterKey(new C4CustomKey(
C4KeyCodeEx(cfg.Button[iCtrl]),
keyCodeEx,
sPlrCtrlName.getData(), KEYSCOPE_Control,
new C4KeyCBExPassKey<C4Game, C4KeySetCtrl>(*this, C4KeySetCtrl(iGamepad, iCtrl), &C4Game::LocalControlKey, &C4Game::LocalControlKeyUp),
new C4KeyCBExPassKey(*this, C4KeySetCtrl(iControlSet, iCommand), &C4Game::LocalControlGamepad, &C4Game::LocalControlGamepadUp),
C4CustomKey::PRIO_PlrControl));
}
}
Expand Down Expand Up @@ -3559,6 +3571,53 @@ bool C4Game::LocalControlKeyUp(C4KeyCodeEx key, C4KeySetCtrl Ctrl)
return false;
}

bool C4Game::LocalControlGamepad(C4KeyCodeEx key, C4KeySetCtrl Ctrl)
{
// keyboard callback: Perform local player control
C4Player* pPlr;
if (pPlr = Players.GetLocalByKbdSet(Ctrl.iKeySet))
{
int32_t iCommand = Ctrl.iCtrl;
bool cursorMenuActive = pPlr->Cursor && pPlr->Cursor->Menu && pPlr->Cursor->Menu->IsActive();
if (Key_IsGamepadButton(key.Key) && (pPlr->Menu.IsActive() || cursorMenuActive)) {
int32_t iGamepad = Ctrl.iKeySet - C4P_Control_GamePad1;
int32_t iButton = Key_GetGamepadButtonIndex(key.Key);
iCommand = Config.Gamepads[iGamepad].ButtonMenuCommand[iButton];
}
else { // No menu active, send normal command
// Swallow a event generated from Keyrepeat for AutoStopControl
if (pPlr->ControlStyle)
{
if (key.IsRepeated())
return true;
}
}
LocalPlayerControl(pPlr->Number, iCommand);
return true;
}
// not processed - must return false here, so unused keyboard control sets do not block used ones
return false;
}

bool C4Game::LocalControlGamepadUp(C4KeyCodeEx key, C4KeySetCtrl Ctrl)
{
// Direct callback for released key in AutoStopControl-mode (ignore repeated)
if (key.IsRepeated())
return true;
C4Player* pPlr;
if ((pPlr = Players.GetLocalByKbdSet(Ctrl.iKeySet)) && pPlr->ControlStyle)
{
// Ensure that Release events are only generate for "standard" control buttons, not e.g. COM_MenuEnter
int32_t iCom = Ctrl.iCtrl | COM_Release;
if (Inside<int>(iCom, COM_ReleaseFirst, COM_ReleaseLast)) {
LocalPlayerControl(pPlr->Number, iCom);
}
return true;
}
// not processed - must return false here, so unused keyboard control sets do not block used ones
return false;
}

void C4Game::LocalPlayerControl(int32_t iPlayer, int32_t iCom)
{
C4Player *pPlr = Players.Get(iPlayer); if (!pPlr) return;
Expand Down
2 changes: 2 additions & 0 deletions src/C4Game.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ class C4Game
void DrawCursors(C4FacetEx &cgo, int32_t iPlayer);
bool LocalControlKey(C4KeyCodeEx key, C4KeySetCtrl Ctrl);
bool LocalControlKeyUp(C4KeyCodeEx key, C4KeySetCtrl Ctrl);
bool LocalControlGamepad(C4KeyCodeEx key, C4KeySetCtrl Ctrl);
bool LocalControlGamepadUp(C4KeyCodeEx key, C4KeySetCtrl Ctrl);
void LocalPlayerControl(int32_t iPlayer, int32_t iCom);
void FixRandom(int32_t iSeed);
bool Init();
Expand Down
Loading