openclonk/src/platform/C4GamePadCon.cpp

375 lines
10 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2010-2013, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
/* Gamepad control */
#include <C4Include.h>
#include <C4GamePadCon.h>
#include <C4Config.h>
#include <C4ObjectCom.h>
#include <C4Log.h>
#include <C4Game.h>
// regardless of WIN32 or SDL
void C4GamePadControl::DoAxisInput()
{
// Send axis strength changes
Execute(true);
}
#ifdef USE_WIN32_WINDOWS
C4GamePadControl *C4GamePadControl::pInstance = NULL;
C4GamePadControl::C4GamePadControl()
{
for (int i=0; i<CStdGamepad_MaxGamePad; ++i)
{
Gamepads[i].pGamepad = NULL;
Gamepads[i].iRefCount = 0;
}
iNumGamepads = 0;
// singleton
if (!pInstance) pInstance = this;
}
C4GamePadControl::~C4GamePadControl()
{
if (pInstance == this) pInstance = NULL;
Clear();
}
void C4GamePadControl::Clear()
{
for (int i=0; i<CStdGamepad_MaxGamePad; ++i)
while (Gamepads[i].iRefCount) CloseGamepad(i);
}
void C4GamePadControl::OpenGamepad(int id)
{
if (!Inside(id, 0, CStdGamepad_MaxGamePad-1)) return;
// add gamepad ref
if (!(Gamepads[id].iRefCount++))
{
// this is the first gamepad opening: Init it
Pad &rPad = Gamepads[id];
rPad.pGamepad = new CStdGamePad(id);
rPad.Buttons= 0;
for (int i=0; i< CStdGamepad_MaxAxis; ++i)
{
rPad.AxisPosis[i] = CStdGamePad::Mid;
rPad.AxisStrengths[i] = 0;
}
rPad.pGamepad->SetCalibration(&(Config.Gamepads[id].AxisMin[0]), &(Config.Gamepads[id].AxisMax[0]), &(Config.Gamepads[id].AxisCalibrated[0]));
++iNumGamepads;
}
}
void C4GamePadControl::CloseGamepad(int id)
{
if (!Inside(id, 0, CStdGamepad_MaxGamePad-1)) return;
// del gamepad ref
if (!--Gamepads[id].iRefCount)
{
Gamepads[id].pGamepad->GetCalibration(&(Config.Gamepads[id].AxisMin[0]), &(Config.Gamepads[id].AxisMax[0]), &(Config.Gamepads[id].AxisCalibrated[0]));
delete Gamepads[id].pGamepad; Gamepads[id].pGamepad = NULL;
--iNumGamepads;
}
}
int C4GamePadControl::GetGamePadCount()
{
JOYINFOEX joy;
ZeroMem(&joy, sizeof(JOYINFOEX)); joy.dwSize = sizeof(JOYINFOEX); joy.dwFlags = JOY_RETURNALL;
int iCnt=0;
while (iCnt<CStdGamepad_MaxGamePad && ::joyGetPosEx(iCnt, &joy) == JOYERR_NOERROR) ++iCnt;
return iCnt;
}
const int MaxGamePadButton=10;
void C4GamePadControl::Execute(bool send_axis_strength_changes)
{
// Get gamepad inputs
int iNum = iNumGamepads;
for (int idGamepad=0; iNum && idGamepad<CStdGamepad_MaxGamePad; ++idGamepad)
{
Pad &rPad = Gamepads[idGamepad];
if (!rPad.iRefCount) continue;
--iNum;
if (!rPad.pGamepad->Update()) continue;
for (int iAxis = 0; iAxis < CStdGamepad_MaxAxis; ++iAxis)
{
int32_t iStrength = 100;
CStdGamePad::AxisPos eAxisPos = rPad.pGamepad->GetAxisPos(iAxis, &iStrength), ePrevAxisPos = rPad.AxisPosis[iAxis];
int32_t iPrevStrength = rPad.AxisStrengths[iAxis];
// Evaluate changes and pass single controls
// this is a generic Gamepad-control: Create events
if (eAxisPos != ePrevAxisPos || (send_axis_strength_changes && Abs(iPrevStrength-iStrength) > AxisStrengthChangeThreshold))
{
rPad.AxisPosis[iAxis] = eAxisPos;
rPad.AxisStrengths[iAxis] = iStrength;
if (ePrevAxisPos != CStdGamePad::Mid && eAxisPos != ePrevAxisPos)
Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Axis(iAxis, (ePrevAxisPos==CStdGamePad::High))), KEYEV_Up, false, false, false, false);
// it's tempting to send fRepeated here for eAxisPos == ePrevAxisPos, but it would cause the key to be ignored for sync controls
// might improve the check in sync controls so they accept repeated keys if strength is updated?
if (eAxisPos != CStdGamePad::Mid)
Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Axis(iAxis, (eAxisPos==CStdGamePad::High))), KEYEV_Down, false, false, false, false, NULL, false, iStrength);
}
}
uint32_t Buttons = rPad.pGamepad->GetButtons();
uint32_t PrevButtons = rPad.Buttons;
if (Buttons != PrevButtons)
{
rPad.Buttons = Buttons;
for (int iButton = 0; iButton < MaxGamePadButton; ++iButton)
if ((Buttons & (1 << iButton)) != (PrevButtons & (1 << iButton)))
{
bool fRelease = ((Buttons & (1 << iButton)) == 0);
Game.DoKeyboardInput(KEY_Gamepad(idGamepad, KEY_JOY_Button(iButton)), fRelease ? KEYEV_Up : KEYEV_Down, false, false, false, false);
}
}
}
}
bool C4GamePadControl::AnyButtonDown()
{
return false;
}
C4GamePadOpener::C4GamePadOpener(int iGamepad)
{
assert(C4GamePadControl::pInstance);
this->iGamePad = iGamepad;
C4GamePadControl::pInstance->OpenGamepad(iGamePad);
}
C4GamePadOpener::~C4GamePadOpener()
{
if (C4GamePadControl::pInstance)
C4GamePadControl::pInstance->CloseGamepad(iGamePad);
}
void C4GamePadOpener::SetGamePad(int iNewGamePad)
{
if (iNewGamePad == iGamePad) return;
assert(C4GamePadControl::pInstance);
C4GamePadControl::pInstance->CloseGamepad(iGamePad);
C4GamePadControl::pInstance->OpenGamepad(iGamePad = iNewGamePad);
}
#elif defined(HAVE_SDL) && !defined(USE_CONSOLE)
#include <SDL.h>
bool C4GamePadControl::AnyButtonDown()
{
return false;
}
C4GamePadControl::C4GamePadControl()
{
// Initialize SDL, if necessary.
if (!SDL_WasInit(SDL_INIT_JOYSTICK)
&& SDL_Init(SDL_INIT_AUDIO | SDL_INIT_JOYSTICK | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE))
LogF("SDL: %s", SDL_GetError());
SDL_JoystickEventState(SDL_ENABLE);
if (!SDL_NumJoysticks()) Log("No Gamepad found");
}
C4GamePadControl::~C4GamePadControl()
{
}
void C4GamePadControl::Execute(bool)
{
#ifndef USE_SDL_MAINLOOP
SDL_Event event;
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_JOYAXISMOTION:
case SDL_JOYBALLMOTION:
case SDL_JOYHATMOTION:
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
FeedEvent(event);
break;
}
}
#endif
}
namespace
{
const int deadZone = 13337;
int amplify(int i)
{
if (i < 0)
return -(deadZone + 1);
if (i > 0)
return deadZone + 1;
return 0;
}
}
void C4GamePadControl::FeedEvent(SDL_Event& event)
{
switch (event.type)
{
case SDL_JOYHATMOTION:
{
SDL_Event fakeX;
fakeX.jaxis.type = SDL_JOYAXISMOTION;
fakeX.jaxis.which = event.jhat.which;
fakeX.jaxis.axis = event.jhat.hat * 2 + 6; /* *magic*number* */
fakeX.jaxis.value = 0;
SDL_Event fakeY = fakeX;
fakeY.jaxis.axis += 1;
switch (event.jhat.value)
{
case SDL_HAT_LEFTUP: fakeX.jaxis.value = amplify(-1); fakeY.jaxis.value = amplify(-1); break;
case SDL_HAT_LEFT: fakeX.jaxis.value = amplify(-1); break;
case SDL_HAT_LEFTDOWN: fakeX.jaxis.value = amplify(-1); fakeY.jaxis.value = amplify(+1); break;
case SDL_HAT_UP: fakeY.jaxis.value = amplify(-1); break;
case SDL_HAT_DOWN: fakeY.jaxis.value = amplify(+1); break;
case SDL_HAT_RIGHTUP: fakeX.jaxis.value = amplify(+1); fakeY.jaxis.value = amplify(-1); break;
case SDL_HAT_RIGHT: fakeX.jaxis.value = amplify(+1); break;
case SDL_HAT_RIGHTDOWN: fakeX.jaxis.value = amplify(+1); fakeY.jaxis.value = amplify(+1); break;
}
FeedEvent(fakeX);
FeedEvent(fakeY);
return;
}
case SDL_JOYBALLMOTION:
{
SDL_Event fake;
fake.jaxis.type = SDL_JOYAXISMOTION;
fake.jaxis.which = event.jball.which;
fake.jaxis.axis = event.jball.ball * 2 + 12; /* *magic*number* */
fake.jaxis.value = amplify(event.jball.xrel);
FeedEvent(event);
fake.jaxis.axis += 1;
fake.jaxis.value = amplify(event.jball.yrel);
FeedEvent(event);
return;
}
case SDL_JOYAXISMOTION:
{
C4KeyCode minCode = KEY_Gamepad(event.jaxis.which, KEY_JOY_Axis(event.jaxis.axis, false));
C4KeyCode maxCode = KEY_Gamepad(event.jaxis.which, KEY_JOY_Axis(event.jaxis.axis, true));
// FIXME: This assumes that the axis really rests around (0, 0) if it is not used, which is not always true.
if (event.jaxis.value < -deadZone)
{
if (PressedAxis.count(minCode) == 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, minCode),
KEYEV_Down, false, false, false, false);
PressedAxis.insert(minCode);
}
}
else
{
if (PressedAxis.count(minCode) != 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, minCode),
KEYEV_Up, false, false, false, false);
PressedAxis.erase(minCode);
}
}
if (event.jaxis.value > +deadZone)
{
if (PressedAxis.count(maxCode) == 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, maxCode),
KEYEV_Down, false, false, false, false);
PressedAxis.insert(maxCode);
}
}
else
{
if (PressedAxis.count(maxCode) != 0)
{
Game.DoKeyboardInput(
KEY_Gamepad(event.jaxis.which, maxCode),
KEYEV_Up, false, false, false, false);
PressedAxis.erase(maxCode);
}
}
break;
}
case SDL_JOYBUTTONDOWN:
Game.DoKeyboardInput(
KEY_Gamepad(event.jbutton.which, KEY_JOY_Button(event.jbutton.button)),
KEYEV_Down, false, false, false, false);
break;
case SDL_JOYBUTTONUP:
Game.DoKeyboardInput(
KEY_Gamepad(event.jbutton.which, KEY_JOY_Button(event.jbutton.button)),
KEYEV_Up, false, false, false, false);
break;
}
}
int C4GamePadControl::GetGamePadCount()
{
return(SDL_NumJoysticks());
}
C4GamePadOpener::C4GamePadOpener(int iGamepad)
{
Joy = SDL_JoystickOpen(iGamepad);
if (!Joy) LogF("SDL: %s", SDL_GetError());
}
C4GamePadOpener::~C4GamePadOpener()
{
if (Joy) SDL_JoystickClose(Joy);
}
void C4GamePadOpener::SetGamePad(int iGamepad)
{
if (Joy)
SDL_JoystickClose(Joy);
Joy = SDL_JoystickOpen(iGamepad);
if (!Joy)
LogF("SDL: %s", SDL_GetError());
}
#else
// Dedicated server and everything else with neither Win32 nor SDL.
C4GamePadControl::C4GamePadControl() { Log("WARNING: Engine without Gamepad support"); }
C4GamePadControl::~C4GamePadControl() { }
void C4GamePadControl::Execute(bool) { }
int C4GamePadControl::GetGamePadCount() { return 0; }
bool C4GamePadControl::AnyButtonDown() { return false; }
C4GamePadOpener::C4GamePadOpener(int iGamepad) { }
C4GamePadOpener::~C4GamePadOpener() {}
void C4GamePadOpener::SetGamePad(int iGamepad) { }
#endif //_WIN32