Maelstrom: Pause when a controller is disconnected

From 5819842923af3949c63711b658e4fa28c760318b Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 31 Mar 2026 21:37:48 -0700
Subject: [PATCH] Pause when a controller is disconnected

---
 game/controls.cpp | 14 ++++++++------
 game/game.cpp     |  9 ++++-----
 game/gameinfo.cpp | 21 +++++++++++++++++++++
 game/gameinfo.h   | 11 ++++++++++-
 4 files changed, 43 insertions(+), 12 deletions(-)

diff --git a/game/controls.cpp b/game/controls.cpp
index 90231dd1..1ed05ffe 100644
--- a/game/controls.cpp
+++ b/game/controls.cpp
@@ -535,7 +535,7 @@ void HandleEvent(SDL_Event *event)
 			switch (event->gbutton.button) {
 			case SDL_GAMEPAD_BUTTON_START:
 				if (!event->gbutton.down) {
-					gGameInfo.ToggleLocalState(STATE_PAUSE);
+					gGameInfo.TogglePauseRequest();
 				}
 				break;
 			case SDL_GAMEPAD_BUTTON_BACK:
@@ -602,7 +602,7 @@ void HandleEvent(SDL_Event *event)
 				screen->ToggleFullScreen();
 				break;
 			} else if ( key == controls.gPauseControl ) {
-				gGameInfo.ToggleLocalState(STATE_PAUSE);
+				gGameInfo.TogglePauseRequest();
 				break;
 			} else if ( key == controls.gQuitControl ) {
 				gGameInfo.SetLocalState(STATE_ABORT, true);
@@ -629,11 +629,11 @@ void HandleEvent(SDL_Event *event)
 			break;
 
 		case SDL_EVENT_WINDOW_MINIMIZED:
-			gGameInfo.SetLocalState(STATE_MINIMIZE, true);
+			gGameInfo.SetPauseReason(PAUSE_MINIMIZED, true);
 			break;
 
 		case SDL_EVENT_WINDOW_RESTORED:
-			gGameInfo.SetLocalState(STATE_MINIMIZE, false);
+			gGameInfo.SetPauseReason(PAUSE_MINIMIZED, false);
 			break;
 	}
 }
@@ -641,14 +641,16 @@ void HandleEvent(SDL_Event *event)
 static bool SDLCALL GamepadEventWatch(void *userdata, SDL_Event *event)
 {
 	switch (event->type) {
-		/* -- Handle joystick added */
+		/* -- Handle gamepad added */
 		case SDL_EVENT_GAMEPAD_ADDED:
 			OpenGamepad(event->gdevice.which);
+			gGameInfo.SetPauseReason(PAUSE_CONTROLLER, false);
 			break;
 
-		/* -- Handle joystick removed */
+		/* -- Handle gamepad removed */
 		case SDL_EVENT_GAMEPAD_REMOVED:
 			CloseGamepad(event->gdevice.which);
+			gGameInfo.SetPauseReason(PAUSE_CONTROLLER, true);
 			break;
 
 		default:
diff --git a/game/game.cpp b/game/game.cpp
index 9d87eae3..7567ee7b 100644
--- a/game/game.cpp
+++ b/game/game.cpp
@@ -959,7 +959,7 @@ GamePanelDelegate::UpdateGameState()
 	for (i = 0; i < gGameInfo.GetNumNodes(); ++i) {
 		Uint8 state = gGameInfo.GetNodeState(i);
 		paused |= state;
-		if (state & (STATE_PAUSE|STATE_MINIMIZE)) {
+		if (state & STATE_PAUSE) {
 			if (i == gGameInfo.GetLocalIndex()) {
 				locally_paused = true;
 			} else {
@@ -967,13 +967,12 @@ GamePanelDelegate::UpdateGameState()
 			}
 		}
 	}
-	if ((paused & (STATE_PAUSE|STATE_MINIMIZE)) &&
-	    !(gPaused & (STATE_PAUSE|STATE_MINIMIZE))) {
+	if ((paused & STATE_PAUSE) && !(gPaused & STATE_PAUSE)) {
 		sound->PlaySound(gPauseSound, 5);
 	}
 	if (m_paused) {
 		// Update the pause label
-		if (paused & (STATE_PAUSE|STATE_MINIMIZE)) {
+		if (paused & STATE_PAUSE) {
 			char label[128] = { 0 };
 			if (!locally_paused) {
 				const GameInfoPlayer *player = gGameInfo.GetPlayer(index_paused);
@@ -986,7 +985,7 @@ GamePanelDelegate::UpdateGameState()
 			}
 			m_paused->SetText(label);
 			m_paused->Show();
-		} else if (gPaused & (STATE_PAUSE|STATE_MINIMIZE)) {
+		} else if (gPaused & STATE_PAUSE) {
 			m_paused->Hide();
 		}
 	}
diff --git a/game/gameinfo.cpp b/game/gameinfo.cpp
index 6f6f14bc..36d45e5c 100644
--- a/game/gameinfo.cpp
+++ b/game/gameinfo.cpp
@@ -434,6 +434,27 @@ GameInfo::IsFull() const
 	return true;
 }
 
+void
+GameInfo::TogglePauseRequest()
+{
+	if (paused & (PAUSE_REQUEST|PAUSE_CONTROLLER)) {
+		SetPauseReason((PAUSE_REQUEST|PAUSE_CONTROLLER), false);
+	} else {
+		SetPauseReason(PAUSE_REQUEST, true);
+	}
+}
+
+void
+GameInfo::SetPauseReason(Uint8 reason, bool enabled)
+{
+	if (enabled) {
+		paused |= reason;
+	} else {
+		paused &= ~reason;
+	}
+	SetLocalState(STATE_PAUSE, paused ? true : false);
+}
+
 void
 GameInfo::SetLocalState(Uint8 state, bool enabled)
 {
diff --git a/game/gameinfo.h b/game/gameinfo.h
index 2b431e7f..f7b0642e 100644
--- a/game/gameinfo.h
+++ b/game/gameinfo.h
@@ -56,10 +56,15 @@ enum NODE_STATE_FLAG {
 	STATE_ABORT	= 0x01,
 	STATE_PAUSE	= 0x02,
 	STATE_BONUS	= 0x04,
-	STATE_MINIMIZE	= 0x08,
 	STATE_DIALOG	= 0x10,
 };
 
+enum PAUSE_REASON {
+	PAUSE_REQUEST		= 0x01,
+	PAUSE_MINIMIZED		= 0x02,
+	PAUSE_CONTROLLER	= 0x04,
+};
+
 enum PING_STATUS {
 	PING_LOCAL,
 	PING_GOOD,
@@ -203,6 +208,8 @@ class GameInfo
 	void SetNodeState(int index, Uint8 state);
 	Uint8 GetNodeState(int index) const;
 
+	void TogglePauseRequest();
+	void SetPauseReason(Uint8 reason, bool enabled);
 	void SetLocalState(Uint8 state, bool enabled);
 	void ToggleLocalState(Uint8 state);
 	Uint8 GetLocalState() const {
@@ -231,6 +238,8 @@ class GameInfo
 
 	Uint32 localID;
 
+	Uint8 paused;
+
 protected:
 	Uint8 numNodes;
 	GameInfoNode nodes[MAX_NODES];