Make C4GamePadControl manage all controllers

The available gamepads are distributed automatically among players.

This also implements controller hot-plugging: It is possible to start a
game without a controller and plug it in later, and to reconnect a
controller after plugging it out.
liquid_container
Lukas Werling 2016-02-20 18:26:30 +01:00
parent 9e0143b998
commit 8811356141
11 changed files with 153 additions and 41 deletions

View File

@ -707,7 +707,7 @@ C4Facet C4PlayerControlAssignmentSet::GetPicture() const
{
// get image to be drawn to represent this control set
// picture per set not implemented yet. So just default to out standard images
if (HasGamepad()) return ::GraphicsResource.fctGamepad.GetPhase(GetGamepadIndex());
if (HasGamepad()) return ::GraphicsResource.fctGamepad.GetPhase(0);
// if (HasMouse()) return ::GraphicsResource.fctMouse; // might be useful again with changing control sets
if (HasKeyboard()) return ::GraphicsResource.fctKeyboard.GetPhase(Game.PlayerControlUserAssignmentSets.GetSetIndex(this));
return C4Facet();

View File

@ -277,7 +277,6 @@ public:
bool HasMouse() const { return has_mouse; }
bool HasGamepad() const { return has_gamepad; }
int32_t GetLayoutOrder() const { return 0; } // returns position on keyboard (increasing from left to right) for viewport sorting
int32_t GetGamepadIndex() const { return 0; }
bool IsMouseControlAssigned(int32_t mouseevent) const;
};

View File

@ -569,7 +569,7 @@ namespace C4GUI
}
}
Screen::Screen() : Window(), Mouse(0, 0), pContext(NULL), fExclusive(true), pGamePadOpener(NULL), fZoom(1.0f)
Screen::Screen() : Window(), Mouse(0, 0), pContext(NULL), fExclusive(true), fZoom(1.0f)
{
// no dialog active
pActiveDlg = NULL;
@ -585,9 +585,6 @@ namespace C4GUI
// set size - calcs client area as well
SetBounds(C4Rect(tx,ty,twdt,thgt));
SetPreferredDlgRect(C4Rect(0,0,twdt,thgt));
// GamePad
if (Application.pGamePadControl && Config.Controls.GamepadGuiControl)
pGamePadOpener = new C4GamePadOpener(0);
}
void Screen::Clear()
@ -595,8 +592,6 @@ namespace C4GUI
Container::Clear();
// dtor: Close context menu
AbortContext(false);
// GamePad
if (pGamePadOpener) delete pGamePadOpener;
// fields reset
fExclusive = true;
fZoom = 1.0f;
@ -1055,15 +1050,7 @@ namespace C4GUI
void Screen::UpdateGamepadGUIControlEnabled()
{
// update pGamePadOpener to config value
if (pGamePadOpener && (!Config.Controls.GamepadGuiControl || !Application.pGamePadControl))
{
delete pGamePadOpener; pGamePadOpener = NULL;
}
else if (!pGamePadOpener && (Config.Controls.GamepadGuiControl && Application.pGamePadControl))
{
pGamePadOpener = new C4GamePadOpener(0);
}
// Gamepad is always kept open now.
}
Screen TheScreen;

View File

@ -2590,7 +2590,6 @@ namespace C4GUI
ContextMenu *pContext; // currently opened context menu (lowest submenu)
bool fExclusive; // default true. if false, input is shared with the game
C4Rect PreferredDlgRect; // rectangle in which dialogs should be placed
C4GamePadOpener * pGamePadOpener;
float fZoom;
static Screen *pScreen; // static singleton var

View File

@ -391,7 +391,7 @@ void C4StartupOptionsDlg::ControlConfigListBox::SetUserKey(class C4PlayerControl
// --- C4StartupOptionsDlg::ControlConfigArea
C4StartupOptionsDlg::ControlConfigArea::ControlConfigArea(const C4Rect &rcArea, int32_t iHMargin, int32_t iVMargin, bool fGamepad, C4StartupOptionsDlg *pOptionsDlg)
: C4GUI::Window(), fGamepad(fGamepad), pGamepadOpener(NULL), pOptionsDlg(pOptionsDlg), pGUICtrl(NULL)
: C4GUI::Window(), fGamepad(fGamepad), pOptionsDlg(pOptionsDlg), pGUICtrl(NULL)
{
CStdFont *pUseFontSmall = &(C4Startup::Get()->Graphics.BookSmallFont);
SetBounds(rcArea);
@ -436,7 +436,6 @@ C4StartupOptionsDlg::ControlConfigArea::ControlConfigArea(const C4Rect &rcArea,
C4StartupOptionsDlg::ControlConfigArea::~ControlConfigArea()
{
delete [] ppKeyControlSetBtns;
if (pGamepadOpener) delete pGamepadOpener;
}
void C4StartupOptionsDlg::ControlConfigArea::OnCtrlSetBtn(C4GUI::Control *btn)

View File

@ -227,7 +227,6 @@ private:
int32_t iSelectedCtrlSet; // keyboard or gamepad set that is currently being configured
class C4GUI::IconButton ** ppKeyControlSetBtns; // buttons to select configured control set - array in length of iMaxControlSets
class KeySelButton * KeyControlBtns[C4MaxKey]; // buttons to configure individual kbd set buttons
C4GamePadOpener *pGamepadOpener; // opened gamepad for configuration
C4StartupOptionsDlg *pOptionsDlg;
ControlConfigListBox *control_list;
class C4GUI::CheckBox *pGUICtrl;

View File

@ -167,6 +167,11 @@ void C4AbstractApp::HandleSDLEvent(SDL_Event& e)
case SDL_CONTROLLERBUTTONUP:
Application.pGamePadControl->FeedEvent(e, C4GamePadControl::FEED_BUTTONS);
break;
case SDL_JOYDEVICEADDED:
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
Application.pGamePadControl->CheckGamePad(e);
break;
}
}

View File

@ -57,6 +57,11 @@ void C4GamePadControl::Execute()
case SDL_CONTROLLERBUTTONUP:
FeedEvent(event, FEED_BUTTONS);
break;
case SDL_JOYDEVICEADDED:
case SDL_CONTROLLERDEVICEADDED:
case SDL_CONTROLLERDEVICEREMOVED:
CheckGamePad(event);
break;
}
}
#endif
@ -122,6 +127,29 @@ void C4GamePadControl::FeedEvent(const SDL_Event& event, int feed)
}
}
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)
@ -141,6 +169,31 @@ int C4GamePadControl::GetGamePadCount()
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;
@ -149,14 +202,12 @@ C4GamePadOpener::C4GamePadOpener(int iGamepad)
{
controller = SDL_GameControllerOpen(i);
if (!controller) LogF("SDL: %s", SDL_GetError());
haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(controller));
if (haptic)
{
if (SDL_HapticRumbleSupported(haptic))
SDL_HapticRumbleInit(haptic);
}
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller);
haptic = SDL_HapticOpenFromJoystick(joystick);
if (haptic && SDL_HapticRumbleSupported(haptic))
SDL_HapticRumbleInit(haptic);
else
LogF("SDL: %s", SDL_GetError());
LogF("Gamepad #%d %s does not support rumbling.", SDL_JoystickInstanceID(joystick), SDL_JoystickName(joystick));
break;
}
@ -169,6 +220,16 @@ C4GamePadOpener::~C4GamePadOpener()
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))
@ -190,9 +251,14 @@ C4GamePadControl::~C4GamePadControl() { }
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() {}
int32_t C4GamePadOpener::GetID() { return -1; }
bool C4GamePadOpener::IsAttached() { return false; }
void C4GamePadOpener::PlayRumble(float strength, uint32_t length) { }
void C4GamePadOpener::StopRumble() { }

View File

@ -20,20 +20,17 @@
#ifndef INC_C4GamePadCon
#define INC_C4GamePadCon
#include <memory>
#ifdef HAVE_SDL
#include <C4KeyboardInput.h>
#include <set>
#include <map>
#include <SDL.h>
#endif
struct _SDL_GameController;
typedef struct _SDL_GameController SDL_GameController;
struct _SDL_Haptic;
typedef struct _SDL_Haptic SDL_Haptic;
union SDL_Event;
typedef union SDL_Event SDL_Event;
class C4GamePadOpener;
class C4GamePadControl
{
@ -45,9 +42,11 @@ public:
};
// Called from C4AppSDL
void FeedEvent(const SDL_Event& e, int feed);
void CheckGamePad(const SDL_Event& e);
private:
std::set<C4KeyCode> PressedAxis; // for button emulation
std::map<C4KeyCode, SDL_Event> AxisEvents; // for analog movement events
std::map<int32_t, std::shared_ptr<C4GamePadOpener> > Gamepads; // gamepad instance id -> gamepad
#endif
public:
C4GamePadControl();
@ -56,14 +55,27 @@ public:
int GetGamePadCount();
void Execute();
void DoAxisInput(); // period axis strength update controls sent on each control frame creation
std::shared_ptr<C4GamePadOpener> GetGamePad(int gamepad); // Gets the nth gamepad.
std::shared_ptr<C4GamePadOpener> GetGamePadByID(int32_t id); // Gets a gamepad by its instance id.
std::shared_ptr<C4GamePadOpener> GetAvailableGamePad(); // Looks for a gamepad that doesn't have an assigned player.
};
class C4GamePadOpener
{
int32_t player = -1;
public:
C4GamePadOpener(int iGamePad);
~C4GamePadOpener();
// A gamepad can be assigned to a player.
int32_t GetPlayer() const { return player; }
void SetPlayer(int32_t plr) { player = plr; }
int32_t GetID(); // Returns the gamepad's instance id.
bool IsAttached(); // Returns whether the gamepad is currently attached.
// Force feedback: simple rumbling
void PlayRumble(float strength, uint32_t length); // strength: 0-1, length: milliseconds
void StopRumble();

View File

@ -66,7 +66,6 @@ C4Player::C4Player() : C4PlayerInfoCore()
LastControlType = PCID_None;
LastControlID = 0;
pMsgBoardQuery = NULL;
pGamepad = NULL;
NoEliminationCheck = false;
Evaluated = false;
ZoomLimitMinWdt = ZoomLimitMinHgt = ZoomLimitMaxWdt = ZoomLimitMaxHgt = ZoomWdt = ZoomHgt = 0;
@ -86,7 +85,6 @@ C4Player::~C4Player()
delete pMsgBoardQuery;
pMsgBoardQuery = pNext;
}
delete pGamepad; pGamepad = NULL;
ClearControl();
}
@ -212,6 +210,30 @@ void C4Player::Execute()
Menu.TryClose(false, false);
}
// Do we have a gamepad?
if (pGamepad)
{
// Check whether it's still connected.
if (!pGamepad->IsAttached())
{
// Allow the player to plug the gamepad back in. This allows
// battery replacement or plugging the controller back
// in after someone tripped over the wire.
if (!FindGamepad())
{
LogF("%s: No gamepad available.", Name.getData());
::Game.Pause();
}
}
}
// Should we have one? The player may have started the game
// without turning their controller on, only noticing this
// after the game started.
else if (LocalControl && ControlSet && ControlSet->HasGamepad())
{
FindGamepad();
}
// Tick1
UpdateView();
ExecuteControl();
@ -1349,8 +1371,12 @@ void C4Player::ClearControl()
LocalControl = false;
ControlSetName.Clear();
ControlSet=NULL;
if (pGamepad) { delete pGamepad; pGamepad=NULL; }
MouseControl = false;
if (pGamepad)
{
pGamepad->SetPlayer(NO_OWNER);
pGamepad.reset();
}
// no controls issued yet
ControlCount = ActionCount = 0;
LastControlType = PCID_None;
@ -1382,7 +1408,11 @@ void C4Player::InitControl()
// init gamepad
if (ControlSet && ControlSet->HasGamepad())
{
pGamepad = new C4GamePadOpener(ControlSet->GetGamepadIndex());
if (!FindGamepad())
{
LogF("No gamepad available for %s, please plug one in!", Name.getData());
::Game.Pause();
}
}
// Mouse
if (ControlSet && ControlSet->HasMouse() && PrefMouse)
@ -1396,6 +1426,18 @@ void C4Player::InitControl()
Control.RegisterKeyset(Number, ControlSet);
}
bool C4Player::FindGamepad()
{
auto newPad = Application.pGamePadControl->GetAvailableGamePad();
if (!newPad) return false;
newPad->SetPlayer(ID);
// Release the old gamepad.
if (pGamepad) pGamepad->SetPlayer(NO_OWNER);
pGamepad = newPad;
LogF("%s: Using gamepad #%d.", Name.getData(), pGamepad->GetID());
return true;
}
int igOffX, igOffY;
int VisibilityCheck(int iVis, int sx, int sy, int cx, int cy)

View File

@ -28,6 +28,7 @@
#include "C4PlayerControl.h"
#include <C4Value.h>
#include <set>
#include <memory>
const int32_t C4PVM_Cursor = 0,
C4PVM_Target = 1,
@ -130,7 +131,7 @@ public:
C4PlayerControl Control;
C4ObjectPtr Cursor, ViewCursor;
int32_t CursorFlash;
class C4GamePadOpener *pGamepad;
std::shared_ptr<class C4GamePadOpener> pGamepad;
// Message
int32_t MessageStatus;
char MessageBuf[256+1];
@ -265,6 +266,9 @@ private:
bool AdjustZoomParameter(int32_t *range_par, int32_t new_val, bool no_increase, bool no_decrease);
bool AdjustZoomParameter(C4Fixed *zoom_par, C4Fixed new_val, bool no_increase, bool no_decrease);
// Finds a new gamepad to use, returning true on success.
bool FindGamepad();
public:
// custom scenario achievements
bool GainScenarioAchievement(const char *achievement_id, int32_t value, const char *scen_name_override=NULL);