forked from Mirrors/openclonk
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
parent
9e0143b998
commit
8811356141
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() { }
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue