Maelstrom: Added joystick hotplug support

From cfb87e57a8edb037eb687aa924a389e70d1cd722 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 28 Nov 2025 09:03:21 -0800
Subject: [PATCH] Added joystick hotplug support

---
 game/controls.cpp | 86 ++++++++++++++++++++++++++++++-----------------
 game/controls.h   |  4 ---
 game/gameinfo.h   |  8 ++---
 3 files changed, 57 insertions(+), 41 deletions(-)

diff --git a/game/controls.cpp b/game/controls.cpp
index 0f03fa2a..516353f3 100644
--- a/game/controls.cpp
+++ b/game/controls.cpp
@@ -234,10 +234,49 @@ static Player *GetKeyboardPlayer()
 	return GetControlPlayer(CONTROL_KEYBOARD);
 }
 
-static Player *GetJoystickPlayer(Uint8 which)
+#define MAX_JOYSTICKS	MAX_PLAYERS
+
+static Uint8 joystickMasks[MAX_JOYSTICKS] = {
+	CONTROL_JOYSTICK1,
+	CONTROL_JOYSTICK2,
+	CONTROL_JOYSTICK3
+};
+static SDL_Joystick *joysticks[MAX_JOYSTICKS];
+static SDL_JoystickID joystickIDs[MAX_JOYSTICKS];
+
+static void OpenJoystick(SDL_JoystickID id)
+{
+	for (int i = 0; i < MAX_JOYSTICKS; ++i) {
+		if (joysticks[i] == NULL) {
+			joysticks[i] = SDL_OpenJoystick(id);
+			if (joysticks[i]) {
+				joystickIDs[i] = id;
+			}
+			break;
+		}
+	}
+}
+
+static void CloseJoystick(SDL_JoystickID id)
+{
+	for (int i = 0; i < MAX_JOYSTICKS; ++i) {
+		if (joystickIDs[i] == id) {
+			SDL_CloseJoystick(joysticks[i]);
+			joysticks[i] = NULL;
+			joystickIDs[i] = 0;
+			break;
+		}
+	}
+}
+
+static Player *GetJoystickPlayer(SDL_JoystickID id)
 {
-	Uint8 joystickControl = (CONTROL_JOYSTICK1 << which);
-	return GetControlPlayer(joystickControl);
+	for (int i = 0; i < MAX_JOYSTICKS; ++i) {
+		if (id == joystickIDs[i]) {
+			return GetControlPlayer(joystickMasks[i]);
+		}
+	}
+	return NULL;
 }
 
 static void HandleEvent(SDL_Event *event)
@@ -250,7 +289,16 @@ static void HandleEvent(SDL_Event *event)
 	}
 
 	switch (event->type) {
-#ifdef SDL_INIT_JOYSTICK
+		/* -- Handle joystick added */
+		case SDL_EVENT_JOYSTICK_ADDED:
+			OpenJoystick(event->jdevice.which);
+			break;
+
+		/* -- Handle joystick removed */
+		case SDL_EVENT_JOYSTICK_REMOVED:
+			CloseJoystick(event->jdevice.which);
+			break;
+
 		/* -- Handle joystick axis motion */
 		case SDL_EVENT_JOYSTICK_AXIS_MOTION:
 			player = GetJoystickPlayer(event->jaxis.which);
@@ -327,7 +375,6 @@ static void HandleEvent(SDL_Event *event)
 				}
 			}
 			break;
-#endif
 
 		/* -- Handle key presses/releases */
 		case SDL_EVENT_KEY_DOWN:
@@ -415,35 +462,12 @@ static void HandleEvent(SDL_Event *event)
 	}
 }
 
-#define MAX_JOYSTICKS	MAX_PLAYERS
-
-static Uint8 joystickMasks[MAX_JOYSTICKS] = {
-	CONTROL_JOYSTICK1,
-	CONTROL_JOYSTICK2,
-	CONTROL_JOYSTICK3
-};
-static SDL_Joystick *joysticks[MAX_JOYSTICKS];
-	
 void InitPlayerControls(void)
 {
-	Uint8 controlMask = 0;
-	int i, count;
-
-	SDL_JoystickID *ids = SDL_GetJoysticks(&count);
+	SDL_JoystickID *ids = SDL_GetJoysticks(nullptr);
 	if (ids) {
-		for (i = 0; i < MAX_PLAYERS; ++i) {
-			controlMask |= gPlayers[i]->GetControlType();
-		}
-
-		for (i = 0; i < MAX_JOYSTICKS && i < count; ++i) {
-			if (!(controlMask & joystickMasks[i])) {
-				continue;
-			}
-			joysticks[i] = SDL_OpenJoystick(ids[i]);
-			if (joysticks[i] == NULL) {
-				error("Warning: Couldn't open joystick '%s' : %s\n",
-					SDL_GetJoystickNameForID(ids[i]), SDL_GetError());
-			}
+		for (int i = 0; ids[i]; ++i) {
+			OpenJoystick(ids[i]);
 		}
 		SDL_free(ids);
 	}
diff --git a/game/controls.h b/game/controls.h
index 8988193c..e0afbf22 100644
--- a/game/controls.h
+++ b/game/controls.h
@@ -25,10 +25,6 @@
 
 #include "../screenlib/UIDialog.h"
 
-#if defined(__IPHONEOS__) || defined(__ANDROID__)
-#define USE_TOUCHCONTROL
-#endif
-
 // Functions from controls.cc
 #ifdef USE_JOYSTICK
 extern void	CalibrateJoystick(char *joystick);
diff --git a/game/gameinfo.h b/game/gameinfo.h
index 55d9d068..b0591353 100644
--- a/game/gameinfo.h
+++ b/game/gameinfo.h
@@ -40,11 +40,7 @@ enum PLAYER_CONTROL {
 	CONTROL_TOUCH     = 0x10,
 	CONTROL_NETWORK   = 0x20,
 	CONTROL_REPLAY    = 0x40,
-#ifdef USE_TOUCHCONTROL
-	CONTROL_LOCAL     = CONTROL_TOUCH
-#else
-	CONTROL_LOCAL     = (CONTROL_KEYBOARD|CONTROL_JOYSTICK1)
-#endif
+	CONTROL_LOCAL     = (CONTROL_KEYBOARD|CONTROL_JOYSTICK1|CONTROL_JOYSTICK2|CONTROL_JOYSTICK3|CONTROL_TOUCH)
 };
 
 enum GAME_MODE {
@@ -52,7 +48,7 @@ enum GAME_MODE {
 	GAME_MODE_KIDS       = 0x02,
 };
 
-#define IS_LOCAL_CONTROL(X)	(X != CONTROL_NONE && X != CONTROL_NETWORK && X != CONTROL_REPLAY)
+#define IS_LOCAL_CONTROL(X)	(X & CONTROL_LOCAL)
 
 enum NODE_STATE_FLAG {
 	STATE_NONE	= 0x00,