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

https://github.com/libsdl-org/Maelstrom/commit/9a6992d9433ca9870164b165be80b30a5c6ec9d0

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
 	*/