diff --git a/src/d_main.c b/src/d_main.c
index 50da0a6299086c2c21256ffb9fa41a01274bf0e4..e25ef998e040bc51dda58ff80fc84a1c9be4a9f2 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -274,7 +274,10 @@ static void D_Display(void)
 			 && wipetypepre != UINT8_MAX)
 			{
 				F_WipeStartScreen();
-				F_WipeColorFill(31);
+				// Check for Mega Genesis fade
+				wipestyleflags = WSF_FADEOUT;
+				if (F_TryColormapFade(31))
+					wipetypepost = -1; // Don't run the fade below this one
 				F_WipeEndScreen();
 				F_RunWipe(wipetypepre, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
 			}
@@ -488,6 +491,7 @@ static void D_Display(void)
 		if (rendermode != render_none)
 		{
 			F_WipeEndScreen();
+
 			// Funny.
 			if (WipeStageTitle && st_overlay)
 			{
@@ -497,6 +501,14 @@ static void D_Display(void)
 				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol);
 				F_WipeStartScreen();
 			}
+
+			// Check for Mega Genesis fade
+			if (F_ShouldColormapFade())
+			{
+				wipestyleflags |= WSF_FADEIN;
+				wipestyleflags &= ~WSF_FADEOUT;
+			}
+
 			F_RunWipe(wipetypepost, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
 		}
 
@@ -562,9 +574,6 @@ void D_SRB2Loop(void)
 
 	// Pushing of + parameters is now done back in D_SRB2Main, not here.
 
-	CONS_Printf("I_StartupKeyboard()...\n");
-	I_StartupKeyboard();
-
 #ifdef _WINDOWS
 	CONS_Printf("I_StartupMouse()...\n");
 	I_DoStartupMouse();
@@ -1288,9 +1297,10 @@ void D_SRB2Main(void)
 		I_StartupSound();
 		I_InitMusic();
 		S_InitSfxChannels(cv_soundvolume.value);
-		S_InitMusicDefs();
 	}
 
+	S_InitMusicDefs();
+
 	CONS_Printf("ST_Init(): Init status bar.\n");
 	ST_Init();
 
diff --git a/src/dehacked.c b/src/dehacked.c
index b598d40ac73586dec6ec9d344d3555b8cec4b30d..2c123e010cc4afecdb67c3816de49b13ba3a230c 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1740,10 +1740,27 @@ static void readlevelheader(MYFILE *f, INT32 num)
 					deh_warning("Level header %d: invalid bonus type number %d", num, i);
 			}
 
+			// Title card
+			else if (fastcmp(word, "TITLECARDZIGZAG"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->ltzzpatch, word2,
+					sizeof(mapheaderinfo[num-1]->ltzzpatch), va("Level header %d: title card zigzag patch name", num));
+			}
+			else if (fastcmp(word, "TITLECARDZIGZAGTEXT"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->ltzztext, word2,
+					sizeof(mapheaderinfo[num-1]->ltzztext), va("Level header %d: title card zigzag text patch name", num));
+			}
+			else if (fastcmp(word, "TITLECARDACTDIAMOND"))
+			{
+				deh_strlcpy(mapheaderinfo[num-1]->ltactdiamond, word2,
+					sizeof(mapheaderinfo[num-1]->ltactdiamond), va("Level header %d: title card act diamond patch name", num));
+			}
+
 			else if (fastcmp(word, "MAXBONUSLIVES"))
 				mapheaderinfo[num-1]->maxbonuslives = (SINT8)i;
 			else if (fastcmp(word, "LEVELFLAGS"))
-				mapheaderinfo[num-1]->levelflags = (UINT8)i;
+				mapheaderinfo[num-1]->levelflags = (UINT16)i;
 			else if (fastcmp(word, "MENUFLAGS"))
 				mapheaderinfo[num-1]->menuflags = (UINT8)i;
 
@@ -4714,20 +4731,34 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
 						ignorelines(f);
 					}
 				}
-				// Last I heard this crashes the game if you try to use it
-				// so this is disabled for now
-				// -- Monster Iestyn
-/*				else if (fastcmp(word, "SRB2"))
+				else if (fastcmp(word, "SRB2"))
 				{
-					INT32 ver = searchvalue(strtok(NULL, "\n"));
-					if (ver != PATCHVERSION)
-						deh_warning("Patch is for SRB2 version %d,\nonly version %d is supported", ver, PATCHVERSION);
+					if (isdigit(word2[0]))
+					{
+						i = atoi(word2);
+						if (i != PATCHVERSION)
+						{
+							deh_warning(
+									"Patch is for SRB2 version %d, "
+									"only version %d is supported",
+									i,
+									PATCHVERSION
+							);
+						}
+					}
+					else
+					{
+						deh_warning(
+								"SRB2 version definition has incorrect format, "
+								"use \"SRB2 %d\"",
+								PATCHVERSION
+						);
+					}
 				}
 				// Clear all data in certain locations (mostly for unlocks)
 				// Unless you REALLY want to piss people off,
 				// define a custom gamedata /before/ doing this!!
 				// (then again, modifiedgame will prevent game data saving anyway)
-*/
 				else if (fastcmp(word, "CLEAR"))
 				{
 					boolean clearall = (fastcmp(word2, "ALL"));
diff --git a/src/dehacked.h b/src/dehacked.h
index d4fb07df0f1dd650afe5a156ed7abc7a35338e38..2b34377fd5e97e884f4c2a78f9062e0e34f76088 100644
--- a/src/dehacked.h
+++ b/src/dehacked.h
@@ -47,7 +47,7 @@ extern const char *superactions[MAXRECURSION];
 extern UINT8 superstack;
 
 // If the dehacked patch does not match this version, we throw a warning
-#define PATCHVERSION 210
+#define PATCHVERSION 220
 
 #define MAXLINELEN 1024
 
diff --git a/src/doomdef.h b/src/doomdef.h
index 6c4f1fef3258a8c0eaac31587798b91d86112890..0da1a1fedc3b3deacdf84ead5f6f1a1441cba957 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -127,6 +127,7 @@
 
 #ifdef LOGMESSAGES
 extern FILE *logstream;
+extern char  logfilename[1024];
 #endif
 
 //#define DEVELOP // Disable this for release builds to remove excessive cheat commands and enable MD5 checking and stuff, all in one go. :3
diff --git a/src/doomstat.h b/src/doomstat.h
index 2e3fe9b36a3781e7689d51026ec62319103bec92..b7bb7a36228b5e732db282b0948b2ea4841d2a03 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -313,12 +313,17 @@ typedef struct
 	SINT8 bonustype;      ///< What type of bonus does this level have? (-1 for null.)
 	SINT8 maxbonuslives;  ///< How many bonus lives to award at Intermission? (-1 for unlimited.)
 
-	UINT8 levelflags;     ///< LF_flags:  merged booleans into one UINT8 for space, see below
+	UINT16 levelflags;     ///< LF_flags:  merged booleans into one UINT16 for space, see below
 	UINT8 menuflags;      ///< LF2_flags: options that affect record attack / nights mode menus
 
 	char selectheading[22]; ///< Level select heading. Allows for controllable grouping.
 	UINT16 startrings;      ///< Number of rings players start with.
 
+	// Title card.
+	char ltzzpatch[8];      ///< Zig zag patch.
+	char ltzztext[8];       ///< Zig zag text.
+	char ltactdiamond[8];   ///< Act diamond.
+
 	// Freed animals stuff.
 	UINT8 numFlickies;     ///< Internal. For freed flicky support.
 	mobjtype_t *flickies;  ///< List of freeable flickies in this level. Allocated dynamically for space reasons. Be careful.
diff --git a/src/f_finale.c b/src/f_finale.c
index 424fc7f39c86bd2febcd1be60ec5941c29e28520..1436a159b1285cb40ba302a15f767412f0c0f77c 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -917,8 +917,9 @@ void F_IntroDrawer(void)
 		{
 			if (rendermode != render_none)
 			{
+				wipestyleflags = WSF_FADEOUT;
 				F_WipeStartScreen();
-				F_WipeColorFill(31);
+				F_TryColormapFade(31);
 				F_WipeEndScreen();
 				F_RunWipe(99,true);
 			}
@@ -927,12 +928,11 @@ void F_IntroDrawer(void)
 		}
 		else if (intro_scenenum == 10)
 		{
-			// The only fade to white in the entire damn game.
-			// (not true)
 			if (rendermode != render_none)
 			{
+				wipestyleflags = (WSF_FADEOUT|WSF_TOWHITE);
 				F_WipeStartScreen();
-				F_WipeColorFill(0);
+				F_TryColormapFade(0);
 				F_WipeEndScreen();
 				F_RunWipe(99,true);
 			}
@@ -941,8 +941,9 @@ void F_IntroDrawer(void)
 		{
 			if (rendermode != render_none)
 			{
+				wipestyleflags = WSF_FADEOUT;
 				F_WipeStartScreen();
-				F_WipeColorFill(31);
+				F_TryColormapFade(31);
 				F_WipeEndScreen();
 				F_RunWipe(99,true);
 			}
@@ -977,6 +978,7 @@ void F_IntroDrawer(void)
 
 		F_WipeStartScreen();
 		wipegamestate = -1;
+		wipestyleflags = WSF_CROSSFADE;
 		animtimer = stoptimer = 0;
 	}
 
@@ -3596,6 +3598,8 @@ void F_StartContinue(void)
 		return;
 	}
 
+	wipestyleflags = WSF_FADEOUT;
+	F_TryColormapFade(31);
 	G_SetGamestate(GS_CONTINUING);
 	gameaction = ga_nothing;
 
diff --git a/src/f_finale.h b/src/f_finale.h
index 1636f1967bd2b4d8368356fe11d25dc4cecfe266..efc2de2e663fc1b43c1c87cbe95aa972916f12b7 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -146,7 +146,7 @@ extern boolean WipeStageTitle;
 typedef enum
 {
 	WIPESTYLE_NORMAL,
-	WIPESTYLE_LEVEL
+	WIPESTYLE_COLORMAP
 } wipestyle_t;
 extern wipestyle_t wipestyle;
 
@@ -159,6 +159,11 @@ typedef enum
 } wipestyleflags_t;
 extern wipestyleflags_t wipestyleflags;
 
+// Even my function names are borderline
+boolean F_ShouldColormapFade(void);
+boolean F_TryColormapFade(UINT8 wipecolor);
+void F_DecideWipeStyle(void);
+
 #define FADECOLORMAPDIV 8
 #define FADECOLORMAPROWS (256/FADECOLORMAPDIV)
 
diff --git a/src/f_wipe.c b/src/f_wipe.c
index b0982a957a8525a69bc5d38c7aefc0e3852b2dcf..a83d104f24ed09696cdf10ea33885b64242e0191 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -56,7 +56,7 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
 
 	0,  // wipe_level_toblack
 	UINT8_MAX,  // wipe_intermission_toblack
-	UINT8_MAX,  // wipe_continuing_toblack
+	0,  // wipe_continuing_toblack
 	0,  // wipe_titlescreen_toblack
 	0,  // wipe_timeattack_toblack
 	99, // wipe_credits_toblack
@@ -161,7 +161,7 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
 	{
 		// Determine pixel to use from fademask
 		pcolor = &pMasterPalette[*lump++];
-		if (wipestyle == WIPESTYLE_LEVEL)
+		if (wipestyle == WIPESTYLE_COLORMAP)
 			*mask++ = pcolor->s.red / FADECOLORMAPDIV;
 		else
 			*mask++ = FixedDiv((pcolor->s.red+1)<<FRACBITS, paldiv)>>FRACBITS;
@@ -191,7 +191,7 @@ void F_WipeStageTitle(void)
 {
 	// draw level title
 	if ((WipeStageTitle && st_overlay)
-	&& (wipestyle == WIPESTYLE_LEVEL)
+	&& (wipestyle == WIPESTYLE_COLORMAP)
 	&& !(mapheaderinfo[gamemap-1]->levelflags & LF_NOTITLECARD)
 	&& *mapheaderinfo[gamemap-1]->lvlttl != '\0')
 	{
@@ -282,7 +282,7 @@ static void F_DoWipe(fademask_t *fademask)
 					relativepos += vid.width;
 				}
 			}
-			else if (*mask >= ((wipestyle == WIPESTYLE_LEVEL) ? FADECOLORMAPROWS : 10))
+			else if (*mask >= 10)
 			{
 				// shortcut - memcpy target to work
 				while (draw_linestogo--)
@@ -293,25 +293,113 @@ static void F_DoWipe(fademask_t *fademask)
 			}
 			else
 			{
-				if (wipestyle == WIPESTYLE_LEVEL)
+				// pointer to transtable that this mask would use
+				transtbl = transtables + ((9 - *mask)<<FF_TRANSSHIFT);
+
+				// DRAWING LOOP
+				while (draw_linestogo--)
 				{
-					int nmask;
-					UINT8 *fade = fadecolormap;
+					w = w_base + relativepos;
+					s = s_base + relativepos;
+					e = e_base + relativepos;
+					draw_rowstogo = draw_rowend - draw_rowstart;
+
+					while (draw_rowstogo--)
+						*w++ = transtbl[ ( *e++ << 8 ) + *s++ ];
+
+					relativepos += vid.width;
+				}
+				// END DRAWING LOOP
+			}
 
-					if (wipestyleflags & WSF_TOWHITE)
-						fade = fadecolormap + (FADECOLORMAPROWS * 256);
+			if (++maskx >= fademask->width)
+				++masky, maskx = 0;
+		} while (++mask < maskend);
+
+		free(scrxpos);
+		free(scrypos);
+	}
+}
+
+static void F_DoColormapWipe(fademask_t *fademask, UINT8 *colormap)
+{
+	// Lactozilla: F_DoWipe for WIPESTYLE_COLORMAP
+	{
+		// wipe screen, start, end
+		UINT8       *w = wipe_scr;
+		const UINT8 *s = wipe_scr_start;
+		const UINT8 *e = wipe_scr_end;
+
+		// first pixel for each screen
+		UINT8       *w_base = w;
+		const UINT8 *s_base = s;
+		const UINT8 *e_base = e;
+
+		// mask data, end
+		UINT8       *transtbl;
+		const UINT8 *mask    = fademask->mask;
+		const UINT8 *maskend = mask + fademask->size;
+
+		// rectangle draw hints
+		UINT32 draw_linestart, draw_rowstart;
+		UINT32 draw_lineend,   draw_rowend;
+		UINT32 draw_linestogo, draw_rowstogo;
+
+		// rectangle coordinates, etc.
+		UINT16* scrxpos = (UINT16*)malloc((fademask->width + 1)  * sizeof(UINT16));
+		UINT16* scrypos = (UINT16*)malloc((fademask->height + 1) * sizeof(UINT16));
+		UINT16 maskx, masky;
+		UINT32 relativepos;
+
+		// ---
+		// Screw it, we do the fixed point math ourselves up front.
+		scrxpos[0] = 0;
+		for (relativepos = 0, maskx = 1; maskx < fademask->width; ++maskx)
+			scrxpos[maskx] = (relativepos += fademask->xscale)>>FRACBITS;
+		scrxpos[fademask->width] = vid.width;
+
+		scrypos[0] = 0;
+		for (relativepos = 0, masky = 1; masky < fademask->height; ++masky)
+			scrypos[masky] = (relativepos += fademask->yscale)>>FRACBITS;
+		scrypos[fademask->height] = vid.height;
+		// ---
+
+		maskx = masky = 0;
+		do
+		{
+			draw_rowstart = scrxpos[maskx];
+			draw_rowend   = scrxpos[maskx + 1];
+			draw_linestart = scrypos[masky];
+			draw_lineend   = scrypos[masky + 1];
 
-					nmask = *mask;
-					if (wipestyleflags & WSF_FADEIN)
-						nmask = (FADECOLORMAPROWS-1) - nmask;
+			relativepos = (draw_linestart * vid.width) + draw_rowstart;
+			draw_linestogo = draw_lineend - draw_linestart;
 
-					transtbl = fade + (nmask * 256);
+			if (*mask == 0)
+			{
+				// shortcut - memcpy source to work
+				while (draw_linestogo--)
+				{
+					M_Memcpy(w_base+relativepos, s_base+relativepos, draw_rowend-draw_rowstart);
+					relativepos += vid.width;
 				}
-				else
+			}
+			else if (*mask >= FADECOLORMAPROWS)
+			{
+				// shortcut - memcpy target to work
+				while (draw_linestogo--)
 				{
-					// pointer to transtable that this mask would use
-					transtbl = transtables + ((9 - *mask)<<FF_TRANSSHIFT);
+					M_Memcpy(w_base+relativepos, e_base+relativepos, draw_rowend-draw_rowstart);
+					relativepos += vid.width;
 				}
+			}
+			else
+			{
+				int nmask = *mask;
+				if (wipestyleflags & WSF_FADEIN)
+					nmask = (FADECOLORMAPROWS-1) - nmask;
+
+				transtbl = colormap + (nmask * 256);
 
 				// DRAWING LOOP
 				while (draw_linestogo--)
@@ -321,16 +409,8 @@ static void F_DoWipe(fademask_t *fademask)
 					e = e_base + relativepos;
 					draw_rowstogo = draw_rowend - draw_rowstart;
 
-					if (wipestyle == WIPESTYLE_LEVEL)
-					{
-						while (draw_rowstogo--)
-							*w++ = transtbl[*e++];
-					}
-					else
-					{
-						while (draw_rowstogo--)
-							*w++ = transtbl[ ( *e++ << 8 ) + *s++ ];
-					}
+					while (draw_rowstogo--)
+						*w++ = transtbl[*e++];
 
 					relativepos += vid.width;
 				}
@@ -382,6 +462,62 @@ void F_WipeEndScreen(void)
 #endif
 }
 
+/** Verifies every condition for a colormapped fade.
+  */
+boolean F_ShouldColormapFade(void)
+{
+	if ((wipestyleflags & (WSF_FADEIN|WSF_FADEOUT)) // only if one of those wipestyleflags are actually set
+	&& !(wipestyleflags & WSF_CROSSFADE)) // and if not crossfading
+	{
+		// World
+		return (gamestate == GS_LEVEL
+		|| gamestate == GS_TITLESCREEN
+		// Finales
+		|| gamestate == GS_CONTINUING
+		|| gamestate == GS_CREDITS
+		|| gamestate == GS_EVALUATION
+		|| gamestate == GS_INTRO
+		|| gamestate == GS_ENDING
+		// Menus
+		|| gamestate == GS_TIMEATTACK);
+	}
+	return false;
+}
+
+/** Decides what wipe style to use.
+  */
+void F_DecideWipeStyle(void)
+{
+	// Set default wipe style
+	wipestyle = WIPESTYLE_NORMAL;
+
+	// Check for colormap wipe style
+	if (F_ShouldColormapFade())
+		wipestyle = WIPESTYLE_COLORMAP;
+}
+
+/** Attempt to run a colormap fade,
+    provided all the conditionals were properly met.
+    Returns true if so.
+    I demand you call F_RunWipe after this function.
+  */
+boolean F_TryColormapFade(UINT8 wipecolor)
+{
+	if (F_ShouldColormapFade())
+	{
+#ifdef HWRENDER
+		if (rendermode == render_opengl)
+			F_WipeColorFill(wipecolor);
+#endif
+		return true;
+	}
+	else
+	{
+		F_WipeColorFill(wipecolor);
+		return false;
+	}
+}
+
 /** After setting up the screens you want to wipe,
   * calling this will do a 'typical' wipe.
   */
@@ -399,18 +535,10 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 		paldiv = FixedDiv(257<<FRACBITS, 11<<FRACBITS);
 
 	// Init the wipe
+	F_DecideWipeStyle();
 	WipeInAction = true;
 	wipe_scr = screens[0];
 
-	// don't know where else to put this.
-	// this any good?
-	if ((gamestate == GS_LEVEL || gamestate == GS_TITLESCREEN)
-	&& (wipestyleflags & (WSF_FADEIN|WSF_FADEOUT)) // only if one of those wipestyleflags are actually set
-	&& !(wipestyleflags & WSF_CROSSFADE)) // and if not crossfading
-		wipestyle = WIPESTYLE_LEVEL;
-	else
-		wipestyle = WIPESTYLE_NORMAL;
-
 	// lastwipetic should either be 0 or the tic we last wiped
 	// on for fade-to-black
 	for (;;)
@@ -425,21 +553,39 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 			I_Sleep();
 		lastwipetic = nowtime;
 
-#ifdef HWRENDER
-		if (rendermode == render_opengl)
+		// Wipe styles
+		if (wipestyle == WIPESTYLE_COLORMAP)
 		{
-			// send in the wipe type and wipe frame because we need to cache the graphic
-			if (wipestyle == WIPESTYLE_LEVEL)
+#ifdef HWRENDER
+			if (rendermode == render_opengl)
+			{
+				// send in the wipe type and wipe frame because we need to cache the graphic
 				HWR_DoTintedWipe(wipetype, wipeframe-1);
+			}
 			else
-				HWR_DoWipe(wipetype, wipeframe-1);
-		}
-		else
 #endif
-			F_DoWipe(fmask);
+			{
+				UINT8 *colormap = fadecolormap;
+				if (wipestyleflags & WSF_TOWHITE)
+					colormap += (FADECOLORMAPROWS * 256);
+				F_DoColormapWipe(fmask, colormap);
+			}
 
-		if (wipestyle == WIPESTYLE_LEVEL)
+			// Draw the title card above the wipe
 			F_WipeStageTitle();
+		}
+		else
+		{
+#ifdef HWRENDER
+			if (rendermode == render_opengl)
+			{
+				// send in the wipe type and wipe frame because we need to cache the graphic
+				HWR_DoWipe(wipetype, wipeframe-1);
+			}
+			else
+#endif
+				F_DoWipe(fmask);
+		}
 
 		I_OsPolling();
 		I_UpdateNoBlit();
diff --git a/src/g_game.c b/src/g_game.c
index b39205f504f02b897240435663607d3c2b768073..2c6a802d11a1754ece56ed5f98b1be6ec000dd35 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -1890,9 +1890,6 @@ void G_DoLoadLevel(boolean resetplayer)
 //
 void G_StartTitleCard(void)
 {
-	wipestyleflags |= WSF_FADEIN;
-	wipestyleflags &= ~WSF_FADEOUT;
-
 	// The title card has been disabled for this map.
 	// Oh well.
 	if (!G_IsTitleCardAvailable())
@@ -1936,6 +1933,8 @@ void G_PreLevelTitleCard(void)
 		if (takescreenshot) // Only take screenshots after drawing.
 			M_DoScreenShot();
 	}
+	if (!cv_showhud.value)
+		wipestyleflags = WSF_CROSSFADE;
 }
 
 //
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 13ba007ee8bef1fa797915f2ae9f704eb6ec83f1..630c1e181df8d6055284569606841af5c71123a8 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -963,7 +963,7 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT
 	for (grmip = gpatch->mipmap; grmip->nextcolormap; )
 	{
 		grmip = grmip->nextcolormap;
-		if (grmip->colormap == colormap || grmip->tcindex == skinnum)
+		if (grmip->colormap == colormap || (skinnum < TC_DEFAULT && grmip->tcindex == skinnum))
 		{
 			if (grmip->downloaded && grmip->grInfo.data)
 			{
diff --git a/src/i_system.h b/src/i_system.h
index 831acda3225692a43804c76b4f54db92967813b9..2532ba0ee3827f5d089bfced6ea84852e242f814 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -184,10 +184,6 @@ void I_StartupMouse(void);
 */
 void I_StartupMouse2(void);
 
-/**	\brief keyboard startup, shutdown, handler
-*/
-void I_StartupKeyboard(void);
-
 /**	\brief  setup timer irq and user timer routine.
 */
 void I_StartupTimer(void);
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index b35bb6a41cbaddd7cd09d3095f1e60accf4ac87a..8ad66d04f4b26a67cd94302da4357899e0326587 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -2071,6 +2071,12 @@ static int mapheaderinfo_get(lua_State *L)
 		lua_pushinteger(L, header->levelselect);
 	else if (fastcmp(field,"bonustype"))
 		lua_pushinteger(L, header->bonustype);
+	else if (fastcmp(field,"ltzzpatch"))
+		lua_pushstring(L, header->ltzzpatch);
+	else if (fastcmp(field,"ltzztext"))
+		lua_pushstring(L, header->ltzztext);
+	else if (fastcmp(field,"ltactdiamond"))
+		lua_pushstring(L, header->ltactdiamond);
 	else if (fastcmp(field,"maxbonuslives"))
 		lua_pushinteger(L, header->maxbonuslives);
 	else if (fastcmp(field,"levelflags"))
diff --git a/src/m_anigif.c b/src/m_anigif.c
index 56d3c87075fc7a8779e04c46a9467d5fb70df6d1..f761db143b0cb016ee0395e9f045c8cdeb49d444 100644
--- a/src/m_anigif.c
+++ b/src/m_anigif.c
@@ -20,6 +20,10 @@
 #include "i_video.h"
 #include "m_misc.h"
 
+#ifdef HWRENDER
+#include "hardware/hw_main.h"
+#endif
+
 // GIFs are always little-endian
 #include "byteptr.h"
 
@@ -29,6 +33,7 @@ consvar_t cv_gif_downscale =  {"gif_downscale", "On", CV_SAVE, CV_OnOff, NULL, 0
 #ifdef HAVE_ANIGIF
 static boolean gif_optimize = false; // So nobody can do something dumb
 static boolean gif_downscale = false; // like changing cvars mid output
+static RGBA_t *gif_palette = NULL;
 
 static FILE *gif_out = NULL;
 static INT32 gif_frames = 0;
@@ -428,10 +433,7 @@ static void GIF_headwrite(void)
 
 	// write color table
 	{
-		RGBA_t *pal = ((cv_screenshot_colorprofile.value)
-		? pLocalPalette
-		: pMasterPalette);
-
+		RGBA_t *pal = gif_palette;
 		for (i = 0; i < 256; i++)
 		{
 			WRITEUINT8(p, pal[i].s.red);
@@ -457,6 +459,32 @@ const UINT8 gifframe_gchead[4] = {0x21,0xF9,0x04,0x04}; // GCE, bytes, packed by
 static UINT8 *gifframe_data = NULL;
 static size_t gifframe_size = 8192;
 
+#ifdef HWRENDER
+static void hwrconvert(void)
+{
+	UINT8 *linear = HWR_GetScreenshot();
+	UINT8 *dest = screens[2];
+	UINT8 r, g, b;
+	INT32 x, y;
+	size_t i = 0;
+
+	InitColorLUT(gif_palette);
+
+	for (y = 0; y < vid.height; y++)
+	{
+		for (x = 0; x < vid.width; x++, i += 3)
+		{
+			r = (UINT8)linear[i];
+			g = (UINT8)linear[i + 1];
+			b = (UINT8)linear[i + 2];
+			dest[(y * vid.width) + x] = colorlookup[r >> SHIFTCOLORBITS][g >> SHIFTCOLORBITS][b >> SHIFTCOLORBITS];
+		}
+	}
+
+	free(linear);
+}
+#endif
+
 //
 // GIF_framewrite
 // writes a frame into the file.
@@ -482,7 +510,12 @@ static void GIF_framewrite(void)
 		GIF_optimizeregion(cur_screen, movie_screen, &blitx, &blity, &blitw, &blith);
 
 		// blit to temp screen
-		I_ReadScreen(movie_screen);
+		if (rendermode == render_soft)
+			I_ReadScreen(movie_screen);
+#ifdef HWRENDER
+		else if (rendermode == render_opengl)
+			hwrconvert();
+#endif
 	}
 	else
 	{
@@ -491,7 +524,18 @@ static void GIF_framewrite(void)
 		blith = vid.height;
 
 		if (gif_frames == 0)
-			I_ReadScreen(movie_screen);
+		{
+			if (rendermode == render_soft)
+				I_ReadScreen(movie_screen);
+#ifdef HWRENDER
+			else if (rendermode == render_opengl)
+			{
+				hwrconvert();
+				VID_BlitLinearScreen(screens[2], screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
+			}
+#endif
+		}
+
 		movie_screen = screens[0];
 	}
 
@@ -580,7 +624,7 @@ static void GIF_framewrite(void)
 //
 INT32 GIF_open(const char *filename)
 {
-#ifdef HWRENDER
+#if 0
 	if (rendermode != render_soft)
 	{
 		CONS_Alert(CONS_WARNING, M_GetText("GIFs cannot be taken in non-software modes!\n"));
@@ -594,6 +638,16 @@ INT32 GIF_open(const char *filename)
 
 	gif_optimize = (!!cv_gif_optimize.value);
 	gif_downscale = (!!cv_gif_downscale.value);
+
+	// GIF color table
+	// In hardware mode, uses the master palette
+	gif_palette = ((cv_screenshot_colorprofile.value
+#ifdef HWRENDER
+	&& (rendermode == render_soft)
+#endif
+	) ? pLocalPalette
+	: pMasterPalette);
+
 	GIF_headwrite();
 	gif_frames = 0;
 	return 1;
diff --git a/src/m_menu.c b/src/m_menu.c
index afc3aab0dcb1702301ef8452e7cfda51e4caa154..7614048099de24b36605aef10a2d8183645df9a7 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1309,18 +1309,18 @@ static menuitem_t OP_OpenGLOptionsMenu[] =
 	{IT_STRING|IT_CVAR,         NULL, "Model lighting",      &cv_grmodellighting, 32},
 
 	{IT_HEADER, NULL, "General", NULL, 51},
-	{IT_STRING|IT_CVAR,         NULL, "Field of view",   &cv_grfov,            62},
-	{IT_STRING|IT_CVAR,         NULL, "Quality",         &cv_scr_depth,        72},
-	{IT_STRING|IT_CVAR,         NULL, "Texture Filter",  &cv_grfiltermode,     82},
-	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",     &cv_granisotropicmode,92},
+	{IT_STRING|IT_CVAR,         NULL, "Field of view",   &cv_grfov,            63},
+	{IT_STRING|IT_CVAR,         NULL, "Quality",         &cv_scr_depth,        73},
+	{IT_STRING|IT_CVAR,         NULL, "Texture Filter",  &cv_grfiltermode,     83},
+	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",     &cv_granisotropicmode,93},
 
-	{IT_HEADER, NULL, "Miscellaneous", NULL, 111},
-	{IT_SUBMENU|IT_STRING,      NULL, "Fog...",          &OP_OpenGLFogDef,          123},
+	{IT_HEADER, NULL, "Miscellaneous", NULL, 112},
+	{IT_SUBMENU|IT_STRING,      NULL, "Fog...",          &OP_OpenGLFogDef,          124},
 #ifdef ALAM_LIGHTING
-	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",     &OP_OpenGLLightingDef,     133},
+	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",     &OP_OpenGLLightingDef,     134},
 #endif
 #if defined (_WINDOWS) && (!((defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)))
-	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",      &cv_fullscreen,            143},
+	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",      &cv_fullscreen,            144},
 #endif
 };
 
diff --git a/src/m_misc.c b/src/m_misc.c
index d973833857a339c4aa61589348aa473c959c0d3d..c8383d3fe1d55df99cdc05bd274bc93824940029 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -1161,12 +1161,8 @@ void M_StartMovie(void)
 	switch (cv_moviemode.value)
 	{
 		case MM_GIF:
-			if (rendermode == render_soft)
-			{
-				moviemode = M_StartMovieGIF(pathname);
-				break;
-			}
-			/* FALLTHRU */
+			moviemode = M_StartMovieGIF(pathname);
+			break;
 		case MM_APNG:
 			moviemode = M_StartMovieAPNG(pathname);
 			break;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index d841ede0542729c85e84ebd169fa6f688580519e..62b696ef28d0a36fac94dd0a6a9e0640c42a8305 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -11121,6 +11121,19 @@ void P_PrecipitationEffects(void)
 	}
 }
 
+/** Returns corresponding mobj type from mapthing number.
+ * \param mthingtype Mapthing number in question.
+ * \return Mobj type; MT_UNKNOWN if nothing found.
+ */
+static mobjtype_t P_GetMobjtype(UINT16 mthingtype)
+{
+	mobjtype_t i;
+	for (i = 0; i < NUMMOBJTYPES; i++)
+		if (mthingtype == mobjinfo[i].doomednum)
+			return i;
+	return MT_UNKNOWN;
+}
+
 //
 // P_RespawnSpecials
 //
@@ -11159,17 +11172,12 @@ void P_RespawnSpecials(void)
 
 	if (mthing)
 	{
-		mobjtype_t i;
+		mobjtype_t i = P_GetMobjtype(mthing->type);
 		x = mthing->x << FRACBITS;
 		y = mthing->y << FRACBITS;
 		ss = R_PointInSubsector(x, y);
 
-		// find which type to spawn
-		for (i = 0; i < NUMMOBJTYPES; i++)
-			if (mthing->type == mobjinfo[i].doomednum)
-				break;
-
-		if (i == NUMMOBJTYPES) // prevent creation of objects with this type -- Monster Iestyn 17/12/17
+		if (i == MT_UNKNOWN) // prevent creation of objects with this type -- Monster Iestyn 17/12/17
 		{
 			// 3D Mode start Thing is unlikely to be added to the que,
 			// so don't bother checking for that specific type
@@ -11593,6 +11601,13 @@ static fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const mapthing_t*
 			offset = 288*FRACUNIT;
 		break;
 
+	// Horizontal springs, may float additional units with MTF_AMBUSH.
+	case MT_YELLOWHORIZ:
+	case MT_REDHORIZ:
+	case MT_BLUEHORIZ:
+		offset += mthing->options & MTF_AMBUSH ? 16*FRACUNIT : 0;
+		break;
+
 	// Ring-like items, may float additional units with MTF_AMBUSH.
 	case MT_SPIKEBALL:
 	case MT_EMERALDSPAWN:
@@ -11638,42 +11653,19 @@ static fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const mapthing_t*
 			ss->sector->floorheight) + offset;
 }
 
-//
-// P_SpawnMapThing
-// The fields of the mapthing should
-// already be in host byte order.
-//
-void P_SpawnMapThing(mapthing_t *mthing)
+static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing)
 {
-	mobjtype_t i;
-	mobj_t *mobj;
-	fixed_t x, y, z;
-	boolean doangle = true;
-
-	if (!mthing->type)
-		return; // Ignore type-0 things as NOPs
-
-	// Always spawn in objectplace.
-	// Skip all returning code.
-	if (objectplacing)
+#if MAXPLAYERS > 32
+	You should think about modifying the deathmatch starts to take full advantage of this!
+#endif
+	if (mthing->type <= MAXPLAYERS) // Player starts
 	{
-		// find which type to spawn
-		for (i = 0; i < NUMMOBJTYPES; i++)
-			if (mthing->type == mobjinfo[i].doomednum)
-				break;
-
-		if (i == NUMMOBJTYPES)
-		{
-			if (mthing->type == 3328) // 3D Mode start Thing
-				return;
-			CONS_Alert(CONS_WARNING, M_GetText("Unknown thing type %d placed at (%d, %d)\n"), mthing->type, mthing->x, mthing->y);
-			i = MT_UNKNOWN;
-		}
-		goto noreturns;
+		// save spots for respawning in network games
+		if (!metalrecording)
+			playerstarts[mthing->type - 1] = mthing;
+		return true;
 	}
-
-	// count deathmatch start positions
-	if (mthing->type == 33)
+	else if (mthing->type == 33) // Match starts
 	{
 		if (numdmstarts < MAX_DM_STARTS)
 		{
@@ -11681,10 +11673,9 @@ void P_SpawnMapThing(mapthing_t *mthing)
 			mthing->type = 0;
 			numdmstarts++;
 		}
-		return;
+		return true;
 	}
-
-	else if (mthing->type == 34) // Red CTF Starts
+	else if (mthing->type == 34) // Red CTF starts
 	{
 		if (numredctfstarts < MAXPLAYERS)
 		{
@@ -11692,10 +11683,9 @@ void P_SpawnMapThing(mapthing_t *mthing)
 			mthing->type = 0;
 			numredctfstarts++;
 		}
-		return;
+		return true;
 	}
-
-	else if (mthing->type == 35) // Blue CTF Starts
+	else if (mthing->type == 35) // Blue CTF starts
 	{
 		if (numbluectfstarts < MAXPLAYERS)
 		{
@@ -11703,99 +11693,154 @@ void P_SpawnMapThing(mapthing_t *mthing)
 			mthing->type = 0;
 			numbluectfstarts++;
 		}
-		return;
-	}
-
-	else if (mthing->type == 750) // Slope vertex point (formerly chaos spawn)
-		return;
-
-	else if (mthing->type == mobjinfo[MT_RING].doomednum || mthing->type == mobjinfo[MT_COIN].doomednum
-	 || mthing->type == mobjinfo[MT_REDTEAMRING].doomednum || mthing->type == mobjinfo[MT_BLUETEAMRING].doomednum
-	 || mthing->type == mobjinfo[MT_BLUESPHERE].doomednum || mthing->type == mobjinfo[MT_BOMBSPHERE].doomednum
-	 || (mthing->type >= 600 && mthing->type <= 609) // circles and diagonals
-	 || mthing->type == 1705 || mthing->type == 1713) // hoops
-	{
-		// Don't spawn hoops, wings, or rings yet!
-		return;
-	}
-
-	// check for players specially
-#if MAXPLAYERS > 32
-You should think about modifying the deathmatch starts to take full advantage of this!
-#endif
-	if (mthing->type > 0 && mthing->type <= MAXPLAYERS)
-	{
-		// save spots for respawning in network games
-		if (!metalrecording)
-			playerstarts[mthing->type-1] = mthing;
-		return;
+		return true;
 	}
-
-	if (metalrecording && mthing->type == mobjinfo[MT_METALSONIC_RACE].doomednum)
+	else if (metalrecording && mthing->type == mobjinfo[MT_METALSONIC_RACE].doomednum)
 	{ // If recording, you ARE Metal Sonic. Do not spawn it, do not save normal spawnpoints.
 		playerstarts[0] = mthing;
-		return;
-	}
-
-	// find which type to spawn
-	for (i = 0; i < NUMMOBJTYPES; i++)
-		if (mthing->type == mobjinfo[i].doomednum)
-			break;
-
-	if (i == NUMMOBJTYPES)
-	{
-		if (mthing->type == 3328) // 3D Mode start Thing
-			return;
-		CONS_Alert(CONS_WARNING, M_GetText("Unknown thing type %d placed at (%d, %d)\n"), mthing->type, mthing->x, mthing->y);
-		i = MT_UNKNOWN;
+		return true;
 	}
+	else if (mthing->type == 750 // Slope vertex point (formerly chaos spawn)
+		     || (mthing->type >= 600 && mthing->type <= 609) // Special placement patterns
+		     || mthing->type == 1705 || mthing->type == 1713) // Hoops
+		return true; // These are handled elsewhere.
 
-	if (metalrecording) // Metal Sonic can't use these things.
-		if (mobjinfo[i].flags & (MF_ENEMY|MF_BOSS) || i == MT_TOKEN || i == MT_STARPOST)
-			return;
+	return false;
+}
 
-	if (i >= MT_EMERALD1 && i <= MT_EMERALD7) // Pickupable Emeralds
+static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
+{
+	switch (i)
 	{
+	case MT_RING:
+	case MT_COIN:
+	case MT_REDTEAMRING:
+	case MT_BLUETEAMRING:
+	case MT_BLUESPHERE:
+	case MT_BOMBSPHERE:
+	case MT_NIGHTSSTAR:
+	case MT_NIGHTSCHIP:
+		return false; // These are handled in P_SpawnHoopsAndRings().
+	case MT_EMERALD1:
+	case MT_EMERALD2:
+	case MT_EMERALD3:
+	case MT_EMERALD4:
+	case MT_EMERALD5:
+	case MT_EMERALD6:
+	case MT_EMERALD7:
 		if (gametype != GT_COOP) // Don't place emeralds in non-coop modes
-			return;
+			return false;
 
 		if (metalrecording)
-			return; // Metal Sonic isn't for collecting emeralds.
+			return false; // Metal Sonic isn't for collecting emeralds.
 
 		if (emeralds & mobjinfo[i].speed) // You already have this emerald!
-			return;
-	}
+			return false;
 
-	if (i == MT_EMERHUNT)
-	{
+		break;
+	case MT_EMERHUNT:
 		// Emerald Hunt is Coop only.
 		if (!(gametyperules & GTR_EMERALDHUNT))
-			return;
+			return false;
 
 		if (numhuntemeralds < MAXHUNTEMERALDS)
 			huntemeralds[numhuntemeralds++] = mthing;
-		return;
-	}
-
-	if (i == MT_EMERALDSPAWN)
-	{
+		return false;
+	case MT_EMERALDSPAWN:
 		if (!cv_powerstones.value)
-			return;
+			return false;
 
 		if (!(gametyperules & GTR_MATCHEMERALDS))
-			return;
+			return false;
 
 		runemeraldmanager = true;
+		break;
+	case MT_ROSY:
+		if (!(gametype == GT_COOP || (mthing->options & MTF_EXTRA)))
+			return false; // she doesn't hang out here
+
+		if (!mariomode && !(netgame || multiplayer) && players[consoleplayer].skin == 3)
+			return false; // no doubles
+
+		break;
+	case MT_TOKEN:
+		if (!(gametyperules & GTR_EMERALDTOKENS))
+			return false; // Gametype's not right
+
+		if (tokenbits == 30)
+			return false; // Too many tokens
+
+		if (tokenlist & (1 << tokenbits++))
+			return false; // You already got this token
+
+		break;
+	case MT_EMBLEM:
+		if (netgame || multiplayer)
+			return false; // Single player
+
+		if (modifiedgame && !savemoddata)
+			return false; // No cheating!!
+
+		break;
+	default:
+		break;
 	}
 
-	if (!(gametyperules & GTR_SPAWNENEMIES)) // No enemies in match or CTF modes
-		if ((mobjinfo[i].flags & MF_ENEMY) || (mobjinfo[i].flags & MF_BOSS))
-			return;
+	if (metalrecording) // Metal Sonic can't use these things.
+		if (mobjinfo[i].flags & (MF_ENEMY|MF_BOSS) || i == MT_TOKEN || i == MT_STARPOST)
+			return false;
+
+	if (((mobjinfo[i].flags & MF_ENEMY) || (mobjinfo[i].flags & MF_BOSS)) && !(gametyperules & GTR_SPAWNENEMIES))
+		return false; // No enemies in ringslinger modes
+
+	if (!(gametyperules & GTR_ALLOWEXIT) && (i == MT_SIGN))
+		return false; // Don't spawn exit signs in wrong game modes
+
+	if (!G_PlatformGametype() && (i == MT_STARPOST))
+		return false; // Don't spawn starposts in wrong game modes
 
 	if (!G_RingSlingerGametype() || !cv_specialrings.value)
 		if (P_WeaponOrPanel(i))
-			return; // Don't place weapons/panels in non-ringslinger modes
+			return false; // Don't place weapons/panels in non-ringslinger modes
+
+	if (gametype != GT_CTF) // CTF specific things
+	{
+		if (i == MT_BLUEFLAG || i == MT_REDFLAG)
+			return false; // No flags in non-CTF modes!
+	}
+	else
+	{
+		if ((i == MT_BLUEFLAG && blueflag) || (i == MT_REDFLAG && redflag))
+		{
+			CONS_Alert(CONS_ERROR, M_GetText("Only one flag per team allowed in CTF!\n"));
+			return false;
+		}
+	}
+
+	if (modeattacking) // Record Attack special stuff
+	{
+		// Don't spawn starposts that wouldn't be usable
+		if (i == MT_STARPOST)
+			return false;
+	}
+
+	if (ultimatemode)
+	{
+		if (i == MT_PITY_BOX || i == MT_ELEMENTAL_BOX || i == MT_ATTRACT_BOX
+			|| i == MT_FORCE_BOX || i == MT_ARMAGEDDON_BOX || i == MT_WHIRLWIND_BOX
+			|| i == MT_FLAMEAURA_BOX || i == MT_BUBBLEWRAP_BOX || i == MT_THUNDERCOIN_BOX
+			|| i == MT_RING_BOX || i == MT_STARPOST)
+			return false; // No rings or shields in Ultimate mode
+
+		// Don't include the gold repeating boxes here please.
+		// They're likely facets of the level's design and therefore required to progress.
+	}
+
+	return true;
+}
 
+static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i)
+{
 	// Altering monitor spawns via cvars
 	// If MF_GRENADEBOUNCE is set in the monitor's info,
 	// skip this step. (Used for gold monitors)
@@ -11805,182 +11850,684 @@ You should think about modifying the deathmatch starts to take full advantage of
 		if (gametyperules & GTR_RACE)
 		{
 			// Set powerup boxes to user settings for competition.
-			if (cv_competitionboxes.value == 1) // Mystery
-				i = MT_MYSTERY_BOX;
-			else if (cv_competitionboxes.value == 2) // Teleport
-				i = MT_MIXUP_BOX;
-			else if (cv_competitionboxes.value == 3) // None
-				return; // Don't spawn!
-			// default case: normal
+			switch (cv_competitionboxes.value)
+			{
+			case 1: // Mystery
+				return MT_MYSTERY_BOX;
+			case 2: // Teleport
+				return MT_MIXUP_BOX;
+			case 3: // None
+				return MT_NULL; // Don't spawn!
+			default:
+				return i;
+			}
 		}
 		// Set powerup boxes to user settings for other netplay modes
 		else if (gametype != GT_COOP)
 		{
-			if (cv_matchboxes.value == 1) // Mystery
-				i = MT_MYSTERY_BOX;
-			else if (cv_matchboxes.value == 2) // Unchanging
+			switch (cv_matchboxes.value)
 			{
+			case 1: // Mystery
+				return MT_MYSTERY_BOX;
+			case 2: // Unchanging
 				if (i == MT_MYSTERY_BOX)
-					return; // don't spawn
+					return MT_NULL; // don't spawn
 				mthing->options &= ~(MTF_AMBUSH|MTF_OBJECTSPECIAL); // no random respawning!
+				return i;
+			case 3: // Don't spawn
+				return MT_NULL;
+			default:
+				return i;
 			}
-			else if (cv_matchboxes.value == 3) // Don't spawn
-				return;
-			// default case: normal
 		}
 	}
 
-	if (!(gametyperules & GTR_TEAMFLAGS)) // CTF specific things
-	{
-		if (i == MT_RING_BLUEBOX || i == MT_RING_REDBOX)
-			i = MT_RING_BOX;
-		else if (i == MT_BLUEFLAG || i == MT_REDFLAG)
-			return; // No flags in non-CTF modes!
-	}
-	else
+	if (!(gametyperules & GTR_TEAMS) && (i == MT_RING_BLUEBOX || i == MT_RING_REDBOX))
+		return MT_RING_BOX;
+
+	if (modeattacking && i == MT_1UP_BOX) // 1UPs -->> Score TVs
 	{
-		if ((i == MT_BLUEFLAG && blueflag) || (i == MT_REDFLAG && redflag))
-		{
-			CONS_Alert(CONS_ERROR, M_GetText("Only one flag per team allowed in CTF!\n"));
-			return;
-		}
+		// Either or, doesn't matter which.
+		if (mthing->options & (MTF_AMBUSH | MTF_OBJECTSPECIAL))
+			return MT_SCORE10K_BOX; // 10,000
+		else
+			return MT_SCORE1K_BOX; // 1,000
 	}
 
-	if (!(gametyperules & GTR_ALLOWEXIT) && i == MT_SIGN)
-		return; // Don't spawn exit signs without the necessary gametype rule
-	if (!G_PlatformGametype() && i == MT_STARPOST)
-		return; // Don't spawn starposts in wrong game modes
+	if (mariomode && i == MT_ROSY)
+		return MT_TOAD; // don't remove on penalty of death
 
-	if (modeattacking) // Record Attack special stuff
-	{
-		// Don't spawn starposts that wouldn't be usable
-		if (i == MT_STARPOST)
-			return;
+	return i;
+}
 
-		// 1UPs -->> Score TVs
-		else if (i == MT_1UP_BOX) // 1UP
-		{
-			// Either or, doesn't matter which.
-			if (mthing->options & (MTF_AMBUSH|MTF_OBJECTSPECIAL))
-				i = MT_SCORE10K_BOX; // 10,000
-			else
-				i = MT_SCORE1K_BOX; // 1,000
-		}
-	}
+static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
+{
+	INT32 j;
+	emblem_t* emblem = M_GetLevelEmblems(gamemap);
+	skincolors_t emcolor;
 
-	if (ultimatemode)
+	while (emblem)
 	{
-		if (i == MT_PITY_BOX || i == MT_ELEMENTAL_BOX || i == MT_ATTRACT_BOX
-		 || i == MT_FORCE_BOX || i == MT_ARMAGEDDON_BOX || i == MT_WHIRLWIND_BOX
-		 || i == MT_FLAMEAURA_BOX || i == MT_BUBBLEWRAP_BOX || i == MT_THUNDERCOIN_BOX
-		 || i == MT_RING_BOX || i == MT_STARPOST)
-			return; // No rings or shields in Ultimate mode
+		if ((emblem->type == ET_GLOBAL || emblem->type == ET_SKIN) && emblem->tag == mthing->angle)
+			break;
 
-		// Don't include the gold repeating boxes here please.
-		// They're likely facets of the level's design and therefore required to progress.
+		emblem = M_GetLevelEmblems(-1);
 	}
 
-	if (i == MT_ROSY)
+	if (!emblem)
 	{
-		if (!(gametype == GT_COOP || (mthing->options & MTF_EXTRA)))
-			return; // she doesn't hang out here
-		else if (mariomode)
-			i = MT_TOAD; // don't remove on penalty of death
-		else if (!(netgame || multiplayer) && players[consoleplayer].skin == 3)
-			return; // no doubles
+		CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, mthing->angle);
+		return false;
 	}
 
-	if (i == MT_TOKEN && (!(gametyperules & GTR_EMERALDTOKENS) || tokenbits == 30 || tokenlist & (1 << tokenbits++)))
-		return; // you already got this token, or there are too many, or the gametype's not right
-
-	if (i == MT_EMBLEM && (netgame || multiplayer || (modifiedgame && !savemoddata))) // No cheating!!
-		return;
-
-	// Objectplace landing point
-	noreturns:
+	j = emblem - emblemlocations;
 
-	// spawn it
-	x = mthing->x << FRACBITS;
-	y = mthing->y << FRACBITS;
-	z = P_GetMobjSpawnHeight(i, mthing, x, y);
+	I_Assert(emblemlocations[j].sprite >= 'A' && emblemlocations[j].sprite <= 'Z');
+	P_SetMobjState(mobj, mobj->info->spawnstate + (emblemlocations[j].sprite - 'A'));
 
-	mobj = P_SpawnMobj(x, y, z, i);
-	mobj->spawnpoint = mthing;
+	mobj->health = j + 1;
+	emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting
+	mobj->color = (UINT8)emcolor;
 
-#ifdef HAVE_BLUA
-	if (LUAh_MapThingSpawn(mobj, mthing))
+	if (emblemlocations[j].collected
+		|| (emblemlocations[j].type == ET_SKIN && emblemlocations[j].var != players[0].skin))
 	{
-		if (P_MobjWasRemoved(mobj))
-			return;
+		P_UnsetThingPosition(mobj);
+		mobj->flags |= MF_NOCLIP;
+		mobj->flags &= ~MF_SPECIAL;
+		mobj->flags |= MF_NOBLOCKMAP;
+		mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
+		P_SetThingPosition(mobj);
 	}
-	else if (P_MobjWasRemoved(mobj))
-		return;
 	else
-#endif
-	switch(mobj->type)
 	{
-	case MT_EMBLEM:
-	{
-		INT32 j;
-		emblem_t *emblem = M_GetLevelEmblems(gamemap);
-		skincolors_t emcolor;
-
-		while (emblem)
-		{
-			if ((emblem->type == ET_GLOBAL || emblem->type == ET_SKIN) && emblem->tag == mthing->angle)
-				break;
-
-			emblem = M_GetLevelEmblems(-1);
-		}
+		mobj->frame &= ~FF_TRANSMASK;
 
-		if (!emblem)
+		if (emblemlocations[j].type == ET_GLOBAL)
 		{
-			CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, mthing->angle);
-			break;
+			mobj->reactiontime = emblemlocations[j].var;
+			if (emblemlocations[j].var & GE_NIGHTSITEM)
+			{
+				mobj->flags |= MF_NIGHTSITEM;
+				mobj->flags &= ~MF_SPECIAL;
+				mobj->flags2 |= MF2_DONTDRAW;
+			}
 		}
+	}
+	return true;
+}
 
-		j = emblem - emblemlocations;
-
-		I_Assert(emblemlocations[j].sprite >= 'A' && emblemlocations[j].sprite <= 'Z');
-		P_SetMobjState(mobj, mobj->info->spawnstate + (emblemlocations[j].sprite - 'A'));
+static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
+{
+	fixed_t mlength, mmaxlength, mlengthset, mspeed, mphase, myaw, mpitch, mminlength, mnumspokes, mpinch, mroll, mnumnospokes, mwidth, mwidthset, mmin, msound, radiusfactor, widthfactor;
+	angle_t mspokeangle;
+	mobjtype_t chainlink, macetype, firsttype, linktype;
+	boolean mdosound, mdocenter, mchainlike = false;
+	mobj_t *spawnee = NULL, *hprev = mobj;
+	mobjflag_t mflagsapply;
+	mobjflag2_t mflags2apply;
+	mobjeflag_t meflagsapply;
+	INT32 line;
+	const size_t mthingi = (size_t)(mthing - mapthings);
+
+	// Find the corresponding linedef special, using angle as tag
+	// P_FindSpecialLineFromTag works here now =D
+	line = P_FindSpecialLineFromTag(9, mthing->angle, -1);
+
+	if (line == -1)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "Mace chain (mapthing #%s) needs to be tagged to a #9 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
+		return false;
+	}
+	/*
+	mapthing -
+	MTF_AMBUSH :
+		MT_SPRINGBALLPOINT - upgrade from yellow to red spring
+		anything else - bigger mace/chain theory
+	MTF_OBJECTSPECIAL - force silent
+	MTF_GRAVFLIP - flips objects, doesn't affect chain arrangements
+	Parameter value : number of "spokes"
+
+	linedef -
+	ML_NOCLIMB :
+		MT_CHAINPOINT/MT_CHAINMACEPOINT with ML_EFFECT1 applied - Direction not controllable
+		anything else - no functionality
+	ML_EFFECT1 : Swings instead of spins
+	ML_EFFECT2 : Linktype is replaced with macetype for all spokes not ending in chains (inverted for MT_FIREBARPOINT)
+	ML_EFFECT3 : Spawn a bonus linktype at the hinge point
+	ML_EFFECT4 : Don't clip inside the ground
+	ML_EFFECT5 : Don't stop thinking when too far away
+	*/
+	mlength = abs(lines[line].dx >> FRACBITS);
+	mspeed = abs(lines[line].dy >> (FRACBITS - 4));
+	mphase = (sides[lines[line].sidenum[0]].textureoffset >> FRACBITS) % 360;
+	if ((mminlength = -sides[lines[line].sidenum[0]].rowoffset >> FRACBITS) < 0)
+		mminlength = 0;
+	else if (mminlength > mlength - 1)
+		mminlength = mlength - 1;
+	mpitch = (lines[line].frontsector->floorheight >> FRACBITS) % 360;
+	myaw = (lines[line].frontsector->ceilingheight >> FRACBITS) % 360;
+
+	mnumspokes = mthing->extrainfo + 1;
+	mspokeangle = FixedAngle((360*FRACUNIT)/mnumspokes) >> ANGLETOFINESHIFT;
+
+	if (lines[line].backsector)
+	{
+		mpinch = (lines[line].backsector->floorheight >> FRACBITS) % 360;
+		mroll = (lines[line].backsector->ceilingheight >> FRACBITS) % 360;
+		mnumnospokes = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS);
+		if ((mwidth = sides[lines[line].sidenum[1]].rowoffset >> FRACBITS) < 0)
+			mwidth = 0;
+	}
+	else
+		mpinch = mroll = mnumnospokes = mwidth = 0;
+
+	CONS_Debug(DBG_GAMELOGIC, "Mace/Chain (mapthing #%s):\n"
+		"Length is %d (minus %d)\n"
+		"Speed is %d\n"
+		"Phase is %d\n"
+		"Yaw is %d\n"
+		"Pitch is %d\n"
+		"No. of spokes is %d (%d antispokes)\n"
+		"Pinch is %d\n"
+		"Roll is %d\n"
+		"Width is %d\n",
+		sizeu1(mthingi), mlength, mminlength, mspeed, mphase, myaw, mpitch, mnumspokes, mnumnospokes, mpinch, mroll, mwidth);
+
+	if (mnumnospokes > 0 && (mnumnospokes < mnumspokes))
+		mnumnospokes = mnumspokes/mnumnospokes;
+	else
+		mnumnospokes = ((mobj->type == MT_CHAINMACEPOINT) ? (mnumspokes) : 0);
 
-		mobj->health = j + 1;
-		emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting
-		mobj->color = (UINT8)emcolor;
+	mobj->lastlook = mspeed;
+	mobj->movecount = mobj->lastlook;
+	mobj->angle = FixedAngle(myaw << FRACBITS);
+	*doangle = false;
+	mobj->threshold = (FixedAngle(mpitch << FRACBITS) >> ANGLETOFINESHIFT);
+	mobj->movefactor = mpinch;
+	mobj->movedir = 0;
 
-		if (emblemlocations[j].collected
-			|| (emblemlocations[j].type == ET_SKIN && emblemlocations[j].var != players[0].skin))
+	// Mobjtype selection
+	switch (mobj->type)
+	{
+	case MT_SPRINGBALLPOINT:
+		macetype = ((mthing->options & MTF_AMBUSH)
+			? MT_REDSPRINGBALL
+			: MT_YELLOWSPRINGBALL);
+		chainlink = MT_SMALLMACECHAIN;
+		break;
+	case MT_FIREBARPOINT:
+		macetype = ((mthing->options & MTF_AMBUSH)
+			? MT_BIGFIREBAR
+			: MT_SMALLFIREBAR);
+		chainlink = MT_NULL;
+		break;
+	case MT_CUSTOMMACEPOINT:
+		macetype = (mobjtype_t)sides[lines[line].sidenum[0]].toptexture;
+		if (lines[line].backsector)
+			chainlink = (mobjtype_t)sides[lines[line].sidenum[1]].toptexture;
+		else
+			chainlink = MT_NULL;
+		break;
+	case MT_CHAINPOINT:
+		if (mthing->options & MTF_AMBUSH)
 		{
-			P_UnsetThingPosition(mobj);
-			mobj->flags |= MF_NOCLIP;
-			mobj->flags &= ~MF_SPECIAL;
-			mobj->flags |= MF_NOBLOCKMAP;
-			mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
-			P_SetThingPosition(mobj);
+			macetype = MT_BIGGRABCHAIN;
+			chainlink = MT_BIGMACECHAIN;
 		}
 		else
 		{
-			mobj->frame &= ~FF_TRANSMASK;
-
-			if (emblemlocations[j].type == ET_GLOBAL)
-			{
-				mobj->reactiontime = emblemlocations[j].var;
-				if (emblemlocations[j].var & GE_NIGHTSITEM)
-				{
-					mobj->flags |= MF_NIGHTSITEM;
-					mobj->flags &= ~MF_SPECIAL;
-					mobj->flags2 |= MF2_DONTDRAW;
-				}
-			}
+			macetype = MT_SMALLGRABCHAIN;
+			chainlink = MT_SMALLMACECHAIN;
 		}
+		mchainlike = true;
 		break;
-	}
-	case MT_SKYBOX:
-		if (mthing->options & MTF_OBJECTSPECIAL)
-			skyboxcenterpnts[mthing->extrainfo] = mobj;
+	default:
+		if (mthing->options & MTF_AMBUSH)
+		{
+			macetype = MT_BIGMACE;
+			chainlink = MT_BIGMACECHAIN;
+		}
 		else
-			skyboxviewpnts[mthing->extrainfo] = mobj;
-		break;
+		{
+			macetype = MT_SMALLMACE;
+			chainlink = MT_SMALLMACECHAIN;
+		}
+		break;
+	}
+
+	if (!macetype && !chainlink)
+		return true;
+
+	if (mobj->type == MT_CHAINPOINT)
+	{
+		if (!mlength)
+			return true;
+	}
+	else
+		mlength++;
+
+	firsttype = macetype;
+
+	// Adjustable direction
+	if (lines[line].flags & ML_NOCLIMB)
+		mobj->flags |= MF_SLIDEME;
+
+	// Swinging
+	if (lines[line].flags & ML_EFFECT1)
+	{
+		mobj->flags2 |= MF2_STRONGBOX;
+		mmin = ((mnumnospokes > 1) ? 1 : 0);
+	}
+	else
+		mmin = mnumspokes;
+
+	// If over distance away, don't move UNLESS this flag is applied
+	if (lines[line].flags & ML_EFFECT5)
+		mobj->flags2 |= MF2_BOSSNOTRAP;
+
+	// Make the links the same type as the end - repeated below
+	if ((mobj->type != MT_CHAINPOINT) && (((lines[line].flags & ML_EFFECT2) == ML_EFFECT2) != (mobj->type == MT_FIREBARPOINT))) // exclusive or
+	{
+		linktype = macetype;
+		radiusfactor = 2; // Double the radius.
+	}
+	else
+		radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
+
+	if (!mchainlike)
+		mchainlike = (firsttype == chainlink);
+	widthfactor = (mchainlike ? 1 : 2);
+
+	mflagsapply = ((lines[line].flags & ML_EFFECT4) ? 0 : (MF_NOCLIP | MF_NOCLIPHEIGHT));
+	mflags2apply = ((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0);
+	meflagsapply = ((mthing->options & MTF_OBJECTFLIP) ? MFE_VERTICALFLIP : 0);
+
+	msound = (mchainlike ? 0 : (mwidth & 1));
+
+	// Quick and easy preparatory variable setting
+	mphase = (FixedAngle(mphase << FRACBITS) >> ANGLETOFINESHIFT);
+	mroll = (FixedAngle(mroll << FRACBITS) >> ANGLETOFINESHIFT);
+
+#define makemace(mobjtype, dist, moreflags2) {\
+	spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
+	P_SetTarget(&spawnee->tracer, mobj);\
+	spawnee->threshold = mphase;\
+	spawnee->friction = mroll;\
+	spawnee->movefactor = mwidthset;\
+	spawnee->movecount = dist;\
+	spawnee->angle = myaw;\
+	spawnee->flags |= (MF_NOGRAVITY|mflagsapply);\
+	spawnee->flags2 |= (mflags2apply|moreflags2);\
+	spawnee->eflags |= meflagsapply;\
+	P_SetTarget(&hprev->hnext, spawnee);\
+	P_SetTarget(&spawnee->hprev, hprev);\
+	hprev = spawnee;\
+}
+
+	mdosound = (mspeed && !(mthing->options & MTF_OBJECTSPECIAL));
+	mdocenter = (macetype && (lines[line].flags & ML_EFFECT3));
+
+	// The actual spawning of spokes
+	while (mnumspokes-- > 0)
+	{
+		// Offsets
+		if (lines[line].flags & ML_EFFECT1) // Swinging
+			mroll = (mroll - mspokeangle) & FINEMASK;
+		else // Spinning
+			mphase = (mphase - mspokeangle) & FINEMASK;
+
+		if (mnumnospokes && !(mnumspokes % mnumnospokes)) // Skipping a "missing" spoke
+		{
+			if (mobj->type != MT_CHAINMACEPOINT)
+				continue;
+
+			linktype = chainlink;
+			firsttype = ((mthing->options & MTF_AMBUSH) ? MT_BIGGRABCHAIN : MT_SMALLGRABCHAIN);
+			mmaxlength = 1 + (mlength - 1) * radiusfactor;
+			radiusfactor = widthfactor = 1;
+		}
+		else
+		{
+			if (mobj->type == MT_CHAINMACEPOINT)
+			{
+				// Make the links the same type as the end - repeated above
+				if (lines[line].flags & ML_EFFECT2)
+				{
+					linktype = macetype;
+					radiusfactor = 2;
+				}
+				else
+					radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
+
+				firsttype = macetype;
+				widthfactor = 2;
+			}
+
+			mmaxlength = mlength;
+		}
+
+		mwidthset = mwidth;
+		mlengthset = mminlength;
+
+		if (mdocenter) // Innermost link
+			makemace(linktype, 0, 0);
+
+		// Out from the center...
+		if (linktype)
+		{
+			while ((++mlengthset) < mmaxlength)
+				makemace(linktype, radiusfactor*mlengthset, 0);
+		}
+		else
+			mlengthset = mmaxlength;
+
+		// Outermost mace/link
+		if (firsttype)
+			makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
+
+		if (!mwidth)
+		{
+			if (mdosound && mnumspokes <= mmin) // Can it make a sound?
+				spawnee->flags2 |= MF2_BOSSNOTRAP;
+		}
+		else
+		{
+			// Across the bar!
+			if (!firsttype)
+				mwidthset = -mwidth;
+			else if (mwidth > 0)
+			{
+				while ((mwidthset -= widthfactor) > -mwidth)
+				{
+					makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
+					if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
+						spawnee->flags2 |= MF2_BOSSNOTRAP;
+				}
+			}
+			else
+			{
+				while ((mwidthset += widthfactor) < -mwidth)
+				{
+					makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
+					if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
+						spawnee->flags2 |= MF2_BOSSNOTRAP;
+				}
+			}
+			mwidth = -mwidth;
+
+			// Outermost mace/link again!
+			if (firsttype)
+				makemace(firsttype, radiusfactor*(mlengthset--), MF2_AMBUSH);
+
+			// ...and then back into the center!
+			if (linktype)
+				while (mlengthset > mminlength)
+					makemace(linktype, radiusfactor*(mlengthset--), 0);
+
+			if (mdocenter) // Innermost link
+				makemace(linktype, 0, 0);
+		}
+	}
+#undef makemace
+	return true;
+}
+
+static boolean P_SetupParticleGen(mapthing_t *mthing, mobj_t *mobj)
+{
+	fixed_t radius, speed;
+	INT32 type, numdivisions, anglespeed, ticcount;
+	angle_t angledivision;
+	INT32 line;
+	const size_t mthingi = (size_t)(mthing - mapthings);
+
+	// Find the corresponding linedef special, using angle as tag
+	line = P_FindSpecialLineFromTag(15, mthing->angle, -1);
+
+	if (line == -1)
+	{
+		CONS_Debug(DBG_GAMELOGIC, "Particle generator (mapthing #%s) needs to be tagged to a #15 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
+		return false;
+	}
+
+	if (sides[lines[line].sidenum[0]].toptexture)
+		type = sides[lines[line].sidenum[0]].toptexture; // Set as object type in p_setup.c...
+	else
+		type = (INT32)MT_PARTICLE;
+
+	if (!lines[line].backsector
+		|| (ticcount = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS)) < 1)
+		ticcount = 3;
+
+	numdivisions = (mthing->options >> ZSHIFT);
+
+	if (numdivisions)
+	{
+		radius = R_PointToDist2(lines[line].v1->x, lines[line].v1->y, lines[line].v2->x, lines[line].v2->y);
+		anglespeed = (sides[lines[line].sidenum[0]].rowoffset >> FRACBITS) % 360;
+		angledivision = 360/numdivisions;
+	}
+	else
+	{
+		numdivisions = 1; // Simple trick to make A_ParticleSpawn simpler.
+		radius = 0;
+		anglespeed = 0;
+		angledivision = 0;
+	}
+
+	speed = abs(sides[lines[line].sidenum[0]].textureoffset);
+	if (mthing->options & MTF_OBJECTFLIP)
+		speed *= -1;
+
+	CONS_Debug(DBG_GAMELOGIC, "Particle Generator (mapthing #%s):\n"
+		"Radius is %d\n"
+		"Speed is %d\n"
+		"Anglespeed is %d\n"
+		"Numdivisions is %d\n"
+		"Angledivision is %d\n"
+		"Type is %d\n"
+		"Tic seperation is %d\n",
+		sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, type, ticcount);
+
+	mobj->angle = 0;
+	mobj->movefactor = speed;
+	mobj->lastlook = numdivisions;
+	mobj->movedir = angledivision*ANG1;
+	mobj->movecount = anglespeed*ANG1;
+	mobj->friction = radius;
+	mobj->threshold = type;
+	mobj->reactiontime = ticcount;
+	mobj->cvmem = line;
+	mobj->watertop = mobj->waterbottom = 0;
+	return true;
+}
+
+static boolean P_SetupNiGHTSDrone(mapthing_t* mthing, mobj_t* mobj)
+{
+	boolean flip = mthing->options & MTF_OBJECTFLIP;
+	boolean topaligned = (mthing->options & MTF_OBJECTSPECIAL) && !(mthing->options & MTF_EXTRA);
+	boolean middlealigned = (mthing->options & MTF_EXTRA) && !(mthing->options & MTF_OBJECTSPECIAL);
+	boolean bottomoffsetted = !(mthing->options & MTF_OBJECTSPECIAL) && !(mthing->options & MTF_EXTRA);
+
+	INT16 timelimit = mthing->angle & 0xFFF;
+	fixed_t hitboxradius = ((mthing->angle & 0xF000) >> 12)*32*FRACUNIT;
+	fixed_t hitboxheight = mthing->extrainfo*32*FRACUNIT;
+	fixed_t oldheight = mobj->height;
+	fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
+
+	if (timelimit > 0)
+		mobj->health = timelimit;
+
+	if (hitboxradius > 0)
+		mobj->radius = hitboxradius;
+
+	if (hitboxheight > 0)
+		mobj->height = hitboxheight;
+	else
+		mobj->height = mobjinfo[MT_NIGHTSDRONE].height;
+
+	droneboxmandiff = max(mobj->height - mobjinfo[MT_NIGHTSDRONE_MAN].height, 0);
+	dronemangoaldiff = max(mobjinfo[MT_NIGHTSDRONE_MAN].height - mobjinfo[MT_NIGHTSDRONE_GOAL].height, 0);
+
+	if (flip && mobj->height != oldheight)
+		P_TeleportMove(mobj, mobj->x, mobj->y, mobj->z - (mobj->height - oldheight));
+
+	if (!flip)
+	{
+		if (topaligned) // Align droneman to top of hitbox
+		{
+			dronemanoffset = droneboxmandiff;
+			goaloffset = dronemangoaldiff/2 + dronemanoffset;
+		}
+		else if (middlealigned) // Align droneman to center of hitbox
+		{
+			dronemanoffset = droneboxmandiff/2;
+			goaloffset = dronemangoaldiff/2 + dronemanoffset;
+		}
+		else if (bottomoffsetted)
+		{
+			dronemanoffset = 24*FRACUNIT;
+			goaloffset = dronemangoaldiff + dronemanoffset;
+		}
+		else
+		{
+			dronemanoffset = 0;
+			goaloffset = dronemangoaldiff/2 + dronemanoffset;
+		}
+
+		sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
+	}
+	else
+	{
+		mobj->eflags |= MFE_VERTICALFLIP;
+		mobj->flags2 |= MF2_OBJECTFLIP;
+
+		if (topaligned) // Align droneman to top of hitbox
+		{
+			dronemanoffset = 0;
+			goaloffset = dronemangoaldiff/2 + dronemanoffset;
+		}
+		else if (middlealigned) // Align droneman to center of hitbox
+		{
+			dronemanoffset = droneboxmandiff/2;
+			goaloffset = dronemangoaldiff/2 + dronemanoffset;
+		}
+		else if (bottomoffsetted)
+		{
+			dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
+			goaloffset = dronemangoaldiff + dronemanoffset;
+		}
+		else
+		{
+			dronemanoffset = droneboxmandiff;
+			goaloffset = dronemangoaldiff/2 + dronemanoffset;
+		}
+
+		sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
+	}
+
+	// spawn visual elements
+	{
+		mobj_t* goalpost = P_SpawnMobjFromMobj(mobj, 0, 0, goaloffset, MT_NIGHTSDRONE_GOAL);
+		mobj_t* sparkle = P_SpawnMobjFromMobj(mobj, 0, 0, sparkleoffset, MT_NIGHTSDRONE_SPARKLING);
+		mobj_t* droneman = P_SpawnMobjFromMobj(mobj, 0, 0, dronemanoffset, MT_NIGHTSDRONE_MAN);
+
+		P_SetTarget(&mobj->target, goalpost);
+		P_SetTarget(&goalpost->target, sparkle);
+		P_SetTarget(&goalpost->tracer, droneman);
+
+		// correct Z position
+		if (flip)
+		{
+			P_TeleportMove(goalpost, goalpost->x, goalpost->y, mobj->z + goaloffset);
+			P_TeleportMove(sparkle, sparkle->x, sparkle->y, mobj->z + sparkleoffset);
+			P_TeleportMove(droneman, droneman->x, droneman->y, mobj->z + dronemanoffset);
+		}
+
+		// Remember position preference for later
+		mobj->flags &= ~(MF_SLIDEME|MF_GRENADEBOUNCE);
+		if (topaligned)
+			mobj->flags |= MF_SLIDEME;
+		else if (middlealigned)
+			mobj->flags |= MF_GRENADEBOUNCE;
+		else if (!bottomoffsetted)
+			mobj->flags |= MF_SLIDEME|MF_GRENADEBOUNCE;
+
+		// Remember old Z position and flags for correction detection
+		goalpost->movefactor = mobj->z;
+		goalpost->friction = mobj->height;
+		goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE);
+	}
+	return true;
+}
+
+static boolean P_SetupBooster(mapthing_t* mthing, mobj_t* mobj, boolean strong)
+{
+	angle_t angle = FixedAngle(mthing->angle << FRACBITS);
+	fixed_t x1 = FINECOSINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
+	fixed_t y1 = FINESINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
+	fixed_t x2 = FINECOSINE(((angle + ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
+	fixed_t y2 = FINESINE(((angle + ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
+	statenum_t facestate = strong ? S_REDBOOSTERSEG_FACE : S_YELLOWBOOSTERSEG_FACE;
+	statenum_t leftstate = strong ? S_REDBOOSTERSEG_LEFT : S_YELLOWBOOSTERSEG_LEFT;
+	statenum_t rightstate = strong ? S_REDBOOSTERSEG_RIGHT : S_YELLOWBOOSTERSEG_RIGHT;
+	statenum_t rollerstate = strong ? S_REDBOOSTERROLLER : S_YELLOWBOOSTERROLLER;
+
+	mobj_t *seg = P_SpawnMobjFromMobj(mobj, 26*x1, 26*y1, 0, MT_BOOSTERSEG);
+	seg->angle = angle - ANGLE_90;
+	P_SetMobjState(seg, facestate);
+	seg = P_SpawnMobjFromMobj(mobj, -26*x1, -26*y1, 0, MT_BOOSTERSEG);
+	seg->angle = angle + ANGLE_90;
+	P_SetMobjState(seg, facestate);
+	seg = P_SpawnMobjFromMobj(mobj, 21*x2, 21*y2, 0, MT_BOOSTERSEG);
+	seg->angle = angle;
+	P_SetMobjState(seg, leftstate);
+	seg = P_SpawnMobjFromMobj(mobj, -21*x2, -21*y2, 0, MT_BOOSTERSEG);
+	seg->angle = angle;
+	P_SetMobjState(seg, rightstate);
+
+	seg = P_SpawnMobjFromMobj(mobj, 13*(x1 + x2), 13*(y1 + y2), 0, MT_BOOSTERROLLER);
+	seg->angle = angle;
+	P_SetMobjState(seg, rollerstate);
+	seg = P_SpawnMobjFromMobj(mobj, 13*(x1 - x2), 13*(y1 - y2), 0, MT_BOOSTERROLLER);
+	seg->angle = angle;
+	P_SetMobjState(seg, rollerstate);
+	seg = P_SpawnMobjFromMobj(mobj, -13*(x1 + x2), -13*(y1 + y2), 0, MT_BOOSTERROLLER);
+	seg->angle = angle;
+	P_SetMobjState(seg, rollerstate);
+	seg = P_SpawnMobjFromMobj(mobj, -13*(x1 - x2), -13*(y1 - y2), 0, MT_BOOSTERROLLER);
+	seg->angle = angle;
+	P_SetMobjState(seg, rollerstate);
+
+	return true;
+}
+
+static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
+{
+#ifdef HAVE_BLUA
+	boolean override = LUAh_MapThingSpawn(mobj, mthing);
+
+	if (P_MobjWasRemoved(mobj))
+		return false;
+
+	if (override)
+		return true;
+#endif
+
+	switch (mobj->type)
+	{
+	case MT_EMBLEM:
+	{
+		if (!P_SetupEmblem(mthing, mobj))
+			return false;
+		break;
+	}
+	case MT_SKYBOX:
+		if (mthing->options & MTF_OBJECTSPECIAL)
+			skyboxcenterpnts[mthing->extrainfo] = mobj;
+		else
+			skyboxviewpnts[mthing->extrainfo] = mobj;
+		break;
 	case MT_EGGSTATUE:
 		if (mthing->options & MTF_EXTRA)
 		{
@@ -12006,7 +12553,7 @@ You should think about modifying the deathmatch starts to take full advantage of
 		if (mthing->angle)
 			mobj->health = mthing->angle;
 		else
-			mobj->health = FixedMul(mobj->subsector->sector->ceilingheight - mobj->subsector->sector->floorheight, 3*(FRACUNIT/4))>>FRACBITS;
+			mobj->health = FixedMul(mobj->subsector->sector->ceilingheight - mobj->subsector->sector->floorheight, 3*(FRACUNIT/4)) >> FRACBITS;
 		break;
 	case MT_METALSONIC_RACE:
 	case MT_METALSONIC_BATTLE:
@@ -12021,7 +12568,7 @@ You should think about modifying the deathmatch starts to take full advantage of
 		break;
 	case MT_BALLOON:
 		if (mthing->angle > 0)
-			mobj->color = ((mthing->angle-1) % (MAXSKINCOLORS-1))+1;
+			mobj->color = ((mthing->angle - 1) % (MAXSKINCOLORS - 1)) + 1;
 		break;
 #define makesoftwarecorona(mo, h) \
 			corona = P_SpawnMobjFromMobj(mo, 0, 0, h<<FRACBITS, MT_PARTICLE);\
@@ -12068,416 +12615,35 @@ You should think about modifying the deathmatch starts to take full advantage of
 		{
 			mobj_t *overlay = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY);
 			P_SetTarget(&overlay->target, mobj);
-			P_SetMobjState(overlay, mobj->info->raisestate);
-		}
-		break;
-	case MT_WATERDRIP:
-		if (mthing->angle)
-			mobj->tics = 3*TICRATE + mthing->angle;
-		else
-			mobj->tics = 3*TICRATE;
-		break;
-	case MT_FLAMEJET:
-	case MT_VERTICALFLAMEJET:
-		mobj->threshold = (mthing->angle >> 10) & 7;
-		mobj->movecount = (mthing->angle >> 13);
-
-		mobj->threshold *= (TICRATE/2);
-		mobj->movecount *= (TICRATE/2);
-
-		mobj->movedir = mthing->extrainfo;
-		break;
-	case MT_MACEPOINT:
-	case MT_CHAINMACEPOINT:
-	case MT_SPRINGBALLPOINT:
-	case MT_CHAINPOINT:
-	case MT_FIREBARPOINT:
-	case MT_CUSTOMMACEPOINT:
-	{
-		fixed_t mlength, mmaxlength, mlengthset, mspeed, mphase, myaw, mpitch, mminlength, mnumspokes, mpinch, mroll, mnumnospokes, mwidth, mwidthset, mmin, msound, radiusfactor, widthfactor;
-		angle_t mspokeangle;
-		mobjtype_t chainlink, macetype, firsttype, linktype;
-		boolean mdosound, mdocenter, mchainlike = false;
-		mobj_t *spawnee = NULL, *hprev = mobj;
-		mobjflag_t mflagsapply;
-		mobjflag2_t mflags2apply;
-		mobjeflag_t meflagsapply;
-		INT32 line;
-		const size_t mthingi = (size_t)(mthing - mapthings);
-
-		// Find the corresponding linedef special, using angle as tag
-		// P_FindSpecialLineFromTag works here now =D
-		line = P_FindSpecialLineFromTag(9, mthing->angle, -1);
-
-		if (line == -1)
-		{
-			CONS_Debug(DBG_GAMELOGIC, "Mace chain (mapthing #%s) needs to be tagged to a #9 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
-			return;
-		}
-/*
-mapthing -
-MTF_AMBUSH :
-	MT_SPRINGBALLPOINT - upgrade from yellow to red spring
-	anything else - bigger mace/chain theory
-MTF_OBJECTSPECIAL - force silent
-MTF_GRAVFLIP - flips objects, doesn't affect chain arrangements
-Parameter value : number of "spokes"
-
-linedef -
-ML_NOCLIMB :
-	MT_CHAINPOINT/MT_CHAINMACEPOINT with ML_EFFECT1 applied - Direction not controllable
-	anything else - no functionality
-ML_EFFECT1 : Swings instead of spins
-ML_EFFECT2 : Linktype is replaced with macetype for all spokes not ending in chains (inverted for MT_FIREBARPOINT)
-ML_EFFECT3 : Spawn a bonus linktype at the hinge point
-ML_EFFECT4 : Don't clip inside the ground
-ML_EFFECT5 : Don't stop thinking when too far away
-*/
-		mlength = abs(lines[line].dx >> FRACBITS);
-		mspeed = abs(lines[line].dy >> (FRACBITS - 4));
-		mphase = (sides[lines[line].sidenum[0]].textureoffset >> FRACBITS) % 360;
-		if ((mminlength = -sides[lines[line].sidenum[0]].rowoffset>>FRACBITS) < 0)
-			mminlength = 0;
-		else if (mminlength > mlength-1)
-			mminlength = mlength-1;
-		mpitch = (lines[line].frontsector->floorheight >> FRACBITS) % 360;
-		myaw = (lines[line].frontsector->ceilingheight >> FRACBITS) % 360;
-
-		mnumspokes = mthing->extrainfo + 1;
-		mspokeangle = FixedAngle((360*FRACUNIT)/mnumspokes)>>ANGLETOFINESHIFT;
-
-		if (lines[line].backsector)
-		{
-			mpinch = (lines[line].backsector->floorheight >> FRACBITS) % 360;
-			mroll = (lines[line].backsector->ceilingheight >> FRACBITS) % 360;
-			mnumnospokes = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS);
-			if ((mwidth = sides[lines[line].sidenum[1]].rowoffset >> FRACBITS) < 0)
-				mwidth = 0;
-		}
-		else
-			mpinch = mroll = mnumnospokes = mwidth = 0;
-
-		CONS_Debug(DBG_GAMELOGIC, "Mace/Chain (mapthing #%s):\n"
-				"Length is %d (minus %d)\n"
-				"Speed is %d\n"
-				"Phase is %d\n"
-				"Yaw is %d\n"
-				"Pitch is %d\n"
-				"No. of spokes is %d (%d antispokes)\n"
-				"Pinch is %d\n"
-				"Roll is %d\n"
-				"Width is %d\n",
-				sizeu1(mthingi), mlength, mminlength, mspeed, mphase, myaw, mpitch, mnumspokes, mnumnospokes, mpinch, mroll, mwidth);
-
-		if (mnumnospokes > 0 && (mnumnospokes < mnumspokes))
-			mnumnospokes = mnumspokes/mnumnospokes;
-		else
-			mnumnospokes = ((mobj->type == MT_CHAINMACEPOINT) ? (mnumspokes) : 0);
-
-		mobj->lastlook = mspeed;
-		mobj->movecount = mobj->lastlook;
-		mobj->angle = FixedAngle(myaw*FRACUNIT);
-		doangle = false;
-		mobj->threshold = (FixedAngle(mpitch*FRACUNIT)>>ANGLETOFINESHIFT);
-		mobj->movefactor = mpinch;
-		mobj->movedir = 0;
-
-		// Mobjtype selection
-		switch(mobj->type)
-		{
-			case MT_SPRINGBALLPOINT:
-				macetype = ((mthing->options & MTF_AMBUSH)
-					? MT_REDSPRINGBALL
-					: MT_YELLOWSPRINGBALL);
-				chainlink = MT_SMALLMACECHAIN;
-				break;
-			case MT_FIREBARPOINT:
-				macetype = ((mthing->options & MTF_AMBUSH)
-						? MT_BIGFIREBAR
-						: MT_SMALLFIREBAR);
-				chainlink = MT_NULL;
-				break;
-			case MT_CUSTOMMACEPOINT:
-				macetype = (mobjtype_t)sides[lines[line].sidenum[0]].toptexture;
-				if (lines[line].backsector)
-					chainlink = (mobjtype_t)sides[lines[line].sidenum[1]].toptexture;
-				else
-					chainlink = MT_NULL;
-				break;
-			case MT_CHAINPOINT:
-				if (mthing->options & MTF_AMBUSH)
-				{
-					macetype = MT_BIGGRABCHAIN;
-					chainlink = MT_BIGMACECHAIN;
-				}
-				else
-				{
-					macetype = MT_SMALLGRABCHAIN;
-					chainlink = MT_SMALLMACECHAIN;
-				}
-				mchainlike = true;
-				break;
-			default:
-				if (mthing->options & MTF_AMBUSH)
-				{
-					macetype = MT_BIGMACE;
-					chainlink = MT_BIGMACECHAIN;
-				}
-				else
-				{
-					macetype = MT_SMALLMACE;
-					chainlink = MT_SMALLMACECHAIN;
-				}
-				break;
-		}
-
-		if (!macetype && !chainlink)
-			break;
-
-		if (mobj->type == MT_CHAINPOINT)
-		{
-			if (!mlength)
-				break;
-		}
-		else
-			mlength++;
-
-		firsttype = macetype;
-
-		// Adjustable direction
-		if (lines[line].flags & ML_NOCLIMB)
-			mobj->flags |= MF_SLIDEME;
-
-		// Swinging
-		if (lines[line].flags & ML_EFFECT1)
-		{
-			mobj->flags2 |= MF2_STRONGBOX;
-			mmin = ((mnumnospokes > 1) ? 1 : 0);
-		}
-		else
-			mmin = mnumspokes;
-
-		// If over distance away, don't move UNLESS this flag is applied
-		if (lines[line].flags & ML_EFFECT5)
-			mobj->flags2 |= MF2_BOSSNOTRAP;
-
-		// Make the links the same type as the end - repeated below
-		if ((mobj->type != MT_CHAINPOINT) && (((lines[line].flags & ML_EFFECT2) == ML_EFFECT2) != (mobj->type == MT_FIREBARPOINT))) // exclusive or
-		{
-			linktype = macetype;
-			radiusfactor = 2; // Double the radius.
-		}
-		else
-			radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
-
-		if (!mchainlike)
-			mchainlike = (firsttype == chainlink);
-		widthfactor = (mchainlike ? 1 : 2);
-
-		mflagsapply = ((lines[line].flags & ML_EFFECT4) ? 0 : (MF_NOCLIP|MF_NOCLIPHEIGHT));
-		mflags2apply = ((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0);
-		meflagsapply = ((mthing->options & MTF_OBJECTFLIP) ? MFE_VERTICALFLIP : 0);
-
-		msound = (mchainlike ? 0 : (mwidth & 1));
-
-		// Quick and easy preparatory variable setting
-		mphase = (FixedAngle(mphase*FRACUNIT)>>ANGLETOFINESHIFT);
-		mroll = (FixedAngle(mroll*FRACUNIT)>>ANGLETOFINESHIFT);
-
-#define makemace(mobjtype, dist, moreflags2) {\
-	spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
-	P_SetTarget(&spawnee->tracer, mobj);\
-	spawnee->threshold = mphase;\
-	spawnee->friction = mroll;\
-	spawnee->movefactor = mwidthset;\
-	spawnee->movecount = dist;\
-	spawnee->angle = myaw;\
-	spawnee->flags |= (MF_NOGRAVITY|mflagsapply);\
-	spawnee->flags2 |= (mflags2apply|moreflags2);\
-	spawnee->eflags |= meflagsapply;\
-	P_SetTarget(&hprev->hnext, spawnee);\
-	P_SetTarget(&spawnee->hprev, hprev);\
-	hprev = spawnee;\
-}
-
-		mdosound = (mspeed && !(mthing->options & MTF_OBJECTSPECIAL));
-		mdocenter = (macetype && (lines[line].flags & ML_EFFECT3));
-
-		// The actual spawning of spokes
-		while (mnumspokes-- > 0)
-		{
-			// Offsets
-			if (lines[line].flags & ML_EFFECT1) // Swinging
-				mroll = (mroll - mspokeangle) & FINEMASK;
-			else // Spinning
-				mphase = (mphase - mspokeangle) & FINEMASK;
-
-			if (mnumnospokes && !(mnumspokes % mnumnospokes)) // Skipping a "missing" spoke
-			{
-				if (mobj->type != MT_CHAINMACEPOINT)
-					continue;
-
-				linktype = chainlink;
-				firsttype = ((mthing->options & MTF_AMBUSH) ? MT_BIGGRABCHAIN : MT_SMALLGRABCHAIN);
-				mmaxlength = 1 + (mlength - 1)*radiusfactor;
-				radiusfactor = widthfactor = 1;
-			}
-			else
-			{
-				if (mobj->type == MT_CHAINMACEPOINT)
-				{
-					// Make the links the same type as the end - repeated above
-					if (lines[line].flags & ML_EFFECT2)
-					{
-						linktype = macetype;
-						radiusfactor = 2;
-					}
-					else
-						radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
-
-					firsttype = macetype;
-					widthfactor = 2;
-				}
-
-				mmaxlength = mlength;
-			}
-
-			mwidthset = mwidth;
-			mlengthset = mminlength;
-
-			if (mdocenter) // Innermost link
-				makemace(linktype, 0, 0);
-
-			// Out from the center...
-			if (linktype)
-			{
-				while ((++mlengthset) < mmaxlength)
-					makemace(linktype, radiusfactor*mlengthset, 0);
-			}
-			else
-				mlengthset = mmaxlength;
-
-			// Outermost mace/link
-			if (firsttype)
-				makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
-
-			if (!mwidth)
-			{
-				if (mdosound && mnumspokes <= mmin) // Can it make a sound?
-					spawnee->flags2 |= MF2_BOSSNOTRAP;
-			}
-			else
-			{
-				// Across the bar!
-				if (!firsttype)
-					mwidthset = -mwidth;
-				else if (mwidth > 0)
-				{
-					while ((mwidthset -= widthfactor) > -mwidth)
-					{
-						makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
-						if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
-							spawnee->flags2 |= MF2_BOSSNOTRAP;
-					}
-				}
-				else
-				{
-					while ((mwidthset += widthfactor) < -mwidth)
-					{
-						makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
-						if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
-							spawnee->flags2 |= MF2_BOSSNOTRAP;
-					}
-				}
-				mwidth = -mwidth;
-
-				// Outermost mace/link again!
-				if (firsttype)
-					makemace(firsttype, radiusfactor*(mlengthset--), MF2_AMBUSH);
-
-				// ...and then back into the center!
-				if (linktype)
-					while (mlengthset > mminlength)
-						makemace(linktype, radiusfactor*(mlengthset--), 0);
-
-				if (mdocenter) // Innermost link
-					makemace(linktype, 0, 0);
-			}
-		}
-
-#undef makemace
-
-		break;
-	}
-	case MT_PARTICLEGEN:
-	{
-		fixed_t radius, speed;
-		INT32 type, numdivisions, anglespeed, ticcount;
-		angle_t angledivision;
-		INT32 line;
-		const size_t mthingi = (size_t)(mthing - mapthings);
-
-		// Find the corresponding linedef special, using angle as tag
-		line = P_FindSpecialLineFromTag(15, mthing->angle, -1);
-
-		if (line == -1)
-		{
-			CONS_Debug(DBG_GAMELOGIC, "Particle generator (mapthing #%s) needs to be tagged to a #15 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
-			return;
-		}
-
-		if (sides[lines[line].sidenum[0]].toptexture)
-			type = sides[lines[line].sidenum[0]].toptexture; // Set as object type in p_setup.c...
-		else
-			type = (INT32)MT_PARTICLE;
-
-		if (!lines[line].backsector
-		|| (ticcount = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS)) < 1)
-			ticcount = 3;
-
-		numdivisions = (mthing->options >> ZSHIFT);
-
-		if (numdivisions)
-		{
-			radius = R_PointToDist2(lines[line].v1->x, lines[line].v1->y, lines[line].v2->x, lines[line].v2->y);
-			anglespeed = (sides[lines[line].sidenum[0]].rowoffset >> FRACBITS) % 360;
-			angledivision = 360/numdivisions;
-		}
-		else
-		{
-			numdivisions = 1; // Simple trick to make A_ParticleSpawn simpler.
-			radius = 0;
-			anglespeed = 0;
-			angledivision = 0;
+			P_SetMobjState(overlay, mobj->info->raisestate);
 		}
+		break;
+	case MT_WATERDRIP:
+		mobj->tics = 3*TICRATE + mthing->angle;
+		break;
+	case MT_FLAMEJET:
+	case MT_VERTICALFLAMEJET:
+		mobj->threshold = (mthing->angle >> 10) & 7;
+		mobj->movecount = (mthing->angle >> 13);
 
-		speed = abs(sides[lines[line].sidenum[0]].textureoffset);
-		if (mthing->options & MTF_OBJECTFLIP)
-			speed *= -1;
-
-		CONS_Debug(DBG_GAMELOGIC, "Particle Generator (mapthing #%s):\n"
-				"Radius is %d\n"
-				"Speed is %d\n"
-				"Anglespeed is %d\n"
-				"Numdivisions is %d\n"
-				"Angledivision is %d\n"
-				"Type is %d\n"
-				"Tic seperation is %d\n",
-				sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, type, ticcount);
-
-		mobj->angle = 0;
-		mobj->movefactor = speed;
-		mobj->lastlook = numdivisions;
-		mobj->movedir = angledivision*ANG1;
-		mobj->movecount = anglespeed*ANG1;
-		mobj->friction = radius;
-		mobj->threshold = type;
-		mobj->reactiontime = ticcount;
-		mobj->cvmem = line;
-		mobj->watertop = mobj->waterbottom = 0;
+		mobj->threshold *= (TICRATE/2);
+		mobj->movecount *= (TICRATE/2);
 
+		mobj->movedir = mthing->extrainfo;
+		break;
+	case MT_MACEPOINT:
+	case MT_CHAINMACEPOINT:
+	case MT_SPRINGBALLPOINT:
+	case MT_CHAINPOINT:
+	case MT_FIREBARPOINT:
+	case MT_CUSTOMMACEPOINT:
+		if (!P_SetupMace(mthing, mobj, doangle))
+			return false;
+		break;
+	case MT_PARTICLEGEN:
+		if (!P_SetupParticleGen(mthing, mobj))
+			return false;
 		break;
-	}
 	case MT_ROCKSPAWNER:
 		mobj->threshold = mthing->angle;
 		mobj->movecount = mthing->extrainfo;
@@ -12492,7 +12658,7 @@ ML_EFFECT5 : Don't stop thinking when too far away
 		// Lower 4 bits specify the angle of
 		// the bumper in 30 degree increments.
 		mobj->threshold = (mthing->options & 15) % 12; // It loops over, etc
-		P_SetMobjState(mobj, mobj->info->spawnstate+mobj->threshold);
+		P_SetMobjState(mobj, mobj->info->spawnstate + mobj->threshold);
 		break;
 	case MT_EGGCAPSULE:
 		if (mthing->angle <= 0)
@@ -12509,122 +12675,8 @@ ML_EFFECT5 : Don't stop thinking when too far away
 		mobj->health = mthing->extrainfo;
 		break;
 	case MT_NIGHTSDRONE:
-		{
-			boolean flip = mthing->options & MTF_OBJECTFLIP;
-			boolean topaligned = (mthing->options & MTF_OBJECTSPECIAL) && !(mthing->options & MTF_EXTRA);
-			boolean middlealigned = (mthing->options & MTF_EXTRA) && !(mthing->options & MTF_OBJECTSPECIAL);
-			boolean bottomoffsetted = !(mthing->options & MTF_OBJECTSPECIAL) && !(mthing->options & MTF_EXTRA);
-
-			INT16 timelimit = mthing->angle & 0xFFF;
-			fixed_t hitboxradius = ((mthing->angle & 0xF000) >> 12) * 32 * FRACUNIT;
-			fixed_t hitboxheight = mthing->extrainfo * 32 * FRACUNIT;
-			fixed_t oldheight = mobj->height;
-			fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
-
-			if (timelimit > 0)
-				mobj->health = timelimit;
-
-			if (hitboxradius > 0)
-				mobj->radius = hitboxradius;
-
-			if (hitboxheight > 0)
-				mobj->height = hitboxheight;
-			else
-				mobj->height = mobjinfo[MT_NIGHTSDRONE].height;
-
-			droneboxmandiff = max(mobj->height - mobjinfo[MT_NIGHTSDRONE_MAN].height, 0);
-			dronemangoaldiff = max(mobjinfo[MT_NIGHTSDRONE_MAN].height - mobjinfo[MT_NIGHTSDRONE_GOAL].height, 0);
-
-			if (flip && mobj->height != oldheight)
-				P_TeleportMove(mobj, mobj->x, mobj->y, mobj->z - (mobj->height - oldheight));
-
-			if (!flip)
-			{
-				if (topaligned) // Align droneman to top of hitbox
-				{
-					dronemanoffset = droneboxmandiff;
-					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-				}
-				else if (middlealigned) // Align droneman to center of hitbox
-				{
-					dronemanoffset = droneboxmandiff / 2;
-					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-				}
-				else if (bottomoffsetted)
-				{
-					dronemanoffset = 24*FRACUNIT;
-					goaloffset = dronemangoaldiff + dronemanoffset;
-				}
-				else
-				{
-					dronemanoffset = 0;
-					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-				}
-
-				sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
-			}
-			else
-			{
-				mobj->eflags |= MFE_VERTICALFLIP;
-				mobj->flags2 |= MF2_OBJECTFLIP;
-
-				if (topaligned) // Align droneman to top of hitbox
-				{
-					dronemanoffset = 0;
-					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-				}
-				else if (middlealigned) // Align droneman to center of hitbox
-				{
-					dronemanoffset = droneboxmandiff / 2;
-					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-				}
-				else if (bottomoffsetted)
-				{
-					dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
-					goaloffset = dronemangoaldiff + dronemanoffset;
-				}
-				else
-				{
-					dronemanoffset = droneboxmandiff;
-					goaloffset = dronemangoaldiff / 2 + dronemanoffset;
-				}
-
-				sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
-			}
-
-			// spawn visual elements
-			{
-				mobj_t *goalpost = P_SpawnMobjFromMobj(mobj, 0, 0, goaloffset, MT_NIGHTSDRONE_GOAL);
-				mobj_t *sparkle = P_SpawnMobjFromMobj(mobj, 0, 0, sparkleoffset, MT_NIGHTSDRONE_SPARKLING);
-				mobj_t *droneman = P_SpawnMobjFromMobj(mobj, 0, 0, dronemanoffset, MT_NIGHTSDRONE_MAN);
-
-				P_SetTarget(&mobj->target, goalpost);
-				P_SetTarget(&goalpost->target, sparkle);
-				P_SetTarget(&goalpost->tracer, droneman);
-
-				// correct Z position
-				if (flip)
-				{
-					P_TeleportMove(goalpost, goalpost->x, goalpost->y, mobj->z + goaloffset);
-					P_TeleportMove(sparkle, sparkle->x, sparkle->y, mobj->z + sparkleoffset);
-					P_TeleportMove(droneman, droneman->x, droneman->y, mobj->z + dronemanoffset);
-				}
-
-				// Remember position preference for later
-				mobj->flags &= ~(MF_SLIDEME | MF_GRENADEBOUNCE);
-				if (topaligned)
-					mobj->flags |= MF_SLIDEME;
-				else if (middlealigned)
-					mobj->flags |= MF_GRENADEBOUNCE;
-				else if (!bottomoffsetted)
-					mobj->flags |= MF_SLIDEME | MF_GRENADEBOUNCE;
-
-				// Remember old Z position and flags for correction detection
-				goalpost->movefactor = mobj->z;
-				goalpost->friction = mobj->height;
-				goalpost->threshold = mobj->flags & (MF_SLIDEME | MF_GRENADEBOUNCE);
-			}
-		}
+		if (!P_SetupNiGHTSDrone(mthing, mobj))
+			return false;
 		break;
 	case MT_HIVEELEMENTAL:
 		if (mthing->extrainfo)
@@ -12635,7 +12687,7 @@ ML_EFFECT5 : Don't stop thinking when too far away
 	case MT_GLAREGOYLEDOWN:
 	case MT_GLAREGOYLELONG:
 		if (mthing->angle >= 360)
-			mobj->tics += 7*(mthing->angle / 360) + 1; // starting delay
+			mobj->tics += 7*(mthing->angle/360) + 1; // starting delay
 		break;
 	case MT_DSZSTALAGMITE:
 	case MT_DSZ2STALAGMITE:
@@ -12646,39 +12698,39 @@ ML_EFFECT5 : Don't stop thinking when too far away
 		}
 		break;
 	case MT_THZTREE:
-		{ // Spawn the branches
-			angle_t mobjangle = FixedAngle((mthing->angle % 113)<<FRACBITS);
-			P_SpawnMobjFromMobj(mobj, 1*FRACUNIT,  0,          0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_22h;
-			P_SpawnMobjFromMobj(mobj, 0,           1*FRACUNIT, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_157h;
-			P_SpawnMobjFromMobj(mobj, -1*FRACUNIT, 0,          0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_270;
-		}
-		break;
+	{ // Spawn the branches
+		angle_t mobjangle = FixedAngle((mthing->angle % 113) << FRACBITS);
+		P_SpawnMobjFromMobj(mobj, FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_22h;
+		P_SpawnMobjFromMobj(mobj, 0, FRACUNIT, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_157h;
+		P_SpawnMobjFromMobj(mobj, -FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_270;
+	}
+	break;
 	case MT_CEZPOLE1:
 	case MT_CEZPOLE2:
-		{ // Spawn the banner
-			angle_t mobjangle = FixedAngle(mthing->angle<<FRACBITS);
-			P_SpawnMobjFromMobj(mobj,
-				P_ReturnThrustX(mobj, mobjangle, 4<<FRACBITS),
-				P_ReturnThrustY(mobj, mobjangle, 4<<FRACBITS),
-				0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2))->angle = mobjangle + ANGLE_90;
-		}
-			break;
+	{ // Spawn the banner
+		angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS);
+		P_SpawnMobjFromMobj(mobj,
+			P_ReturnThrustX(mobj, mobjangle, 4 << FRACBITS),
+			P_ReturnThrustY(mobj, mobjangle, 4 << FRACBITS),
+			0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2))->angle = mobjangle + ANGLE_90;
+	}
+	break;
 	case MT_HHZTREE_TOP:
-		{ // Spawn the branches
-			angle_t mobjangle = FixedAngle(mthing->angle<<FRACBITS) & (ANGLE_90-1);
-			mobj_t *leaf;
+	{ // Spawn the branches
+		angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS) & (ANGLE_90 - 1);
+		mobj_t* leaf;
 #define doleaf(x, y) \
 			leaf = P_SpawnMobjFromMobj(mobj, x, y, 0, MT_HHZTREE_PART);\
 			leaf->angle = mobjangle;\
 			P_SetMobjState(leaf, leaf->info->seestate);\
 			mobjangle += ANGLE_90
-			doleaf(1*FRACUNIT, 0);
-			doleaf(0, 1*FRACUNIT);
-			doleaf(-1*FRACUNIT, 0);
-			doleaf(0, -1*FRACUNIT);
+		doleaf(FRACUNIT, 0);
+		doleaf(0, FRACUNIT);
+		doleaf(-FRACUNIT, 0);
+		doleaf(0, -FRACUNIT);
 #undef doleaf
-		}
-		break;
+	}
+	break;
 	case MT_SMASHINGSPIKEBALL:
 		if (mthing->angle > 0)
 			mobj->tics += mthing->angle;
@@ -12709,94 +12761,27 @@ ML_EFFECT5 : Don't stop thinking when too far away
 			angle_t fa = (angle >> ANGLETOFINESHIFT) & FINEMASK;
 			fixed_t xoffs = FINECOSINE(fa);
 			fixed_t yoffs = FINESINE(fa);
-			mobj_t *leaf = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, 0, MT_BIGFERNLEAF);
+			mobj_t* leaf = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, 0, MT_BIGFERNLEAF);
 			leaf->angle = angle;
 			angle += ANGLE_45;
 		}
 		break;
 	}
 	case MT_REDBOOSTER:
-	{
-		angle_t angle = FixedAngle(mthing->angle << FRACBITS);
-		fixed_t x1 = FINECOSINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
-		fixed_t y1 = FINESINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
-		fixed_t x2 = FINECOSINE(((angle+ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
-		fixed_t y2 = FINESINE(((angle+ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
-
-		mobj_t *seg = P_SpawnMobjFromMobj(mobj, 26*x1, 26*y1, 0, MT_BOOSTERSEG);
-		seg->angle = angle-ANGLE_90;
-		P_SetMobjState(seg, S_REDBOOSTERSEG_FACE);
-		seg = P_SpawnMobjFromMobj(mobj, -26*x1, -26*y1, 0, MT_BOOSTERSEG);
-		seg->angle = angle+ANGLE_90;
-		P_SetMobjState(seg, S_REDBOOSTERSEG_FACE);
-		seg = P_SpawnMobjFromMobj(mobj, 21*x2, 21*y2, 0, MT_BOOSTERSEG);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_REDBOOSTERSEG_LEFT);
-		seg = P_SpawnMobjFromMobj(mobj, -21*x2, -21*y2, 0, MT_BOOSTERSEG);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_REDBOOSTERSEG_RIGHT);
-
-		seg = P_SpawnMobjFromMobj(mobj, 13*(x1+x2), 13*(y1+y2), 0, MT_BOOSTERROLLER);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_REDBOOSTERROLLER);
-		seg = P_SpawnMobjFromMobj(mobj, 13*(x1-x2), 13*(y1-y2), 0, MT_BOOSTERROLLER);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_REDBOOSTERROLLER);
-		seg = P_SpawnMobjFromMobj(mobj, -13*(x1+x2), -13*(y1+y2), 0, MT_BOOSTERROLLER);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_REDBOOSTERROLLER);
-		seg = P_SpawnMobjFromMobj(mobj, -13*(x1-x2), -13*(y1-y2), 0, MT_BOOSTERROLLER);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_REDBOOSTERROLLER);
-		break;
-	}
 	case MT_YELLOWBOOSTER:
-	{
-		angle_t angle = FixedAngle(mthing->angle << FRACBITS);
-		fixed_t x1 = FINECOSINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
-		fixed_t y1 = FINESINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
-		fixed_t x2 = FINECOSINE(((angle+ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
-		fixed_t y2 = FINESINE(((angle+ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
-
-		mobj_t *seg = P_SpawnMobjFromMobj(mobj, 26*x1, 26*y1, 0, MT_BOOSTERSEG);
-		seg->angle = angle-ANGLE_90;
-		P_SetMobjState(seg, S_YELLOWBOOSTERSEG_FACE);
-		seg = P_SpawnMobjFromMobj(mobj, -26*x1, -26*y1, 0, MT_BOOSTERSEG);
-		seg->angle = angle+ANGLE_90;
-		P_SetMobjState(seg, S_YELLOWBOOSTERSEG_FACE);
-		seg = P_SpawnMobjFromMobj(mobj, 21*x2, 21*y2, 0, MT_BOOSTERSEG);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_YELLOWBOOSTERSEG_LEFT);
-		seg = P_SpawnMobjFromMobj(mobj, -21*x2, -21*y2, 0, MT_BOOSTERSEG);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_YELLOWBOOSTERSEG_RIGHT);
-
-		seg = P_SpawnMobjFromMobj(mobj, 13*(x1+x2), 13*(y1+y2), 0, MT_BOOSTERROLLER);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_YELLOWBOOSTERROLLER);
-		seg = P_SpawnMobjFromMobj(mobj, 13*(x1-x2), 13*(y1-y2), 0, MT_BOOSTERROLLER);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_YELLOWBOOSTERROLLER);
-		seg = P_SpawnMobjFromMobj(mobj, -13*(x1+x2), -13*(y1+y2), 0, MT_BOOSTERROLLER);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_YELLOWBOOSTERROLLER);
-		seg = P_SpawnMobjFromMobj(mobj, -13*(x1-x2), -13*(y1-y2), 0, MT_BOOSTERROLLER);
-		seg->angle = angle;
-		P_SetMobjState(seg, S_YELLOWBOOSTERROLLER);
-		break;
-	}
-	default:
+		if (!P_SetupBooster(mthing, mobj, mobj->type == MT_REDBOOSTER))
+			return false;
 		break;
-	}
-
-	if (mobj->flags & MF_BOSS)
-	{
-		if (mthing->options & MTF_OBJECTSPECIAL) // No egg trap for this boss
-			mobj->flags2 |= MF2_BOSSNOTRAP;
-	}
+	case MT_AXIS:
+		// Inverted if uppermost bit is set
+		if (mthing->angle & 16384)
+			mobj->flags2 |= MF2_AMBUSH;
 
-	if (i == MT_AXIS || i == MT_AXISTRANSFER || i == MT_AXISTRANSFERLINE) // Axis Points
-	{
+		if (mthing->angle > 0)
+			mobj->radius = (mthing->angle & 16383) << FRACBITS;
+		// FALLTHRU
+	case MT_AXISTRANSFER:
+	case MT_AXISTRANSFERLINE:
 		// Mare it belongs to
 		mobj->threshold = min(mthing->extrainfo, 7);
 
@@ -12804,38 +12789,29 @@ ML_EFFECT5 : Don't stop thinking when too far away
 		mobj->health = mthing->options;
 
 		mobj->flags2 |= MF2_AXIS;
-
-		if (i == MT_AXIS)
-		{
-			// Inverted if uppermost bit is set
-			if (mthing->angle & 16384)
-				mobj->flags2 |= MF2_AMBUSH;
-
-			if (mthing->angle > 0)
-				mobj->radius = (mthing->angle & 16383)*FRACUNIT;
-		}
-	}
-	else if (i == MT_TOKEN)
-	{
+		break;
+	case MT_TOKEN:
 		// We advanced tokenbits earlier due to the return check.
 		// Subtract 1 here for the correct value.
 		mobj->health = 1 << (tokenbits - 1);
-	}
-	else if (i == MT_CYBRAKDEMON && mthing->options & MTF_AMBUSH)
-	{
-		mobj_t *elecmobj;
-		elecmobj = P_SpawnMobj(x, y, z, MT_CYBRAKDEMON_ELECTRIC_BARRIER);
-		P_SetTarget(&elecmobj->target, mobj);
-		elecmobj->angle = FixedAngle(mthing->angle<<FRACBITS);;
-		elecmobj->destscale = mobj->scale*2;
-		P_SetScale(elecmobj, elecmobj->destscale);
-	}
-	else if (i == MT_STARPOST)
+		break;
+	case MT_CYBRAKDEMON:
+		if (mthing->options & MTF_AMBUSH)
+		{
+			mobj_t* elecmobj;
+			elecmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_CYBRAKDEMON_ELECTRIC_BARRIER);
+			P_SetTarget(&elecmobj->target, mobj);
+			elecmobj->angle = FixedAngle(mthing->angle << FRACBITS);
+			elecmobj->destscale = mobj->scale*2;
+			P_SetScale(elecmobj, elecmobj->destscale);
+		}
+		break;
+	case MT_STARPOST:
 	{
-		thinker_t *th;
-		mobj_t *mo2;
+		thinker_t* th;
+		mobj_t* mo2;
 		boolean foundanother = false;
-		mobj->health = (mthing->angle / 360) + 1;
+		mobj->health = (mthing->angle/360) + 1;
 
 		// See if other starposts exist in this level that have the same value.
 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
@@ -12843,7 +12819,7 @@ ML_EFFECT5 : Don't stop thinking when too far away
 			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 				continue;
 
-			mo2 = (mobj_t *)th;
+			mo2 = (mobj_t*)th;
 
 			if (mo2 == mobj)
 				continue;
@@ -12857,14 +12833,14 @@ ML_EFFECT5 : Don't stop thinking when too far away
 
 		if (!foundanother)
 			numstarposts++;
+		break;
 	}
-	else if (i == MT_SPIKE)
-	{
+	case MT_SPIKE:
 		// Pop up spikes!
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
 			mobj->flags &= ~MF_SCENERY;
-			mobj->fuse = (16 - mthing->extrainfo) * (mthing->angle + mobj->info->speed) / 16;
+			mobj->fuse = (16 - mthing->extrainfo)*(mthing->angle + mobj->info->speed)/16;
 			if (mthing->options & MTF_EXTRA)
 				P_SetMobjState(mobj, mobj->info->meleestate);
 		}
@@ -12876,14 +12852,13 @@ ML_EFFECT5 : Don't stop thinking when too far away
 			mobj->flags |= MF_SOLID;
 			P_SetThingPosition(mobj);
 		}
-	}
-	else if (i == MT_WALLSPIKE)
-	{
+		break;
+	case MT_WALLSPIKE:
 		// Pop up spikes!
 		if (mthing->options & MTF_OBJECTSPECIAL)
 		{
 			mobj->flags &= ~MF_SCENERY;
-			mobj->fuse = (16 - mthing->extrainfo) * ((mthing->angle/360) + mobj->info->speed) / 16;
+			mobj->fuse = (16 - mthing->extrainfo)*((mthing->angle/360) + mobj->info->speed)/16;
 			if (mthing->options & MTF_EXTRA)
 				P_SetMobjState(mobj, mobj->info->meleestate);
 		}
@@ -12891,64 +12866,51 @@ ML_EFFECT5 : Don't stop thinking when too far away
 		if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
 		{
 			P_UnsetThingPosition(mobj);
-			mobj->flags &= ~(MF_NOBLOCKMAP|MF_NOCLIPHEIGHT);
+			mobj->flags &= ~(MF_NOBLOCKMAP | MF_NOCLIPHEIGHT);
 			mobj->flags |= MF_SOLID;
 			P_SetThingPosition(mobj);
 		}
 
 		// spawn base
 		{
-			const angle_t mobjangle = FixedAngle(mthing->angle<<FRACBITS); // the mobj's own angle hasn't been set quite yet so...
+			const angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS); // the mobj's own angle hasn't been set quite yet so...
 			const fixed_t baseradius = mobj->radius - mobj->scale;
-			mobj_t *base = P_SpawnMobj(
-					mobj->x - P_ReturnThrustX(mobj, mobjangle, baseradius),
-					mobj->y - P_ReturnThrustY(mobj, mobjangle, baseradius),
-					mobj->z, MT_WALLSPIKEBASE);
+			mobj_t* base = P_SpawnMobj(
+				mobj->x - P_ReturnThrustX(mobj, mobjangle, baseradius),
+				mobj->y - P_ReturnThrustY(mobj, mobjangle, baseradius),
+				mobj->z, MT_WALLSPIKEBASE);
 			base->angle = mobjangle + ANGLE_90;
 			base->destscale = mobj->destscale;
 			P_SetScale(base, mobj->scale);
 			P_SetTarget(&base->target, mobj);
 			P_SetTarget(&mobj->tracer, base);
 		}
-	}
-
-	//count 10 ring boxes into the number of rings equation too.
-	if (i == MT_RING_BOX && nummaprings >= 0)
-		nummaprings += 10;
-
-	if (i == MT_BIGTUMBLEWEED || i == MT_LITTLETUMBLEWEED)
-	{
+		break;
+	case MT_RING_BOX:
+		//count 10 ring boxes into the number of rings equation too.
+		if (nummaprings >= 0)
+			nummaprings += 10;
+		break;
+	case MT_BIGTUMBLEWEED:
+	case MT_LITTLETUMBLEWEED:
 		if (mthing->options & MTF_AMBUSH)
 		{
-			mobj->momz += FixedMul(16*FRACUNIT, mobj->scale);
-
-			if (P_RandomChance(FRACUNIT/2))
-				mobj->momx += FixedMul(16*FRACUNIT, mobj->scale);
-			else
-				mobj->momx -= FixedMul(16*FRACUNIT, mobj->scale);
-
-			if (P_RandomChance(FRACUNIT/2))
-				mobj->momy += FixedMul(16*FRACUNIT, mobj->scale);
-			else
-				mobj->momy -= FixedMul(16*FRACUNIT,mobj->scale);
+			fixed_t offset = FixedMul(16*FRACUNIT, mobj->scale);
+			mobj->momx += P_RandomChance(FRACUNIT/2) ? offset : -offset;
+			mobj->momy += P_RandomChance(FRACUNIT/2) ? offset : -offset;
+			mobj->momz += offset;
 		}
-	}
-
-	// CTF flag pointers
-	if (i == MT_REDFLAG)
-	{
+		break;
+	case MT_REDFLAG:
 		redflag = mobj;
 		rflagpoint = mobj->spawnpoint;
-	}
-	if (i == MT_BLUEFLAG)
-	{
+		break;
+	case MT_BLUEFLAG:
 		blueflag = mobj;
 		bflagpoint = mobj->spawnpoint;
-	}
-
-	// special push/pull stuff
-	if (i == MT_PUSH || i == MT_PULL)
-	{
+		break;
+	case MT_PUSH:
+	case MT_PULL:
 		mobj->health = 0; // Default behaviour: pushing uses XY, fading uses XYZ
 
 		if (mthing->options & MTF_AMBUSH)
@@ -12957,23 +12919,129 @@ ML_EFFECT5 : Don't stop thinking when too far away
 			mobj->health |= 2; // If object special is set, fade using XY
 
 		if (G_IsSpecialStage(gamemap))
-		{
-			if (i == MT_PUSH)
-				P_SetMobjState(mobj, S_GRAVWELLGREEN);
-			if (i == MT_PULL)
-				P_SetMobjState(mobj, S_GRAVWELLRED);
-		}
+			P_SetMobjState(mobj, (mobj->type == MT_PUSH) ? S_GRAVWELLGREEN : S_GRAVWELLRED);
+		break;
+	default:
+		break;
+	}
+
+	if (mobj->flags & MF_BOSS)
+	{
+		if (mthing->options & MTF_OBJECTSPECIAL) // No egg trap for this boss
+			mobj->flags2 |= MF2_BOSSNOTRAP;
+	}
+
+	return true;
+}
+
+static void P_SetAmbush(mobj_t *mobj)
+{
+	if (mobj->type == MT_YELLOWDIAG || mobj->type == MT_REDDIAG || mobj->type == MT_BLUEDIAG)
+		mobj->angle += ANGLE_22h;
+
+	if (mobj->flags & MF_NIGHTSITEM)
+	{
+		// Spawn already displayed
+		mobj->flags |= MF_SPECIAL;
+		mobj->flags &= ~MF_NIGHTSITEM;
+	}
+
+	if (mobj->flags & MF_PUSHABLE)
+		mobj->flags &= ~MF_PUSHABLE;
+
+	if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
+	{
+		// flag for strong/weak random boxes
+		// any monitor with nonzero speed is allowed to respawn like this
+		mobj->flags2 |= MF2_AMBUSH;
+	}
+
+	else if (mobj->type != MT_AXIS &&
+		mobj->type != MT_AXISTRANSFER &&
+		mobj->type != MT_AXISTRANSFERLINE &&
+		mobj->type != MT_NIGHTSBUMPER &&
+		mobj->type != MT_STARPOST)
+		mobj->flags2 |= MF2_AMBUSH;
+}
+
+static void P_SetObjectSpecial(mobj_t *mobj)
+{
+	if (mobj->type == MT_YELLOWDIAG || mobj->type == MT_REDDIAG || mobj->type == MT_BLUEDIAG)
+		mobj->flags |= MF_NOGRAVITY;
+
+	if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
+	{
+		// flag for strong/weak random boxes
+		// any monitor with nonzero speed is allowed to respawn like this
+		mobj->flags2 |= MF2_STRONGBOX;
+	}
+
+	// Requires you to be in bonus time to activate
+	if (mobj->flags & MF_NIGHTSITEM)
+		mobj->flags2 |= MF2_STRONGBOX;
+
+	// Pushables bounce and slide coolly with object special flag set
+	if (mobj->flags & MF_PUSHABLE)
+	{
+		mobj->flags2 |= MF2_SLIDEPUSH;
+		mobj->flags |= MF_BOUNCE;
+	}
+}
+//
+// P_SpawnMapThing
+// The fields of the mapthing should
+// already be in host byte order.
+//
+void P_SpawnMapThing(mapthing_t *mthing)
+{
+	mobjtype_t i;
+	mobj_t *mobj;
+	fixed_t x, y, z;
+	boolean doangle = true;
+
+	if (!mthing->type)
+		return; // Ignore type-0 things as NOPs
+
+	if (mthing->type == 3328) // 3D Mode start Thing
+		return;
+
+	if (!objectplacing && P_SpawnNonMobjMapThing(mthing))
+		return;
+
+	i = P_GetMobjtype(mthing->type);
+	if (i == MT_UNKNOWN)
+		CONS_Alert(CONS_WARNING, M_GetText("Unknown thing type %d placed at (%d, %d)\n"), mthing->type, mthing->x, mthing->y);
+
+	// Skip all returning/substitution code in objectplace.
+	if (!objectplacing)
+	{
+		if (!P_AllowMobjSpawn(mthing, i))
+			return;
+
+		i = P_GetMobjtypeSubstitute(mthing, i);
+		if (i == MT_NULL) // Don't spawn mobj
+			return;
 	}
 
+	// spawn it
+	x = mthing->x << FRACBITS;
+	y = mthing->y << FRACBITS;
+	z = P_GetMobjSpawnHeight(i, mthing, x, y);
+
+	mobj = P_SpawnMobj(x, y, z, i);
+	mobj->spawnpoint = mthing;
+
+	if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle))
+		return;
+
 	if (doangle)
 		mobj->angle = FixedAngle(mthing->angle<<FRACBITS);
 
+	mthing->mobj = mobj;
+
 	// ignore MTF_ flags and return early
 	if (i == MT_NIGHTSBUMPER)
-	{
-		mthing->mobj = mobj;
 		return;
-	}
 
 	if ((mthing->options & MTF_AMBUSH)
 	&& (mthing->options & MTF_OBJECTSPECIAL)
@@ -12982,67 +13050,10 @@ ML_EFFECT5 : Don't stop thinking when too far away
 	else
 	{
 		if (mthing->options & MTF_AMBUSH)
-		{
-			if (i == MT_YELLOWDIAG || i == MT_REDDIAG || i == MT_BLUEDIAG)
-				mobj->angle += ANGLE_22h;
-
-			if (i == MT_YELLOWHORIZ || i == MT_REDHORIZ || i == MT_BLUEHORIZ)
-			{
-				if (mthing->options & MTF_OBJECTFLIP)
-					mobj->z -= 16*FRACUNIT;
-				else
-					mobj->z += 16*FRACUNIT;
-			}
-
-
-			if (mobj->flags & MF_NIGHTSITEM)
-			{
-				// Spawn already displayed
-				mobj->flags |= MF_SPECIAL;
-				mobj->flags &= ~MF_NIGHTSITEM;
-			}
-
-			if (mobj->flags & MF_PUSHABLE)
-				mobj->flags &= ~MF_PUSHABLE;
-
-			if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
-			{
-				// flag for strong/weak random boxes
-				// any monitor with nonzero speed is allowed to respawn like this
-				mobj->flags2 |= MF2_AMBUSH;
-			}
-
-			else if (mthing->type != mobjinfo[MT_AXIS].doomednum &&
-				mthing->type != mobjinfo[MT_AXISTRANSFER].doomednum &&
-				mthing->type != mobjinfo[MT_AXISTRANSFERLINE].doomednum &&
-				mthing->type != mobjinfo[MT_NIGHTSBUMPER].doomednum &&
-				mthing->type != mobjinfo[MT_STARPOST].doomednum)
-				mobj->flags2 |= MF2_AMBUSH;
-		}
+			P_SetAmbush(mobj);
 
 		if (mthing->options & MTF_OBJECTSPECIAL)
-		{
-			if (i == MT_YELLOWDIAG || i == MT_REDDIAG || i == MT_BLUEDIAG)
-				mobj->flags |= MF_NOGRAVITY;
-
-			if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
-			{
-				// flag for strong/weak random boxes
-				// any monitor with nonzero speed is allowed to respawn like this
-				mobj->flags2 |= MF2_STRONGBOX;
-			}
-
-			// Requires you to be in bonus time to activate
-			if (mobj->flags & MF_NIGHTSITEM)
-				mobj->flags2 |= MF2_STRONGBOX;
-
-			// Pushables bounce and slide coolly with object special flag set
-			if (mobj->flags & MF_PUSHABLE)
-			{
-				mobj->flags2 |= MF2_SLIDEPUSH;
-				mobj->flags |= MF_BOUNCE;
-			}
-		}
+			P_SetObjectSpecial(mobj);
 	}
 
 	// Generic reverse gravity for individual objects flag.
@@ -13065,8 +13076,6 @@ ML_EFFECT5 : Don't stop thinking when too far away
 	// Final set of not being able to draw nightsitems.
 	if (mobj->flags & MF_NIGHTSITEM)
 		mobj->flags2 |= MF2_DONTDRAW;
-
-	mthing->mobj = mobj;
 }
 
 static void P_SpawnHoop(mapthing_t* mthing, fixed_t x, fixed_t y, fixed_t z, sector_t* sec, INT32 hoopsize, fixed_t sizefactor)
@@ -13198,10 +13207,10 @@ static void P_SpawnRingItem(mapthing_t *mthing, fixed_t x, fixed_t y, boolean bo
 			ringthing = MT_NIGHTSSTAR;
 		else if (mthing->type == mobjinfo[MT_COIN].doomednum)
 			ringthing = MT_COIN;
-		else if (mthing->type == mobjinfo[MT_REDTEAMRING].doomednum) // No team rings in non-CTF
-			ringthing = (gametyperules & GTR_TEAMFLAGS) ? MT_REDTEAMRING : MT_RING;
+		else if (mthing->type == mobjinfo[MT_REDTEAMRING].doomednum) // No team rings in non-team gametypes
+			ringthing = (gametyperules & GTR_TEAMS) ? MT_REDTEAMRING : MT_RING;
 		else if (mthing->type == mobjinfo[MT_BLUETEAMRING].doomednum) // Ditto
-			ringthing = (gametyperules & GTR_TEAMFLAGS) ? MT_BLUETEAMRING : MT_RING;
+			ringthing = (gametyperules & GTR_TEAMS) ? MT_BLUETEAMRING : MT_RING;
 	}
 
 	z = P_GetMobjSpawnHeight(ringthing, mthing, x, y);
diff --git a/src/p_setup.c b/src/p_setup.c
index b8b82e7f3171445a66bae7f812930682031d0f83..2eee0be76061d005bd05640b8249017bbd588f8b 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -213,6 +213,9 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
 	mapheaderinfo[num]->lvlttl[0] = '\0';
 	mapheaderinfo[num]->selectheading[0] = '\0';
 	mapheaderinfo[num]->subttl[0] = '\0';
+	mapheaderinfo[num]->ltzzpatch[0] = '\0';
+	mapheaderinfo[num]->ltzztext[0] = '\0';
+	mapheaderinfo[num]->ltactdiamond[0] = '\0';
 	mapheaderinfo[num]->actnum = 0;
 	mapheaderinfo[num]->typeoflevel = 0;
 	mapheaderinfo[num]->nextlevel = (INT16)(i + 1);
diff --git a/src/r_data.c b/src/r_data.c
index 12e0702c10f184e7bbb0f3b8aea3168dcebbab37..cecd4840ccb4badf6d42434a6800ec3c7259d2bb 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -536,9 +536,7 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	}
 
 	// multi-patch textures (or 'composite')
-#ifndef NO_PNG_LUMPS
 	multipatch:
-#endif
 	texture->holes = false;
 	texture->flip = 0;
 	blocksize = (texture->width * 4) + (texture->width * texture->height);
diff --git a/src/r_data.h b/src/r_data.h
index 3dcb22ec4989e870cf72dc623f3494a902611aba..f028f2f5d16ef54da544d56780448adc84d8b858 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -171,6 +171,8 @@ const char *R_NameForColormap(extracolormap_t *extra_colormap);
 #define R_PutRgbaRGB(r, g, b) (R_PutRgbaR(r) + R_PutRgbaG(g) + R_PutRgbaB(b))
 #define R_PutRgbaRGBA(r, g, b, a) (R_PutRgbaRGB(r, g, b) + R_PutRgbaA(a))
 
+UINT8 NearestColor(UINT8 r, UINT8 g, UINT8 b);
+
 extern INT32 numtextures;
 
 #endif
diff --git a/src/r_plane.c b/src/r_plane.c
index 3214fa23d8c3a0cadc96b0322e0b696b55b7c736..5d5e1f20df6899bddabe214991567c8681cb41e4 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -788,6 +788,8 @@ static UINT8 *R_GetTextureFlat(levelflat_t *levelflat, boolean leveltexture, boo
 	patch_t *patch = NULL;
 	boolean texturechanged = (leveltexture ? (levelflat->u.texture.num != levelflat->u.texture.lastnum) : false);
 
+	(void)ispng;
+
 	// Check if the texture changed.
 	if (leveltexture && (!texturechanged))
 	{
diff --git a/src/sdl/i_main.c b/src/sdl/i_main.c
index 029febc059a10e8458de222eccce1180927e1a52..d0830396fa8741cc99ec3e3f7aa32484bb16f2de 100644
--- a/src/sdl/i_main.c
+++ b/src/sdl/i_main.c
@@ -47,6 +47,7 @@ extern int SDL_main(int argc, char *argv[]);
 
 #ifdef LOGMESSAGES
 FILE *logstream = NULL;
+char  logfilename[1024];
 #endif
 
 #ifndef DOXYGEN
@@ -116,7 +117,6 @@ int main(int argc, char **argv)
 #endif
 {
 	const char *logdir = NULL;
-	char logfile[MAX_WADPATH];
 	myargc = argc;
 	myargv = argv; /// \todo pull out path to exe from this string
 
@@ -141,7 +141,7 @@ int main(int argc, char **argv)
 		timeinfo = localtime(&my_time);
 
 		strftime(buf, 26, "%Y-%m-%d %H-%M-%S", timeinfo);
-		strcpy(logfile, va("log-%s.txt", buf));
+		strcpy(logfilename, va("log-%s.txt", buf));
 
 #ifdef DEFAULTDIR
 		if (logdir)
@@ -149,14 +149,16 @@ int main(int argc, char **argv)
 			// Create dirs here because D_SRB2Main() is too late.
 			I_mkdir(va("%s%s"DEFAULTDIR, logdir, PATHSEP), 0755);
 			I_mkdir(va("%s%s"DEFAULTDIR"%slogs",logdir, PATHSEP, PATHSEP), 0755);
-			logstream = fopen(va("%s%s"DEFAULTDIR"%slogs%s%s",logdir, PATHSEP, PATHSEP, PATHSEP, logfile), "wt");
+			strcpy(logfilename, va("%s%s"DEFAULTDIR"%slogs%s%s",logdir, PATHSEP, PATHSEP, PATHSEP, logfilename));
 		}
 		else
 #endif
 		{
 			I_mkdir("."PATHSEP"logs"PATHSEP, 0755);
-			logstream = fopen(va("."PATHSEP"logs"PATHSEP"%s", logfile), "wt");
+			strcpy(logfilename, va("."PATHSEP"logs"PATHSEP"%s", logfilename));
 		}
+
+		logstream = fopen(logfilename, "wt");
 	}
 #endif
 
@@ -181,12 +183,13 @@ int main(int argc, char **argv)
 #endif
 	MakeCodeWritable();
 #endif
+
 	// startup SRB2
 	CONS_Printf("Setting up SRB2...\n");
 	D_SRB2Main();
 #ifdef LOGMESSAGES
 	if (!M_CheckParm("-nolog"))
-		CONS_Printf("Logfile: %s\n", logfile);
+		CONS_Printf("Logfile: %s\n", logfilename);
 #endif
 	CONS_Printf("Entering main game loop...\n");
 	// never return
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index 13fb25bea3e6384df3f195148d5d64f3a301c8e5..52186baae20dfca6e924aea5d87105f2decfb607 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -102,6 +102,12 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T);
 #endif
 #endif
 
+#if (defined (__unix__) && !defined (_MSDOS)) || defined (UNIXCOMMON)
+#include <errno.h>
+#include <sys/wait.h>
+#define NEWSIGNALHANDLER
+#endif
+
 #ifndef NOMUMBLE
 #ifdef __linux__ // need -lrt
 #include <sys/mman.h>
@@ -229,13 +235,11 @@ SDL_bool framebuffer = SDL_FALSE;
 
 UINT8 keyboard_started = false;
 
-FUNCNORETURN static ATTRNORETURN void signal_handler(INT32 num)
+static void I_ReportSignal(int num, int coredumped)
 {
 	//static char msg[] = "oh no! back to reality!\r\n";
 	const char *      sigmsg;
-	char        sigdef[32];
-
-	D_QuitNetGame(); // Fix server freezes
+	char msg[128];
 
 	switch (num)
 	{
@@ -261,20 +265,41 @@ FUNCNORETURN static ATTRNORETURN void signal_handler(INT32 num)
 		sigmsg = "SIGABRT - abnormal termination triggered by abort call";
 		break;
 	default:
-		sprintf(sigdef,"signal number %d", num);
-		sigmsg = sigdef;
+		sprintf(msg,"signal number %d", num);
+		if (coredumped)
+			sigmsg = 0;
+		else
+			sigmsg = msg;
+	}
+
+	if (coredumped)
+	{
+		if (sigmsg)
+			sprintf(msg, "%s (core dumped)", sigmsg);
+		else
+			strcat(msg, " (core dumped)");
+
+		sigmsg = msg;
 	}
 
-	I_OutputMsg("\nsignal_handler() error: %s\n", sigmsg);
+	I_OutputMsg("\nProcess killed by signal: %s\n\n", sigmsg);
 
 	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
-		"Signal caught",
+		"Process killed by signal",
 		sigmsg, NULL);
+}
+
+#ifndef NEWSIGNALHANDLER
+FUNCNORETURN static ATTRNORETURN void signal_handler(INT32 num)
+{
+	D_QuitNetGame(); // Fix server freezes
+	I_ReportSignal(num, 0);
 	I_ShutdownSystem();
 	signal(num, SIG_DFL);               //default signal action
 	raise(num);
 	I_Quit();
 }
+#endif
 
 FUNCNORETURN static ATTRNORETURN void quit_handler(int num)
 {
@@ -650,7 +675,7 @@ static inline void I_ShutdownConsole(void){}
 //
 // StartupKeyboard
 //
-void I_StartupKeyboard (void)
+static void I_RegisterSignals (void)
 {
 #ifdef SIGINT
 	signal(SIGINT , quit_handler);
@@ -664,10 +689,12 @@ void I_StartupKeyboard (void)
 
 	// If these defines don't exist,
 	// then compilation would have failed above us...
+#ifndef NEWSIGNALHANDLER
 	signal(SIGILL , signal_handler);
 	signal(SIGSEGV , signal_handler);
 	signal(SIGABRT , signal_handler);
 	signal(SIGFPE , signal_handler);
+#endif
 }
 
 //
@@ -2139,6 +2166,85 @@ void I_Sleep(void)
 		SDL_Delay(cv_sleep.value);
 }
 
+#ifdef NEWSIGNALHANDLER
+static void newsignalhandler_Warn(const char *pr)
+{
+	char text[128];
+
+	snprintf(text, sizeof text,
+			"Error while setting up signal reporting: %s: %s",
+			pr,
+			strerror(errno)
+	);
+
+	I_OutputMsg("%s\n", text);
+
+	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
+		"Startup error",
+		text, NULL);
+
+	I_ShutdownConsole();
+	exit(-1);
+}
+
+static void I_Fork(void)
+{
+	int child;
+	int status;
+	int signum;
+	int c;
+
+	child = fork();
+
+	switch (child)
+	{
+		case -1:
+			newsignalhandler_Warn("fork()");
+			break;
+		case 0:
+			break;
+		default:
+			if (logstream)
+				fclose(logstream);/* the child has this */
+
+			c = wait(&status);
+
+#ifdef LOGMESSAGES
+			/* By the way, exit closes files. */
+			logstream = fopen(logfilename, "at");
+#else
+			logstream = 0;
+#endif
+
+			if (c == -1)
+			{
+				kill(child, SIGKILL);
+				newsignalhandler_Warn("wait()");
+			}
+			else
+			{
+				if (WIFSIGNALED (status))
+				{
+					signum = WTERMSIG (status);
+#ifdef WCOREDUMP
+					I_ReportSignal(signum, WCOREDUMP (status));
+#else
+					I_ReportSignal(signum, 0);
+#endif
+					status = 128 + signum;
+				}
+				else if (WIFEXITED (status))
+				{
+					status = WEXITSTATUS (status);
+				}
+
+				I_ShutdownConsole();
+				exit(status);
+			}
+	}
+}
+#endif/*NEWSIGNALHANDLER*/
+
 INT32 I_StartupSystem(void)
 {
 	SDL_version SDLcompiled;
@@ -2146,6 +2252,10 @@ INT32 I_StartupSystem(void)
 	SDL_VERSION(&SDLcompiled)
 	SDL_GetVersion(&SDLlinked);
 	I_StartupConsole();
+#ifdef NEWSIGNALHANDLER
+	I_Fork();
+#endif
+	I_RegisterSignals();
 	I_OutputMsg("Compiled for SDL version: %d.%d.%d\n",
 	 SDLcompiled.major, SDLcompiled.minor, SDLcompiled.patch);
 	I_OutputMsg("Linked with SDL version: %d.%d.%d\n",
@@ -2169,7 +2279,6 @@ void I_Quit(void)
 	if (quiting) goto death;
 	SDLforceUngrabMouse();
 	quiting = SDL_FALSE;
-	I_ShutdownConsole();
 	M_SaveConfig(NULL); //save game config, cvars..
 #ifndef NONET
 	D_SaveBan(); // save the ban list
@@ -2287,8 +2396,6 @@ void I_Error(const char *error, ...)
 	I_OutputMsg("\nI_Error(): %s\n", buffer);
 	// ---
 
-	I_ShutdownConsole();
-
 	M_SaveConfig(NULL); // save game config, cvars..
 #ifndef NONET
 	D_SaveBan(); // save the ban list
@@ -2388,6 +2495,10 @@ void I_ShutdownSystem(void)
 {
 	INT32 c;
 
+#ifndef NEWSIGNALHANDLER
+	I_ShutdownConsole();
+#endif
+
 	for (c = MAX_QUIT_FUNCS-1; c >= 0; c--)
 		if (quit_funcs[c])
 			(*quit_funcs[c])();
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index c876a323f3fbbce1b4a716994cc92a34a09c8913..e054381eec6183cba9b46d4948dc4f0723a0b088 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1441,6 +1441,7 @@ INT32 VID_SetMode(INT32 modeNum)
 	//Impl_SetWindowName("SRB2 "VERSIONSTRING);
 
 	SDLSetMode(vid.width, vid.height, USE_FULLSCREEN);
+	Impl_VideoSetupBuffer();
 
 	if (rendermode == render_soft)
 	{
@@ -1449,8 +1450,6 @@ INT32 VID_SetMode(INT32 modeNum)
 			SDL_FreeSurface(bufSurface);
 			bufSurface = NULL;
 		}
-
-		Impl_VideoSetupBuffer();
 	}
 
 	return SDL_TRUE;
@@ -1573,7 +1572,7 @@ static void Impl_VideoSetupSDLBuffer(void)
 static void Impl_VideoSetupBuffer(void)
 {
 	// Set up game's software render buffer
-	if (rendermode == render_soft)
+	//if (rendermode == render_soft)
 	{
 		vid.rowbytes = vid.width * vid.bpp;
 		vid.direct = NULL;
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index 369d04ced820943cfe6214bd133613aa6a6cbf5d..b60dab14c9e7c0e90bd2afa2b78141df1f01099b 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -9,6 +9,26 @@
 /// \file
 /// \brief SDL Mixer interface for sound
 
+#ifdef HAVE_LIBGME
+#ifdef HAVE_ZLIB
+#ifndef _MSC_VER
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+#endif
+
+#ifndef _LFS64_LARGEFILE
+#define _LFS64_LARGEFILE
+#endif
+
+#ifndef _FILE_OFFSET_BITS
+#define _FILE_OFFSET_BITS 0
+#endif
+
+#include <zlib.h>
+#endif // HAVE_ZLIB
+#endif // HAVE_LIBGME
+
 #include "../doomdef.h"
 #include "../doomstat.h" // menuactive
 
@@ -57,24 +77,6 @@
 #include "gme/gme.h"
 #define GME_TREBLE 5.0f
 #define GME_BASS 1.0f
-
-#ifdef HAVE_ZLIB
-#ifndef _MSC_VER
-#ifndef _LARGEFILE64_SOURCE
-#define _LARGEFILE64_SOURCE
-#endif
-#endif
-
-#ifndef _LFS64_LARGEFILE
-#define _LFS64_LARGEFILE
-#endif
-
-#ifndef _FILE_OFFSET_BITS
-#define _FILE_OFFSET_BITS 0
-#endif
-
-#include "zlib.h"
-#endif // HAVE_ZLIB
 #endif // HAVE_LIBGME
 
 static UINT16 BUFFERSIZE = 2048;
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 3299c9d391c9653b7835756b147574b45a256211..5e05030c38d305f90d81ffa9a1db24217650738e 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -1177,21 +1177,33 @@ tic_t lt_exitticker = 0, lt_endtime = 0;
 
 //
 // Load the graphics for the title card.
+// Don't let LJ see this
 //
 static void ST_cacheLevelTitle(void)
 {
-	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_WARNINGTITLE))
-	{
-		lt_patches[0] = (patch_t *)W_CachePatchName("LTACTBLU", PU_HUDGFX);
-		lt_patches[1] = (patch_t *)W_CachePatchName("LTZIGZAG", PU_HUDGFX);
-		lt_patches[2] = (patch_t *)W_CachePatchName("LTZZTEXT", PU_HUDGFX);
-	}
-	else // boss map
-	{
-		lt_patches[0] = (patch_t *)W_CachePatchName("LTACTRED", PU_HUDGFX);
-		lt_patches[1] = (patch_t *)W_CachePatchName("LTZIGRED", PU_HUDGFX);
-		lt_patches[2] = (patch_t *)W_CachePatchName("LTZZWARN", PU_HUDGFX);
-	}
+#define SETPATCH(default, warning, custom, idx) \
+{ \
+	lumpnum_t patlumpnum = LUMPERROR; \
+	if (mapheaderinfo[gamemap-1]->custom[0] != '\0') \
+	{ \
+		patlumpnum = W_CheckNumForName(mapheaderinfo[gamemap-1]->custom); \
+		if (patlumpnum != LUMPERROR) \
+			lt_patches[idx] = (patch_t *)W_CachePatchNum(patlumpnum, PU_HUDGFX); \
+	} \
+	if (patlumpnum == LUMPERROR) \
+	{ \
+		if (!(mapheaderinfo[gamemap-1]->levelflags & LF_WARNINGTITLE)) \
+			lt_patches[idx] = (patch_t *)W_CachePatchName(default, PU_HUDGFX); \
+		else \
+			lt_patches[idx] = (patch_t *)W_CachePatchName(warning, PU_HUDGFX); \
+	} \
+}
+
+	SETPATCH("LTACTBLU", "LTACTRED", ltactdiamond, 0)
+	SETPATCH("LTZIGZAG", "LTZIGRED", ltzzpatch, 1)
+	SETPATCH("LTZZTEXT", "LTZZWARN", ltzztext, 2)
+
+#undef SETPATCH
 }
 
 //
diff --git a/src/v_video.c b/src/v_video.c
index c6ec22767f6f4518e7ba1ee231ac73edf800fc65..c91e5f213e8fb7ec0eca1ddc9d29176dbc2d8440 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -3244,6 +3244,29 @@ Unoptimized version
 #endif
 }
 
+// Taken from my videos-in-SRB2 project
+// Generates a color look-up table
+// which has up to 64 colors at each channel
+// (see the defines in v_video.h)
+
+UINT8 colorlookup[CLUTSIZE][CLUTSIZE][CLUTSIZE];
+
+void InitColorLUT(RGBA_t *palette)
+{
+	UINT8 r, g, b;
+	static boolean clutinit = false;
+	static RGBA_t *lastpalette = NULL;
+	if ((!clutinit) || (lastpalette != palette))
+	{
+		for (r = 0; r < CLUTSIZE; r++)
+			for (g = 0; g < CLUTSIZE; g++)
+				for (b = 0; b < CLUTSIZE; b++)
+					colorlookup[r][g][b] = NearestColor(r << SHIFTCOLORBITS, g << SHIFTCOLORBITS, b << SHIFTCOLORBITS);
+		clutinit = true;
+		lastpalette = palette;
+	}
+}
+
 // V_Init
 // old software stuff, buffers are allocated at video mode setup
 // here we set the screens[x] pointers accordingly
@@ -3255,13 +3278,9 @@ void V_Init(void)
 	const INT32 screensize = vid.rowbytes * vid.height;
 
 	LoadMapPalette();
-	// hardware modes do not use screens[] pointers
+
 	for (i = 0; i < NUMSCREENS; i++)
 		screens[i] = NULL;
-	if (rendermode != render_soft)
-	{
-		return; // be sure to cause a NULL read/write error so we detect it, in case of..
-	}
 
 	// start address of NUMSCREENS * width*height vidbuffers
 	if (base)
diff --git a/src/v_video.h b/src/v_video.h
index 2434fec816b002f3fa61412901ef16f39bfa5f13..36d1914af6edec7f1b9996e4c767d507dcaf8a71 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -37,6 +37,18 @@ cv_allcaps;
 // Allocates buffer screens, call before R_Init.
 void V_Init(void);
 
+// Taken from my videos-in-SRB2 project
+// Generates a color look-up table
+// which has up to 64 colors at each channel
+
+#define COLORBITS 6
+#define SHIFTCOLORBITS (8-COLORBITS)
+#define CLUTSIZE (1<<COLORBITS)
+
+extern UINT8 colorlookup[CLUTSIZE][CLUTSIZE][CLUTSIZE];
+
+void InitColorLUT(RGBA_t *palette);
+
 // Set the current RGB palette lookup to use for palettized graphics
 void V_SetPalette(INT32 palettenum);
 
diff --git a/src/w_wad.c b/src/w_wad.c
index a86e99237bf7f0bbeef2aea99033b85605f469fc..62992441a0d2cb6d28da7788930ce791b54ed0b6 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -11,32 +11,34 @@
 /// \file  w_wad.c
 /// \brief Handles WAD file header, directory, lump I/O
 
-#ifdef __GNUC__
-#include <unistd.h>
+#ifdef HAVE_ZLIB
+#ifndef _MSC_VER
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
 #endif
 
-#define ZWAD
-
-#ifdef ZWAD
-#include <errno.h>
-#include "lzf.h"
+#ifndef _LFS64_LARGEFILE
+#define _LFS64_LARGEFILE
 #endif
 
 #ifndef _FILE_OFFSET_BITS
 #define _FILE_OFFSET_BITS 0
 #endif
 
-#ifndef _LARGEFILE64_SOURCE
-#define _LARGEFILE64_SOURCE
+#include <zlib.h>
 #endif
 
-#ifndef _LFS64_LARGEFILE
-#define _LFS64_LARGEFILE
+#ifdef __GNUC__
+#include <unistd.h>
 #endif
 
-//#ifdef HAVE_ZLIB
-#include "zlib.h"
-//#endif // HAVE_ZLIB
+#define ZWAD
+
+#ifdef ZWAD
+#include <errno.h>
+#include "lzf.h"
+#endif
 
 #include "doomdef.h"
 #include "doomstat.h"
@@ -77,24 +79,6 @@ int	snprintf(char *str, size_t n, const char *fmt, ...);
 #define O_BINARY 0
 #endif
 
-#ifdef HAVE_ZLIB
-#ifndef _MSC_VER
-#ifndef _LARGEFILE64_SOURCE
-#define _LARGEFILE64_SOURCE
-#endif
-#endif
-
-#ifndef _LFS64_LARGEFILE
-#define _LFS64_LARGEFILE
-#endif
-
-#ifndef _FILE_OFFSET_BITS
-#define _FILE_OFFSET_BITS 0
-#endif
-
-#include "zlib.h"
-#endif
-
 
 typedef struct
 {