diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 16ed731e56ad606486da9943ab962260ab328d57..352321e19c934b68a20e2fe8bf6456533c694c06 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -3257,14 +3257,16 @@ static void Got_KickCmd(UINT8 **p, INT32 playernum)
 static CV_PossibleValue_t netticbuffer_cons_t[] = {{0, "MIN"}, {3, "MAX"}, {0, NULL}};
 consvar_t cv_netticbuffer = {"netticbuffer", "1", CV_SAVE, netticbuffer_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL	};
+static void Joinable_OnChange(void);
+
+consvar_t cv_allownewplayer = {"allowjoin", "On", CV_SAVE|CV_CALL, CV_OnOff, Joinable_OnChange, 0, NULL, NULL, 0, 0, NULL};
+
 #ifdef VANILLAJOINNEXTROUND
 consvar_t cv_joinnextround = {"joinnextround", "Off", CV_SAVE|CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
 #endif
 
-static void MaxPlayers_OnChange(void);
 static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {MAXPLAYERS, "MAX"}, {0, NULL}};
-consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE|CV_NETVAR|CV_CALL, maxplayers_cons_t, MaxPlayers_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE|CV_CALL, maxplayers_cons_t, Joinable_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t resynchattempts_cons_t[] = {{0, "MIN"}, {20, "MAX"}, {0, NULL}};
 consvar_t cv_resynchattempts = {"resynchattempts", "5", CV_SAVE, resynchattempts_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL	};
@@ -3282,10 +3284,10 @@ consvar_t cv_downloadspeed = {"downloadspeed", "16", CV_SAVE, downloadspeed_cons
 static void Got_AddPlayer(UINT8 **p, INT32 playernum);
 static void Got_RemovePlayer(UINT8 **p, INT32 playernum);
 
-static void MaxPlayers_OnChange(void)
+static void Joinable_OnChange(void)
 {
 #ifdef HAVE_DISCORDRPC
-	DRPC_UpdatePresence();
+	DRPC_SendDiscordInfo();
 #else
 	return;
 #endif
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 213ba5d48ebdd1113dd8d229eb218432b2134550..8778a242011552c8a8d5187e89a832cd403eb461 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -708,10 +708,14 @@ void D_RegisterServerCommands(void)
 	CV_RegisterVar(&cv_showping);
 
 #ifdef SEENAMES
-	 CV_RegisterVar(&cv_allowseenames);
+	CV_RegisterVar(&cv_allowseenames);
 #endif
 
 	CV_RegisterVar(&cv_dummyconsvar);
+
+#ifdef HAVE_DISCORDRPC
+	RegisterNetXCmd(XD_DISCORD, DRPC_RecieveDiscordInfo);
+#endif
 }
 
 // =========================================================================
@@ -1007,6 +1011,9 @@ void D_RegisterClientCommands(void)
 
 #ifdef HAVE_DISCORDRPC
 	CV_RegisterVar(&cv_discordrp);
+	CV_RegisterVar(&cv_discordstreamer);
+	CV_RegisterVar(&cv_discordasks);
+	CV_RegisterVar(&cv_discordinvites);
 #endif
 }
 
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index 2d8e5705a5717cc7196c689e0051cf895af04ada..d6e7e08dbfd611605bee336cab5f0c78c9c6d34e 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -181,6 +181,9 @@ typedef enum
 #ifdef HAVE_BLUA
 	XD_LUACMD,      // 26
 	XD_LUAVAR,      // 27
+#endif
+#ifdef HAVE_DISCORDRPC
+	XD_DISCORD,     // 28
 #endif
 	MAXNETXCMD
 } netxcmd_t;
diff --git a/src/discord.c b/src/discord.c
index 2c2bed5b85276ac5b9a14a8d9d0ac4d87b786485..7e20cde658301528fbd92a7032fb119e75310fad 100644
--- a/src/discord.c
+++ b/src/discord.c
@@ -27,6 +27,7 @@
 #include "mserv.h" // ms_RoomId
 #include "z_zone.h"
 #include "m_random.h" // P_GetInitSeed
+#include "byteptr.h"
 
 #include "discord.h"
 #include "doomdef.h"
@@ -38,6 +39,13 @@
 #define IP_SIZE 16
 
 consvar_t cv_discordrp = {"discordrp", "On", CV_SAVE|CV_CALL, CV_OnOff, DRPC_UpdatePresence, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_discordstreamer = {"discordstreamer", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_discordasks = {"discordasks", "Yes", CV_SAVE|CV_CALL, CV_YesNo, DRPC_UpdatePresence, 0, NULL, NULL, 0, 0, NULL};
+
+static CV_PossibleValue_t discordinvites_cons_t[] = {{0, "Admins Only"}, {1, "Everyone"}, {0, NULL}};
+consvar_t cv_discordinvites = {"discordinvites", "Everyone", CV_SAVE|CV_CALL, discordinvites_cons_t, DRPC_SendDiscordInfo, 0, NULL, NULL, 0, 0, NULL};
+
+struct discordInfo_s discordInfo;
 
 discordRequest_t *discordRequestList = NULL;
 
@@ -87,7 +95,14 @@ static char *DRPC_XORIPString(const char *input)
 --------------------------------------------------*/
 static void DRPC_HandleReady(const DiscordUser *user)
 {
-	CONS_Printf("Discord: connected to %s#%s (%s)\n", user->username, user->discriminator, user->userId);
+	if (cv_discordstreamer.value)
+	{
+		CONS_Printf("Discord: connected to %s\n", user->username);
+	}
+	else
+	{
+		CONS_Printf("Discord: connected to %s#%s (%s)\n", user->username, user->discriminator, user->userId);
+	}
 }
 
 /*--------------------------------------------------
@@ -145,6 +160,50 @@ static void DRPC_HandleJoin(const char *secret)
 	free(ip);
 }
 
+/*--------------------------------------------------
+	static boolean DRPC_InvitesAreAllowed(void)
+
+		Determines whenever or not invites or
+		ask to join requests are allowed.
+
+	Input Arguments:-
+		None
+
+	Return:-
+		true if invites are allowed, false otherwise.
+--------------------------------------------------*/
+static boolean DRPC_InvitesAreAllowed(void)
+{
+	if (!Playing())
+	{
+		// We're not playing, so we should not be getting invites.
+		return false;
+	}
+
+	if (cv_discordasks.value == 0)
+	{
+		// Client has the CVar set to off, so never allow invites from this client.
+		return false;
+	}
+
+	if (discordInfo.joinsAllowed == true)
+	{
+		if (discordInfo.everyoneCanInvite == true)
+		{
+			// Everyone's allowed!
+			return true;
+		}
+		else if (consoleplayer == serverplayer || IsPlayerAdmin(consoleplayer))
+		{
+			// Only admins are allowed!
+			return true;
+		}
+	}
+
+	// Did not pass any of the checks
+	return false;
+}
+
 /*--------------------------------------------------
 	static void DRPC_HandleJoinRequest(const DiscordUser *requestUser)
 
@@ -161,16 +220,25 @@ static void DRPC_HandleJoin(const char *secret)
 static void DRPC_HandleJoinRequest(const DiscordUser *requestUser)
 {
 	discordRequest_t *append = discordRequestList;
-	discordRequest_t *newRequest = Z_Calloc(sizeof(discordRequest_t), PU_STATIC, NULL);
+	discordRequest_t *newRequest;
 
-	// Discord requests exprie after 30 seconds
+	if (DRPC_InvitesAreAllowed() == false)
+	{
+		// Something weird happened if this occurred...
+		Discord_Respond(requestUser->userId, DISCORD_REPLY_IGNORE);
+		return;
+	}
+
+	newRequest = Z_Calloc(sizeof(discordRequest_t), PU_STATIC, NULL);
+
+	// Discord requests expire after 30 seconds
 	newRequest->timer = (30*TICRATE)-1;
 
-	newRequest->username = Z_Calloc(344+1+8, PU_STATIC, NULL);
-	snprintf(newRequest->username, 344+1+8, "%s#%s",
-		requestUser->username,
-		requestUser->discriminator
-	);
+	newRequest->username = Z_Calloc(344, PU_STATIC, NULL);
+	snprintf(newRequest->username, 344, "%s", requestUser->username);
+
+	newRequest->discriminator = Z_Calloc(8, PU_STATIC, NULL);
+	snprintf(newRequest->discriminator, 8, "%s", requestUser->discriminator);
 
 	newRequest->userID = Z_Calloc(32, PU_STATIC, NULL);
 	snprintf(newRequest->userID, 32, "%s", requestUser->userId);
@@ -257,6 +325,68 @@ void DRPC_Init(void)
 	DRPC_UpdatePresence();
 }
 
+/*--------------------------------------------------
+	void DRPC_SendDiscordInfo(void)
+
+		See header file for description.
+--------------------------------------------------*/
+void DRPC_SendDiscordInfo(void)
+{
+	UINT8 buf[3];
+	UINT8 *p = buf;
+	UINT8 maxplayer;
+
+	if (!server)
+		return;
+
+	maxplayer = (UINT8)(min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value));
+
+	WRITEUINT8(p, maxplayer);
+	WRITEUINT8(p, cv_allownewplayer.value);
+	WRITEUINT8(p, cv_discordinvites.value);
+
+	SendNetXCmd(XD_DISCORD, &buf, 3);
+}
+
+/*--------------------------------------------------
+	void DRPC_RecieveDiscordInfo(UINT8 **p, INT32 playernum)
+
+		See header file for description.
+--------------------------------------------------*/
+void DRPC_RecieveDiscordInfo(UINT8 **p, INT32 playernum)
+{
+	if (playernum != serverplayer /*&& !IsPlayerAdmin(playernum)*/)
+	{
+		// protect against hacked/buggy client
+		CONS_Alert(CONS_WARNING, M_GetText("Illegal Discord info command received from %s\n"), player_names[playernum]);
+		if (server)
+		{
+			XBOXSTATIC UINT8 buf[2];
+
+			buf[0] = (UINT8)playernum;
+			buf[1] = KICK_MSG_CON_FAIL;
+			SendNetXCmd(XD_KICK, &buf, 2);
+		}
+		return;
+	}
+
+	discordInfo.maxPlayers = READUINT8(*p);
+	discordInfo.joinsAllowed = (boolean)READUINT8(*p);
+	discordInfo.everyoneCanInvite = (boolean)READUINT8(*p);
+
+	DRPC_UpdatePresence();
+
+	if (DRPC_InvitesAreAllowed() == false)
+	{
+		// Flush the request list, if it still exists
+		while (discordRequestList != NULL)
+		{
+			Discord_Respond(discordRequestList->userID, DISCORD_REPLY_IGNORE);
+			DRPC_RemoveRequest(discordRequestList);
+		}
+	}
+}
+
 #ifdef HAVE_CURL
 /*--------------------------------------------------
 	static size_t DRPC_WriteServerIP(char *s, size_t size, size_t n, void *userdata)
@@ -410,8 +540,6 @@ void DRPC_UpdatePresence(void)
 	// Server info
 	if (netgame)
 	{
-		const char *join;
-
 		switch (ms_RoomId)
 		{
 			case -1: discordPresence.state = "Private"; break; // Private server
@@ -426,12 +554,17 @@ void DRPC_UpdatePresence(void)
 		discordPresence.partySize = D_NumPlayers(); // Players in server
 		discordPresence.partyMax = cv_maxplayers.value; // Max players (TODO: another variable should hold this, so that maxplayers doesn't have to be a netvar)
 
-		// Grab the host's IP for joining.
-		if (cv_allownewplayer.value && ((join = DRPC_GetServerIP()) != NULL))
+		if (DRPC_InvitesAreAllowed() == true)
 		{
-			char *xorjoin = DRPC_XORIPString(join);
-			discordPresence.joinSecret = xorjoin;
-			free(xorjoin);
+			const char *join;
+
+			// Grab the host's IP for joining.
+			if ((join = DRPC_GetServerIP()) != NULL)
+			{
+				char *xorjoin = DRPC_XORIPString(join);
+				discordPresence.joinSecret = xorjoin;
+				free(xorjoin);
+			}
 		}
 	}
 	else
diff --git a/src/discord.h b/src/discord.h
index fd306fa8cd3a6d59c989a1e25d5a393a8978e066..6bd081a981f473b0fd87fb7148ad537b1fb8c291 100644
--- a/src/discord.h
+++ b/src/discord.h
@@ -18,15 +18,26 @@
 #include "discord_rpc.h"
 
 extern consvar_t cv_discordrp;
+extern consvar_t cv_discordstreamer;
+extern consvar_t cv_discordasks;
+extern consvar_t cv_discordinvites;
+
+extern struct discordInfo_s {
+	UINT8 maxPlayers;
+	boolean joinsAllowed;
+	boolean everyoneCanInvite;
+} discordInfo;
 
 typedef struct discordRequest_s {
 	tic_t timer; // Tics left on the request before it expires.
-	char *username; // Discord user name + their discriminator.
+	char *username; // Discord user name.
+	char *discriminator; // Discord discriminator (The little hashtag thing after the username). Separated for a "hide discriminators" cvar.
 	char *userID; // The ID of the Discord user, gets used with Discord_Respond()
 
 	// HAHAHA, no.
 	// *Maybe* if it was only PNG I would boot up curl just to get AND convert this to Doom GFX,
-	// but it can be a JEPG, WebP, or GIF too :)
+	// but it can *also* be a JEPG, WebP, or GIF :)
+	// Hey, wanna add ImageMagick as a dependency? :dying:
 	//patch_t *avatar;
 
 	struct discordRequest_s *next; // Next request in the list.
@@ -55,6 +66,26 @@ void DRPC_RemoveRequest(discordRequest_t *removeRequest);
 void DRPC_Init(void);
 
 
+/*--------------------------------------------------
+	void DRPC_SendDiscordInfo(void);
+
+		Sends the server's information needed for
+		the rich presence state.
+--------------------------------------------------*/
+
+void DRPC_SendDiscordInfo(void);
+
+
+/*--------------------------------------------------
+	void DRPC_RecieveDiscordInfo(UINT8 **p, INT32 playernum);
+
+		Recieves the server's information needed for
+		the rich presence state.
+--------------------------------------------------*/
+
+void DRPC_RecieveDiscordInfo(UINT8 **p, INT32 playernum);
+
+
 /*--------------------------------------------------
 	void DRPC_UpdatePresence(void);
 
diff --git a/src/m_menu.c b/src/m_menu.c
index db0dd9a368a64da927cfdacc1cbe3fd897df6b4a..da88ea831b7548434ea8bfc1bf6e9853c9f08f69 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -304,6 +304,9 @@ menu_t OP_SoundOptionsDef;
 
 //Misc
 menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef;
+#ifdef HAVE_DISCORDRPC
+menu_t OP_DiscordOptionsDef;
+#endif
 menu_t OP_HUDOptionsDef, OP_ChatOptionsDef;
 menu_t OP_GameOptionsDef, OP_ServerOptionsDef;
 #ifndef NONET
@@ -1361,19 +1364,15 @@ static menuitem_t OP_SoundOptionsMenu[] =
 
 static menuitem_t OP_DataOptionsMenu[] =
 {
-#ifdef HAVE_DISCORDRPC
-	{IT_STRING | IT_CVAR,		NULL, "Discord Rich Presence",	&cv_discordrp,			 10},
 
-	{IT_STRING | IT_CALL,		NULL, "Screenshot Options...",	M_ScreenshotOptions,	 30},
-	{IT_STRING | IT_CALL,		NULL, "Addon Options...",		M_AddonsOptions,		 40},
-	{IT_STRING | IT_SUBMENU,	NULL, "Replay Options...",		&MISC_ReplayOptionsDef,	 50},
-
-	{IT_STRING | IT_SUBMENU,	NULL, "Erase Data...",			&OP_EraseDataDef,		 70},
-#else
 	{IT_STRING | IT_CALL,		NULL, "Screenshot Options...",	M_ScreenshotOptions,	 10},
 	{IT_STRING | IT_CALL,		NULL, "Addon Options...",		M_AddonsOptions,		 20},
 	{IT_STRING | IT_SUBMENU,	NULL, "Replay Options...",		&MISC_ReplayOptionsDef,	 30},
+#ifdef HAVE_DISCORDRPC
+	{IT_STRING | IT_SUBMENU,	NULL, "Discord Options...",		&OP_DiscordOptionsDef,	 40},
 
+	{IT_STRING | IT_SUBMENU,	NULL, "Erase Data...",			&OP_EraseDataDef,		 60},
+#else
 	{IT_STRING | IT_SUBMENU,	NULL, "Erase Data...",			&OP_EraseDataDef,		 50},
 #endif
 };
@@ -1424,7 +1423,7 @@ static menuitem_t OP_AddonsOptionsMenu[] =
 	{IT_HEADER,                      NULL, "Menu",                        NULL,                    0},
 	{IT_STRING|IT_CVAR,              NULL, "Location",                    &cv_addons_option,      10},
 	{IT_STRING|IT_CVAR|IT_CV_STRING, NULL, "Custom Folder",               &cv_addons_folder,      20},
-	{IT_STRING|IT_CVAR,              NULL, "Identify addons via",        &cv_addons_md5,         48},
+	{IT_STRING|IT_CVAR,              NULL, "Identify addons via",         &cv_addons_md5,         48},
 	{IT_STRING|IT_CVAR,              NULL, "Show unsupported file types", &cv_addons_showall,     58},
 
 	{IT_HEADER,                      NULL, "Search",                      NULL,                   76},
@@ -1437,6 +1436,19 @@ enum
 	op_addons_folder = 2,
 };
 
+#ifdef HAVE_DISCORDRPC
+static menuitem_t OP_DiscordOptionsMenu[] =
+{
+	{IT_STRING | IT_CVAR,		NULL, "Rich Presence",			&cv_discordrp,			 10},
+
+	{IT_HEADER,					NULL, "Rich Presence Settings",	NULL,					 30},
+	{IT_STRING | IT_CVAR,		NULL, "Streamer Mode",			&cv_discordstreamer,	 40},
+
+	{IT_STRING | IT_CVAR,		NULL, "Allow Ask To Join",		&cv_discordasks,		 60},
+	{IT_STRING | IT_CVAR,		NULL, "Allow Invites",			&cv_discordinvites,		 70},
+};
+#endif
+
 static menuitem_t OP_HUDOptionsMenu[] =
 {
 	{IT_STRING | IT_CVAR, NULL, "Show HUD (F3)",			&cv_showhud,			 10},
@@ -2118,6 +2130,7 @@ menu_t OP_OpenGLColorDef =
 menu_t OP_DataOptionsDef = DEFAULTMENUSTYLE("M_DATA", OP_DataOptionsMenu, &OP_MainDef, 60, 30);
 menu_t OP_ScreenshotOptionsDef = DEFAULTMENUSTYLE("M_SCSHOT", OP_ScreenshotOptionsMenu, &OP_DataOptionsDef, 30, 30);
 menu_t OP_AddonsOptionsDef = DEFAULTMENUSTYLE("M_ADDONS", OP_AddonsOptionsMenu, &OP_DataOptionsDef, 30, 30);
+menu_t OP_DiscordOptionsDef = DEFAULTMENUSTYLE(NULL, OP_DiscordOptionsMenu, &OP_DataOptionsDef, 30, 30);
 menu_t OP_EraseDataDef = DEFAULTMENUSTYLE("M_DATA", OP_EraseDataMenu, &OP_DataOptionsDef, 30, 30);
 
 // ==========================================================================
@@ -6275,7 +6288,12 @@ static void M_Options(INT32 choice)
 	OP_MainMenu[4].status = OP_MainMenu[5].status = (Playing() && !(server || IsPlayerAdmin(consoleplayer))) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU);
 
 	OP_MainMenu[8].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_CALL); // Play credits
+
+#ifdef HAVE_DISCORDRPC
+	OP_DataOptionsMenu[4].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); // Erase data
+#else
 	OP_DataOptionsMenu[3].status = (Playing()) ? (IT_GRAYEDOUT) : (IT_STRING|IT_SUBMENU); // Erase data
+#endif
 
 	OP_GameOptionsMenu[3].status =
 		(M_SecretUnlocked(SECRET_ENCORE)) ? (IT_CVAR|IT_STRING) : IT_SECRET; // cv_kartencore
@@ -11360,14 +11378,20 @@ static void M_DrawDiscordRequests(void)
 		return;
 	}
 
-	V_DrawThinString(x, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE, curRequest->username);
+	V_DrawThinString(x, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE,
+		cv_discordstreamer.value ? curRequest->username :
+		va("%s#%s", curRequest->username, curRequest->discriminator)
+	);
 	V_DrawThinString(x, y + 24, V_ALLOWLOWERCASE|V_6WIDTHSPACE, "A - Accept   B - Decline");
 	y -= 16;
 
 	while (curRequest->next != NULL)
 	{
 		curRequest = curRequest->next;
-		V_DrawThinString(x, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE, curRequest->username);
+		V_DrawThinString(x, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE,
+			cv_discordstreamer.value ? curRequest->username :
+			va("%s#%s", curRequest->username, curRequest->discriminator)
+		);
 		y -= 8;
 	}
 }