forked from Mirrors/openclonk
278 lines
7.5 KiB
C++
278 lines
7.5 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-2016, 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 "C4ForbidLibraryCompilation.h"
|
|
#include "platform/C4GamePadCon.h"
|
|
|
|
#include "game/C4Application.h"
|
|
#include "object/C4ObjectCom.h"
|
|
|
|
#if defined(HAVE_SDL) && !defined(USE_CONSOLE)
|
|
|
|
#include <SDL.h>
|
|
|
|
C4GamePadControl::C4GamePadControl()
|
|
{
|
|
// SDL2 will only report events when the window has focus, so set
|
|
// this hint as we don't have a window
|
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
|
|
|
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC | SDL_INIT_EVENTS) != 0)
|
|
LogF("SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER): %s", SDL_GetError());
|
|
SDL_GameControllerEventState(SDL_ENABLE);
|
|
if (!GetGamePadCount()) Log("No Gamepad found");
|
|
}
|
|
|
|
C4GamePadControl::~C4GamePadControl()
|
|
{
|
|
// All gamepads have to be released before quitting SDL.
|
|
Gamepads.clear();
|
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_EVENTS);
|
|
}
|
|
|
|
void C4GamePadControl::Execute()
|
|
{
|
|
#ifdef USE_SDL_MAINLOOP
|
|
if (!Application.isEditor) return;
|
|
#endif
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event))
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case SDL_CONTROLLERAXISMOTION:
|
|
case SDL_CONTROLLERBUTTONDOWN:
|
|
case SDL_CONTROLLERBUTTONUP:
|
|
FeedEvent(event, FEED_BUTTONS);
|
|
break;
|
|
case SDL_JOYDEVICEADDED:
|
|
case SDL_CONTROLLERDEVICEADDED:
|
|
case SDL_CONTROLLERDEVICEREMOVED:
|
|
CheckGamePad(event);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
const int deadZone = 16000;
|
|
|
|
// Axis strength uses the full signed 16 bit integer range. As we're
|
|
// splitting axes in left/right and up/down, it's preferable to have
|
|
// symmetrical ranges [0, 2^15 - 1] in both directions.
|
|
inline int32_t abs_strength(int32_t strength)
|
|
{
|
|
return strength >= 0 ? strength : -(strength + 1);
|
|
}
|
|
}
|
|
|
|
void C4GamePadControl::FeedEvent(const SDL_Event& event, int feed)
|
|
{
|
|
switch (event.type)
|
|
{
|
|
case SDL_CONTROLLERAXISMOTION:
|
|
{
|
|
C4KeyCode minCode = KEY_Gamepad(KEY_CONTROLLER_Axis(event.caxis.axis, false));
|
|
C4KeyCode maxCode = KEY_Gamepad(KEY_CONTROLLER_Axis(event.caxis.axis, true));
|
|
int32_t value = abs_strength(event.caxis.value);
|
|
uint8_t which = event.caxis.which;
|
|
C4KeyCode keyCode = event.caxis.value >= 0 ? maxCode : minCode;
|
|
|
|
auto doInput = [&](C4KeyEventType event, int32_t strength)
|
|
{
|
|
Game.DoKeyboardInput(
|
|
C4KeyCodeEx(KEY_Gamepad(keyCode), KEYS_None, false, which),
|
|
event, nullptr, false, strength);
|
|
};
|
|
|
|
if (feed & FEED_BUTTONS)
|
|
{
|
|
// Also emulate button presses.
|
|
if (PressedAxis.count(keyCode) && value <= deadZone)
|
|
{
|
|
PressedAxis.erase(keyCode);
|
|
doInput(KEYEV_Up, -1);
|
|
}
|
|
else if (!PressedAxis.count(keyCode) && value > deadZone)
|
|
{
|
|
PressedAxis.insert(keyCode);
|
|
doInput(KEYEV_Down, -1);
|
|
}
|
|
}
|
|
if (feed & FEED_MOVED)
|
|
doInput(KEYEV_Moved, value);
|
|
|
|
AxisEvents[keyCode] = event;
|
|
|
|
break;
|
|
}
|
|
case SDL_CONTROLLERBUTTONDOWN:
|
|
if (feed & FEED_BUTTONS)
|
|
Game.DoKeyboardInput(
|
|
C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_Button(event.cbutton.button)), KEYS_None, false, event.cbutton.which),
|
|
KEYEV_Down);
|
|
break;
|
|
case SDL_CONTROLLERBUTTONUP:
|
|
if (feed & FEED_BUTTONS)
|
|
Game.DoKeyboardInput(
|
|
C4KeyCodeEx(KEY_Gamepad(KEY_CONTROLLER_Button(event.cbutton.button)), KEYS_None, false, event.cbutton.which),
|
|
KEYEV_Up);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void C4GamePadControl::CheckGamePad(const SDL_Event& e)
|
|
{
|
|
switch (e.type)
|
|
{
|
|
case SDL_JOYDEVICEADDED:
|
|
// Report that an unsupported joystick device has been detected, to help with controller issues.
|
|
if (!SDL_IsGameController(e.jdevice.which))
|
|
LogF("Gamepad %s isn't supported.", SDL_JoystickNameForIndex(e.jdevice.which));
|
|
break;
|
|
case SDL_CONTROLLERDEVICEADDED:
|
|
{
|
|
auto device = std::make_shared<C4GamePadOpener>(e.cdevice.which);
|
|
Gamepads[device->GetID()] = device;
|
|
LogF("Gamepad #%d connected: %s", device->GetID(), SDL_JoystickNameForIndex(e.cdevice.which));
|
|
break;
|
|
}
|
|
case SDL_CONTROLLERDEVICEREMOVED:
|
|
LogF("Gamepad #%d disconnected.", e.cdevice.which);
|
|
Gamepads.erase(e.cdevice.which);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void C4GamePadControl::DoAxisInput()
|
|
{
|
|
for (auto const &e : AxisEvents)
|
|
{
|
|
FeedEvent(e.second, FEED_MOVED);
|
|
}
|
|
AxisEvents.clear();
|
|
}
|
|
|
|
int C4GamePadControl::GetGamePadCount()
|
|
{
|
|
// Not all Joysticks are game controllers.
|
|
int count = 0;
|
|
for (int i = 0; i < SDL_NumJoysticks(); i++)
|
|
if (SDL_IsGameController(i))
|
|
count++;
|
|
return count;
|
|
}
|
|
|
|
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePad(int gamepad)
|
|
{
|
|
if (gamepad >= 0)
|
|
for (const auto& p : Gamepads)
|
|
if (gamepad-- == 0)
|
|
return p.second;
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePadByID(int32_t id)
|
|
{
|
|
auto it = Gamepads.find(id);
|
|
if (it != Gamepads.end())
|
|
return it->second;
|
|
return nullptr;
|
|
}
|
|
|
|
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetAvailableGamePad()
|
|
{
|
|
for (const auto& p : Gamepads)
|
|
if (p.second->GetPlayer() < 0)
|
|
return p.second;
|
|
return nullptr;
|
|
}
|
|
|
|
C4GamePadOpener::C4GamePadOpener(int iGamepad)
|
|
{
|
|
int n = iGamepad;
|
|
for (int i = 0; i < SDL_NumJoysticks(); i++)
|
|
if (SDL_IsGameController(i) && n-- == 0)
|
|
{
|
|
controller = SDL_GameControllerOpen(i);
|
|
if (!controller) LogF("SDL: %s", SDL_GetError());
|
|
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);
|
|
haptic = SDL_HapticOpenFromJoystick(joystick);
|
|
if (haptic && SDL_HapticRumbleSupported(haptic))
|
|
SDL_HapticRumbleInit(haptic);
|
|
else
|
|
LogF("Gamepad #%d %s does not support rumbling.", SDL_JoystickInstanceID(joystick), SDL_JoystickName(joystick));
|
|
break;
|
|
}
|
|
|
|
if (!controller) LogF("Gamepad %d not available", iGamepad);
|
|
}
|
|
|
|
C4GamePadOpener::~C4GamePadOpener()
|
|
{
|
|
if (haptic) SDL_HapticClose(haptic);
|
|
if (controller) SDL_GameControllerClose(controller);
|
|
}
|
|
|
|
int32_t C4GamePadOpener::GetID()
|
|
{
|
|
return SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller));
|
|
}
|
|
|
|
bool C4GamePadOpener::IsAttached()
|
|
{
|
|
return !!SDL_GameControllerGetAttached(controller);
|
|
}
|
|
|
|
void C4GamePadOpener::PlayRumble(float strength, uint32_t length)
|
|
{
|
|
if (SDL_HapticRumbleSupported(haptic))
|
|
SDL_HapticRumblePlay(haptic, strength, length);
|
|
}
|
|
|
|
void C4GamePadOpener::StopRumble()
|
|
{
|
|
if (SDL_HapticRumbleSupported(haptic))
|
|
SDL_HapticRumbleStop(haptic);
|
|
}
|
|
|
|
#else
|
|
|
|
// Dedicated server and everything else with neither Win32 nor SDL.
|
|
|
|
C4GamePadControl::C4GamePadControl() { Log("WARNING: Engine without Gamepad support"); }
|
|
C4GamePadControl::~C4GamePadControl() = default;
|
|
void C4GamePadControl::Execute() { }
|
|
void C4GamePadControl::DoAxisInput() { }
|
|
int C4GamePadControl::GetGamePadCount() { return 0; }
|
|
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePad(int gamepad) { return nullptr; }
|
|
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetGamePadByID(int32_t id) { return nullptr; }
|
|
std::shared_ptr<C4GamePadOpener> C4GamePadControl::GetAvailableGamePad() { return nullptr; }
|
|
|
|
C4GamePadOpener::C4GamePadOpener(int iGamepad) { }
|
|
C4GamePadOpener::~C4GamePadOpener() = default;
|
|
int32_t C4GamePadOpener::GetID() { return -1; }
|
|
bool C4GamePadOpener::IsAttached() { return false; }
|
|
void C4GamePadOpener::PlayRumble(float strength, uint32_t length) { }
|
|
void C4GamePadOpener::StopRumble() { }
|
|
|
|
#endif
|