# Maelstrom: Added state transition and completed protocol implementation for join/leave/kick

**URL:** <https://discourse.libsdl.org/t/maelstrom-added-state-transition-and-completed-protocol-implementation-for-join-leave-kick/40011>\
**Category:** SDL Commits\
**Created:** [November 17, 2022, 7:44am UTC](https://discourse.libsdl.org/t/maelstrom-added-state-transition-and-completed-protocol-implementation-for-join-leave-kick/40011 "2022-11-17T07:44:30Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![SDLPushPostBot](https://discourse.libsdl.org/user_avatar/discourse.libsdl.org/sdlpushpostbot/32/4370_2.png) [@SDLPushPostBot](https://discourse.libsdl.org/u/SDLPushPostBot)\
**Post date:** [November 17, 2022, 7:44am UTC](https://discourse.libsdl.org/t/maelstrom-added-state-transition-and-completed-protocol-implementation-for-join-leave-kick/40011/1 "2022-11-17T07:44:30Z")

</div>

[https://github.com/libsdl-org/Maelstrom/commit/9a6992d9433ca9870164b165be80b30a5c6ec9d0](https://github.com/libsdl-org/Maelstrom/commit/9a6992d9433ca9870164b165be80b30a5c6ec9d0)

```diff
From 9a6992d9433ca9870164b165be80b30a5c6ec9d0 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 6 Nov 2011 11:55:11 -0500
Subject: [PATCH] Added state transition and completed protocol implementation
 for join/leave/kick

---
 netlogic/gameinfo.h | 11 +++
 netlogic/lobby.cpp | 173 +++++++++++++++++++++++++++++++++++---------
 netlogic/lobby.h | 10 ++-
 netlogic/protocol.h | 9 ++-
 4 files changed, 164 insertions(+), 39 deletions(-)

diff --git a/netlogic/gameinfo.h b/netlogic/gameinfo.h
index bde72ab7..da505a4e 100644
--- a/netlogic/gameinfo.h
+++ b/netlogic/gameinfo.h
@@ -77,9 +77,20 @@ class GameInfo
 	bool ReadFromPacket(DynamicPacket &packet);
 	void WriteToPacket(DynamicPacket &packet);
 
+	GameInfoPlayer *GetHost() {
+ return GetPlayer(0);
+	}
 	GameInfoPlayer *GetPlayer(int index) {
 		return &players[index];
 	}
+	GameInfoPlayer *GetPlayerByID(Uint32 playerID) {
+ for (int i = 0; i < MAX_PLAYERS; ++i) {
+ if (players[i].playerID == playerID) {
+ return &players[i];
+ }
+ }
+ return NULL;
+	}
 
 	bool HasPlayer(Uint32 playerID) {
 		for (int i = 0; i < MAX_PLAYERS; ++i) {
diff --git a/netlogic/lobby.cpp b/netlogic/lobby.cpp
index 42aa66b0..9d1dadb6 100644
--- a/netlogic/lobby.cpp
+++ b/netlogic/lobby.cpp
@@ -155,6 +155,8 @@ LobbyDialogDelegate::OnHide()
 			}
 		}
 		NewGame();
+	} else {
+ SetState(STATE_NONE);
 	}
 
 	// Shut down networking
@@ -176,6 +178,8 @@ LobbyDialogDelegate::OnTick()
 			AdvertiseGame();
 		} else if (m_state == STATE_LISTING) {
 			GetGameList();
+ } else if (m_state == STATE_JOINING) {
+ SendJoinRequest();
 		} else {
 			GetGameInfo();
 		}
@@ -204,23 +208,17 @@ LobbyDialogDelegate::SetHostOrJoin(void*, int value)
 			return;
 		}
 
- if (value == HOST_GAME) {
- m_state = STATE_HOSTING;
- } else {
- m_state = STATE_LISTING;
- }
 		m_uniqueID = rand();
- m_lastRefresh = 0;
+ m_game.SetLocalID(m_uniqueID);
 
- if (m_state == STATE_HOSTING) {
- m_game.SetHostInfo(m_uniqueID, prefs->GetString(PREFERENCES_HANDLE));
+ if (value == HOST_GAME) {
+ SetState(STATE_HOSTING);
+ } else {
+ SetState(STATE_LISTING);
 		}
- m_game.SetLocalID(m_uniqueID);
 	} else {
- m_state = STATE_NONE;
+ SetState(STATE_NONE);
 	}
-
-	UpdateUI();
 }
 
 void
@@ -281,6 +279,38 @@ LobbyDialogDelegate::UpdateUI()
 	}
 }
 
+void
+LobbyDialogDelegate::SetState(LOBBY_STATE state)
+{
+	// Handle any state transitions here
+	if (state == STATE_NONE) {
+ if (m_state == STATE_HOSTING) {
+ // Notify the players that the game is gone
+ for (int i = 0; i < MAX_PLAYERS; ++i) {
+ SendKick(i);
+ }
+ } else if (m_state == STATE_JOINING ||
+ m_state == STATE_JOINED) {
+ // Notify the host that we're gone
+ SendLeaveRequest();
+ }
+	} else if (state == STATE_HOSTING) {
+ m_game.SetHostInfo(m_uniqueID, prefs->GetString(PREFERENCES_HANDLE));
+	} else if (state == STATE_LISTING) {
+ ClearGameList();
+	}
+
+	// Set the state
+	m_state = state;
+
+	// Update the UI for the new state
+	UpdateUI();
+
+	// Send any packet requests immediately
+	// Comment this out to simulate initial packet loss
+	m_lastRefresh = 0;
+}
+
 void
 LobbyDialogDelegate::AdvertiseGame()
 {
@@ -325,24 +355,56 @@ void
 LobbyDialogDelegate::GetGameInfo()
 {
 	m_packet.StartLobbyMessage(LOBBY_REQUEST_GAME_INFO);
-	m_packet.address = m_game.players[0].address;
+	m_packet.address = m_game.GetHost()->address;
 	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
 }
 
 void
 LobbyDialogDelegate::JoinGame(GameInfo &game)
+{
+	m_game.CopyFrom(game);
+	SetState(STATE_JOINING);
+}
+
+void
+LobbyDialogDelegate::SendJoinRequest()
 {
 	m_packet.StartLobbyMessage(LOBBY_REQUEST_JOIN);
-	m_packet.Write(game.gameID);
+	m_packet.Write(m_game.gameID);
 	m_packet.Write(m_uniqueID);
 	m_packet.Write(prefs->GetString(PREFERENCES_HANDLE));
-	m_packet.address = game.players[0].address;;
+	m_packet.address = m_game.GetHost()->address;
 
 	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+}
 
-	m_game.CopyFrom(game);
-	m_state = STATE_JOINING;
-	UpdateUI();
+void
+LobbyDialogDelegate::SendLeaveRequest()
+{
+	m_packet.StartLobbyMessage(LOBBY_REQUEST_LEAVE);
+	m_packet.Write(m_game.gameID);
+	m_packet.Write(m_uniqueID);
+	m_packet.address = m_game.GetHost()->address;
+
+	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
+}
+
+void
+LobbyDialogDelegate::SendKick(int index)
+{
+	GameInfoPlayer *player;
+
+	player = m_game.GetPlayer(index);
+	if (!player->playerID || player->playerID == m_uniqueID) {
+ return;
+	}
+
+	m_packet.StartLobbyMessage(LOBBY_KICK);
+	m_packet.Write(m_game.gameID);
+	m_packet.Write(player->playerID);
+	m_packet.address = player->address;
+
+	SDLNet_UDP_Send(gNetFD, -1, &m_packet);
 }
 
 void
@@ -408,7 +470,7 @@ LobbyDialogDelegate::ProcessPacket(DynamicPacket &packet)
 		} else if (cmd == LOBBY_REQUEST_JOIN) {
 			ProcessRequestJoin(packet);
 		} else if (cmd == LOBBY_REQUEST_LEAVE) {
- //ProcessRequestLeave(packet);
+ ProcessRequestLeave(packet);
 		}
 		return;
 
@@ -421,7 +483,6 @@ LobbyDialogDelegate::ProcessPacket(DynamicPacket &packet)
 			}
 			return;
 		}
-
 	}
 
 	// These packets we handle in all the join states
@@ -430,6 +491,8 @@ LobbyDialogDelegate::ProcessPacket(DynamicPacket &packet)
 		//RejectPing(packet);
 	} else if (cmd == LOBBY_GAME_INFO) {
 		ProcessGameInfo(packet);
+	} else if (cmd == LOBBY_KICK) {
+ ProcessKick(packet);
 	}
 }
 
@@ -531,6 +594,27 @@ LobbyDialogDelegate::ProcessRequestJoin(DynamicPacket &packet)
 	UpdateUI();
 }
 
+void
+LobbyDialogDelegate::ProcessRequestLeave(DynamicPacket &packet)
+{
+	Uint32 gameID;
+	Uint32 playerID;
+
+	if (!packet.Read(gameID) || gameID != m_game.gameID) {
+ return;
+	}
+	if (!packet.Read(playerID) || !m_game.HasPlayer(playerID)) {
+ return;
+	}
+
+	// Okay, clear them from the list!
+	GameInfoPlayer *player = m_game.GetPlayerByID(playerID);
+	SDL_zero(*player);
+
+	// Update our own UI
+	UpdateUI();
+}
+
 void
 LobbyDialogDelegate::ProcessGameInfo(DynamicPacket &packet)
 {
@@ -540,7 +624,19 @@ LobbyDialogDelegate::ProcessGameInfo(DynamicPacket &packet)
 		return;
 	}
 
-	if (m_state != STATE_LISTING) {
+	if (m_state == STATE_LISTING) {
+ // Add or update the game list
+ int i;
+ for (i = 0; i < m_gameList.length(); ++i) {
+ if (game.gameID == m_gameList[i].gameID) {
+ m_gameList[i].CopyFrom(game);
+ break;
+ }
+ }
+ if (i == m_gameList.length()) {
+ m_gameList.add(game);
+ }
+	} else {
 		if (game.gameID != m_game.gameID) {
 			// Probably an old packet...
 			return;
@@ -551,31 +647,36 @@ LobbyDialogDelegate::ProcessGameInfo(DynamicPacket &packet)
 		if (m_state == STATE_JOINING) {
 			if (m_game.HasPlayer(m_uniqueID)) {
 				// We successfully joined the game
- m_state = STATE_JOINED;
+ SetState(STATE_JOINED);
 			}
 		} else {
 			if (!m_game.HasPlayer(m_uniqueID)) {
 				// We were kicked from the game
- m_state = STATE_LISTING;
+ SetState(STATE_LISTING);
 			}
 		}
 	}
 
-	if (m_state == STATE_LISTING) {
- // Add or update the game list
- int i;
- for (i = 0; i < m_gameList.length(); ++i) {
- if (game.gameID == m_gameList[i].gameID) {
- m_gameList[i].CopyFrom(game);
- break;
- }
- }
- if (i == m_gameList.length()) {
- m_gameList.add(game);
- }
+	UpdateUI();
+}
+
+void
+LobbyDialogDelegate::ProcessKick(DynamicPacket &packet)
+{
+	Uint32 gameID;
+	Uint32 playerID;
+
+	if (m_state != STATE_JOINING && m_state != STATE_JOINED) {
+ return;
+	}
+	if (!packet.Read(gameID) || gameID != m_game.gameID) {
+ return;
+	}
+	if (!packet.Read(playerID) || playerID != m_uniqueID) {
+ return;
 	}
 
-	UpdateUI();
+	SetState(STATE_LISTING);
 }
 
 void
diff --git a/netlogic/lobby.h b/netlogic/lobby.h
index 9ee95bd9..a33a5a52 100644
--- a/netlogic/lobby.h
+++ b/netlogic/lobby.h
@@ -62,6 +62,9 @@ class LobbyDialogDelegate : public UIDialogDelegate
 	void GetGameList();
 	void GetGameInfo();
 	void JoinGame(GameInfo &game);
+	void SendJoinRequest();
+	void SendLeaveRequest();
+	void SendKick(int index);
 	void ClearGameInfo();
 	void ClearGameList();
 
@@ -72,14 +75,16 @@ class LobbyDialogDelegate : public UIDialogDelegate
 	void ProcessAnnouncePlayer(DynamicPacket &packet);
 	void ProcessRequestGameInfo(DynamicPacket &packet);
 	void ProcessRequestJoin(DynamicPacket &packet);
+	void ProcessRequestLeave(DynamicPacket &packet);
 	void ProcessGameServerList(DynamicPacket &packet);
 	void ProcessGameInfo(DynamicPacket &packet);
+	void ProcessKick(DynamicPacket &packet);
 
 protected:
 	IPaddress m_globalServer;
 	array<IPaddress> m_addresses;
 
-	enum {
+	enum LOBBY_STATE {
 		STATE_NONE,
 		STATE_HOSTING,
 		STATE_LISTING,
@@ -103,6 +108,9 @@ class LobbyDialogDelegate : public UIDialogDelegate
 	UIElement *m_gameInfoArea;
 	UIElement *m_gameInfoPlayers[MAX_PLAYERS];
 	UIElement *m_playButton;
+
+protected:
+	void SetState(LOBBY_STATE state);
 };
 
 #endif // _lobby_h
diff --git a/netlogic/protocol.h b/netlogic/protocol.h
index fc69178c..40185b26 100644
--- a/netlogic/protocol.h
+++ b/netlogic/protocol.h
@@ -151,7 +151,6 @@ enum LobbyProtocol {
 	LOBBY_REQUEST_JOIN,
 	/* Sent by the joining game
 
- Uint32 sequence;
 		Uint32 gameID
 		Uint32 playerID
 		Uint8 namelen
@@ -161,7 +160,13 @@ enum LobbyProtocol {
 	LOBBY_REQUEST_LEAVE,
 	/* Sent by the joining game
 
- Uint32 sequence;
+ Uint32 gameID
+ Uint32 playerID
+	*/
+
+	LOBBY_KICK,
+	/* Sent by the hosting game
+
 		Uint32 gameID
 		Uint32 playerID
 	*/

```
