diff --git a/src/command.c b/src/command.c
index a5d45bc1e4c7c2b8626eff7647f6b2c02e6d2649..74b9ef51f07f236359352f83d05c42dae4763b43 100644
--- a/src/command.c
+++ b/src/command.c
@@ -50,6 +50,7 @@ static void COM_Exec_f(void);
 static void COM_Wait_f(void);
 static void COM_Help_f(void);
 static void COM_Toggle_f(void);
+static void COM_Add_f(void);
 
 static void CV_EnforceExecVersion(void);
 static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr);
@@ -291,6 +292,7 @@ void COM_Init(void)
 	COM_AddCommand("wait", COM_Wait_f);
 	COM_AddCommand("help", COM_Help_f);
 	COM_AddCommand("toggle", COM_Toggle_f);
+	COM_AddCommand("add", COM_Add_f);
 	RegisterNetXCmd(XD_NETVAR, Got_NetVar);
 }
 
@@ -855,6 +857,27 @@ static void COM_Toggle_f(void)
 	CV_AddValue(cvar, +1);
 }
 
+/** Command variant of CV_AddValue
+  */
+static void COM_Add_f(void)
+{
+	consvar_t *cvar;
+
+	if (COM_Argc() != 3)
+	{
+		CONS_Printf(M_GetText("Add <cvar_name> <value>: Add to the value of a cvar. Negative values work too!\n"));
+		return;
+	}
+	cvar = CV_FindVar(COM_Argv(1));
+	if (!cvar)
+	{
+		CONS_Alert(CONS_NOTICE, M_GetText("%s is not a cvar\n"), COM_Argv(1));
+		return;
+	}
+
+	CV_AddValue(cvar, atoi(COM_Argv(2)));
+}
+
 // =========================================================================
 //                      VARIABLE SIZE BUFFERS
 // =========================================================================
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index e227ce2edecf5e0255b98d6178bb2aa5a5a15207..f3b07d343fde61d24f0280c4e7149567f59b6c8d 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -167,7 +167,7 @@ ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS];
 static textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE] = {NULL};
 
 
-consvar_t cv_showjoinaddress = {"showjoinaddress", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_showjoinaddress = {"showjoinaddress", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 
 static CV_PossibleValue_t playbackspeed_cons_t[] = {{1, "MIN"}, {10, "MAX"}, {0, NULL}};
 consvar_t cv_playbackspeed = {"playbackspeed", "1", 0, playbackspeed_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -1451,33 +1451,13 @@ static void SV_SendPlayerInfo(INT32 node)
 			continue;
 		}
 
-		netbuffer->u.playerinfo[i].node = (UINT8)playernode[i];
+		netbuffer->u.playerinfo[i].node = i;
 		strncpy(netbuffer->u.playerinfo[i].name, (const char *)&player_names[i], MAXPLAYERNAME+1);
 		netbuffer->u.playerinfo[i].name[MAXPLAYERNAME] = '\0';
 
 		//fetch IP address
-		{
-			const char *claddress;
-			UINT32 numericaddress[4];
-
-			memset(netbuffer->u.playerinfo[i].address, 0, 4);
-			if (playernode[i] == 0)
-			{
-				//127.0.0.1
-				netbuffer->u.playerinfo[i].address[0] = 127;
-				netbuffer->u.playerinfo[i].address[3] = 1;
-			}
-			else if (playernode[i] > 0 && I_GetNodeAddress && (claddress = I_GetNodeAddress(playernode[i])) != NULL)
-			{
-				if (sscanf(claddress, "%d.%d.%d.%d", &numericaddress[0], &numericaddress[1], &numericaddress[2], &numericaddress[3]) < 4)
-					goto badaddress;
-				netbuffer->u.playerinfo[i].address[0] = (UINT8)numericaddress[0];
-				netbuffer->u.playerinfo[i].address[1] = (UINT8)numericaddress[1];
-				netbuffer->u.playerinfo[i].address[2] = (UINT8)numericaddress[2];
-				netbuffer->u.playerinfo[i].address[3] = (UINT8)numericaddress[3];
-			}
-		}
-		badaddress:
+		//No, don't do that, you fuckface.
+		memset(netbuffer->u.playerinfo[i].address, 0, 4);
 
 		if (G_GametypeHasTeams())
 		{
diff --git a/src/d_main.c b/src/d_main.c
index 84d5a6f3295621acb71b95afc2ead168c819f62b..82f3721a5b2724915be2dc45463889e1475afd27 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -1390,10 +1390,9 @@ void D_SRB2Main(void)
 		midi_disabled = true;
 #endif
 	}
-	if (M_CheckParm("-nosound"))
-		sound_disabled = true;
-	if (M_CheckParm("-nomusic")) // combines -nomidimusic and -nodigmusic
+	if (M_CheckParm("-noaudio")) // combines -nosound and -nomusic
 	{
+		sound_disabled = true;
 		digital_disabled = true;
 #ifndef NO_MIDI
 		midi_disabled = true;
@@ -1401,12 +1400,24 @@ void D_SRB2Main(void)
 	}
 	else
 	{
+		if (M_CheckParm("-nosound"))
+			sound_disabled = true;
+		if (M_CheckParm("-nomusic")) // combines -nomidimusic and -nodigmusic
+		{
+			digital_disabled = true;
 #ifndef NO_MIDI
-		if (M_CheckParm("-nomidimusic"))
-			midi_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
+			midi_disabled = true;
 #endif
-		if (M_CheckParm("-nodigmusic"))
-			digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
+		}
+		else
+		{
+#ifndef NO_MIDI
+			if (M_CheckParm("-nomidimusic"))
+				midi_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
+#endif
+			if (M_CheckParm("-nodigmusic"))
+				digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
+		}
 	}
 	if (!( sound_disabled && digital_disabled
 #ifndef NO_MIDI
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 08bf3318539128b99b270b3c8fa558d5831e5124..b605c95415ce0519a30332a7d60264bc0158a1ca 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -423,7 +423,7 @@ consvar_t cv_numlaps = {"numlaps", "3", CV_NETVAR|CV_CALL|CV_NOINIT, numlaps_con
 static CV_PossibleValue_t basenumlaps_cons_t[] = {{1, "MIN"}, {50, "MAX"}, {0, "Map default"}, {0, NULL}};
 consvar_t cv_basenumlaps = {"basenumlaps", "Map default", CV_NETVAR|CV_CALL|CV_CHEAT, basenumlaps_cons_t, BaseNumLaps_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_forceskin = {"forceskin", "-1", CV_NETVAR|CV_CALL|CV_CHEAT, NULL, ForceSkin_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_forceskin = {"forceskin", "Off", CV_NETVAR|CV_CALL|CV_CHEAT, Forceskin_cons_t, ForceSkin_OnChange, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_downloading = {"downloading", "On", 0, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_allowexitlevel = {"allowexitlevel", "No", CV_NETVAR, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -514,6 +514,17 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
   */
 void D_RegisterServerCommands(void)
 {
+	int i;
+	Forceskin_cons_t[0].value = -1;
+	Forceskin_cons_t[0].strvalue = "Off";
+
+	// Set the values to 0/NULl, it will be overwritten later when a skin is assigned to the slot.
+	for (i = 1; i < MAXSKINS; i++)
+	{
+		Forceskin_cons_t[i].value = 0;
+		Forceskin_cons_t[i].strvalue = NULL;
+	}
+
 	RegisterNetXCmd(XD_NAMEANDCOLOR, Got_NameAndColor);
 	RegisterNetXCmd(XD_WEAPONPREF, Got_WeaponPref);
 	RegisterNetXCmd(XD_MAP, Got_Mapcmd);
@@ -5152,27 +5163,11 @@ static void Command_Archivetest_f(void)
 
 /** Makes a change to ::cv_forceskin take effect immediately.
   *
-  * \todo Move the enforcement code out of SendNameAndColor() so this hack
-  *       isn't needed.
   * \sa Command_SetForcedSkin_f, cv_forceskin, forcedskin
   * \author Graue <graue@oceanbase.org>
   */
 static void ForceSkin_OnChange(void)
 {
-	if ((server || IsPlayerAdmin(consoleplayer)) && (cv_forceskin.value < -1 || cv_forceskin.value >= numskins))
-	{
-		if (cv_forceskin.value == -2)
-			CV_SetValue(&cv_forceskin, numskins-1);
-		else
-		{
-			// hack because I can't restrict this and still allow added skins to be used with forceskin.
-			if (!menuactive)
-				CONS_Printf(M_GetText("Valid skin numbers are 0 to %d (-1 disables)\n"), numskins - 1);
-			CV_SetValue(&cv_forceskin, -1);
-		}
-		return;
-	}
-
 	// NOT in SP, silly!
 	if (!(netgame || multiplayer))
 		return;
@@ -5181,7 +5176,7 @@ static void ForceSkin_OnChange(void)
 		CONS_Printf("The server has lifted the forced skin restrictions.\n");
 	else
 	{
-		CONS_Printf("The server is restricting all players to skin \"%s\".\n",skins[cv_forceskin.value].name);
+		CONS_Printf("The server is restricting all players to skin \"%s\".\n",cv_forceskin.string);
 		ForceAllSkins(cv_forceskin.value);
 	}
 }
diff --git a/src/g_input.c b/src/g_input.c
index cab35830302b1016ed1c674120cc01dd097345bc..08a323c78723e8fefc56b0cb1700e131b743245a 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -1239,6 +1239,8 @@ void G_ClearAllControlKeys(void)
 	{
 		G_ClearControlKeys(gamecontrol, i);
 		G_ClearControlKeys(gamecontrolbis, i);
+		G_ClearControlKeys(gamecontrol3, i);
+		G_ClearControlKeys(gamecontrol4, i);
 	}
 }
 
diff --git a/src/m_menu.c b/src/m_menu.c
index 3ad076ff7ce070375f43145a1188df150d061253..fefafff31a56e99e380402685c90127536054ba7 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -7456,7 +7456,10 @@ static void M_ConnectMenu(INT32 choice)
 
 	// first page of servers
 	serverlistpage = 0;
-	M_SetupNextMenu(&MP_ConnectDef);
+	if (ms_RoomId < 0)
+		M_RoomMenu(0); // Select a room instead of staring at an empty list
+	else
+		M_SetupNextMenu(&MP_ConnectDef);
 	itemOn = 0;
 	M_Refresh(0);
 }
@@ -7529,7 +7532,15 @@ static void M_ChooseRoom(INT32 choice)
 	}
 
 	serverlistpage = 0;
-	M_SetupNextMenu(currentMenu->prevMenu);
+	/*
+	We were on the Multiplayer menu? That means that we must have been trying to
+	view the server browser, but we hadn't selected a room yet. So we need to go
+	to the browser next, not back there.
+	*/
+	if (currentMenu->prevMenu == &MP_MainDef)
+		M_SetupNextMenu(&MP_ConnectDef);
+	else
+		M_SetupNextMenu(currentMenu->prevMenu);
 	if (currentMenu == &MP_ConnectDef)
 		M_Refresh(0);
 }
diff --git a/src/r_things.c b/src/r_things.c
index a40830ac5d3be1c885477bf54d1a08d7016538f9..f8d131bbe99a4fcd849b3dfce87f43cc78d4fee7 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -40,6 +40,8 @@ int	snprintf(char *str, size_t n, const char *fmt, ...);
 //int	vsnprintf(char *str, size_t n, const char *fmt, va_list ap);
 #endif
 
+CV_PossibleValue_t Forceskin_cons_t[MAXSKINS+2];
+
 static void R_InitSkins(void);
 
 #define MINZ (FRACUNIT*4)
@@ -1796,7 +1798,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel, UINT8 viewnumber)
 		}
 	}
 
-	// Someone seriously wants infinite draw distance for precipitation?
+	// no, no infinite draw distance for precipitation. this option at zero is supposed to turn it off
 	if ((limit_dist = (fixed_t)cv_drawdist_precip.value << FRACBITS))
 	{
 		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
@@ -1812,13 +1814,6 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel, UINT8 viewnumber)
 			R_ProjectPrecipitationSprite(precipthing);
 		}
 	}
-	else
-	{
-		// Draw everything in sector, no checks
-		for (precipthing = sec->preciplist; precipthing; precipthing = precipthing->snext)
-			if (!(precipthing->precipflags & PCF_INVISIBLE))
-				R_ProjectPrecipitationSprite(precipthing);
-	}
 }
 
 //
@@ -2588,6 +2583,10 @@ void R_InitSkins(void)
 	skin->spritedef.spriteframes = sprites[SPR_PLAY].spriteframes;
 	ST_LoadFaceGraphics(skin->facerank, skin->facewant, skin->facemmap, 0);
 
+	// Set values for Sonic skin
+	Forceskin_cons_t[1].value = 0;
+	Forceskin_cons_t[1].strvalue = skin->name;
+
 	//MD2 for sonic doesn't want to load in Linux.
 #ifdef HWRENDER
 	if (rendermode == render_opengl)
@@ -2971,6 +2970,10 @@ next_token:
 		skin_cons_t[numskins].strvalue = skin->name;
 #endif
 
+		// Update the forceskin possiblevalues
+		Forceskin_cons_t[numskins+1].value = numskins;
+		Forceskin_cons_t[numskins+1].strvalue = skins[numskins].name;
+
 		// add face graphics
 		ST_LoadFaceGraphics(skin->facerank, skin->facewant, skin->facemmap, numskins);
 
diff --git a/src/r_things.h b/src/r_things.h
index 6f48cc5bffb11fa055c37c5c0e47854a56b01a05..825ff3b9836edb5fc9a93c314f0be6312ccc0048 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -97,6 +97,8 @@ typedef struct
 	sfxenum_t soundsid[NUMSKINSOUNDS]; // sound # in S_sfx table
 } skin_t;
 
+extern CV_PossibleValue_t Forceskin_cons_t[];
+
 // -----------
 // NOT SKINS STUFF !
 // -----------
diff --git a/src/s_sound.c b/src/s_sound.c
index 2ddffa3f531c7f3872828859249b4ee82f6dbb4e..58cc0592f771e746ef41a69ea48e42023c2ce2c0 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -2182,7 +2182,7 @@ static void Command_RestartAudio_f(void)
 
 void GameSounds_OnChange(void)
 {
-	if (M_CheckParm("-nosound"))
+	if (M_CheckParm("-nosound") || M_CheckParm("-noaudio"))
 		return;
 
 	if (sound_disabled)
@@ -2196,7 +2196,7 @@ void GameSounds_OnChange(void)
 
 void GameDigiMusic_OnChange(void)
 {
-	if (M_CheckParm("-nomusic"))
+	if (M_CheckParm("-nomusic") || M_CheckParm("-noaudio"))
 		return;
 	else if (M_CheckParm("-nodigmusic"))
 		return;
@@ -2239,7 +2239,7 @@ void GameDigiMusic_OnChange(void)
 #ifndef NO_MIDI
 void GameMIDIMusic_OnChange(void)
 {
-	if (M_CheckParm("-nomusic"))
+	if (M_CheckParm("-nomusic") || M_CheckParm("-noaudio"))
 		return;
 	else if (M_CheckParm("-nomidimusic"))
 		return;
diff --git a/src/y_inter.c b/src/y_inter.c
index 095b4ad36b4841f261f5b8a7bff8d42a98cf15f5..c7e966c5fda863e34367efb535f56cdd4e6e20db 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -1505,11 +1505,11 @@ void Y_EndVote(void)
 //
 static void Y_UnloadVoteData(void)
 {
+	voteclient.loaded = false;
+
 	if (rendermode != render_soft)
 		return;
 
-	voteclient.loaded = false;
-
 	UNLOAD(widebgpatch);
 	UNLOAD(bgpatch);
 	UNLOAD(cursor);