diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 3603bb20d62ffee551c7b1ba12170ec3384068a5..f7755c148d24ef78d1709a38b192b40a1b0e952f 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -85,6 +85,10 @@ tic_t jointimeout = (10*TICRATE);
 static boolean sendingsavegame[MAXNETNODES]; // Are we sending the savegame?
 static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the server before getting a timeout?
 
+// Incremented by cv_joindelay when a client joins, decremented each tic.
+// If higher than cv_joindelay * 2 (3 joins in a short timespan), joins are temporarily disabled.
+static tic_t joindelay = 0;
+
 UINT16 pingmeasurecount = 1;
 UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone.
 UINT32 playerpingtable[MAXPLAYERS]; //table of player latency values.
@@ -3077,6 +3081,8 @@ consvar_t cv_allownewplayer = {"allowjoin", "On", CV_NETVAR, CV_OnOff, NULL, 0,
 consvar_t cv_joinnextround = {"joinnextround", "Off", CV_NETVAR, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; /// \todo not done
 static CV_PossibleValue_t maxplayers_cons_t[] = {{2, "MIN"}, {32, "MAX"}, {0, NULL}};
 consvar_t cv_maxplayers = {"maxplayers", "8", CV_SAVE, maxplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+static CV_PossibleValue_t joindelay_cons_t[] = {{1, "MIN"}, {3600, "MAX"}, {0, "Off"}, {0, NULL}};
+consvar_t cv_joindelay = {"joindelay", "10", CV_SAVE, joindelay_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 static CV_PossibleValue_t rejointimeout_cons_t[] = {{1, "MIN"}, {60 * FRACUNIT, "MAX"}, {0, "Off"}, {0, NULL}};
 consvar_t cv_rejointimeout = {"rejointimeout", "Off", CV_SAVE|CV_FLOAT, rejointimeout_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -3164,6 +3170,8 @@ void SV_ResetServer(void)
 	neededtic = maketic;
 	tictoclear = maketic;
 
+	joindelay = 0;
+
 	for (i = 0; i < MAXNETNODES; i++)
 		ResetNode(i);
 
@@ -3613,6 +3621,9 @@ static void HandleConnect(SINT8 node)
 		SV_SendRefuse(node, M_GetText("No players from\nthis node."));
 	else if (luafiletransfers)
 		SV_SendRefuse(node, M_GetText("The server is broadcasting a file\nrequested by a Lua script.\nPlease wait a bit and then\ntry rejoining."));
+	else if (netgame && joindelay > 2 * (tic_t)cv_joindelay.value * TICRATE)
+		SV_SendRefuse(node, va(M_GetText("Too many people are connecting.\nPlease wait %d seconds and then\ntry rejoining."),
+			(joindelay - 2 * cv_joindelay.value * TICRATE) / TICRATE));
 	else
 	{
 #ifndef NONET
@@ -3670,6 +3681,7 @@ static void HandleConnect(SINT8 node)
 				DEBFILE("send savegame\n");
 			}
 			SV_AddWaitingPlayers(names[0], names[1]);
+			joindelay += cv_joindelay.value * TICRATE;
 			player_joining = true;
 		}
 #else
@@ -5038,12 +5050,21 @@ void NetUpdate(void)
 				hu_resynching = true;
 		}
 	}
+
 	Net_AckTicker();
+
 	// Handle timeouts to prevent definitive freezes from happenning
 	if (server)
+	{
 		for (i = 1; i < MAXNETNODES; i++)
 			if (nodeingame[i] && freezetimeout[i] < I_GetTime())
 				Net_ConnectionTimeout(i);
+
+		// In case the cvar value was lowered
+		if (joindelay)
+			joindelay = min(joindelay - 1, 3 * cv_joindelay.value * TICRATE);
+	}
+
 	nowtime /= NEWTICRATERATIO;
 	if (nowtime > resptime)
 	{
@@ -5051,6 +5072,7 @@ void NetUpdate(void)
 		M_Ticker();
 		CON_Ticker();
 	}
+
 	SV_FileSendTicker();
 }
 
diff --git a/src/d_clisrv.h b/src/d_clisrv.h
index 7e5061ff297ee5b2334c8ca6667015c1313f11d6..463240a2adc87289f8e00f319c7f90df062bf5f2 100644
--- a/src/d_clisrv.h
+++ b/src/d_clisrv.h
@@ -515,7 +515,7 @@ extern UINT32 realpingtable[MAXPLAYERS];
 extern UINT32 playerpingtable[MAXPLAYERS];
 extern tic_t servermaxping;
 
-extern consvar_t cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_rejointimeout;
+extern consvar_t cv_allownewplayer, cv_joinnextround, cv_maxplayers, cv_joindelay, cv_rejointimeout;
 extern consvar_t cv_resynchattempts, cv_blamecfail;
 extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed;
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 1fd53499a6043bd3cdf1bab2d2f54a40bd8a3c71..dfc7351f5808ba8feff8ba0525d72774d4550993 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -573,6 +573,7 @@ void D_RegisterServerCommands(void)
 
 	// d_clisrv
 	CV_RegisterVar(&cv_maxplayers);
+	CV_RegisterVar(&cv_joindelay);
 	CV_RegisterVar(&cv_rejointimeout);
 	CV_RegisterVar(&cv_resynchattempts);
 	CV_RegisterVar(&cv_maxsend);
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index 818e760c9d9b6b39fb1aff73394784ebf794c427..703b924bb908fe7b2adfeb2fe16b453fd5b05bd0 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -412,9 +412,9 @@ static int libd_cachePatch(lua_State *L)
 	HUDONLY
 
 	luapat = patchinfohead;
-	lumpnum = W_CheckNumForName(luaL_checkstring(L, 1));
+	lumpnum = W_CheckNumForLongName(luaL_checkstring(L, 1));
 	if (lumpnum == LUMPERROR)
-		lumpnum = W_GetNumForName("MISSING");
+		lumpnum = W_GetNumForLongName("MISSING");
 
 	for (i = 0; i < numluapatches; i++)
 	{
@@ -454,7 +454,7 @@ static int libd_cachePatch(lua_State *L)
 	numluapatches++;
 #else
 	HUDONLY
-	LUA_PushUserdata(L, W_CachePatchName(luaL_checkstring(L, 1), PU_PATCH), META_PATCH);
+	LUA_PushUserdata(L, W_CachePatchLongName(luaL_checkstring(L, 1), PU_PATCH), META_PATCH);
 #endif
 	return 1;
 }
diff --git a/src/lua_script.c b/src/lua_script.c
index c7ff4d4c37815cf9f420524fc19f2662706e22f2..50b729b25d04b1f4d7c1540085692284ddbc03d4 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -1013,16 +1013,8 @@ static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
 			if (!rover)
 				WRITEUINT8(save_p, ARCH_NULL);
 			else {
-				ffloor_t *r2;
-				UINT16 i = 0;
-				// search for id
-				for (r2 = rover->target->ffloors; r2; r2 = r2->next)
-				{
-					if (r2 == rover)
-						break;
-					i++;
-				}
-				if (!r2)
+				UINT16 i = P_GetFFloorID(rover);
+				if (i == UINT16_MAX) // invalid ID
 					WRITEUINT8(save_p, ARCH_NULL);
 				else
 				{
diff --git a/src/m_menu.c b/src/m_menu.c
index 1069f0f30211747d7a8dc4b4ecfda2847e851815..c5f10b2bd1853d4169d2d181ad3bbcb30361b3a7 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1583,7 +1583,7 @@ static menuitem_t OP_ServerOptionsMenu[] =
 	{IT_HEADER, NULL, "General", NULL, 0},
 #ifndef NONET
 	{IT_STRING | IT_CVAR | IT_CV_STRING,
-	                         NULL, "Server name",                      &cv_servername,          7},
+	                         NULL, "Server name",                      &cv_servername,           7},
 	{IT_STRING | IT_CVAR,    NULL, "Max Players",                      &cv_maxplayers,          21},
 	{IT_STRING | IT_CVAR,    NULL, "Allow Add-on Downloading",         &cv_downloading,         26},
 	{IT_STRING | IT_CVAR,    NULL, "Allow players to join",            &cv_allownewplayer,      31},
@@ -1628,8 +1628,9 @@ static menuitem_t OP_ServerOptionsMenu[] =
 
 #ifndef NONET
 	{IT_HEADER, NULL, "Advanced", NULL, 225},
-	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server",        &cv_masterserver,        231},
-	{IT_STRING | IT_CVAR,    NULL, "Attempts to resynchronise",        &cv_resynchattempts,     245},
+	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "Master server",        &cv_masterserver,       231},
+	{IT_STRING | IT_CVAR,    NULL, "Join delay",                       &cv_joindelay,          246},
+	{IT_STRING | IT_CVAR,    NULL, "Attempts to resynchronise",        &cv_resynchattempts,    251},
 #endif
 };
 
@@ -10822,7 +10823,8 @@ static void M_ServerOptions(INT32 choice)
 		OP_ServerOptionsMenu[ 3].status = IT_GRAYEDOUT; // Allow add-on downloading
 		OP_ServerOptionsMenu[ 4].status = IT_GRAYEDOUT; // Allow players to join
 		OP_ServerOptionsMenu[35].status = IT_GRAYEDOUT; // Master server
-		OP_ServerOptionsMenu[36].status = IT_GRAYEDOUT; // Attempts to resynchronise
+		OP_ServerOptionsMenu[36].status = IT_GRAYEDOUT; // Minimum delay between joins
+		OP_ServerOptionsMenu[37].status = IT_GRAYEDOUT; // Attempts to resynchronise
 	}
 	else
 	{
@@ -10834,6 +10836,7 @@ static void M_ServerOptions(INT32 choice)
 			? IT_GRAYEDOUT
 			: (IT_STRING | IT_CVAR | IT_CV_STRING));
 		OP_ServerOptionsMenu[36].status = IT_STRING | IT_CVAR;
+		OP_ServerOptionsMenu[37].status = IT_STRING | IT_CVAR;
 	}
 #endif
 
diff --git a/src/p_ceilng.c b/src/p_ceilng.c
index 31a4895ba99b9c5e14f353aedf853e15db8f833f..3c3c507cd168e46a50ccdce3a503c8570c028797 100644
--- a/src/p_ceilng.c
+++ b/src/p_ceilng.c
@@ -395,7 +395,7 @@ INT32 EV_DoCeiling(line_t *line, ceiling_e type)
 	sector_t *sec;
 	ceiling_t *ceiling;
 
-	while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
@@ -615,7 +615,7 @@ INT32 EV_DoCrush(line_t *line, ceiling_e type)
 	sector_t *sec;
 	ceiling_t *ceiling;
 
-	while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
diff --git a/src/p_floor.c b/src/p_floor.c
index 179cf73c70d8c2099c312dd8895ef791d8b1862f..d6ffb95bfd1096379f3a836da1020c24fe8d0abc 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -757,7 +757,7 @@ void T_BounceCheese(bouncecheese_t *bouncer)
 // T_StartCrumble ////////////////////////////////
 //////////////////////////////////////////////////
 // Crumbling platform Tails 03-11-2002
-void T_StartCrumble(elevator_t *elevator)
+void T_StartCrumble(crumble_t *crumble)
 {
 	ffloor_t *rover;
 	sector_t *sector;
@@ -765,84 +765,96 @@ void T_StartCrumble(elevator_t *elevator)
 
 	// Once done, the no-return thinker just sits there,
 	// constantly 'returning'... kind of an oxymoron, isn't it?
-	if (((elevator->floordestheight == 1 && elevator->direction == -1)
-		|| (elevator->floordestheight == 0 && elevator->direction == 1))
-		&& elevator->type == elevateContinuous) // No return crumbler
+	if ((((crumble->flags & CF_REVERSE) && crumble->direction == -1)
+		|| (!(crumble->flags & CF_REVERSE) && crumble->direction == 1))
+		&& !(crumble->flags & CF_RETURN))
 	{
-		elevator->sector->ceilspeed = 0;
-		elevator->sector->floorspeed = 0;
+		crumble->sector->ceilspeed = 0;
+		crumble->sector->floorspeed = 0;
 		return;
 	}
 
-	if (elevator->distance != 0)
+	if (crumble->timer != 0)
 	{
-		if (elevator->distance > 0) // Count down the timer
+		if (crumble->timer > 0) // Count down the timer
 		{
-			elevator->distance--;
-			if (elevator->distance <= 0)
-				elevator->distance = -15*TICRATE; // Timer until platform returns to original position.
+			if (--crumble->timer <= 0)
+				crumble->timer = -15*TICRATE; // Timer until platform returns to original position.
 			else
 			{
 				// Timer isn't up yet, so just keep waiting.
-				elevator->sector->ceilspeed = 0;
-				elevator->sector->floorspeed = 0;
+				crumble->sector->ceilspeed = 0;
+				crumble->sector->floorspeed = 0;
 				return;
 			}
 		}
-		else if (++elevator->distance == 0) // Reposition back to original spot
+		else if (++crumble->timer == 0) // Reposition back to original spot
 		{
-			for (i = -1; (i = P_FindSectorFromTag(elevator->sourceline->tag, i)) >= 0 ;)
+			for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
 			{
 				sector = &sectors[i];
 
 				for (rover = sector->ffloors; rover; rover = rover->next)
 				{
-					if (rover->flags & FF_CRUMBLE && rover->flags & FF_FLOATBOB
-						&& rover->master == elevator->sourceline)
-					{
-						rover->alpha = elevator->origspeed;
+					if (!(rover->flags & FF_CRUMBLE))
+						continue;
 
-						if (rover->alpha == 0xff)
-							rover->flags &= ~FF_TRANSLUCENT;
-					}
+					if (!(rover->flags & FF_FLOATBOB))
+						continue;
+
+					if (rover->master != crumble->sourceline)
+						continue;
+
+					rover->alpha = crumble->origalpha;
+
+					if (rover->alpha == 0xff)
+						rover->flags &= ~FF_TRANSLUCENT;
 				}
 			}
 
 			// Up!
-			if (elevator->floordestheight == 1)
-				elevator->direction = -1;
+			if (crumble->flags & CF_REVERSE)
+				crumble->direction = -1;
 			else
-				elevator->direction = 1;
+				crumble->direction = 1;
 
-			elevator->sector->ceilspeed = 0;
-			elevator->sector->floorspeed = 0;
+			crumble->sector->ceilspeed = 0;
+			crumble->sector->floorspeed = 0;
 			return;
 		}
 
 		// Flash to indicate that the platform is about to return.
-		if (elevator->distance > -224 && (leveltime % ((abs(elevator->distance)/8) + 1) == 0))
+		if (crumble->timer > -224 && (leveltime % ((abs(crumble->timer)/8) + 1) == 0))
 		{
-			for (i = -1; (i = P_FindSectorFromTag(elevator->sourceline->tag, i)) >= 0 ;)
+			for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
 			{
 				sector = &sectors[i];
 
 				for (rover = sector->ffloors; rover; rover = rover->next)
 				{
-					if (!(rover->flags & FF_NORETURN) && rover->flags & FF_CRUMBLE && rover->flags & FF_FLOATBOB
-						&& rover->master == elevator->sourceline)
+					if (rover->flags & FF_NORETURN)
+						continue;
+
+					if (!(rover->flags & FF_CRUMBLE))
+						continue;
+
+					if (!(rover->flags & FF_FLOATBOB))
+						continue;
+
+					if (rover->master != crumble->sourceline)
+						continue;
+
+					if (rover->alpha == crumble->origalpha)
 					{
-						if (rover->alpha == elevator->origspeed)
-						{
-							rover->flags |= FF_TRANSLUCENT;
-							rover->alpha = 0x00;
-						}
-						else
-						{
-							if (elevator->origspeed == 0xff)
-								rover->flags &= ~FF_TRANSLUCENT;
-
-							rover->alpha = elevator->origspeed;
-						}
+						rover->flags |= FF_TRANSLUCENT;
+						rover->alpha = 0x00;
+					}
+					else
+					{
+						rover->alpha = crumble->origalpha;
+
+						if (rover->alpha == 0xff)
+							rover->flags &= ~FF_TRANSLUCENT;
 					}
 				}
 			}
@@ -851,74 +863,62 @@ void T_StartCrumble(elevator_t *elevator)
 		// We're about to go back to the original position,
 		// so set this to let other thinkers know what is
 		// about to happen.
-		if (elevator->distance < 0 && elevator->distance > -3)
-			elevator->sector->crumblestate = CRUMBLE_RESTORE; // makes T_BounceCheese remove itself
+		if (crumble->timer < 0 && crumble->timer > -3)
+			crumble->sector->crumblestate = CRUMBLE_RESTORE; // makes T_BounceCheese remove itself
 	}
 
-	if ((elevator->floordestheight == 0 && elevator->direction == -1)
-		|| (elevator->floordestheight == 1 && elevator->direction == 1)) // Down
+	if ((!(crumble->flags & CF_REVERSE) && crumble->direction == -1)
+		|| ((crumble->flags & CF_REVERSE) && crumble->direction == 1)) // Down
 	{
-		elevator->sector->crumblestate = CRUMBLE_FALL; // Allow floating now.
+		crumble->sector->crumblestate = CRUMBLE_FALL; // Allow floating now.
 
 		// Only fall like this if it isn't meant to float on water
-		if (elevator->high != 42)
+		if (!(crumble->flags & CF_FLOATBOB))
 		{
-			elevator->speed += gravity; // Gain more and more speed
+			crumble->speed += gravity; // Gain more and more speed
 
-			if ((elevator->floordestheight == 0 && !(elevator->sector->ceilingheight < -16384*FRACUNIT))
-				|| (elevator->floordestheight == 1 && !(elevator->sector->ceilingheight > 16384*FRACUNIT)))
+			if ((!(crumble->flags & CF_REVERSE) && crumble->sector->ceilingheight >= -16384*FRACUNIT)
+				|| ((crumble->flags & CF_REVERSE) && crumble->sector->ceilingheight <= 16384*FRACUNIT))
 			{
-				fixed_t dest;
-
-				if (elevator->floordestheight == 1)
-					dest = elevator->sector->ceilingheight + (elevator->speed*2);
-				else
-					dest = elevator->sector->ceilingheight - (elevator->speed*2);
-
 				T_MovePlane             //jff 4/7/98 reverse order of ceiling/floor
 				(
-				  elevator->sector,
-				  elevator->speed,
-				  dest,
+				  crumble->sector,
+				  crumble->speed,
+				  crumble->sector->ceilingheight + crumble->direction*crumble->speed*2,
 				  false,
 				  true, // move ceiling
-				  elevator->direction
+				  crumble->direction
 				);
 
-				if (elevator->floordestheight == 1)
-					dest = elevator->sector->floorheight + (elevator->speed*2);
-				else
-					dest = elevator->sector->floorheight - (elevator->speed*2);
-
-				  T_MovePlane
-				  (
-					elevator->sector,
-					elevator->speed,
-					dest,
-					false,
-					false, // move floor
-					elevator->direction
+				T_MovePlane
+				(
+				  crumble->sector,
+				  crumble->speed,
+				  crumble->sector->floorheight + crumble->direction*crumble->speed*2,
+				  false,
+				  false, // move floor
+				  crumble->direction
 				);
 
-				elevator->sector->ceilspeed = 42;
-				elevator->sector->floorspeed = elevator->speed*elevator->direction;
+				crumble->sector->ceilspeed = 42;
+				crumble->sector->floorspeed = crumble->speed*crumble->direction;
 			}
 		}
 	}
 	else // Up (restore to original position)
 	{
-		elevator->sector->crumblestate = CRUMBLE_WAIT;
-		elevator->sector->ceilingheight = elevator->ceilingwasheight;
-		elevator->sector->floorheight = elevator->floorwasheight;
-		elevator->sector->floordata = NULL;
-		elevator->sector->ceilingdata = NULL;
-		elevator->sector->ceilspeed = 0;
-		elevator->sector->floorspeed = 0;
-		elevator->sector->moved = true;
-		P_RemoveThinker(&elevator->thinker);
+		crumble->sector->crumblestate = CRUMBLE_WAIT;
+		crumble->sector->ceilingheight = crumble->ceilingwasheight;
+		crumble->sector->floorheight = crumble->floorwasheight;
+		crumble->sector->floordata = NULL;
+		crumble->sector->ceilingdata = NULL;
+		crumble->sector->ceilspeed = 0;
+		crumble->sector->floorspeed = 0;
+		crumble->sector->moved = true;
+		P_RemoveThinker(&crumble->thinker);
 	}
 
-	for (i = -1; (i = P_FindSectorFromTag(elevator->sourceline->tag, i)) >= 0 ;)
+	for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
 	{
 		sector = &sectors[i];
 		sector->moved = true;
@@ -1284,7 +1284,7 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 	INT32 secnum = -1;
 	boolean FOFsector = false;
 
-	while ((secnum = P_FindSectorFromLineTag(nobaddies->sourceline, secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(nobaddies->sourceline->tag, secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
@@ -1300,7 +1300,7 @@ void T_NoEnemiesSector(noenemies_t *nobaddies)
 
 			FOFsector = true;
 
-			while ((targetsecnum = P_FindSectorFromLineTag(sec->lines[i], targetsecnum)) >= 0)
+			while ((targetsecnum = P_FindSectorFromTag(sec->lines[i]->tag, targetsecnum)) >= 0)
 			{
 				if (T_SectorHasEnemies(&sectors[targetsecnum]))
 					return;
@@ -1395,7 +1395,7 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 		eachtime->playersOnArea[i] = false;
 	}
 
-	while ((secnum = P_FindSectorFromLineTag(eachtime->sourceline, secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(eachtime->sourceline->tag, secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
@@ -1418,7 +1418,7 @@ void T_EachTimeThinker(eachtime_t *eachtime)
 
 			FOFsector = true;
 
-			while ((targetsecnum = P_FindSectorFromLineTag(sec->lines[i], targetsecnum)) >= 0)
+			while ((targetsecnum = P_FindSectorFromTag(sec->lines[i]->tag, targetsecnum)) >= 0)
 			{
 				targetsec = &sectors[targetsecnum];
 
@@ -1556,7 +1556,7 @@ void T_RaiseSector(raise_t *raise)
 	if (raise->sector->crumblestate >= CRUMBLE_FALL || raise->sector->ceilingdata)
 		return;
 
-	for (i = -1; (i = P_FindSectorFromTag(raise->sourceline->tag, i)) >= 0 ;)
+	for (i = -1; (i = P_FindSectorFromTag(raise->tag, i)) >= 0 ;)
 	{
 		sector = &sectors[i];
 
@@ -1683,7 +1683,7 @@ void T_RaiseSector(raise_t *raise)
 	raise->sector->ceilspeed = 42;
 	raise->sector->floorspeed = speed*direction;
 
-	for (i = -1; (i = P_FindSectorFromTag(raise->sourceline->tag, i)) >= 0 ;)
+	for (i = -1; (i = P_FindSectorFromTag(raise->tag, i)) >= 0 ;)
 		P_RecalcPrecipInSector(&sectors[i]);
 }
 
@@ -1801,7 +1801,7 @@ void EV_DoFloor(line_t *line, floor_e floortype)
 	sector_t *sec;
 	floormove_t *dofloor;
 
-	while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
@@ -2017,7 +2017,7 @@ void EV_DoElevator(line_t *line, elevator_e elevtype, boolean customspeed)
 	elevator_t *elevator;
 
 	// act on all sectors with the same tag as the triggering linedef
-	while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
+	while ((secnum = P_FindSectorFromTag(line->tag,secnum)) >= 0)
 	{
 		sec = &sectors[secnum];
 
@@ -2309,7 +2309,7 @@ void EV_DoContinuousFall(sector_t *sec, sector_t *backsector, fixed_t spd, boole
 INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 	player_t *player, fixed_t origalpha, boolean crumblereturn)
 {
-	elevator_t *elevator;
+	crumble_t *crumble;
 	sector_t *foundsec;
 	INT32 i;
 
@@ -2320,55 +2320,45 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating,
 	if (sec->crumblestate >= CRUMBLE_ACTIVATED)
 		return 0;
 
-	// create and initialize new elevator thinker
-	elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
-	P_AddThinker(THINK_MAIN, &elevator->thinker);
-	elevator->thinker.function.acp1 = (actionf_p1)T_StartCrumble;
-
-	// Does this crumbler return?
-	if (crumblereturn)
-		elevator->type = elevateBounce;
-	else
-		elevator->type = elevateContinuous;
+	// create and initialize new crumble thinker
+	crumble = Z_Calloc(sizeof (*crumble), PU_LEVSPEC, NULL);
+	P_AddThinker(THINK_MAIN, &crumble->thinker);
+	crumble->thinker.function.acp1 = (actionf_p1)T_StartCrumble;
 
-	// set up the fields according to the type of elevator action
-	elevator->sector = sec;
-	elevator->speed = 0;
+	// set up the fields
+	crumble->sector = sec;
+	crumble->speed = 0;
 
 	if (player && player->mo && (player->mo->eflags & MFE_VERTICALFLIP))
 	{
-		elevator->direction = 1; // Up
-		elevator->floordestheight = 1;
+		crumble->direction = 1; // Up
+		crumble->flags |= CF_REVERSE;
 	}
 	else
-	{
-		elevator->direction = -1; // Down
-		elevator->floordestheight = 0;
-	}
+		crumble->direction = -1; // Down
 
-	elevator->floorwasheight = elevator->sector->floorheight;
-	elevator->ceilingwasheight = elevator->sector->ceilingheight;
-	elevator->distance = TICRATE; // Used for delay time
-	elevator->low = 0;
-	elevator->player = player;
-	elevator->origspeed = origalpha;
+	crumble->floorwasheight = crumble->sector->floorheight;
+	crumble->ceilingwasheight = crumble->sector->ceilingheight;
+	crumble->timer = TICRATE;
+	crumble->player = player;
+	crumble->origalpha = origalpha;
 
-	elevator->sourceline = rover->master;
+	crumble->sourceline = rover->master;
 
-	sec->floordata = elevator;
+	sec->floordata = crumble;
 
+	if (crumblereturn)
+		crumble->flags |= CF_RETURN;
 	if (floating)
-		elevator->high = 42;
-	else
-		elevator->high = 0;
+		crumble->flags |= CF_FLOATBOB;
 
-	elevator->sector->crumblestate = CRUMBLE_ACTIVATED;
+	crumble->sector->crumblestate = CRUMBLE_ACTIVATED;
 
-	for (i = -1; (i = P_FindSectorFromTag(elevator->sourceline->tag, i)) >= 0 ;)
+	for (i = -1; (i = P_FindSectorFromTag(crumble->sourceline->tag, i)) >= 0 ;)
 	{
 		foundsec = &sectors[i];
 
-		P_SpawnMobj(foundsec->soundorg.x, foundsec->soundorg.y, elevator->direction == 1 ? elevator->sector->floorheight : elevator->sector->ceilingheight, MT_CRUMBLEOBJ);
+		P_SpawnMobj(foundsec->soundorg.x, foundsec->soundorg.y, crumble->direction == 1 ? crumble->sector->floorheight : crumble->sector->ceilingheight, MT_CRUMBLEOBJ);
 	}
 
 	return 1;
diff --git a/src/p_map.c b/src/p_map.c
index accc5283625048c8e2a761ff9e986652dbeb2261..0c21e3e69bd191ad7bcd8d8c985a5189edba443d 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -4223,21 +4223,19 @@ static boolean PIT_ChangeSector(mobj_t *thing, boolean realcrush)
 					{
 						//If the thing was crushed by a crumbling FOF, reward the player who made it crumble!
 						thinker_t *think;
-						elevator_t *crumbler;
+						crumble_t *crumbler;
 
 						for (think = thlist[THINK_MAIN].next; think != &thlist[THINK_MAIN]; think = think->next)
 						{
 							if (think->function.acp1 != (actionf_p1)T_StartCrumble)
 								continue;
 
-							crumbler = (elevator_t *)think;
+							crumbler = (crumble_t *)think;
 
 							if (crumbler->player && crumbler->player->mo
 								&& crumbler->player->mo != thing
 								&& crumbler->actionsector == thing->subsector->sector
-								&& crumbler->sector == rover->master->frontsector
-								&& (crumbler->type == elevateBounce
-								|| crumbler->type == elevateContinuous))
+								&& crumbler->sector == rover->master->frontsector)
 							{
 								killer = crumbler->player->mo;
 							}
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 8932f0bf36575dfddefd8685e4229b47343fc48f..1e2b74c3c3deb9511f56fea090ef57ef623f0097 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -59,9 +59,6 @@ typedef enum
 	DRONE      = 0x80,
 } player_saveflags;
 
-//
-// P_ArchivePlayer
-//
 static inline void P_ArchivePlayer(void)
 {
 	const player_t *player = &players[consoleplayer];
@@ -77,9 +74,6 @@ static inline void P_ArchivePlayer(void)
 	WRITEINT32(save_p, player->continues);
 }
 
-//
-// P_UnArchivePlayer
-//
 static inline void P_UnArchivePlayer(void)
 {
 	INT16 skininfo = READUINT16(save_p);
@@ -92,9 +86,6 @@ static inline void P_UnArchivePlayer(void)
 	savedata.continues = READINT32(save_p);
 }
 
-//
-// P_NetArchivePlayers
-//
 static void P_NetArchivePlayers(void)
 {
 	INT32 i, j;
@@ -300,9 +291,6 @@ static void P_NetArchivePlayers(void)
 	}
 }
 
-//
-// P_NetUnArchivePlayers
-//
 static void P_NetUnArchivePlayers(void)
 {
 	INT32 i, j;
@@ -799,29 +787,102 @@ static boolean P_AreStringArgsEqual(const line_t *li, const line_t *spawnli)
 	return true;
 }
 
-//
-// P_NetArchiveWorld
-//
-static void P_NetArchiveWorld(void)
+#define FD_FLAGS 0x01
+#define FD_ALPHA 0x02
+
+// Check if any of the sector's FOFs differ from how they spawned
+static boolean CheckFFloorDiff(const sector_t *ss)
 {
-	size_t i;
-	INT32 statsec = 0, statline = 0;
-	const line_t *li = lines;
-	const line_t *spawnli = spawnlines;
-	const side_t *si;
-	const side_t *spawnsi;
-	UINT8 *put;
+	ffloor_t *rover;
+
+	for (rover = ss->ffloors; rover; rover = rover->next)
+	{
+		if (rover->flags != rover->spawnflags
+		|| rover->alpha != rover->spawnalpha)
+			{
+				return true; // we found an FOF that changed!
+				// don't bother checking for more, we do that later
+			}
+	}
+	return false;
+}
+
+// Special case: save the stats of all modified ffloors along with their ffloor "number"s
+// we don't bother with ffloors that haven't changed, that would just add to savegame even more than is really needed
+static void ArchiveFFloors(const sector_t *ss)
+{
+	size_t j = 0; // ss->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
+	ffloor_t *rover;
+	UINT8 fflr_diff;
+	for (rover = ss->ffloors; rover; rover = rover->next)
+	{
+		fflr_diff = 0; // reset diff flags
+		if (rover->flags != rover->spawnflags)
+			fflr_diff |= FD_FLAGS;
+		if (rover->alpha != rover->spawnalpha)
+			fflr_diff |= FD_ALPHA;
+
+		if (fflr_diff)
+		{
+			WRITEUINT16(save_p, j); // save ffloor "number"
+			WRITEUINT8(save_p, fflr_diff);
+			if (fflr_diff & FD_FLAGS)
+				WRITEUINT32(save_p, rover->flags);
+			if (fflr_diff & FD_ALPHA)
+				WRITEINT16(save_p, rover->alpha);
+		}
+		j++;
+	}
+	WRITEUINT16(save_p, 0xffff);
+}
+
+static void UnArchiveFFloors(const sector_t *ss)
+{
+	UINT16 j = 0; // number of current ffloor in loop
+	UINT16 fflr_i; // saved ffloor "number" of next modified ffloor
+	UINT16 fflr_diff; // saved ffloor diff
+	ffloor_t *rover;
+
+	rover = ss->ffloors;
+	if (!rover) // it is assumed sectors[i].ffloors actually exists, but just in case...
+		I_Error("Sector does not have any ffloors!");
+
+	fflr_i = READUINT16(save_p); // get first modified ffloor's number ready
+	for (;;) // for some reason the usual for (rover = x; ...) thing doesn't work here?
+	{
+		if (fflr_i == 0xffff) // end of modified ffloors list, let's stop already
+			break;
+		// should NEVER need to be checked
+		//if (rover == NULL)
+			//break;
+		if (j != fflr_i) // this ffloor was not modified
+		{
+			j++;
+			rover = rover->next;
+			continue;
+		}
+
+		fflr_diff = READUINT8(save_p);
+
+		if (fflr_diff & FD_FLAGS)
+			rover->flags = READUINT32(save_p);
+		if (fflr_diff & FD_ALPHA)
+			rover->alpha = READINT16(save_p);
+
+		fflr_i = READUINT16(save_p); // get next ffloor "number" ready
+
+		j++;
+		rover = rover->next;
+	}
+}
 
+static void ArchiveSectors(void)
+{
+	size_t i;
 	const sector_t *ss = sectors;
 	const sector_t *spawnss = spawnsectors;
 	UINT8 diff, diff2, diff3;
 
-	// initialize colormap vars because paranoia
-	ClearNetColormaps();
-
-	WRITEUINT32(save_p, ARCHIVEBLOCK_WORLD);
-	put = save_p;
-
 	for (i = 0; i < numsectors; i++, ss++, spawnss++)
 	{
 		diff = diff2 = diff3 = 0;
@@ -865,20 +926,8 @@ static void P_NetArchiveWorld(void)
 		if (ss->crumblestate)
 			diff3 |= SD_CRUMBLESTATE;
 
-		// Check if any of the sector's FOFs differ from how they spawned
-		if (ss->ffloors)
-		{
-			ffloor_t *rover;
-			for (rover = ss->ffloors; rover; rover = rover->next)
-			{
-				if (rover->flags != rover->spawnflags
-				|| rover->alpha != rover->spawnalpha)
-					{
-						diff |= SD_FFLOORS; // we found an FOF that changed!
-						break; // don't bother checking for more, we do that later
-					}
-			}
-		}
+		if (ss->ffloors && CheckFFloorDiff(ss))
+			diff |= SD_FFLOORS;
 
 		if (diff3)
 			diff2 |= SD_DIFF3;
@@ -888,89 +937,142 @@ static void P_NetArchiveWorld(void)
 
 		if (diff)
 		{
-			statsec++;
-
-			WRITEUINT16(put, i);
-			WRITEUINT8(put, diff);
+			WRITEUINT16(save_p, i);
+			WRITEUINT8(save_p, diff);
 			if (diff & SD_DIFF2)
-				WRITEUINT8(put, diff2);
+				WRITEUINT8(save_p, diff2);
 			if (diff2 & SD_DIFF3)
-				WRITEUINT8(put, diff3);
+				WRITEUINT8(save_p, diff3);
 			if (diff & SD_FLOORHT)
-				WRITEFIXED(put, ss->floorheight);
+				WRITEFIXED(save_p, ss->floorheight);
 			if (diff & SD_CEILHT)
-				WRITEFIXED(put, ss->ceilingheight);
+				WRITEFIXED(save_p, ss->ceilingheight);
 			if (diff & SD_FLOORPIC)
-				WRITEMEM(put, levelflats[ss->floorpic].name, 8);
+				WRITEMEM(save_p, levelflats[ss->floorpic].name, 8);
 			if (diff & SD_CEILPIC)
-				WRITEMEM(put, levelflats[ss->ceilingpic].name, 8);
+				WRITEMEM(save_p, levelflats[ss->ceilingpic].name, 8);
 			if (diff & SD_LIGHT)
-				WRITEINT16(put, ss->lightlevel);
+				WRITEINT16(save_p, ss->lightlevel);
 			if (diff & SD_SPECIAL)
-				WRITEINT16(put, ss->special);
+				WRITEINT16(save_p, ss->special);
 			if (diff2 & SD_FXOFFS)
-				WRITEFIXED(put, ss->floor_xoffs);
+				WRITEFIXED(save_p, ss->floor_xoffs);
 			if (diff2 & SD_FYOFFS)
-				WRITEFIXED(put, ss->floor_yoffs);
+				WRITEFIXED(save_p, ss->floor_yoffs);
 			if (diff2 & SD_CXOFFS)
-				WRITEFIXED(put, ss->ceiling_xoffs);
+				WRITEFIXED(save_p, ss->ceiling_xoffs);
 			if (diff2 & SD_CYOFFS)
-				WRITEFIXED(put, ss->ceiling_yoffs);
+				WRITEFIXED(save_p, ss->ceiling_yoffs);
 			if (diff2 & SD_FLOORANG)
-				WRITEANGLE(put, ss->floorpic_angle);
+				WRITEANGLE(save_p, ss->floorpic_angle);
 			if (diff2 & SD_CEILANG)
-				WRITEANGLE(put, ss->ceilingpic_angle);
+				WRITEANGLE(save_p, ss->ceilingpic_angle);
 			if (diff2 & SD_TAG) // save only the tag
-				WRITEINT16(put, ss->tag);
+				WRITEINT16(save_p, ss->tag);
 			if (diff3 & SD_TAGLIST) // save both firsttag and nexttag
 			{ // either of these could be changed even if tag isn't
-				WRITEINT32(put, ss->firsttag);
-				WRITEINT32(put, ss->nexttag);
+				WRITEINT32(save_p, ss->firsttag);
+				WRITEINT32(save_p, ss->nexttag);
 			}
 
 			if (diff3 & SD_COLORMAP)
-				WRITEUINT32(put, CheckAddNetColormapToList(ss->extra_colormap));
+				WRITEUINT32(save_p, CheckAddNetColormapToList(ss->extra_colormap));
 					// returns existing index if already added, or appends to net_colormaps and returns new index
 			if (diff3 & SD_CRUMBLESTATE)
-				WRITEINT32(put, ss->crumblestate);
-
-			// Special case: save the stats of all modified ffloors along with their ffloor "number"s
-			// we don't bother with ffloors that haven't changed, that would just add to savegame even more than is really needed
+				WRITEINT32(save_p, ss->crumblestate);
 			if (diff & SD_FFLOORS)
-			{
-				size_t j = 0; // ss->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc
-				ffloor_t *rover;
-				UINT8 fflr_diff;
-				for (rover = ss->ffloors; rover; rover = rover->next)
-				{
-					fflr_diff = 0; // reset diff flags
-					if (rover->flags != rover->spawnflags)
-						fflr_diff |= 1;
-					if (rover->alpha != rover->spawnalpha)
-						fflr_diff |= 2;
+				ArchiveFFloors(ss);
+		}
+	}
 
-					if (fflr_diff)
-					{
-						WRITEUINT16(put, j); // save ffloor "number"
-						WRITEUINT8(put, fflr_diff);
-						if (fflr_diff & 1)
-							WRITEUINT32(put, rover->flags);
-						if (fflr_diff & 2)
-							WRITEINT16(put, rover->alpha);
-					}
-					j++;
-				}
-				WRITEUINT16(put, 0xffff);
-			}
+	WRITEUINT16(save_p, 0xffff);
+}
+
+static void UnArchiveSectors(void)
+{
+	UINT16 i;
+	UINT8 diff, diff2, diff3;
+	for (;;)
+	{
+		i = READUINT16(save_p);
+
+		if (i == 0xffff)
+			break;
+
+		if (i > numsectors)
+			I_Error("Invalid sector number %u from server (expected end at %s)", i, sizeu1(numsectors));
+
+		diff = READUINT8(save_p);
+		if (diff & SD_DIFF2)
+			diff2 = READUINT8(save_p);
+		else
+			diff2 = 0;
+		if (diff2 & SD_DIFF3)
+			diff3 = READUINT8(save_p);
+		else
+			diff3 = 0;
+
+		if (diff & SD_FLOORHT)
+			sectors[i].floorheight = READFIXED(save_p);
+		if (diff & SD_CEILHT)
+			sectors[i].ceilingheight = READFIXED(save_p);
+		if (diff & SD_FLOORPIC)
+		{
+			sectors[i].floorpic = P_AddLevelFlatRuntime((char *)save_p);
+			save_p += 8;
 		}
+		if (diff & SD_CEILPIC)
+		{
+			sectors[i].ceilingpic = P_AddLevelFlatRuntime((char *)save_p);
+			save_p += 8;
+		}
+		if (diff & SD_LIGHT)
+			sectors[i].lightlevel = READINT16(save_p);
+		if (diff & SD_SPECIAL)
+			sectors[i].special = READINT16(save_p);
+
+		if (diff2 & SD_FXOFFS)
+			sectors[i].floor_xoffs = READFIXED(save_p);
+		if (diff2 & SD_FYOFFS)
+			sectors[i].floor_yoffs = READFIXED(save_p);
+		if (diff2 & SD_CXOFFS)
+			sectors[i].ceiling_xoffs = READFIXED(save_p);
+		if (diff2 & SD_CYOFFS)
+			sectors[i].ceiling_yoffs = READFIXED(save_p);
+		if (diff2 & SD_FLOORANG)
+			sectors[i].floorpic_angle  = READANGLE(save_p);
+		if (diff2 & SD_CEILANG)
+			sectors[i].ceilingpic_angle = READANGLE(save_p);
+		if (diff2 & SD_TAG)
+			sectors[i].tag = READINT16(save_p); // DON'T use P_ChangeSectorTag
+		if (diff3 & SD_TAGLIST)
+		{
+			sectors[i].firsttag = READINT32(save_p);
+			sectors[i].nexttag = READINT32(save_p);
+		}
+
+		if (diff3 & SD_COLORMAP)
+			sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(save_p));
+		if (diff3 & SD_CRUMBLESTATE)
+			sectors[i].crumblestate = READINT32(save_p);
+
+		if (diff & SD_FFLOORS)
+			UnArchiveFFloors(&sectors[i]);
 	}
+}
 
-	WRITEUINT16(put, 0xffff);
+static void ArchiveLines(void)
+{
+	size_t i;
+	const line_t *li = lines;
+	const line_t *spawnli = spawnlines;
+	const side_t *si;
+	const side_t *spawnsi;
+	UINT8 diff, diff2; // no diff3
 
-	// do lines
 	for (i = 0; i < numlines; i++, spawnli++, li++)
 	{
-		diff = diff2 = diff3 = 0;
+		diff = diff2 = 0;
 
 		if (li->special != spawnli->special)
 			diff |= LD_SPECIAL;
@@ -1017,42 +1119,41 @@ static void P_NetArchiveWorld(void)
 
 		if (diff)
 		{
-			statline++;
-			WRITEINT16(put, i);
-			WRITEUINT8(put, diff);
+			WRITEINT16(save_p, i);
+			WRITEUINT8(save_p, diff);
 			if (diff & LD_DIFF2)
-				WRITEUINT8(put, diff2);
+				WRITEUINT8(save_p, diff2);
 			if (diff & LD_FLAG)
-				WRITEINT16(put, li->flags);
+				WRITEINT16(save_p, li->flags);
 			if (diff & LD_SPECIAL)
-				WRITEINT16(put, li->special);
+				WRITEINT16(save_p, li->special);
 			if (diff & LD_CLLCOUNT)
-				WRITEINT16(put, li->callcount);
+				WRITEINT16(save_p, li->callcount);
 
 			si = &sides[li->sidenum[0]];
 			if (diff & LD_S1TEXOFF)
-				WRITEFIXED(put, si->textureoffset);
+				WRITEFIXED(save_p, si->textureoffset);
 			if (diff & LD_S1TOPTEX)
-				WRITEINT32(put, si->toptexture);
+				WRITEINT32(save_p, si->toptexture);
 			if (diff & LD_S1BOTTEX)
-				WRITEINT32(put, si->bottomtexture);
+				WRITEINT32(save_p, si->bottomtexture);
 			if (diff & LD_S1MIDTEX)
-				WRITEINT32(put, si->midtexture);
+				WRITEINT32(save_p, si->midtexture);
 
 			si = &sides[li->sidenum[1]];
 			if (diff2 & LD_S2TEXOFF)
-				WRITEFIXED(put, si->textureoffset);
+				WRITEFIXED(save_p, si->textureoffset);
 			if (diff2 & LD_S2TOPTEX)
-				WRITEINT32(put, si->toptexture);
+				WRITEINT32(save_p, si->toptexture);
 			if (diff2 & LD_S2BOTTEX)
-				WRITEINT32(put, si->bottomtexture);
+				WRITEINT32(save_p, si->bottomtexture);
 			if (diff2 & LD_S2MIDTEX)
-				WRITEINT32(put, si->midtexture);
+				WRITEINT32(save_p, si->midtexture);
 			if (diff2 & LD_ARGS)
 			{
 				UINT8 j;
 				for (j = 0; j < NUMLINEARGS; j++)
-					WRITEINT32(put, li->args[j]);
+					WRITEINT32(save_p, li->args[j]);
 			}
 			if (diff2 & LD_STRINGARGS)
 			{
@@ -1063,213 +1164,83 @@ static void P_NetArchiveWorld(void)
 
 					if (!li->stringargs[j])
 					{
-						WRITEINT32(put, 0);
+						WRITEINT32(save_p, 0);
 						continue;
 					}
 
 					len = strlen(li->stringargs[j]);
-					WRITEINT32(put, len);
+					WRITEINT32(save_p, len);
 					for (k = 0; k < len; k++)
-						WRITECHAR(put, li->stringargs[j][k]);
+						WRITECHAR(save_p, li->stringargs[j][k]);
 				}
 			}
 		}
 	}
-	WRITEUINT16(put, 0xffff);
-	R_ClearTextureNumCache(false);
-
-	save_p = put;
+	WRITEUINT16(save_p, 0xffff);
 }
 
-//
-// P_NetUnArchiveWorld
-//
-static void P_NetUnArchiveWorld(void)
+static void UnArchiveLines(void)
 {
 	UINT16 i;
 	line_t *li;
 	side_t *si;
-	UINT8 *get;
-	UINT8 diff, diff2, diff3;
-
-	if (READUINT32(save_p) != ARCHIVEBLOCK_WORLD)
-		I_Error("Bad $$$.sav at archive block World");
-
-	// initialize colormap vars because paranoia
-	ClearNetColormaps();
-
-	// count the level's ffloors so that colormap loading can have an upper limit
-	for (i = 0; i < numsectors; i++)
-	{
-		ffloor_t *rover;
-		for (rover = sectors[i].ffloors; rover; rover = rover->next)
-			num_ffloors++;
-	}
-
-	get = save_p;
-
-	for (;;)
-	{
-		i = READUINT16(get);
-
-		if (i == 0xffff)
-			break;
-
-		if (i > numsectors)
-			I_Error("Invalid sector number %u from server (expected end at %s)", i, sizeu1(numsectors));
-
-		diff = READUINT8(get);
-		if (diff & SD_DIFF2)
-			diff2 = READUINT8(get);
-		else
-			diff2 = 0;
-		if (diff2 & SD_DIFF3)
-			diff3 = READUINT8(get);
-		else
-			diff3 = 0;
-
-		if (diff & SD_FLOORHT)
-			sectors[i].floorheight = READFIXED(get);
-		if (diff & SD_CEILHT)
-			sectors[i].ceilingheight = READFIXED(get);
-		if (diff & SD_FLOORPIC)
-		{
-			sectors[i].floorpic = P_AddLevelFlatRuntime((char *)get);
-			get += 8;
-		}
-		if (diff & SD_CEILPIC)
-		{
-			sectors[i].ceilingpic = P_AddLevelFlatRuntime((char *)get);
-			get += 8;
-		}
-		if (diff & SD_LIGHT)
-			sectors[i].lightlevel = READINT16(get);
-		if (diff & SD_SPECIAL)
-			sectors[i].special = READINT16(get);
-
-		if (diff2 & SD_FXOFFS)
-			sectors[i].floor_xoffs = READFIXED(get);
-		if (diff2 & SD_FYOFFS)
-			sectors[i].floor_yoffs = READFIXED(get);
-		if (diff2 & SD_CXOFFS)
-			sectors[i].ceiling_xoffs = READFIXED(get);
-		if (diff2 & SD_CYOFFS)
-			sectors[i].ceiling_yoffs = READFIXED(get);
-		if (diff2 & SD_FLOORANG)
-			sectors[i].floorpic_angle  = READANGLE(get);
-		if (diff2 & SD_CEILANG)
-			sectors[i].ceilingpic_angle = READANGLE(get);
-		if (diff2 & SD_TAG)
-			sectors[i].tag = READINT16(get); // DON'T use P_ChangeSectorTag
-		if (diff3 & SD_TAGLIST)
-		{
-			sectors[i].firsttag = READINT32(get);
-			sectors[i].nexttag = READINT32(get);
-		}
-
-		if (diff3 & SD_COLORMAP)
-			sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(get));
-		if (diff3 & SD_CRUMBLESTATE)
-			sectors[i].crumblestate = READINT32(get);
-
-		if (diff & SD_FFLOORS)
-		{
-			UINT16 j = 0; // number of current ffloor in loop
-			UINT16 fflr_i; // saved ffloor "number" of next modified ffloor
-			UINT16 fflr_diff; // saved ffloor diff
-			ffloor_t *rover;
-
-			rover = sectors[i].ffloors;
-			if (!rover) // it is assumed sectors[i].ffloors actually exists, but just in case...
-				I_Error("Sector does not have any ffloors!");
-
-			fflr_i = READUINT16(get); // get first modified ffloor's number ready
-			for (;;) // for some reason the usual for (rover = x; ...) thing doesn't work here?
-			{
-				if (fflr_i == 0xffff) // end of modified ffloors list, let's stop already
-					break;
-				// should NEVER need to be checked
-				//if (rover == NULL)
-					//break;
-				if (j != fflr_i) // this ffloor was not modified
-				{
-					j++;
-					rover = rover->next;
-					continue;
-				}
-
-				fflr_diff = READUINT8(get);
-
-				if (fflr_diff & 1)
-					rover->flags = READUINT32(get);
-				if (fflr_diff & 2)
-					rover->alpha = READINT16(get);
-
-				fflr_i = READUINT16(get); // get next ffloor "number" ready
-
-				j++;
-				rover = rover->next;
-			}
-		}
-	}
+	UINT8 diff, diff2; // no diff3
 
 	for (;;)
 	{
-		i = READUINT16(get);
+		i = READUINT16(save_p);
 
 		if (i == 0xffff)
 			break;
 		if (i > numlines)
 			I_Error("Invalid line number %u from server", i);
 
-		diff = READUINT8(get);
+		diff = READUINT8(save_p);
 		li = &lines[i];
 
 		if (diff & LD_DIFF2)
-			diff2 = READUINT8(get);
+			diff2 = READUINT8(save_p);
 		else
 			diff2 = 0;
 
-		diff3 = 0;
-
 		if (diff & LD_FLAG)
-			li->flags = READINT16(get);
+			li->flags = READINT16(save_p);
 		if (diff & LD_SPECIAL)
-			li->special = READINT16(get);
+			li->special = READINT16(save_p);
 		if (diff & LD_CLLCOUNT)
-			li->callcount = READINT16(get);
+			li->callcount = READINT16(save_p);
 
 		si = &sides[li->sidenum[0]];
 		if (diff & LD_S1TEXOFF)
-			si->textureoffset = READFIXED(get);
+			si->textureoffset = READFIXED(save_p);
 		if (diff & LD_S1TOPTEX)
-			si->toptexture = READINT32(get);
+			si->toptexture = READINT32(save_p);
 		if (diff & LD_S1BOTTEX)
-			si->bottomtexture = READINT32(get);
+			si->bottomtexture = READINT32(save_p);
 		if (diff & LD_S1MIDTEX)
-			si->midtexture = READINT32(get);
+			si->midtexture = READINT32(save_p);
 
 		si = &sides[li->sidenum[1]];
 		if (diff2 & LD_S2TEXOFF)
-			si->textureoffset = READFIXED(get);
+			si->textureoffset = READFIXED(save_p);
 		if (diff2 & LD_S2TOPTEX)
-			si->toptexture = READINT32(get);
+			si->toptexture = READINT32(save_p);
 		if (diff2 & LD_S2BOTTEX)
-			si->bottomtexture = READINT32(get);
+			si->bottomtexture = READINT32(save_p);
 		if (diff2 & LD_S2MIDTEX)
-			si->midtexture = READINT32(get);
+			si->midtexture = READINT32(save_p);
 		if (diff2 & LD_ARGS)
 		{
 			UINT8 j;
 			for (j = 0; j < NUMLINEARGS; j++)
-				li->args[j] = READINT32(get);
+				li->args[j] = READINT32(save_p);
 		}
 		if (diff2 & LD_STRINGARGS)
 		{
 			UINT8 j;
 			for (j = 0; j < NUMLINESTRINGARGS; j++)
 			{
-				size_t len = READINT32(get);
+				size_t len = READINT32(save_p);
 				size_t k;
 
 				if (!len)
@@ -1281,13 +1252,45 @@ static void P_NetUnArchiveWorld(void)
 
 				li->stringargs[j] = Z_Realloc(li->stringargs[j], len + 1, PU_LEVEL, NULL);
 				for (k = 0; k < len; k++)
-					li->stringargs[j][k] = READCHAR(get);
+					li->stringargs[j][k] = READCHAR(save_p);
 				li->stringargs[j][len] = '\0';
 			}
 		}
 	}
+}
+
+static void P_NetArchiveWorld(void)
+{
+	// initialize colormap vars because paranoia
+	ClearNetColormaps();
 
-	save_p = get;
+	WRITEUINT32(save_p, ARCHIVEBLOCK_WORLD);
+
+	ArchiveSectors();
+	ArchiveLines();
+	R_ClearTextureNumCache(false);
+}
+
+static void P_NetUnArchiveWorld(void)
+{
+	UINT16 i;
+
+	if (READUINT32(save_p) != ARCHIVEBLOCK_WORLD)
+		I_Error("Bad $$$.sav at archive block World");
+
+	// initialize colormap vars because paranoia
+	ClearNetColormaps();
+
+	// count the level's ffloors so that colormap loading can have an upper limit
+	for (i = 0; i < numsectors; i++)
+	{
+		ffloor_t *rover;
+		for (rover = sectors[i].ffloors; rover; rover = rover->next)
+			num_ffloors++;
+	}
+
+	UnArchiveSectors();
+	UnArchiveLines();
 }
 
 //
@@ -1427,11 +1430,6 @@ static UINT32 SaveSlope(const pslope_t *slope)
 	return 0xFFFFFFFF;
 }
 
-//
-// SaveMobjThinker
-//
-// Saves a mobj_t thinker
-//
 static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 {
 	const mobj_t *mobj = (const mobj_t *)th;
@@ -1585,42 +1583,14 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 
 	if (diff2 & MD2_FLOORROVER)
 	{
-		ffloor_t *rover;
-		size_t i = 0;
-		UINT32 roverindex = 0;
-
-		for (rover = mobj->floorrover->target->ffloors; rover; rover = rover->next)
-		{
-			if (rover == mobj->floorrover)
-			{
-				roverindex = i;
-				break;
-			}
-			i++;
-		}
-
-		WRITEUINT32(save_p, (UINT32)(mobj->floorrover->target - sectors));
-		WRITEUINT32(save_p, rover ? roverindex : i); // store max index to denote invalid ffloor ref
+		WRITEUINT32(save_p, SaveSector(mobj->floorrover->target));
+		WRITEUINT16(save_p, P_GetFFloorID(mobj->floorrover));
 	}
 
 	if (diff2 & MD2_CEILINGROVER)
 	{
-		ffloor_t *rover;
-		size_t i = 0;
-		UINT32 roverindex = 0;
-
-		for (rover = mobj->ceilingrover->target->ffloors; rover; rover = rover->next)
-		{
-			if (rover == mobj->ceilingrover)
-			{
-				roverindex = i;
-				break;
-			}
-			i++;
-		}
-
-		WRITEUINT32(save_p, (UINT32)(mobj->ceilingrover->target - sectors));
-		WRITEUINT32(save_p, rover ? roverindex : i); // store max index to denote invalid ffloor ref
+		WRITEUINT32(save_p, SaveSector(mobj->ceilingrover->target));
+		WRITEUINT16(save_p, P_GetFFloorID(mobj->ceilingrover));
 	}
 
 	if (diff & MD_SPAWNPOINT)
@@ -1736,11 +1706,6 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT32(save_p, mobj->mobjnum);
 }
 
-//
-// SaveNoEnemiesThinker
-//
-// Saves a noenemies_t thinker
-//
 static void SaveNoEnemiesThinker(const thinker_t *th, const UINT8 type)
 {
 	const noenemies_t *ht  = (const void *)th;
@@ -1748,11 +1713,6 @@ static void SaveNoEnemiesThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT32(save_p, SaveLine(ht->sourceline));
 }
 
-//
-// SaveBounceCheeseThinker
-//
-// Saves a bouncecheese_t thinker
-//
 static void SaveBounceCheeseThinker(const thinker_t *th, const UINT8 type)
 {
 	const bouncecheese_t *ht  = (const void *)th;
@@ -1766,11 +1726,6 @@ static void SaveBounceCheeseThinker(const thinker_t *th, const UINT8 type)
 	WRITECHAR(save_p, ht->low);
 }
 
-//
-// SaveContinuousFallThinker
-//
-// Saves a continuousfall_t thinker
-//
 static void SaveContinuousFallThinker(const thinker_t *th, const UINT8 type)
 {
 	const continuousfall_t *ht  = (const void *)th;
@@ -1783,11 +1738,6 @@ static void SaveContinuousFallThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->destheight);
 }
 
-//
-// SaveMarioBlockThinker
-//
-// Saves a mariothink_t thinker
-//
 static void SaveMarioBlockThinker(const thinker_t *th, const UINT8 type)
 {
 	const mariothink_t *ht  = (const void *)th;
@@ -1800,11 +1750,6 @@ static void SaveMarioBlockThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT16(save_p, ht->tag);
 }
 
-//
-// SaveMarioCheckThinker
-//
-// Saves a mariocheck_t thinker
-//
 static void SaveMarioCheckThinker(const thinker_t *th, const UINT8 type)
 {
 	const mariocheck_t *ht  = (const void *)th;
@@ -1813,11 +1758,6 @@ static void SaveMarioCheckThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT32(save_p, SaveSector(ht->sector));
 }
 
-//
-// SaveThwompThinker
-//
-// Saves a thwomp_t thinker
-//
 static void SaveThwompThinker(const thinker_t *th, const UINT8 type)
 {
 	const thwomp_t *ht  = (const void *)th;
@@ -1834,11 +1774,6 @@ static void SaveThwompThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT16(save_p, ht->sound);
 }
 
-//
-// SaveFloatThinker
-//
-// Saves a floatthink_t thinker
-//
 static void SaveFloatThinker(const thinker_t *th, const UINT8 type)
 {
 	const floatthink_t *ht  = (const void *)th;
@@ -1848,10 +1783,6 @@ static void SaveFloatThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT16(save_p, ht->tag);
 }
 
-// SaveEachTimeThinker
-//
-// Loads a eachtime_t from a save game
-//
 static void SaveEachTimeThinker(const thinker_t *th, const UINT8 type)
 {
 	const eachtime_t *ht  = (const void *)th;
@@ -1866,15 +1797,11 @@ static void SaveEachTimeThinker(const thinker_t *th, const UINT8 type)
 	WRITECHAR(save_p, ht->triggerOnExit);
 }
 
-// SaveRaiseThinker
-//
-// Saves a raise_t thinker
-//
 static void SaveRaiseThinker(const thinker_t *th, const UINT8 type)
 {
 	const raise_t *ht  = (const void *)th;
 	WRITEUINT8(save_p, type);
-	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEINT16(save_p, ht->tag);
 	WRITEUINT32(save_p, SaveSector(ht->sector));
 	WRITEFIXED(save_p, ht->ceilingbottom);
 	WRITEFIXED(save_p, ht->ceilingtop);
@@ -1884,11 +1811,6 @@ static void SaveRaiseThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->flags);
 }
 
-//
-// SaveCeilingThinker
-//
-// Saves a ceiling_t thinker
-//
 static void SaveCeilingThinker(const thinker_t *th, const UINT8 type)
 {
 	const ceiling_t *ht = (const void *)th;
@@ -1909,12 +1831,7 @@ static void SaveCeilingThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->origspeed);
 	WRITEFIXED(save_p, ht->sourceline);
 }
-
-//
-// SaveFloormoveThinker
-//
-// Saves a floormove_t thinker
-//
+
 static void SaveFloormoveThinker(const thinker_t *th, const UINT8 type)
 {
 	const floormove_t *ht = (const void *)th;
@@ -1931,11 +1848,6 @@ static void SaveFloormoveThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->delaytimer);
 }
 
-//
-// SaveLightflashThinker
-//
-// Saves a lightflash_t thinker
-//
 static void SaveLightflashThinker(const thinker_t *th, const UINT8 type)
 {
 	const lightflash_t *ht = (const void *)th;
@@ -1945,11 +1857,6 @@ static void SaveLightflashThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->minlight);
 }
 
-//
-// SaveStrobeThinker
-//
-// Saves a strobe_t thinker
-//
 static void SaveStrobeThinker(const thinker_t *th, const UINT8 type)
 {
 	const strobe_t *ht = (const void *)th;
@@ -1962,11 +1869,6 @@ static void SaveStrobeThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->brighttime);
 }
 
-//
-// SaveGlowThinker
-//
-// Saves a glow_t thinker
-//
 static void SaveGlowThinker(const thinker_t *th, const UINT8 type)
 {
 	const glow_t *ht = (const void *)th;
@@ -1977,11 +1879,7 @@ static void SaveGlowThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->direction);
 	WRITEINT32(save_p, ht->speed);
 }
-//
-// SaveFireflickerThinker
-//
-// Saves a fireflicker_t thinker
-//
+
 static inline void SaveFireflickerThinker(const thinker_t *th, const UINT8 type)
 {
 	const fireflicker_t *ht = (const void *)th;
@@ -1992,11 +1890,7 @@ static inline void SaveFireflickerThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->maxlight);
 	WRITEINT32(save_p, ht->minlight);
 }
-//
-// SaveElevatorThinker
-//
-// Saves a elevator_t thinker
-//
+
 static void SaveElevatorThinker(const thinker_t *th, const UINT8 type)
 {
 	const elevator_t *ht = (const void *)th;
@@ -2016,15 +1910,26 @@ static void SaveElevatorThinker(const thinker_t *th, const UINT8 type)
 	WRITEFIXED(save_p, ht->delaytimer);
 	WRITEFIXED(save_p, ht->floorwasheight);
 	WRITEFIXED(save_p, ht->ceilingwasheight);
-	WRITEUINT32(save_p, SavePlayer(ht->player)); // was dummy
 	WRITEUINT32(save_p, SaveLine(ht->sourceline));
 }
 
-//
-// SaveScrollThinker
-//
-// Saves a scroll_t thinker
-//
+static void SaveCrumbleThinker(const thinker_t *th, const UINT8 type)
+{
+	const crumble_t *ht = (const void *)th;
+	WRITEUINT8(save_p, type);
+	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEUINT32(save_p, SaveSector(ht->sector));
+	WRITEUINT32(save_p, SaveSector(ht->actionsector));
+	WRITEUINT32(save_p, SavePlayer(ht->player)); // was dummy
+	WRITEINT32(save_p, ht->direction);
+	WRITEINT32(save_p, ht->origalpha);
+	WRITEINT32(save_p, ht->timer);
+	WRITEFIXED(save_p, ht->speed);
+	WRITEFIXED(save_p, ht->floorwasheight);
+	WRITEFIXED(save_p, ht->ceilingwasheight);
+	WRITEUINT8(save_p, ht->flags);
+}
+
 static inline void SaveScrollThinker(const thinker_t *th, const UINT8 type)
 {
 	const scroll_t *ht = (const void *)th;
@@ -2041,11 +1946,6 @@ static inline void SaveScrollThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->type);
 }
 
-//
-// SaveFrictionThinker
-//
-// Saves a friction_t thinker
-//
 static inline void SaveFrictionThinker(const thinker_t *th, const UINT8 type)
 {
 	const friction_t *ht = (const void *)th;
@@ -2057,11 +1957,6 @@ static inline void SaveFrictionThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->roverfriction);
 }
 
-//
-// SavePusherThinker
-//
-// Saves a pusher_t thinker
-//
 static inline void SavePusherThinker(const thinker_t *th, const UINT8 type)
 {
 	const pusher_t *ht = (const void *)th;
@@ -2081,11 +1976,6 @@ static inline void SavePusherThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->slider);
 }
 
-//
-// SaveLaserThinker
-//
-// Saves a laserthink_t thinker
-//
 static void SaveLaserThinker(const thinker_t *th, const UINT8 type)
 {
 	const laserthink_t *ht = (const void *)th;
@@ -2093,13 +1983,9 @@ static void SaveLaserThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT32(save_p, SaveSector(ht->sector));
 	WRITEUINT32(save_p, SaveSector(ht->sec));
 	WRITEUINT32(save_p, SaveLine(ht->sourceline));
+	WRITEUINT8(save_p, ht->nobosses);
 }
 
-//
-// SaveLightlevelThinker
-//
-// Saves a lightlevel_t thinker
-//
 static void SaveLightlevelThinker(const thinker_t *th, const UINT8 type)
 {
 	const lightlevel_t *ht = (const void *)th;
@@ -2112,11 +1998,6 @@ static void SaveLightlevelThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->timer);
 }
 
-//
-// SaveExecutorThinker
-//
-// Saves a executor_t thinker
-//
 static void SaveExecutorThinker(const thinker_t *th, const UINT8 type)
 {
 	const executor_t *ht = (const void *)th;
@@ -2127,11 +2008,6 @@ static void SaveExecutorThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->timer);
 }
 
-//
-// SaveDisappearThinker
-//
-// Saves a disappear_t thinker
-//
 static void SaveDisappearThinker(const thinker_t *th, const UINT8 type)
 {
 	const disappear_t *ht = (const void *)th;
@@ -2145,11 +2021,6 @@ static void SaveDisappearThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->exists);
 }
 
-//
-// SaveFadeThinker
-//
-// Saves a fade_t thinker
-//
 static void SaveFadeThinker(const thinker_t *th, const UINT8 type)
 {
 	const fade_t *ht = (const void *)th;
@@ -2173,11 +2044,6 @@ static void SaveFadeThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->exactalpha);
 }
 
-//
-// SaveFadeColormapThinker
-//
-// Saves a fadecolormap_t thinker
-//
 static void SaveFadeColormapThinker(const thinker_t *th, const UINT8 type)
 {
 	const fadecolormap_t *ht = (const void *)th;
@@ -2190,11 +2056,6 @@ static void SaveFadeColormapThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->timer);
 }
 
-//
-// SavePlaneDisplaceThinker
-//
-// Saves a planedisplace_t thinker
-//
 static void SavePlaneDisplaceThinker(const thinker_t *th, const UINT8 type)
 {
 	const planedisplace_t *ht = (const void *)th;
@@ -2206,7 +2067,6 @@ static void SavePlaneDisplaceThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->type);
 }
 
-/// Save a dynamic slope thinker.
 static inline void SaveDynamicSlopeThinker(const thinker_t *th, const UINT8 type)
 {
 	const dynplanethink_t* ht = (const void*)th;
@@ -2222,12 +2082,6 @@ static inline void SaveDynamicSlopeThinker(const thinker_t *th, const UINT8 type
 }
 
 #ifdef POLYOBJECTS
-
-//
-// SavePolyrotateThinker
-//
-// Saves a polyrotate_t thinker
-//
 static inline void SavePolyrotatetThinker(const thinker_t *th, const UINT8 type)
 {
 	const polyrotate_t *ht = (const void *)th;
@@ -2237,11 +2091,6 @@ static inline void SavePolyrotatetThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->distance);
 }
 
-//
-// SavePolymoveThinker
-//
-// Saves a polymovet_t thinker
-//
 static void SavePolymoveThinker(const thinker_t *th, const UINT8 type)
 {
 	const polymove_t *ht = (const void *)th;
@@ -2254,11 +2103,6 @@ static void SavePolymoveThinker(const thinker_t *th, const UINT8 type)
 	WRITEANGLE(save_p, ht->angle);
 }
 
-//
-// SavePolywaypointThinker
-//
-// Saves a polywaypoint_t thinker
-//
 static void SavePolywaypointThinker(const thinker_t *th, UINT8 type)
 {
 	const polywaypoint_t *ht = (const void *)th;
@@ -2278,11 +2122,6 @@ static void SavePolywaypointThinker(const thinker_t *th, UINT8 type)
 	WRITEUINT32(save_p, SaveMobjnum(ht->target));
 }
 
-//
-// SavePolyslidedoorThinker
-//
-// Saves a polyslidedoor_t thinker
-//
 static void SavePolyslidedoorThinker(const thinker_t *th, const UINT8 type)
 {
 	const polyslidedoor_t *ht = (const void *)th;
@@ -2302,11 +2141,6 @@ static void SavePolyslidedoorThinker(const thinker_t *th, const UINT8 type)
 	WRITEUINT8(save_p, ht->closing);
 }
 
-//
-// SavePolyswingdoorThinker
-//
-// Saves a polyswingdoor_t thinker
-//
 static void SavePolyswingdoorThinker(const thinker_t *th, const UINT8 type)
 {
 	const polyswingdoor_t *ht = (const void *)th;
@@ -2356,25 +2190,8 @@ static void SavePolyfadeThinker(const thinker_t *th, const UINT8 type)
 	WRITEINT32(save_p, ht->duration);
 	WRITEINT32(save_p, ht->timer);
 }
-
 #endif
-/*
-//
-// SaveWhatThinker
-//
-// Saves a what_t thinker
-//
-static inline void SaveWhatThinker(const thinker_t *th, const UINT8 type)
-{
-	const what_t *ht = (const void *)th;
-	WRITEUINT8(save_p, type);
-}
-*/
 
-//
-// P_NetArchiveThinkers
-//
-//
 static void P_NetArchiveThinkers(void)
 {
 	const thinker_t *th;
@@ -2492,7 +2309,7 @@ static void P_NetArchiveThinkers(void)
 			}
 			else if (th->function.acp1 == (actionf_p1)T_StartCrumble)
 			{
-				SaveElevatorThinker(th, tc_startcrumble);
+				SaveCrumbleThinker(th, tc_startcrumble);
 				continue;
 			}
 			else if (th->function.acp1 == (actionf_p1)T_MarioBlock)
@@ -2674,11 +2491,6 @@ static inline pslope_t *LoadSlope(UINT32 slopeid)
 	return NULL;
 }
 
-//
-// LoadMobjThinker
-//
-// Loads a mobj_t from a save game
-//
 static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 {
 	thinker_t *next;
@@ -2703,34 +2515,16 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 
 	if (diff2 & MD2_FLOORROVER)
 	{
-		size_t floor_sectornum = (size_t)READUINT32(save_p);
-		size_t floor_rovernum = (size_t)READUINT32(save_p);
-		ffloor_t *rover = NULL;
-		size_t rovernum = 0;
-
-		for (rover = sectors[floor_sectornum].ffloors; rover; rover = rover->next)
-		{
-			if (rovernum == floor_rovernum)
-				break;
-			rovernum++;
-		}
-		floorrover = rover;
+		sector_t *sec = LoadSector(READUINT32(save_p));
+		UINT16 id = READUINT16(save_p);
+		floorrover = P_GetFFloorByID(sec, id);
 	}
 
 	if (diff2 & MD2_CEILINGROVER)
 	{
-		size_t ceiling_sectornum = (size_t)READUINT32(save_p);
-		size_t ceiling_rovernum = (size_t)READUINT32(save_p);
-		ffloor_t *rover = NULL;
-		size_t rovernum = 0;
-
-		for (rover = sectors[ceiling_sectornum].ffloors; rover; rover = rover->next)
-		{
-			if (rovernum == ceiling_rovernum)
-				break;
-			rovernum++;
-		}
-		ceilingrover = rover;
+		sector_t *sec = LoadSector(READUINT32(save_p));
+		UINT16 id = READUINT16(save_p);
+		ceilingrover = P_GetFFloorByID(sec, id);
 	}
 
 	if (diff & MD_SPAWNPOINT)
@@ -2959,10 +2753,6 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 	return &mobj->thinker;
 }
 
-// LoadNoEnemiesThinker
-//
-// Loads a noenemies_t from a save game
-//
 static thinker_t* LoadNoEnemiesThinker(actionf_p1 thinker)
 {
 	noenemies_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -2971,10 +2761,6 @@ static thinker_t* LoadNoEnemiesThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-// LoadBounceCheeseThinker
-//
-// Loads a bouncecheese_t from a save game
-//
 static thinker_t* LoadBounceCheeseThinker(actionf_p1 thinker)
 {
 	bouncecheese_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -2986,13 +2772,13 @@ static thinker_t* LoadBounceCheeseThinker(actionf_p1 thinker)
 	ht->floorwasheight = READFIXED(save_p);
 	ht->ceilingwasheight = READFIXED(save_p);
 	ht->low = READCHAR(save_p);
+
+	if (ht->sector)
+		ht->sector->ceilingdata = ht;
+
 	return &ht->thinker;
 }
 
-// LoadContinuousFallThinker
-//
-// Loads a continuousfall_t from a save game
-//
 static thinker_t* LoadContinuousFallThinker(actionf_p1 thinker)
 {
 	continuousfall_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3003,13 +2789,16 @@ static thinker_t* LoadContinuousFallThinker(actionf_p1 thinker)
 	ht->floorstartheight = READFIXED(save_p);
 	ht->ceilingstartheight = READFIXED(save_p);
 	ht->destheight = READFIXED(save_p);
+
+	if (ht->sector)
+	{
+		ht->sector->ceilingdata = ht;
+		ht->sector->floordata = ht;
+	}
+
 	return &ht->thinker;
 }
 
-// LoadMarioBlockThinker
-//
-// Loads a mariothink_t from a save game
-//
 static thinker_t* LoadMarioBlockThinker(actionf_p1 thinker)
 {
 	mariothink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3020,13 +2809,16 @@ static thinker_t* LoadMarioBlockThinker(actionf_p1 thinker)
 	ht->floorstartheight = READFIXED(save_p);
 	ht->ceilingstartheight = READFIXED(save_p);
 	ht->tag = READINT16(save_p);
+
+	if (ht->sector)
+	{
+		ht->sector->ceilingdata = ht;
+		ht->sector->floordata = ht;
+	}
+
 	return &ht->thinker;
 }
 
-// LoadMarioCheckThinker
-//
-// Loads a mariocheck_t from a save game
-//
 static thinker_t* LoadMarioCheckThinker(actionf_p1 thinker)
 {
 	mariocheck_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3036,10 +2828,6 @@ static thinker_t* LoadMarioCheckThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-// LoadThwompThinker
-//
-// Loads a thwomp_t from a save game
-//
 static thinker_t* LoadThwompThinker(actionf_p1 thinker)
 {
 	thwomp_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3054,13 +2842,16 @@ static thinker_t* LoadThwompThinker(actionf_p1 thinker)
 	ht->delay = READINT32(save_p);
 	ht->tag = READINT16(save_p);
 	ht->sound = READUINT16(save_p);
+
+	if (ht->sector)
+	{
+		ht->sector->ceilingdata = ht;
+		ht->sector->floordata = ht;
+	}
+
 	return &ht->thinker;
 }
 
-// LoadFloatThinker
-//
-// Loads a floatthink_t from a save game
-//
 static thinker_t* LoadFloatThinker(actionf_p1 thinker)
 {
 	floatthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3071,10 +2862,6 @@ static thinker_t* LoadFloatThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-// LoadEachTimeThinker
-//
-// Loads a eachtime_t from a save game
-//
 static thinker_t* LoadEachTimeThinker(actionf_p1 thinker)
 {
 	size_t i;
@@ -3090,15 +2877,11 @@ static thinker_t* LoadEachTimeThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-// LoadRaiseThinker
-//
-// Loads a raise_t from a save game
-//
 static thinker_t* LoadRaiseThinker(actionf_p1 thinker)
 {
 	raise_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
-	ht->sourceline = LoadLine(READUINT32(save_p));
+	ht->tag = READINT16(save_p);
 	ht->sector = LoadSector(READUINT32(save_p));
 	ht->ceilingbottom = READFIXED(save_p);
 	ht->ceilingtop = READFIXED(save_p);
@@ -3109,11 +2892,6 @@ static thinker_t* LoadRaiseThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadCeilingThinker
-//
-// Loads a ceiling_t from a save game
-//
 static thinker_t* LoadCeilingThinker(actionf_p1 thinker)
 {
 	ceiling_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3138,11 +2916,6 @@ static thinker_t* LoadCeilingThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadFloormoveThinker
-//
-// Loads a floormove_t from a save game
-//
 static thinker_t* LoadFloormoveThinker(actionf_p1 thinker)
 {
 	floormove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3162,11 +2935,6 @@ static thinker_t* LoadFloormoveThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadLightflashThinker
-//
-// Loads a lightflash_t from a save game
-//
 static thinker_t* LoadLightflashThinker(actionf_p1 thinker)
 {
 	lightflash_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3179,11 +2947,6 @@ static thinker_t* LoadLightflashThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadStrobeThinker
-//
-// Loads a strobe_t from a save game
-//
 static thinker_t* LoadStrobeThinker(actionf_p1 thinker)
 {
 	strobe_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3199,11 +2962,6 @@ static thinker_t* LoadStrobeThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadGlowThinker
-//
-// Loads a glow_t from a save game
-//
 static thinker_t* LoadGlowThinker(actionf_p1 thinker)
 {
 	glow_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3217,11 +2975,7 @@ static thinker_t* LoadGlowThinker(actionf_p1 thinker)
 		ht->sector->lightingdata = ht;
 	return &ht->thinker;
 }
-//
-// LoadFireflickerThinker
-//
-// Loads a fireflicker_t from a save game
-//
+
 static thinker_t* LoadFireflickerThinker(actionf_p1 thinker)
 {
 	fireflicker_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3235,12 +2989,8 @@ static thinker_t* LoadFireflickerThinker(actionf_p1 thinker)
 		ht->sector->lightingdata = ht;
 	return &ht->thinker;
 }
-//
-// LoadElevatorThinker
-//
-// Loads a elevator_t from a save game
-//
-static thinker_t* LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
+
+static thinker_t* LoadElevatorThinker(actionf_p1 thinker, boolean setplanedata)
 {
 	elevator_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
 	ht->thinker.function.acp1 = thinker;
@@ -3259,25 +3009,39 @@ static thinker_t* LoadElevatorThinker(actionf_p1 thinker, UINT8 floorOrCeiling)
 	ht->delaytimer = READFIXED(save_p);
 	ht->floorwasheight = READFIXED(save_p);
 	ht->ceilingwasheight = READFIXED(save_p);
-	ht->player = LoadPlayer(READUINT32(save_p)); // was dummy
 	ht->sourceline = LoadLine(READUINT32(save_p));
 
-	if (ht->sector)
+	if (ht->sector && setplanedata)
 	{
-		if (floorOrCeiling & 2)
-			ht->sector->ceilingdata = ht;
-		if (floorOrCeiling & 1)
-			ht->sector->floordata = ht;
+		ht->sector->ceilingdata = ht;
+		ht->sector->floordata = ht;
 	}
 
 	return &ht->thinker;
 }
 
-//
-// LoadScrollThinker
-//
-// Loads a scroll_t from a save game
-//
+static thinker_t* LoadCrumbleThinker(actionf_p1 thinker)
+{
+	crumble_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
+	ht->thinker.function.acp1 = thinker;
+	ht->sourceline = LoadLine(READUINT32(save_p));
+	ht->sector = LoadSector(READUINT32(save_p));
+	ht->actionsector = LoadSector(READUINT32(save_p));
+	ht->player = LoadPlayer(READUINT32(save_p));
+	ht->direction = READINT32(save_p);
+	ht->origalpha = READINT32(save_p);
+	ht->timer = READINT32(save_p);
+	ht->speed = READFIXED(save_p);
+	ht->floorwasheight = READFIXED(save_p);
+	ht->ceilingwasheight = READFIXED(save_p);
+	ht->flags = READUINT8(save_p);
+
+	if (ht->sector)
+		ht->sector->floordata = ht;
+
+	return &ht->thinker;
+}
+
 static thinker_t* LoadScrollThinker(actionf_p1 thinker)
 {
 	scroll_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3295,11 +3059,6 @@ static thinker_t* LoadScrollThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadFrictionThinker
-//
-// Loads a friction_t from a save game
-//
 static inline thinker_t* LoadFrictionThinker(actionf_p1 thinker)
 {
 	friction_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3312,11 +3071,6 @@ static inline thinker_t* LoadFrictionThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPusherThinker
-//
-// Loads a pusher_t from a save game
-//
 static thinker_t* LoadPusherThinker(actionf_p1 thinker)
 {
 	pusher_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3338,11 +3092,6 @@ static thinker_t* LoadPusherThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadLaserThinker
-//
-// Loads a laserthink_t from a save game
-//
 static inline thinker_t* LoadLaserThinker(actionf_p1 thinker)
 {
 	laserthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3351,6 +3100,7 @@ static inline thinker_t* LoadLaserThinker(actionf_p1 thinker)
 	ht->sector = LoadSector(READUINT32(save_p));
 	ht->sec = LoadSector(READUINT32(save_p));
 	ht->sourceline = LoadLine(READUINT32(save_p));
+	ht->nobosses = READUINT8(save_p);
 	for (rover = ht->sector->ffloors; rover; rover = rover->next)
 		if (rover->secnum == (size_t)(ht->sec - sectors)
 		&& rover->master == ht->sourceline)
@@ -3358,11 +3108,6 @@ static inline thinker_t* LoadLaserThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadLightlevelThinker
-//
-// Loads a lightlevel_t from a save game
-//
 static inline thinker_t* LoadLightlevelThinker(actionf_p1 thinker)
 {
 	lightlevel_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3378,11 +3123,6 @@ static inline thinker_t* LoadLightlevelThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadExecutorThinker
-//
-// Loads a executor_t from a save game
-//
 static inline thinker_t* LoadExecutorThinker(actionf_p1 thinker)
 {
 	executor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3394,11 +3134,6 @@ static inline thinker_t* LoadExecutorThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadDisappearThinker
-//
-// Loads a disappear_t thinker
-//
 static inline thinker_t* LoadDisappearThinker(actionf_p1 thinker)
 {
 	disappear_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3413,11 +3148,6 @@ static inline thinker_t* LoadDisappearThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadFadeThinker
-//
-// Loads a fade_t thinker
-//
 static inline thinker_t* LoadFadeThinker(actionf_p1 thinker)
 {
 	sector_t *ss;
@@ -3460,10 +3190,6 @@ static inline thinker_t* LoadFadeThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-// LoadFadeColormapThinker
-//
-// Loads a fadecolormap_t from a save game
-//
 static inline thinker_t* LoadFadeColormapThinker(actionf_p1 thinker)
 {
 	fadecolormap_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3479,11 +3205,6 @@ static inline thinker_t* LoadFadeColormapThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPlaneDisplaceThinker
-//
-// Loads a planedisplace_t thinker
-//
 static inline thinker_t* LoadPlaneDisplaceThinker(actionf_p1 thinker)
 {
 	planedisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3497,7 +3218,6 @@ static inline thinker_t* LoadPlaneDisplaceThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-/// Save a dynamic slope thinker.
 static inline thinker_t* LoadDynamicSlopeThinker(actionf_p1 thinker)
 {
 	dynplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL);
@@ -3513,12 +3233,6 @@ static inline thinker_t* LoadDynamicSlopeThinker(actionf_p1 thinker)
 }
 
 #ifdef POLYOBJECTS
-
-//
-// LoadPolyrotateThinker
-//
-// Loads a polyrotate_t thinker
-//
 static inline thinker_t* LoadPolyrotatetThinker(actionf_p1 thinker)
 {
 	polyrotate_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3529,11 +3243,6 @@ static inline thinker_t* LoadPolyrotatetThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolymoveThinker
-//
-// Loads a polymovet_t thinker
-//
 static thinker_t* LoadPolymoveThinker(actionf_p1 thinker)
 {
 	polymove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3547,11 +3256,6 @@ static thinker_t* LoadPolymoveThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolywaypointThinker
-//
-// Loads a polywaypoint_t thinker
-//
 static inline thinker_t* LoadPolywaypointThinker(actionf_p1 thinker)
 {
 	polywaypoint_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3572,11 +3276,6 @@ static inline thinker_t* LoadPolywaypointThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolyslidedoorThinker
-//
-// loads a polyslidedoor_t thinker
-//
 static inline thinker_t* LoadPolyslidedoorThinker(actionf_p1 thinker)
 {
 	polyslidedoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3597,11 +3296,6 @@ static inline thinker_t* LoadPolyslidedoorThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolyswingdoorThinker
-//
-// Loads a polyswingdoor_t thinker
-//
 static inline thinker_t* LoadPolyswingdoorThinker(actionf_p1 thinker)
 {
 	polyswingdoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3617,11 +3311,6 @@ static inline thinker_t* LoadPolyswingdoorThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolydisplaceThinker
-//
-// Loads a polydisplace_t thinker
-//
 static inline thinker_t* LoadPolydisplaceThinker(actionf_p1 thinker)
 {
 	polydisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3646,11 +3335,6 @@ static inline thinker_t* LoadPolyrotdisplaceThinker(actionf_p1 thinker)
 	return &ht->thinker;
 }
 
-//
-// LoadPolyfadeThinker
-//
-// Loads a polyfadet_t thinker
-//
 static thinker_t* LoadPolyfadeThinker(actionf_p1 thinker)
 {
 	polyfade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
@@ -3667,22 +3351,6 @@ static thinker_t* LoadPolyfadeThinker(actionf_p1 thinker)
 }
 #endif
 
-/*
-//
-// LoadWhatThinker
-//
-// load a what_t thinker
-//
-static inline void LoadWhatThinker(actionf_p1 thinker)
-{
-	what_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
-	ht->thinker.function.acp1 = thinker;
-}
-*/
-
-//
-// P_NetUnArchiveThinkers
-//
 static void P_NetUnArchiveThinkers(void)
 {
 	thinker_t *currentthinker;
@@ -3767,7 +3435,7 @@ static void P_NetUnArchiveThinkers(void)
 					break;
 
 				case tc_elevator:
-					th = LoadElevatorThinker((actionf_p1)T_MoveElevator, 3);
+					th = LoadElevatorThinker((actionf_p1)T_MoveElevator, true);
 					break;
 
 				case tc_continuousfalling:
@@ -3790,10 +3458,8 @@ static void P_NetUnArchiveThinkers(void)
 					th = LoadRaiseThinker((actionf_p1)T_RaiseSector);
 					break;
 
-				/// \todo rewrite all the code that uses an elevator_t but isn't an elevator
-				/// \note working on it!
 				case tc_camerascanner:
-					th = LoadElevatorThinker((actionf_p1)T_CameraScanner, 0);
+					th = LoadElevatorThinker((actionf_p1)T_CameraScanner, false);
 					break;
 
 				case tc_bouncecheese:
@@ -3801,7 +3467,7 @@ static void P_NetUnArchiveThinkers(void)
 					break;
 
 				case tc_startcrumble:
-					th = LoadElevatorThinker((actionf_p1)T_StartCrumble, 1);
+					th = LoadCrumbleThinker((actionf_p1)T_StartCrumble);
 					break;
 
 				case tc_marioblock:
@@ -4033,9 +3699,7 @@ static inline void P_UnArchivePolyObjects(void)
 		P_UnArchivePolyObj(&PolyObjects[i]);
 }
 #endif
-//
-// P_FinishMobjs
-//
+
 static inline void P_FinishMobjs(void)
 {
 	thinker_t *currentthinker;
@@ -4144,9 +3808,6 @@ static void P_RelinkPointers(void)
 	}
 }
 
-//
-// P_NetArchiveSpecials
-//
 static inline void P_NetArchiveSpecials(void)
 {
 	size_t i, z;
@@ -4187,9 +3848,6 @@ static inline void P_NetArchiveSpecials(void)
 		WRITEUINT8(save_p, 0x00);
 }
 
-//
-// P_NetUnArchiveSpecials
-//
 static void P_NetUnArchiveSpecials(void)
 {
 	size_t i;
diff --git a/src/p_spec.c b/src/p_spec.c
index f1f8adef231be6753df8775c3c4d099bf1f4c987..6d2345d9fd23ea6cfbd90a3a1cd3840c993e8de0 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -987,42 +987,12 @@ static sector_t *P_FindModelCeilingSector(fixed_t ceildestheight, INT32 secnum)
 }
 #endif
 
-/** Searches the tag lists for the next sector tagged to a line.
-  *
-  * \param line  Tagged line used as a reference.
-  * \param start -1 to start at the beginning, or the result of a previous call
-  *              to keep searching.
-  * \return Number of the next tagged sector found.
-  * \sa P_FindSectorFromTag, P_FindLineFromLineTag
-  */
-INT32 P_FindSectorFromLineTag(line_t *line, INT32 start)
-{
-	if (line->tag == -1)
-	{
-		start++;
-
-		if (start >= (INT32)numsectors)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? sectors[start].nexttag :
-			sectors[(unsigned)line->tag % numsectors].firsttag;
-		while (start >= 0 && sectors[start].tag != line->tag)
-			start = sectors[start].nexttag;
-		return start;
-	}
-}
-
 /** Searches the tag lists for the next sector with a given tag.
   *
   * \param tag   Tag number to look for.
   * \param start -1 to start anew, or the result of a previous call to keep
   *              searching.
   * \return Number of the next tagged sector found.
-  * \sa P_FindSectorFromLineTag
   */
 INT32 P_FindSectorFromTag(INT16 tag, INT32 start)
 {
@@ -1045,42 +1015,12 @@ INT32 P_FindSectorFromTag(INT16 tag, INT32 start)
 	}
 }
 
-/** Searches the tag lists for the next line tagged to a line.
-  *
-  * \param line  Tagged line used as a reference.
-  * \param start -1 to start anew, or the result of a previous call to keep
-  *              searching.
-  * \return Number of the next tagged line found.
-  * \sa P_FindSectorFromLineTag
-  */
-static INT32 P_FindLineFromLineTag(const line_t *line, INT32 start)
-{
-	if (line->tag == -1)
-	{
-		start++;
-
-		if (start >= (INT32)numlines)
-			return -1;
-
-		return start;
-	}
-	else
-	{
-		start = start >= 0 ? lines[start].nexttag :
-			lines[(unsigned)line->tag % numlines].firsttag;
-		while (start >= 0 && lines[start].tag != line->tag)
-			start = lines[start].nexttag;
-		return start;
-	}
-}
-#if 0
 /** Searches the tag lists for the next line with a given tag and special.
   *
   * \param tag     Tag number.
   * \param start   -1 to start anew, or the result of a previous call to keep
   *                searching.
   * \return Number of next suitable line found.
-  * \sa P_FindLineFromLineTag
   * \author Graue <graue@oceanbase.org>
   */
 static INT32 P_FindLineFromTag(INT32 tag, INT32 start)
@@ -1089,7 +1029,7 @@ static INT32 P_FindLineFromTag(INT32 tag, INT32 start)
 	{
 		start++;
 
-		if (start >= numlines)
+		if (start >= (INT32)numlines)
 			return -1;
 
 		return start;
@@ -1103,7 +1043,7 @@ static INT32 P_FindLineFromTag(INT32 tag, INT32 start)
 		return start;
 	}
 }
-#endif
+
 //
 // P_FindSpecialLineFromTag
 //
@@ -2500,7 +2440,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				newceilinglightsec = line->frontsector->ceilinglightsec;
 
 				// act on all sectors with the same tag as the triggering linedef
-				while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+				while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 				{
 					if (sectors[secnum].lightingdata)
 					{
@@ -2555,7 +2495,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 409: // Change tagged sectors' tag
 		// (formerly "Change calling sectors' tag", but behavior was changed)
 		{
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 				P_ChangeSectorTag(secnum,(INT16)(sides[line->sidenum[0]].textureoffset>>FRACBITS));
 			break;
 		}
@@ -2565,7 +2505,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 411: // Stop floor/ceiling movement in tagged sector(s)
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				if (sectors[secnum].floordata)
 				{
@@ -2635,7 +2575,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				}
 				else
 				{
-					if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
+					if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
 						return;
 
 					dest = P_GetObjectTypeInSectorNum(MT_TELEPORTMAN, secnum);
@@ -2750,7 +2690,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 						// Additionally play the sound from tagged sectors' soundorgs
 						sector_t *sec;
 
-						while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+						while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 						{
 							sec = &sectors[secnum];
 							S_StartSound(&sec->soundorg, sfxnum);
@@ -2865,7 +2805,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 416: // Spawn adjustable fire flicker
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2899,7 +2839,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 417: // Spawn adjustable glowing light
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2933,7 +2873,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 418: // Spawn adjustable strobe flash (unsynchronized)
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -2967,7 +2907,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 419: // Spawn adjustable strobe flash (synchronized)
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				if (line->flags & ML_NOCLIMB && line->backsector)
 				{
@@ -3015,7 +2955,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			break;
 
 		case 421: // Stop lighting effect in tagged sectors
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 				if (sectors[secnum].lightingdata)
 				{
 					P_RemoveThinker(&((elevator_t *)sectors[secnum].lightingdata)->thinker);
@@ -3030,7 +2970,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				if ((!mo || !mo->player) && !titlemapinaction) // only players have views, and title screens
 					return;
 
-				if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
+				if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
 					return;
 
 				altview = P_GetObjectTypeInSectorNum(MT_ALTVIEWMAN, secnum);
@@ -3349,7 +3289,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 			if (line->sidenum[1] != 0xffff)
 				state = (statenum_t)sides[line->sidenum[1]].toptexture;
 
-			while ((secnum = P_FindSectorFromLineTag(line, secnum)) >= 0)
+			while ((secnum = P_FindSectorFromTag(line->tag, secnum)) >= 0)
 			{
 				boolean tryagain;
 				sec = sectors + secnum;
@@ -3953,7 +3893,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				boolean persist = (line->flags & ML_EFFECT2);
 				mobj_t *anchormo;
 
-				if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
+				if ((secnum = P_FindSectorFromTag(line->tag, -1)) < 0)
 					return;
 
 				anchormo = P_GetObjectTypeInSectorNum(MT_ANGLEMAN, secnum);
@@ -5696,6 +5636,35 @@ void P_UpdateSpecials(void)
 	}
 }
 
+//
+// Floor over floors (FOFs), 3Dfloors, 3Dblocks, fake floors (ffloors), rovers, or whatever you want to call them
+//
+
+/** Gets the ID number for a 3Dfloor in its target sector.
+  *
+  * \param fflr The 3Dfloor we want an ID for.
+  * \return ID of 3Dfloor in target sector. Note that the first FOF's ID is 0. UINT16_MAX is given if invalid.
+  * \sa P_GetFFloorByID
+  */
+UINT16 P_GetFFloorID(ffloor_t *fflr)
+{
+	ffloor_t *rover;
+	sector_t *sec;
+	UINT16 i = 0;
+
+	if (!fflr)
+		return UINT16_MAX;
+
+	sec = fflr->target;
+
+	if (!sec->ffloors)
+		return UINT16_MAX;
+	for (rover = sec->ffloors; rover; rover = rover->next, i++)
+		if (rover == fflr)
+			return i;
+	return UINT16_MAX;
+}
+
 /** Gets a 3Dfloor by control sector.
   *
   * \param sec  Target sector.
@@ -5720,7 +5689,7 @@ static inline ffloor_t *P_GetFFloorBySec(sector_t *sec, sector_t *sec2)
   * \param sec Target sector.
   * \param id  ID of 3Dfloor in target sector. Note that the first FOF's ID is 0.
   * \return Pointer to found 3Dfloor, or NULL.
-  * \sa P_GetFFloorBySec
+  * \sa P_GetFFloorBySec, P_GetFFloorID
   */
 ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id)
 {
@@ -6026,12 +5995,10 @@ static void P_AddBlockThinker(sector_t *sec, line_t *sourceline)
   * there already.
   *
   * \param sec          Control sector.
-  * \param actionsector Target sector.
-  * \param sourceline   Control linedef.
   * \sa P_SpawnSpecials, T_RaiseSector
   * \author SSNTails <http://www.ssntails.org>
   */
-static void P_AddRaiseThinker(sector_t *sec, line_t *sourceline, boolean lower, boolean spindash)
+static void P_AddRaiseThinker(sector_t *sec, INT16 tag, fixed_t speed, fixed_t ceilingtop, fixed_t ceilingbottom, boolean lower, boolean spindash)
 {
 	raise_t *raise;
 
@@ -6040,13 +6007,13 @@ static void P_AddRaiseThinker(sector_t *sec, line_t *sourceline, boolean lower,
 
 	raise->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
 
-	raise->sourceline = sourceline;
+	raise->tag = tag;
 	raise->sector = sec;
 
-	raise->ceilingtop = P_FindHighestCeilingSurrounding(sec);
-	raise->ceilingbottom = P_FindLowestCeilingSurrounding(sec);
+	raise->ceilingtop = ceilingtop;
+	raise->ceilingbottom = ceilingbottom;
 
-	raise->basespeed =  FixedDiv(P_AproxDistance(sourceline->dx, sourceline->dy), 4*FRACUNIT);
+	raise->basespeed = speed;
 
 	if (lower)
 		raise->flags |= RF_REVERSE;
@@ -6054,7 +6021,7 @@ static void P_AddRaiseThinker(sector_t *sec, line_t *sourceline, boolean lower,
 		raise->flags |= RF_SPINDASH;
 }
 
-static void P_AddAirbob(sector_t *sec, line_t *sourceline, fixed_t dist, boolean raise, boolean spindash, boolean dynamic)
+static void P_AddAirbob(sector_t *sec, INT16 tag, fixed_t dist, boolean raise, boolean spindash, boolean dynamic)
 {
 	raise_t *airbob;
 
@@ -6063,7 +6030,7 @@ static void P_AddAirbob(sector_t *sec, line_t *sourceline, fixed_t dist, boolean
 
 	airbob->thinker.function.acp1 = (actionf_p1)T_RaiseSector;
 
-	airbob->sourceline = sourceline;
+	airbob->tag = tag;
 	airbob->sector = sec;
 
 	airbob->ceilingtop = sec->ceilingheight;
@@ -6083,12 +6050,10 @@ static void P_AddAirbob(sector_t *sec, line_t *sourceline, fixed_t dist, boolean
   * Even thwomps need to think!
   *
   * \param sec          Control sector.
-  * \param actionsector Target sector.
-  * \param sourceline   Control linedef.
   * \sa P_SpawnSpecials, T_ThwompSector
   * \author SSNTails <http://www.ssntails.org>
   */
-static inline void P_AddThwompThinker(sector_t *sec, sector_t *actionsector, line_t *sourceline)
+static inline void P_AddThwompThinker(sector_t *sec, INT16 tag, line_t *sourceline, fixed_t crushspeed, fixed_t retractspeed, UINT16 sound)
 {
 	thwomp_t *thwomp;
 
@@ -6106,14 +6071,14 @@ static inline void P_AddThwompThinker(sector_t *sec, sector_t *actionsector, lin
 	// set up the fields according to the type of elevator action
 	thwomp->sourceline = sourceline;
 	thwomp->sector = sec;
-	thwomp->crushspeed = (sourceline->flags & ML_EFFECT5) ? sourceline->dy >> 3 : 10*FRACUNIT;
-	thwomp->retractspeed = (sourceline->flags & ML_EFFECT5) ? sourceline->dx >> 3 : 2*FRACUNIT;
+	thwomp->crushspeed = crushspeed;
+	thwomp->retractspeed = retractspeed;
 	thwomp->direction = 0;
 	thwomp->floorstartheight = sec->floorheight;
 	thwomp->ceilingstartheight = sec->ceilingheight;
 	thwomp->delay = 1;
-	thwomp->tag = actionsector->tag;
-	thwomp->sound = (sourceline->flags & ML_EFFECT4) ? sides[sourceline->sidenum[0]].textureoffset >> FRACBITS : sfx_thwomp;
+	thwomp->tag = tag;
+	thwomp->sound = sound;
 
 	sec->floordata = thwomp;
 	sec->ceilingdata = thwomp;
@@ -6174,6 +6139,8 @@ static inline void P_AddCameraScanner(sector_t *sourcesec, sector_t *actionsecto
 {
 	elevator_t *elevator; // Why not? LOL
 
+	CONS_Alert(CONS_WARNING, M_GetText("Detected a camera scanner effect (linedef type 5). This effect is deprecated and will be removed in the future!\n"));
+
 	// create and initialize new elevator thinker
 	elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL);
 	P_AddThinker(THINK_MAIN, &elevator->thinker);
@@ -6228,8 +6195,7 @@ void T_LaserFlash(laserthink_t *flash)
 	{
 		thing = node->m_thing;
 
-		if ((fflr->master->flags & ML_EFFECT1)
-			&& thing->flags & MF_BOSS)
+		if (flash->nobosses && thing->flags & MF_BOSS)
 			continue; // Don't hurt bosses
 
 		// Don't endlessly kill egg guard shields (or anything else for that matter)
@@ -6258,7 +6224,7 @@ void T_LaserFlash(laserthink_t *flash)
   * \sa T_LaserFlash
   * \author SSNTails <http://www.ssntails.org>
   */
-static inline void EV_AddLaserThinker(sector_t *sec, sector_t *sec2, line_t *line, thinkerlist_t *secthinkers)
+static inline void EV_AddLaserThinker(sector_t *sec, sector_t *sec2, line_t *line, thinkerlist_t *secthinkers, boolean nobosses)
 {
 	laserthink_t *flash;
 	ffloor_t *fflr = P_AddFakeFloor(sec, sec2, line, laserflags, secthinkers);
@@ -6275,6 +6241,7 @@ static inline void EV_AddLaserThinker(sector_t *sec, sector_t *sec2, line_t *lin
 	flash->sector = sec; // For finding mobjs
 	flash->sec = sec2;
 	flash->sourceline = line;
+	flash->nobosses = nobosses;
 }
 
 //
@@ -6495,7 +6462,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 1: // Definable gravity per sector
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 				{
 					sectors[s].gravity = &sectors[sec].floorheight; // This allows it to change in realtime!
 
@@ -6519,7 +6486,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 5: // Change camera info
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_AddCameraScanner(&sectors[sec], &sectors[s], R_PointToAngle2(lines[i].v2->x, lines[i].v2->y, lines[i].v1->x, lines[i].v1->y));
 				break;
 
@@ -6546,7 +6513,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 						P_ApplyFlatAlignment(lines + i, lines[i].frontsector, flatangle, xoffs, yoffs);
 					else
 					{
-						for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0;)
+						for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0;)
 							P_ApplyFlatAlignment(lines + i, sectors + s, flatangle, xoffs, yoffs);
 					}
 				}
@@ -6557,7 +6524,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 8: // Sector Parameters
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 				{
 					if (lines[i].flags & ML_NOCLIMB)
 					{
@@ -6585,7 +6552,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 10: // Vertical culling plane for sprites and FOFs
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					sectors[s].cullheight = &lines[i]; // This allows it to change in realtime!
 				break;
 
@@ -6646,18 +6613,18 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 63: // support for drawn heights coming from different sector
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					sectors[s].heightsec = (INT32)sec;
 				break;
 
 			case 64: // Appearing/Disappearing FOF option
 				if (lines[i].flags & ML_BLOCKMONSTERS) { // Find FOFs by control sector tag
-					for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+					for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 						for (j = 0; (unsigned)j < sectors[s].linecount; j++)
 							if (sectors[s].lines[j]->special >= 100 && sectors[s].lines[j]->special < 300)
 								Add_MasterDisappearer(abs(lines[i].dx>>FRACBITS), abs(lines[i].dy>>FRACBITS), abs(sides[lines[i].sidenum[0]].sector->floorheight>>FRACBITS), (INT32)(sectors[s].lines[j]-lines), (INT32)i);
 				} else // Find FOFs by effect sector tag
-					for (s = -1; (s = P_FindLineFromLineTag(lines + i, s)) >= 0 ;)
+					for (s = -1; (s = P_FindLineFromTag(lines[i].tag, s)) >= 0 ;)
 					{
 						if ((size_t)s == i)
 							continue;
@@ -6667,15 +6634,15 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 66: // Displace floor by front sector
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_AddPlaneDisplaceThinker(pd_floor, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 67: // Displace ceiling by front sector
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_AddPlaneDisplaceThinker(pd_ceiling, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 			case 68: // Displace both floor AND ceiling by front sector
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_AddPlaneDisplaceThinker(pd_both, P_AproxDistance(lines[i].dx, lines[i].dy)>>8, sides[lines[i].sidenum[0]].sector-sectors, s, !!(lines[i].flags & ML_NOCLIMB));
 				break;
 
@@ -6872,16 +6839,16 @@ void P_SpawnSpecials(boolean fromnetsave)
 			{
 				fixed_t dist = (lines[i].special == 150) ? 16*FRACUNIT : P_AproxDistance(lines[i].dx, lines[i].dy);
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines + i, dist, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, dist, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 			}
 			case 152: // Adjustable air bobbing platform in reverse
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines + i, P_AproxDistance(lines[i].dx, lines[i].dy), true, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, P_AproxDistance(lines[i].dx, lines[i].dy), true, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 			case 153: // Dynamic Sinking Platform
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines + i, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, P_AproxDistance(lines[i].dx, lines[i].dy), false, !!(lines[i].flags & ML_NOCLIMB), true);
 				break;
 
 			case 160: // Float/bob platform
@@ -6931,13 +6898,13 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 176: // Air bobbing platform that will crumble and bob on the water when it falls and hits
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_FLOATBOB|FF_CRUMBLE, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines + i, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 177: // Air bobbing platform that will crumble and bob on
 				// the water when it falls and hits, then never return
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_FLOATBOB|FF_CRUMBLE|FF_NORETURN, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines + i, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 178: // Crumbling platform that will float when it hits water
@@ -6950,48 +6917,36 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 180: // Air bobbing platform that will crumble
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL|FF_CRUMBLE, secthinkers);
-				P_AddAirbob(lines[i].frontsector, lines + i, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
+				P_AddAirbob(lines[i].frontsector, lines[i].tag, 16*FRACUNIT, false, !!(lines[i].flags & ML_NOCLIMB), false);
 				break;
 
 			case 190: // Rising Platform FOF (solid, opaque, shadows)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i], !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
-				break;
-
 			case 191: // Rising Platform FOF (solid, opaque, no shadows)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_CUTLEVEL, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i], !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
-				break;
-
 			case 192: // Rising Platform TL block: FOF (solid, translucent)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_NOSHADE|FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i], !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
-				break;
-
 			case 193: // Rising Platform FOF (solid, invisible)
-				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_SOLID|FF_NOSHADE, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i], !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
-				break;
-
 			case 194: // Rising Platform 'Platform' - You can jump up through it
-				// If line has no-climb set, don't give it shadows, otherwise do
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
-				if (lines[i].flags & ML_NOCLIMB)
-					ffloorflags |= FF_NOSHADE;
-
-				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i], !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
-				break;
-
 			case 195: // Rising Platform Translucent "platform"
-				// If line has no-climb set, don't give it shadows, otherwise do
-				ffloorflags = FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_PLATFORM|FF_TRANSLUCENT|FF_BOTHPLANES|FF_ALLSIDES|FF_EXTRA|FF_CUTEXTRA;
-				if (lines[i].flags & ML_NOCLIMB)
+			{
+				fixed_t speed = FixedDiv(P_AproxDistance(lines[i].dx, lines[i].dy), 4*FRACUNIT);
+				fixed_t ceilingtop = P_FindHighestCeilingSurrounding(lines[i].frontsector);
+				fixed_t ceilingbottom = P_FindLowestCeilingSurrounding(lines[i].frontsector);
+
+				ffloorflags = FF_EXISTS|FF_SOLID;
+				if (lines[i].special != 193)
+					ffloorflags |= FF_RENDERALL;
+				if (lines[i].special <= 191)
+					ffloorflags |= FF_CUTLEVEL;
+				if (lines[i].special == 192 || lines[i].special == 195)
+					ffloorflags |= FF_TRANSLUCENT|FF_EXTRA|FF_CUTEXTRA;
+				if (lines[i].special >= 194)
+					ffloorflags |= FF_PLATFORM|FF_BOTHPLANES|FF_ALLSIDES;
+				if (lines[i].special != 190 && (lines[i].special <= 193 || lines[i].flags & ML_NOCLIMB))
 					ffloorflags |= FF_NOSHADE;
-
 				P_AddFakeFloorsByLine(i, ffloorflags, secthinkers);
-				P_AddRaiseThinker(lines[i].frontsector, &lines[i], !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
+
+				P_AddRaiseThinker(lines[i].frontsector, lines[i].tag, speed, ceilingtop, ceilingbottom, !!(lines[i].flags & ML_BLOCKMONSTERS), !!(lines[i].flags & ML_NOCLIMB));
 				break;
+			}
 
 			case 200: // Double light effect
 				P_AddFakeFloorsByLine(i, FF_EXISTS|FF_CUTSPRITES|FF_DOUBLESHADOW, secthinkers);
@@ -7047,14 +7002,20 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 251: // A THWOMP!
+			{
+				fixed_t crushspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dy >> 3 : 10*FRACUNIT;
+				fixed_t retractspeed = (lines[i].flags & ML_EFFECT5) ? lines[i].dx >> 3 : 2*FRACUNIT;
+				UINT16 sound = (lines[i].flags & ML_EFFECT4) ? sides[lines[i].sidenum[0]].textureoffset >> FRACBITS : sfx_thwomp;
+
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 				{
-					P_AddThwompThinker(&sectors[sec], &sectors[s], &lines[i]);
+					P_AddThwompThinker(&sectors[sec], lines[i].tag, &lines[i], crushspeed, retractspeed, sound);
 					P_AddFakeFloor(&sectors[s], &sectors[sec], lines + i,
 						FF_EXISTS|FF_SOLID|FF_RENDERALL|FF_CUTLEVEL, secthinkers);
 				}
 				break;
+			}
 
 			case 252: // Shatter block (breaks when touched)
 				ffloorflags = FF_EXISTS|FF_BLOCKOTHERS|FF_RENDERALL|FF_BUSTUP|FF_SHATTER;
@@ -7096,8 +7057,8 @@ void P_SpawnSpecials(boolean fromnetsave)
 				sec = sides[*lines[i].sidenum].sector - sectors;
 
 				// No longer totally disrupts netgames
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
-					EV_AddLaserThinker(&sectors[s], &sectors[sec], lines + i, secthinkers);
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
+					EV_AddLaserThinker(&sectors[s], &sectors[sec], lines + i, secthinkers, !!(lines[i].flags & ML_EFFECT1));
 				break;
 
 			case 259: // Custom FOF
@@ -7283,40 +7244,40 @@ void P_SpawnSpecials(boolean fromnetsave)
 
 			case 600: // floor lighting independently (e.g. lava)
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					sectors[s].floorlightsec = (INT32)sec;
 				break;
 
 			case 601: // ceiling lighting independently
 				sec = sides[*lines[i].sidenum].sector-sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					sectors[s].ceilinglightsec = (INT32)sec;
 				break;
 
 			case 602: // Adjustable pulsating light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_SpawnAdjustableGlowingLight(&sectors[sec], &sectors[s],
 						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
 				break;
 
 			case 603: // Adjustable flickering light
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_SpawnAdjustableFireFlicker(&sectors[sec], &sectors[s],
 						P_AproxDistance(lines[i].dx, lines[i].dy)>>FRACBITS);
 				break;
 
 			case 604: // Adjustable Blinking Light (unsynchronized)
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
 						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, false);
 				break;
 
 			case 605: // Adjustable Blinking Light (synchronized)
 				sec = sides[*lines[i].sidenum].sector - sectors;
-				for (s = -1; (s = P_FindSectorFromLineTag(lines + i, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(lines[i].tag, s)) >= 0 ;)
 					P_SpawnAdjustableStrobeFlash(&sectors[sec], &sectors[s],
 						abs(lines[i].dx)>>FRACBITS, abs(lines[i].dy)>>FRACBITS, true);
 				break;
@@ -7401,7 +7362,7 @@ static void P_AddFakeFloorsByLine(size_t line, ffloortype_e ffloorflags, thinker
 	INT32 s;
 	size_t sec = sides[*lines[line].sidenum].sector-sectors;
 
-	for (s = -1; (s = P_FindSectorFromLineTag(lines+line, s)) >= 0 ;)
+	for (s = -1; (s = P_FindSectorFromTag(lines[line].tag, s)) >= 0 ;)
 		P_AddFakeFloor(&sectors[s], &sectors[sec], lines+line, ffloorflags, secthinkers);
 }
 
@@ -7763,7 +7724,7 @@ static void P_SpawnScrollers(void)
 
 			case 513: // scroll effect ceiling
 			case 533: // scroll and carry objects on ceiling
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Scroller(sc_ceiling, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				if (special != 533)
 					break;
@@ -7772,13 +7733,13 @@ static void P_SpawnScrollers(void)
 			case 523:	// carry objects on ceiling
 				dx = FixedMul(dx, CARRYFACTOR);
 				dy = FixedMul(dy, CARRYFACTOR);
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Scroller(sc_carry_ceiling, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				break;
 
 			case 510: // scroll effect floor
 			case 530: // scroll and carry objects on floor
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Scroller(sc_floor, -dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				if (special != 530)
 					break;
@@ -7787,14 +7748,14 @@ static void P_SpawnScrollers(void)
 			case 520:	// carry objects on floor
 				dx = FixedMul(dx, CARRYFACTOR);
 				dy = FixedMul(dy, CARRYFACTOR);
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Scroller(sc_carry, dx, dy, control, s, accel, l->flags & ML_NOCLIMB);
 				break;
 
 			// scroll wall according to linedef
 			// (same direction and speed as scrolling floors)
 			case 502:
-				for (s = -1; (s = P_FindLineFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					if (s != (INT32)i)
 						Add_Scroller(sc_side, dx, dy, control, lines[s].sidenum[0], accel, 0);
 				break;
@@ -7864,7 +7825,7 @@ void T_Disappear(disappear_t *d)
 		ffloor_t *rover;
 		register INT32 s;
 
-		for (s = -1; (s = P_FindSectorFromLineTag(&lines[d->affectee], s)) >= 0 ;)
+		for (s = -1; (s = P_FindSectorFromTag(lines[d->affectee].tag, s)) >= 0 ;)
 		{
 			for (rover = sectors[s].ffloors; rover; rover = rover->next)
 			{
@@ -8621,7 +8582,7 @@ static void P_SpawnFriction(void)
 			else
 				movefactor = FRACUNIT;
 
-			for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+			for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 				Add_Friction(friction, movefactor, s, -1);
 		}
 }
@@ -9156,15 +9117,15 @@ static void P_SpawnPushers(void)
 		switch (l->special)
 		{
 			case 541: // wind
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_wind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 544: // current
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_current, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 547: // push/pull
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 				{
 					thing = P_GetPushThing(s);
 					if (thing) // No MT_P* means no effect
@@ -9172,19 +9133,19 @@ static void P_SpawnPushers(void)
 				}
 				break;
 			case 545: // current up
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_upcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 546: // current down
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_downcurrent, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 542: // wind up
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_upwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 			case 543: // wind down
-				for (s = -1; (s = P_FindSectorFromLineTag(l, s)) >= 0 ;)
+				for (s = -1; (s = P_FindSectorFromTag(l->tag, s)) >= 0 ;)
 					Add_Pusher(p_downwind, l->dx, l->dy, NULL, s, -1, l->flags & ML_NOCLIMB, l->flags & ML_EFFECT4);
 				break;
 		}
diff --git a/src/p_spec.h b/src/p_spec.h
index e243e3a61893a89a26db1278e6deed036609f63c..0228f8a028f34051f2db8069e579d7a8385faf33 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -55,7 +55,6 @@ fixed_t P_FindNextLowestFloor(sector_t *sec, fixed_t currentheight);
 fixed_t P_FindLowestCeilingSurrounding(sector_t *sec);
 fixed_t P_FindHighestCeilingSurrounding(sector_t *sec);
 
-INT32 P_FindSectorFromLineTag(line_t *line, INT32 start);
 INT32 P_FindSectorFromTag(INT16 tag, INT32 start);
 INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start);
 
@@ -74,6 +73,7 @@ void P_RunDeNightserizeExecutors(mobj_t *actor);
 void P_RunNightsLapExecutors(mobj_t *actor);
 void P_RunNightsCapsuleTouchExecutors(mobj_t *actor, boolean entering, boolean enoughspheres);
 
+UINT16 P_GetFFloorID(ffloor_t *fflr);
 ffloor_t *P_GetFFloorByID(sector_t *sec, UINT16 id);
 
 //
@@ -108,6 +108,7 @@ typedef struct
 	sector_t *sector;  ///< Sector in which the effect takes place.
 	sector_t *sec;
 	line_t *sourceline;
+	UINT8 nobosses;
 } laserthink_t;
 
 /** Strobe light action structure..
@@ -307,10 +308,32 @@ typedef struct
 	fixed_t delaytimer;
 	fixed_t floorwasheight; // Height the floor WAS at
 	fixed_t ceilingwasheight; // Height the ceiling WAS at
-	player_t *player; // Player who initiated the thinker (used for airbob)
 	line_t *sourceline;
 } elevator_t;
 
+typedef enum
+{
+	CF_RETURN   = 1,    // Return after crumbling
+	CF_FLOATBOB = 1<<1, // Float on water
+	CF_REVERSE  = 1<<2, // Reverse gravity
+} crumbleflag_t;
+
+typedef struct
+{
+	thinker_t thinker;
+	line_t *sourceline;
+	sector_t *sector;
+	sector_t *actionsector; // The sector the rover action is taking place in.
+	player_t *player; // Player who initiated the thinker (used for airbob)
+	INT32 direction;
+	INT32 origalpha;
+	INT32 timer;
+	fixed_t speed;
+	fixed_t floorwasheight; // Height the floor WAS at
+	fixed_t ceilingwasheight; // Height the ceiling WAS at
+	UINT8 flags;
+} crumble_t;
+
 typedef struct
 {
 	thinker_t thinker;
@@ -400,7 +423,7 @@ typedef enum
 typedef struct
 {
 	thinker_t thinker;
-	line_t *sourceline;
+	INT16 tag;
 	sector_t *sector;
 	fixed_t ceilingbottom;
 	fixed_t ceilingtop;
@@ -440,7 +463,7 @@ void T_MoveFloor(floormove_t *movefloor);
 void T_MoveElevator(elevator_t *elevator);
 void T_ContinuousFalling(continuousfall_t *faller);
 void T_BounceCheese(bouncecheese_t *bouncer);
-void T_StartCrumble(elevator_t *elevator);
+void T_StartCrumble(crumble_t *crumble);
 void T_MarioBlock(mariothink_t *block);
 void T_FloatSector(floatthink_t *floater);
 void T_MarioBlockChecker(mariocheck_t *block);
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index c042d141c10362f1ef643ed844a8e31d6bc2d176..ec7bfc1c4573776d438baf27fe1f388f838869c0 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -372,7 +372,9 @@ static boolean IgnoreMouse(void)
 		return false;
 	if (menuactive)
 		return !M_MouseNeeded();
-	if (paused || con_destlines || chat_on || gamestate != GS_LEVEL)
+	if (paused || con_destlines || chat_on)
+		return true;
+	if (gamestate != GS_LEVEL && gamestate != GS_INTERMISSION && gamestate != GS_CUTSCENE)
 		return true;
 	return false;
 }
diff --git a/src/w_wad.c b/src/w_wad.c
index f273753c8dec38310fad33a28a57eb5cc9290afb..a8113235465ccb32fc1771fb27f54142aa277677 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -920,6 +920,40 @@ const char *W_CheckNameForNum(lumpnum_t lumpnum)
 // 'startlump' is the lump number to start the search
 //
 UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump)
+{
+	UINT16 i;
+	static char uname[8 + 1];
+
+	if (!TestValidLump(wad,0))
+		return INT16_MAX;
+
+	strlcpy(uname, name, sizeof uname);
+	strupr(uname);
+
+	//
+	// scan forward
+	// start at 'startlump', useful parameter when there are multiple
+	//                       resources with the same name
+	//
+	if (startlump < wadfiles[wad]->numlumps)
+	{
+		lumpinfo_t *lump_p = wadfiles[wad]->lumpinfo + startlump;
+		for (i = startlump; i < wadfiles[wad]->numlumps; i++, lump_p++)
+			if (!strncmp(lump_p->name, uname, sizeof(uname) - 1))
+				return i;
+	}
+
+	// not found.
+	return INT16_MAX;
+}
+
+//
+// Like W_CheckNumForNamePwad, but can find entries with long names
+//
+// Should be the only version, but that's not possible until we fix
+// all the instances of non null-terminated strings in the codebase...
+//
+UINT16 W_CheckNumForLongNamePwad(const char *name, UINT16 wad, UINT16 startlump)
 {
 	UINT16 i;
 	static char uname[256 + 1];
@@ -1025,7 +1059,8 @@ lumpnum_t W_CheckNumForName(const char *name)
 	// most recent entries first
 	for (i = lumpnumcacheindex + LUMPNUMCACHESIZE; i > lumpnumcacheindex; i--)
 	{
-		if (strcmp(lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname, name) == 0)
+		if (!lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname[8]
+			&& strncmp(lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname, name, 8) == 0)
 		{
 			lumpnumcacheindex = i & (LUMPNUMCACHESIZE - 1);
 			return lumpnumcache[lumpnumcacheindex].lumpnum;
@@ -1045,13 +1080,63 @@ lumpnum_t W_CheckNumForName(const char *name)
 	{
 		// Update the cache.
 		lumpnumcacheindex = (lumpnumcacheindex + 1) & (LUMPNUMCACHESIZE - 1);
-		strlcpy(lumpnumcache[lumpnumcacheindex].lumpname, name, 32);
+		memset(lumpnumcache[lumpnumcacheindex].lumpname, '\0', 32);
+		strncpy(lumpnumcache[lumpnumcacheindex].lumpname, name, 8);
 		lumpnumcache[lumpnumcacheindex].lumpnum = (i<<16)+check;
 
 		return lumpnumcache[lumpnumcacheindex].lumpnum;
 	}
 }
 
+//
+// Like W_CheckNumForName, but can find entries with long names
+//
+// Should be the only version, but that's not possible until we fix
+// all the instances of non null-terminated strings in the codebase...
+//
+lumpnum_t W_CheckNumForLongName(const char *name)
+{
+	INT32 i;
+	lumpnum_t check = INT16_MAX;
+
+	if (!*name) // some doofus gave us an empty string?
+		return LUMPERROR;
+
+	// Check the lumpnumcache first. Loop backwards so that we check
+	// most recent entries first
+	for (i = lumpnumcacheindex + LUMPNUMCACHESIZE; i > lumpnumcacheindex; i--)
+	{
+		if (strcmp(lumpnumcache[i & (LUMPNUMCACHESIZE - 1)].lumpname, name) == 0)
+		{
+			lumpnumcacheindex = i & (LUMPNUMCACHESIZE - 1);
+			return lumpnumcache[lumpnumcacheindex].lumpnum;
+		}
+	}
+
+	// scan wad files backwards so patch lump files take precedence
+	for (i = numwadfiles - 1; i >= 0; i--)
+	{
+		check = W_CheckNumForLongNamePwad(name,(UINT16)i,0);
+		if (check != INT16_MAX)
+			break; //found it
+	}
+
+	if (check == INT16_MAX) return LUMPERROR;
+	else
+	{
+		if (strlen(name) < 32)
+		{
+			// Update the cache.
+			lumpnumcacheindex = (lumpnumcacheindex + 1) & (LUMPNUMCACHESIZE - 1);
+			memset(lumpnumcache[lumpnumcacheindex].lumpname, '\0', 32);
+			strlcpy(lumpnumcache[lumpnumcacheindex].lumpname, name, 32);
+			lumpnumcache[lumpnumcacheindex].lumpnum = (i << 16) + check;
+		}
+
+		return (i << 16) + check;
+	}
+}
+
 // Look for valid map data through all added files in descendant order.
 // Get a map marker for WADs, and a standalone WAD file lump inside PK3s.
 // TODO: Make it search through cache first, maybe...?
@@ -1100,6 +1185,24 @@ lumpnum_t W_GetNumForName(const char *name)
 	return i;
 }
 
+//
+// Like W_GetNumForName, but can find entries with long names
+//
+// Should be the only version, but that's not possible until we fix
+// all the instances of non null-terminated strings in the codebase...
+//
+lumpnum_t W_GetNumForLongName(const char *name)
+{
+	lumpnum_t i;
+
+	i = W_CheckNumForLongName(name);
+
+	if (i == LUMPERROR)
+		I_Error("W_GetNumForLongName: %s not found!\n", name);
+
+	return i;
+}
+
 //
 // W_CheckNumForNameInBlock
 // Checks only in blocks from blockstart lump to blockend lump
@@ -1139,7 +1242,7 @@ UINT8 W_LumpExists(const char *name)
 	{
 		lumpinfo_t *lump_p = wadfiles[i]->lumpinfo;
 		for (j = 0; j < wadfiles[i]->numlumps; ++j, ++lump_p)
-			if (fastcmp(lump_p->name,name))
+			if (fastcmp(lump_p->longname, name))
 				return true;
 	}
 	return false;
@@ -1680,6 +1783,17 @@ void *W_CachePatchName(const char *name, INT32 tag)
 		return W_CachePatchNum(W_GetNumForName("MISSING"), tag);
 	return W_CachePatchNum(num, tag);
 }
+
+void *W_CachePatchLongName(const char *name, INT32 tag)
+{
+	lumpnum_t num;
+
+	num = W_CheckNumForLongName(name);
+
+	if (num == LUMPERROR)
+		return W_CachePatchNum(W_GetNumForLongName("MISSING"), tag);
+	return W_CachePatchNum(num, tag);
+}
 #ifndef NOMD5
 #define MD5_LEN 16
 
diff --git a/src/w_wad.h b/src/w_wad.h
index 3af6148f407dda936d52eeca52bb82094fc1fec2..88b542fca824ef0915f86e380c9f469d933c4638 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -156,6 +156,7 @@ const char *W_CheckNameForNumPwad(UINT16 wad, UINT16 lump);
 const char *W_CheckNameForNum(lumpnum_t lumpnum);
 
 UINT16 W_CheckNumForNamePwad(const char *name, UINT16 wad, UINT16 startlump); // checks only in one pwad
+UINT16 W_CheckNumForLongNamePwad(const char *name, UINT16 wad, UINT16 startlump);
 
 /* Find the first lump after F_START for instance. */
 UINT16 W_CheckNumForMarkerStartPwad(const char *name, UINT16 wad, UINT16 startlump);
@@ -166,7 +167,9 @@ UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump)
 
 lumpnum_t W_CheckNumForMap(const char *name);
 lumpnum_t W_CheckNumForName(const char *name);
+lumpnum_t W_CheckNumForLongName(const char *name);
 lumpnum_t W_GetNumForName(const char *name); // like W_CheckNumForName but I_Error on LUMPERROR
+lumpnum_t W_GetNumForLongName(const char *name);
 lumpnum_t W_CheckNumForNameInBlock(const char *name, const char *blockstart, const char *blockend);
 UINT8 W_LumpExists(const char *name); // Lua uses this.
 
@@ -194,6 +197,7 @@ boolean W_IsPatchCached(lumpnum_t lump, void *ptr);
 
 void *W_CacheLumpName(const char *name, INT32 tag);
 void *W_CachePatchName(const char *name, INT32 tag);
+void *W_CachePatchLongName(const char *name, INT32 tag);
 
 // Returns either a Software patch, or an OpenGL patch.
 // Performs any necessary conversions from PNG images.