diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 5832b2c27e12e95be1a60de7a69b5b90520fe5bf..834b0a327d6533fb21c6d0210ef98e3695d34801 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2254,7 +2254,7 @@ static void Command_connect(void)
 	// Assume we connect directly.
 	boolean viams = false;
 
-	if (COM_Argc() < 2)
+	if (COM_Argc() < 2 || *COM_Argv(1) == 0)
 	{
 		CONS_Printf(M_GetText(
 			"Connect <serveraddress> (port): connect to a server\n"
diff --git a/src/d_main.c b/src/d_main.c
index 05aa3e67570bdbb8ad14f368035c42437049c74d..a5e1a025424dd43ade761e52627e21ea382a84aa 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -734,11 +734,6 @@ void D_StartTitle(void)
 	CON_ToggleOff();
 
 	// Reset the palette
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		HWR_SetPaletteColor(0);
-	else
-#endif
 	if (rendermode != render_none)
 		V_SetPaletteLump("PLAYPAL");
 }
@@ -1049,7 +1044,7 @@ void D_SRB2Main(void)
 
 	// add any files specified on the command line with -file wadfile
 	// to the wad list
-	if (!(M_CheckParm("-connect")))
+	if (!(M_CheckParm("-connect") && !M_CheckParm("-server")))
 	{
 		if (M_CheckParm("-file"))
 		{
@@ -1204,7 +1199,15 @@ void D_SRB2Main(void)
 	R_Init();
 
 	// setting up sound
-	CONS_Printf("S_Init(): Setting up sound.\n");
+	if (dedicated)
+	{
+		nosound = true;
+		nomidimusic = nodigimusic = true;
+	}
+	else
+	{
+		CONS_Printf("S_Init(): Setting up sound.\n");
+	}
 	if (M_CheckParm("-nosound"))
 		nosound = true;
 	if (M_CheckParm("-nomusic")) // combines -nomidimusic and -nodigmusic
@@ -1316,7 +1319,7 @@ void D_SRB2Main(void)
 		}
 	}
 
-	if (autostart || netgame || M_CheckParm("+connect") || M_CheckParm("-connect"))
+	if (autostart || netgame)
 	{
 		gameaction = ga_nothing;
 
@@ -1350,8 +1353,7 @@ void D_SRB2Main(void)
 			}
 		}
 
-		if (server && !M_CheckParm("+map") && !M_CheckParm("+connect")
-			&& !M_CheckParm("-connect"))
+		if (server && !M_CheckParm("+map"))
 		{
 			// Prevent warping to nonexistent levels
 			if (W_CheckNumForName(G_BuildMapName(pstartmap)) == LUMPERROR)
diff --git a/src/d_netfil.c b/src/d_netfil.c
index 614d2064e374ddd7aff45703b6309617de8c1f3e..889de9466760b858e2129d934375cb2ca713de77 100644
--- a/src/d_netfil.c
+++ b/src/d_netfil.c
@@ -950,15 +950,37 @@ filestatus_t checkfilemd5(char *filename, const UINT8 *wantedmd5sum)
 	return FS_FOUND; // will never happen, but makes the compiler shut up
 }
 
+// Rewritten by Monster Iestyn to be less stupid
+// Note: if completepath is true, "filename" is modified, but only if FS_FOUND is going to be returned
+// (Don't worry about WinCE's version of filesearch, nobody cares about that OS anymore)
 filestatus_t findfile(char *filename, const UINT8 *wantedmd5sum, boolean completepath)
 {
-	filestatus_t homecheck = filesearch(filename, srb2home, wantedmd5sum, false, 10);
-	if (homecheck == FS_FOUND)
-		return filesearch(filename, srb2home, wantedmd5sum, completepath, 10);
+	filestatus_t homecheck; // store result of last file search
+	boolean badmd5 = false; // store whether md5 was bad from either of the first two searches (if nothing was found in the third)
 
-	homecheck = filesearch(filename, srb2path, wantedmd5sum, false, 10);
-	if (homecheck == FS_FOUND)
-		return filesearch(filename, srb2path, wantedmd5sum, completepath, 10);
+	// first, check SRB2's "home" directory
+	homecheck = filesearch(filename, srb2home, wantedmd5sum, completepath, 10);
 
-	return filesearch(filename, ".", wantedmd5sum, completepath, 10);
+	if (homecheck == FS_FOUND) // we found the file, so return that we have :)
+		return FS_FOUND;
+	else if (homecheck == FS_MD5SUMBAD) // file has a bad md5; move on and look for a file with the right md5
+		badmd5 = true;
+	// if not found at all, just move on without doing anything
+
+	// next, check SRB2's "path" directory
+	homecheck = filesearch(filename, srb2path, wantedmd5sum, completepath, 10);
+
+	if (homecheck == FS_FOUND) // we found the file, so return that we have :)
+		return FS_FOUND;
+	else if (homecheck == FS_MD5SUMBAD) // file has a bad md5; move on and look for a file with the right md5
+		badmd5 = true;
+	// if not found at all, just move on without doing anything
+
+	// finally check "." directory
+	homecheck = filesearch(filename, ".", wantedmd5sum, completepath, 10);
+
+	if (homecheck != FS_NOTFOUND) // if not found this time, fall back on the below return statement
+		return homecheck; // otherwise return the result we got
+
+	return (badmd5 ? FS_MD5SUMBAD : FS_NOTFOUND); // md5 sum bad or file not found
 }
diff --git a/src/g_game.c b/src/g_game.c
index 509ccf0ab24f344818ec81829905e200233ebda9..e5077fb217c8cbfdf11f7b1a11ebb7a9c5a07e8d 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -3815,7 +3815,8 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
 		unlocktriggers = 0;
 
 		// clear itemfinder, just in case
-		CV_StealthSetValue(&cv_itemfinder, 0);
+		if (!dedicated) // except in dedicated servers, where it is not registered and can actually I_Error debug builds
+			CV_StealthSetValue(&cv_itemfinder, 0);
 	}
 
 	// internal game map
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index ea76f8f3d735b7985b0681d67614e1e46a820541..349f3f3bfa9d67f033c92ac1387336cc713679c9 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -3032,8 +3032,8 @@ static boolean HWR_CheckBBox(fixed_t *bspcoord)
 	return gld_clipper_SafeCheckRange(angle2, angle1);
 #else
 	// check clip list for an open space
-	angle1 = R_PointToAngle(px1, py1) - dup_viewangle;
-	angle2 = R_PointToAngle(px2, py2) - dup_viewangle;
+	angle1 = R_PointToAngle2(dup_viewx>>1, dup_viewy>>1, px1>>1, py1>>1) - dup_viewangle;
+	angle2 = R_PointToAngle2(dup_viewx>>1, dup_viewy>>1, px2>>1, py2>>1) - dup_viewangle;
 
 	span = angle1 - angle2;
 
@@ -4359,6 +4359,9 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 	i = 0;
 	temp = FLOAT_TO_FIXED(realtop);
 
+	if (spr->mobj->frame & FF_FULLBRIGHT)
+		lightlevel = 255;
+
 #ifdef ESLOPE
 	for (i = 1; i < sector->numlights; i++)
 	{
@@ -4366,14 +4369,16 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 					: sector->lightlist[i].height;
 		if (h <= temp)
 		{
-			lightlevel = *list[i-1].lightlevel;
+			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+				lightlevel = *list[i-1].lightlevel;
 			colormap = list[i-1].extra_colormap;
 			break;
 		}
 	}
 #else
 	i = R_GetPlaneLight(sector, temp, false);
-	lightlevel = *list[i].lightlevel;
+	if (!(spr->mobj->frame & FF_FULLBRIGHT))
+		lightlevel = *list[i].lightlevel;
 	colormap = list[i].extra_colormap;
 #endif
 
@@ -4388,7 +4393,8 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 		// even if we aren't changing colormap or lightlevel, we still need to continue drawing down the sprite
 		if (!(list[i].flags & FF_NOSHADE) && (list[i].flags & FF_CUTSPRITES))
 		{
-			lightlevel = *list[i].lightlevel;
+			if (!(spr->mobj->frame & FF_FULLBRIGHT))
+				lightlevel = *list[i].lightlevel;
 			colormap = list[i].extra_colormap;
 		}
 
diff --git a/src/m_fixed.c b/src/m_fixed.c
index ce7471a28dac8cbd57aa96c58ed74d04fc00ded3..014457386030740900f05ebd5048a37e522ddddf 100644
--- a/src/m_fixed.c
+++ b/src/m_fixed.c
@@ -33,7 +33,9 @@
 */
 fixed_t FixedMul(fixed_t a, fixed_t b)
 {
-	return (fixed_t)((((INT64)a * b) ) / FRACUNIT);
+	// Need to cast to unsigned before shifting to avoid undefined behaviour
+	// for negative integers
+	return (fixed_t)(((UINT64)((INT64)a * b)) >> FRACBITS);
 }
 
 #endif //__USE_C_FIXEDMUL__
diff --git a/src/m_menu.c b/src/m_menu.c
index f10c6b653d7a089a12fd9232dd5f383d51a40841..a866dac1b95a8ac5fff4b45ba9d324fc5ea02cac 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -8441,6 +8441,13 @@ Update the maxplayers label...
 static void M_ConnectIP(INT32 choice)
 {
 	(void)choice;
+
+	if (*setupm_ip == 0)
+	{
+		M_StartMessage("You must specify an IP address.\n", NULL, MM_NOTHING);
+		return;
+	}
+
 	COM_BufAddText(va("connect \"%s\"\n", setupm_ip));
 
 	// A little "please wait" message.
diff --git a/src/p_floor.c b/src/p_floor.c
index c72de6b7030ba0dbb1922188431f524230e9b5f9..427dc293d36b99817d1276e5e524d3249ba0d398 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -2144,6 +2144,7 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 	boolean floortouch = false;
 	fixed_t bottomheight, topheight;
 	msecnode_t *node;
+	ffloor_t *rover;
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -2191,6 +2192,19 @@ void T_EachTimeThinker(levelspecthink_t *eachtime)
 			{
 				targetsec = &sectors[targetsecnum];
 
+				// Find the FOF corresponding to the control linedef
+				for (rover = targetsec->ffloors; rover; rover = rover->next)
+				{
+					if (rover->master == sec->lines[i])
+						break;
+				}
+
+				if (!rover) // This should be impossible, but don't complain if it is the case somehow
+					continue;
+
+				if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
+					continue;
+
 				for (j = 0; j < MAXPLAYERS; j++)
 				{
 					if (!playeringame[j])
diff --git a/src/p_setup.c b/src/p_setup.c
index a9fc57652f63d54a5f1b88966c4ac8db49e81649..a5544c26b74f57e11b06c318d047461dc142808c 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2675,11 +2675,6 @@ boolean P_SetupLevel(boolean skipprecip)
 
 
 	// Reset the palette
-#ifdef HWRENDER
-	if (rendermode == render_opengl)
-		HWR_SetPaletteColor(0);
-	else
-#endif
 	if (rendermode != render_none)
 		V_SetPaletteLump("PLAYPAL");
 
@@ -2737,6 +2732,7 @@ boolean P_SetupLevel(boolean skipprecip)
 	{
 		tic_t starttime = I_GetTime();
 		tic_t endtime = starttime + (3*TICRATE)/2;
+		tic_t nowtime;
 
 		S_StartSound(NULL, sfx_s3kaf);
 
@@ -2746,9 +2742,17 @@ boolean P_SetupLevel(boolean skipprecip)
 		F_WipeEndScreen();
 		F_RunWipe(wipedefs[wipe_speclevel_towhite], false);
 
+		nowtime = lastwipetic;
 		// Hold on white for extra effect.
-		while (I_GetTime() < endtime)
-			I_Sleep();
+		while (nowtime < endtime)
+		{
+			// wait loop
+			while (!((nowtime = I_GetTime()) - lastwipetic))
+				I_Sleep();
+			lastwipetic = nowtime;
+			if (moviemode) // make sure we save frames for the white hold too
+				M_SaveFrame();
+		}
 
 		ranspecialwipe = 1;
 	}
@@ -3336,7 +3340,7 @@ boolean P_AddWadFile(const char *wadfilename)
 	if ((numlumps = W_InitFile(wadfilename)) == INT16_MAX)
 	{
 		refreshdirmenu |= REFRESHDIR_NOTLOADED;
-		CONS_Printf(M_GetText("Errors occured while loading %s; not added.\n"), wadfilename);
+		CONS_Printf(M_GetText("Errors occurred while loading %s; not added.\n"), wadfilename);
 		return false;
 	}
 	else
diff --git a/src/p_spec.c b/src/p_spec.c
index e77a783e936df8b76d6d6c07eec399869a7b3934..93b5c89ca1bf26d6118bf40b5c542fb638ec3991 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -6699,6 +6699,7 @@ void T_Scroll(scroll_t *s)
 		line_t *line;
 		size_t i;
 		INT32 sect;
+		ffloor_t *rover;
 
 		case sc_side: // scroll wall texture
 			side = sides + s->affectee;
@@ -6740,6 +6741,19 @@ void T_Scroll(scroll_t *s)
 					sector_t *psec;
 					psec = sectors + sect;
 
+					// Find the FOF corresponding to the control linedef
+					for (rover = psec->ffloors; rover; rover = rover->next)
+					{
+						if (rover->master == sec->lines[i])
+							break;
+					}
+
+					if (!rover) // This should be impossible, but don't complain if it is the case somehow
+						continue;
+
+					if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
+						continue;
+
 					for (node = psec->touching_thinglist; node; node = node->m_thinglist_next)
 					{
 						thing = node->m_thing;
@@ -6803,6 +6817,19 @@ void T_Scroll(scroll_t *s)
 					sector_t *psec;
 					psec = sectors + sect;
 
+					// Find the FOF corresponding to the control linedef
+					for (rover = psec->ffloors; rover; rover = rover->next)
+					{
+						if (rover->master == sec->lines[i])
+							break;
+					}
+
+					if (!rover) // This should be impossible, but don't complain if it is the case somehow
+						continue;
+
+					if (!(rover->flags & FF_EXISTS)) // If the FOF does not "exist", we pretend that nobody's there
+						continue;
+
 					for (node = psec->touching_thinglist; node; node = node->m_thinglist_next)
 					{
 						thing = node->m_thing;
diff --git a/src/p_tick.c b/src/p_tick.c
index 658b1e4ea82255519c1574bf0098a88ddeef8a15..d0e669ee265df2ce65309f97a04d6da3fce4840c 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -606,7 +606,8 @@ void P_Ticker(boolean run)
 	}
 
 	// Keep track of how long they've been playing!
-	totalplaytime++;
+	if (!demoplayback) // Don't increment if a demo is playing.
+		totalplaytime++;
 
 	if (!useNightsSS && G_IsSpecialStage(gamemap))
 		P_DoSpecialStageStuff();
diff --git a/src/p_user.c b/src/p_user.c
index 8ae91f81c0a1472a931804c0881807d15619a0ec..2bcb3bc6c8f102bd1b5023a6e667972567979547 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1796,6 +1796,9 @@ boolean P_InSpaceSector(mobj_t *mo) // Returns true if you are in space
 
 		for (rover = sector->ffloors; rover; rover = rover->next)
 		{
+			if (!(rover->flags & FF_EXISTS))
+				continue;
+
 			if (GETSECSPECIAL(rover->master->frontsector->special, 1) != SPACESPECIAL)
 				continue;
 #ifdef ESLOPE
@@ -2156,6 +2159,12 @@ static void P_CheckBouncySectors(player_t *player)
 
 			for (rover = node->m_sector->ffloors; rover; rover = rover->next)
 			{
+				if (!(rover->flags & FF_EXISTS))
+					continue; // FOFs should not be bouncy if they don't even "exist"
+
+				if (GETSECSPECIAL(rover->master->frontsector->special, 1) != 15)
+					continue; // this sector type is required for FOFs to be bouncy
+
 				topheight = P_GetFOFTopZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 				bottomheight = P_GetFOFBottomZ(player->mo, node->m_sector, rover, player->mo->x, player->mo->y, NULL);
 
@@ -2169,7 +2178,6 @@ static void P_CheckBouncySectors(player_t *player)
 						&& oldz + player->mo->height > P_GetFOFBottomZ(player->mo, node->m_sector, rover, oldx, oldy, NULL))
 					top = false;
 
-				if (GETSECSPECIAL(rover->master->frontsector->special, 1) == 15)
 				{
 					fixed_t linedist;
 
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index d12e70eafebdea8645aafd14a3e5a48777e4e564..a1031209d958651ae26e0e11b08e2193b6ba732d 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -658,6 +658,14 @@ static void Impl_HandleMouseButtonEvent(SDL_MouseButtonEvent evt, Uint32 type)
 
 	SDL_memset(&event, 0, sizeof(event_t));
 
+	// Ignore the event if the mouse is not actually focused on the window.
+	// This can happen if you used the mouse to restore keyboard focus;
+	// this apparently makes a mouse button down event but not a mouse button up event,
+	// resulting in whatever key was pressed down getting "stuck" if we don't ignore it.
+	// -- Monster Iestyn (28/05/18)
+	if (SDL_GetMouseFocus() != window)
+		return;
+
 	/// \todo inputEvent.button.which
 	if (USE_MOUSEINPUT)
 	{
diff --git a/src/sdl/sdl_sound.c b/src/sdl/sdl_sound.c
index 6ca11c9ef46b9c6e30c6935d1b7d633c2e7c1147..63b51c625415b043c09dcc8b3b8937af46f5be9c 100644
--- a/src/sdl/sdl_sound.c
+++ b/src/sdl/sdl_sound.c
@@ -1180,12 +1180,6 @@ void I_StartupSound(void)
 	audio.callback = I_UpdateStream;
 	audio.userdata = &localdata;
 
-	if (dedicated)
-	{
-		nosound = nomidimusic = nodigimusic = true;
-		return;
-	}
-
 	// Configure sound device
 	CONS_Printf("I_StartupSound:\n");
 
@@ -1481,9 +1475,6 @@ void I_InitMusic(void)
 	I_AddExitFunc(I_ShutdownGMEMusic);
 #endif
 
-	if ((nomidimusic && nodigimusic) || dedicated)
-		return;
-
 #ifdef HAVE_MIXER
 	MIX_VERSION(&MIXcompiled)
 	MIXlinked = Mix_Linked_Version();
diff --git a/src/st_stuff.c b/src/st_stuff.c
index a513a028c646eb191a763ec55a7fe73cef973d57..d5cc9cf29da58ddd68505ba895517a5d95b8e6d0 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -202,17 +202,17 @@ void ST_doPaletteStuff(void)
 	else
 		palette = 0;
 
+#ifdef HWRENDER
+	if (rendermode == render_opengl)
+		palette = 0; // No flashpals here in OpenGL
+#endif
+
 	palette = min(max(palette, 0), 13);
 
 	if (palette != st_palette)
 	{
 		st_palette = palette;
 
-#ifdef HWRENDER
-		if (rendermode == render_opengl)
-			HWR_SetPaletteColor(0);
-		else
-#endif
 		if (rendermode != render_none)
 		{
 			V_SetPaletteLump(GetPalette()); // Reset the palette