diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 0e0d6ea9c8d86b57c7267c958982f4c8e2c7eb2d..36e607d45731034dae3ca7efc0a5c6c42cad15ad 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -3167,6 +3167,8 @@ static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
 	// Reset the name
 	sprintf(player_names[playernum], "Player %d", playernum+1);
 
+	player_name_changes[playernum] = 0;
+
 	if (IsPlayerAdmin(playernum))
 	{
 		RemoveAdminPlayer(playernum); // don't stay admin after you're gone
@@ -3783,6 +3785,8 @@ void SV_ResetServer(void)
 		adminplayers[i] = -1; // Populate the entire adminplayers array with -1.
 	}
 
+	memset(player_name_changes, 0, sizeof player_name_changes);
+
 	mynode = 0;
 	cl_packetmissed = false;
 
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index e4c21181987c42490d077c3b0cd575ef0eb2cd45..adc8a7cc9b090cf4bf5d5881f08070bf34ba1093 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -527,6 +527,10 @@ typedef enum
 
 } kickreason_t;
 
+/* the max number of name changes in some time period */
+#define MAXNAMECHANGES (5)
+#define NAMECHANGERATE (60*TICRATE)
+
 extern boolean server;
 extern boolean serverrunning;
 #define client (!server)
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 68b8ecfc1ad1ec65d56cd19de46bb62781a9917e..62b93bba262592d605b3921be2d0034d3473fdbc 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1126,6 +1126,8 @@ static void SetPlayerName(INT32 playernum, char *newname)
 			if (netgame)
 				HU_AddChatText(va("\x82*%s renamed to %s", player_names[playernum], newname), false);
 
+			player_name_changes[playernum]++;
+
 			strcpy(player_names[playernum], newname);
 		}
 	}
@@ -1302,7 +1304,12 @@ static void SendNameAndColor(void)
 	snacpending++;
 
 	// Don't change name if muted
-	if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
+	if (player_name_changes[consoleplayer] >= MAXNAMECHANGES)
+	{
+		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
+		HU_AddChatText("\x85*You must wait to change your name again", false);
+	}
+	else if (cv_mute.value && !(server || IsPlayerAdmin(consoleplayer)))
 		CV_StealthSet(&cv_playername, player_names[consoleplayer]);
 	else // Cleanup name if changing it
 		CleanupPlayerName(consoleplayer, cv_playername.zstring);
@@ -1463,8 +1470,11 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
 	skin = READUINT8(*cp);
 
 	// set name
-	if (strcasecmp(player_names[playernum], name) != 0)
-		SetPlayerName(playernum, name);
+	if (player_name_changes[playernum] < MAXNAMECHANGES)
+	{
+		if (strcasecmp(player_names[playernum], name) != 0)
+			SetPlayerName(playernum, name);
+	}
 
 	// set color
 	p->skincolor = color % numskincolors;
diff --git a/src/g_game.c b/src/g_game.c
index b969eb4a4f2627c04f8e215dea33c71d3893cb9e..15d48e2f549e42dd408dd0c5e29de8ebc0a358e9 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -452,6 +452,8 @@ player_t *seenplayer; // player we're aiming at right now
 // so that it doesn't have to be updated depending on the value of MAXPLAYERS
 char player_names[MAXPLAYERS][MAXPLAYERNAME+1];
 
+INT32 player_name_changes[MAXPLAYERS];
+
 INT16 rw_maximums[NUM_WEAPONS] =
 {
 	800, // MAX_INFINITY
@@ -2345,6 +2347,11 @@ void G_Ticker(boolean run)
 
 		if (camtoggledelay2)
 			camtoggledelay2--;
+
+		if (gametic % NAMECHANGERATE == 0)
+		{
+			memset(player_name_changes, 0, sizeof player_name_changes);
+		}
 	}
 }
 
diff --git a/src/g_game.h b/src/g_game.h
index 26f60e12530856e265e2edca167821b53be25f85..2bcf444c234c244c2f8f76aab62cebee226d18ee 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -28,7 +28,8 @@ extern char customversionstring[32];
 #ifdef SEENAMES
 extern player_t *seenplayer;
 #endif
-extern char player_names[MAXPLAYERS][MAXPLAYERNAME+1];
+extern char  player_names[MAXPLAYERS][MAXPLAYERNAME+1];
+extern INT32 player_name_changes[MAXPLAYERS];
 
 extern player_t players[MAXPLAYERS];
 extern boolean playeringame[MAXPLAYERS];