diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index b1a128b9d12b7abfb7f7aa2d46ac1a398b2bfd9b..e92341a664ee2cf250e5ecd9800db4e768eda01d 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -2041,7 +2041,7 @@ static boolean SV_SendServerConfig(INT32 node)
 }
 
 #ifndef NONET
-#define SAVEGAMESIZE (768*1024)
+#define SAVEGAMESIZE (2048*1024) //(768*1024)
 
 static void SV_SendSaveGame(INT32 node)
 {
@@ -3037,6 +3037,9 @@ static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
 		}
 	}
 
+	if (players[playernum].world)
+		((world_t *)players[playernum].world)->players--;
+
 	if (gametyperules & GTR_TEAMFLAGS)
 		P_PlayerFlagBurst(&players[playernum], false); // Don't take the flag with you!
 
@@ -3903,8 +3906,6 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
 		D_SendPlayerConfig();
 		addedtogame = true;
 
-		P_SwitchPlayerWorld(newplayer, world);
-
 		if (rejoined)
 		{
 			if (newplayer->mo)
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 936e57bc8d7393e5e26c087951e8b5283640ad1b..1490784e171d2f42c3f9e772c5a702cad962168d 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -62,6 +62,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum);
 static void Got_WeaponPref(UINT8 **cp, INT32 playernum);
 static void Got_Mapcmd(UINT8 **cp, INT32 playernum);
 static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum);
+static void Got_Switchworld(UINT8 **cp, INT32 playernum);
 static void Got_RequestAddfilecmd(UINT8 **cp, INT32 playernum);
 static void Got_Addfilecmd(UINT8 **cp, INT32 playernum);
 static void Got_Pause(UINT8 **cp, INT32 playernum);
@@ -406,7 +407,7 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
 	"ADDPLAYER",
 	"TEAMCHANGE",
 	"CLEARSCORES",
-	"LOGIN",
+	"SWITCHWORLD",
 	"VERIFIED",
 	"RANDOMSEED",
 	"RUNSOC",
@@ -444,6 +445,7 @@ void D_RegisterServerCommands(void)
 	RegisterNetXCmd(XD_WEAPONPREF, Got_WeaponPref);
 	RegisterNetXCmd(XD_MAP, Got_Mapcmd);
 	RegisterNetXCmd(XD_EXITLEVEL, Got_ExitLevelcmd);
+	RegisterNetXCmd(XD_SWITCHWORLD, Got_Switchworld);
 	RegisterNetXCmd(XD_ADDFILE, Got_Addfilecmd);
 	RegisterNetXCmd(XD_REQADDFILE, Got_RequestAddfilecmd);
 	RegisterNetXCmd(XD_PAUSE, Got_Pause);
@@ -1783,9 +1785,10 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean addworld, boolean pult
 	else
 	{
 		mapchangepending = false;
+
 		// spawn the server if needed
 		// reset players if there is a new one
-		if (!IsPlayerAdmin(consoleplayer))
+		if (!IsPlayerAdmin(consoleplayer) && !addworld)
 		{
 			if (SV_SpawnServer())
 				buf[0] &= ~(1<<1);
@@ -1848,7 +1851,6 @@ static void Command_Map_f(void)
 	size_t option_gametype;
 	const char *gametypename;
 	boolean newresetplayers;
-	boolean addworld;
 
 	boolean mustmodifygame;
 
@@ -1861,7 +1863,9 @@ static void Command_Map_f(void)
 
 	INT32 d;
 
-	if (client && !IsPlayerAdmin(consoleplayer))
+	boolean addworld = COM_CheckParm("-addworld");
+
+	if (client && !addworld && !IsPlayerAdmin(consoleplayer))
 	{
 		CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
 		return;
@@ -1870,7 +1874,6 @@ static void Command_Map_f(void)
 	option_force    =   COM_CheckPartialParm("-f");
 	option_gametype =   COM_CheckPartialParm("-g");
 	newresetplayers = ! COM_CheckParm("-noresetplayers");
-	addworld        =   COM_CheckParm("-addworld");
 
 	mustmodifygame =
 		!( netgame     || multiplayer ) &&
@@ -2094,7 +2097,8 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	{
 		DEBFILE(va("Warping to %s [resetplayer=%d lastgametype=%d gametype=%d cpnd=%d]\n",
 			mapname, resetplayer, lastgametype, gametype, chmappending));
-		CONS_Printf(M_GetText("Speeding off to level...\n"));
+		if (!addworld || (addworld && playernum == consoleplayer))
+			CONS_Printf(M_GetText("Speeding off to level...\n"));
 	}
 
 	if (demoplayback && !timingdemo)
@@ -2129,6 +2133,44 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
 	demo_start = true;
 }
 
+void SendWorldSwitch(INT32 worldnum, boolean nodetach)
+{
+	UINT8 buf[sizeof(INT32) + sizeof(UINT8)];
+	UINT8 *p = buf;
+
+	WRITEINT32(p, worldnum);
+	WRITEUINT8(p, nodetach ? 1 : 0);
+
+	SendNetXCmd(XD_SWITCHWORLD, buf, sizeof(buf));
+}
+
+static void Got_Switchworld(UINT8 **cp, INT32 playernum)
+{
+	player_t *player = &players[playernum];
+
+	INT32 worldnum = READINT32(*cp);
+	UINT8 nodetach = READUINT8(*cp);
+
+	if (worldnum > numworlds)
+	{
+		CONS_Alert(CONS_WARNING, M_GetText("Illegal world switch command received from %s\n"), player_names[playernum]);
+		if (server)
+			SendKick(playernum, KICK_MSG_CON_FAIL | KICK_MSG_KEEP_BODY);
+		return;
+	}
+
+	if (nodetach) // Don't detach from the current world, if any
+	{
+#if 1
+		if (player->world)
+			((world_t *)player->world)->players--;
+#endif
+		player->world = NULL;
+	}
+
+	P_SwitchWorld(player, worldlist[worldnum]);
+}
+
 static void Command_Pause(void)
 {
 	UINT8 buf[2];
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index a7de0237676a67d57b92b07d82a94a839776b099..674c59d7898d3c56026041f88a05428906e67d00 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -133,8 +133,8 @@ typedef enum
 	XD_ADDPLAYER,   // 10
 	XD_TEAMCHANGE,  // 11
 	XD_CLEARSCORES, // 12
-	// UNUSED          13 (Because I don't want to change these comments)
-	XD_VERIFIED = 14,//14
+	XD_SWITCHWORLD, // 13
+	XD_VERIFIED,    // 14
 	XD_RANDOMSEED,  // 15
 	XD_RUNSOC,      // 16
 	XD_REQADDFILE,  // 17
@@ -206,6 +206,7 @@ void ClearAdminPlayers(void);
 void RemoveAdminPlayer(INT32 playernum);
 void ItemFinder_OnChange(void);
 void D_SetPassword(const char *pw);
+void SendWorldSwitch(INT32 worldnum, boolean nodetach);
 
 // used for the player setup menu
 UINT8 CanChangeSkin(INT32 playernum);
diff --git a/src/d_player.h b/src/d_player.h
index 9ea4d5970c4c2a9e883336a5019b500c37aae8b0..86a375fe91ae08aca88d99115286dc30cc03310f 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -323,6 +323,7 @@ typedef struct player_s
 	playerstate_t playerstate;
 
 	void *world;
+	INT32 worldnum; // Lactozilla: for unarchiving purposes only, just like mobjnum
 
 	// Determine POV, including viewpoint bobbing during movement.
 	fixed_t camerascale;
diff --git a/src/doomdef.h b/src/doomdef.h
index 6cfaba8a1148d073908734dddcd8f45feb4e2ad9..c46e458e122c61fab3b075b82d9d07301548aa95 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -392,6 +392,10 @@ extern skincolor_t skincolors[MAXSKINCOLORS];
 
 #define PUSHACCEL (2*FRACUNIT) // Acceleration for MF2_SLIDEPUSH items.
 
+// Time interval for item respawning.
+// WARNING MUST be a power of 2
+#define ITEMQUESIZE 1024
+
 // Special linedef executor tag numbers!
 enum {
 	LE_PINCHPHASE      =    -2, // A boss entered pinch phase (and, in most cases, is preparing their pinch phase attack!)
diff --git a/src/doomstat.h b/src/doomstat.h
index d48978042283caef1f8a2c20d20ff10905dffc74..aef9541221799f62f9d49c0ee3f030748efabeae 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -40,7 +40,6 @@ extern UINT32 mapmusposition;
 // Use other bits if necessary.
 
 extern UINT32 maptol;
-extern UINT8 globalweather;
 extern INT32 curWeather;
 extern INT32 cursaveslot;
 //extern INT16 lastmapsaved;
diff --git a/src/f_finale.c b/src/f_finale.c
index 50cd5e8d19fd329293663946912874cdf8ae2b1c..477c6373c5470c5cf19ed482770f17fbeac8bc67 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -2541,7 +2541,6 @@ void F_StartTitleScreen(void)
 		P_UnloadWorldList();
 
 		maptol = mapheaderinfo[gamemap-1]->typeoflevel;
-		globalweather = mapheaderinfo[gamemap-1]->weather;
 
 		G_DoLoadLevel(&players[displayplayer], false, true);
 		if (!titlemap)
diff --git a/src/g_game.c b/src/g_game.c
index 1238e5aa2610546913515ed6375546e8c39b6356..405df674e4f2f4e5d77a3a70eedabddf535252e1 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -51,6 +51,7 @@
 gameaction_t gameaction;
 gamestate_t gamestate = GS_NULL;
 UINT8 ultimatemode = false;
+boolean roaming = false;
 
 boolean botingame;
 UINT8 botskin;
@@ -77,7 +78,6 @@ UINT32 mapmusposition; // Position to jump to
 
 INT16 gamemap = 1;
 UINT32 maptol;
-UINT8 globalweather = 0;
 INT32 curWeather = PRECIP_NONE;
 INT32 cursaveslot = 0; // Auto-save 1p savegame slot
 //INT16 lastmapsaved = 0; // Last map we auto-saved at
@@ -1779,6 +1779,14 @@ static void AutoBrake2_OnChange(void)
 	SendWeaponPref2();
 }
 
+static void G_ResetCamera(INT32 playernum)
+{
+	if (playernum == consoleplayer && camera.chase)
+		P_ResetCamera(&players[displayplayer], &camera);
+	if (playernum == 1 && camera2.chase && splitscreen)
+		P_ResetCamera(&players[secondarydisplayplayer], &camera2);
+}
+
 //
 // G_DoLoadLevel
 //
@@ -1849,13 +1857,8 @@ void G_DoLoadLevel(player_t *player, boolean addworld, boolean resetplayer)
 	Z_CheckHeap(-2);
 #endif
 
-	if (player == &players[consoleplayer])
+	if (!addworld)
 	{
-		if (camera.chase)
-			P_ResetCamera(&players[displayplayer], &camera);
-		if (camera2.chase && splitscreen)
-			P_ResetCamera(&players[secondarydisplayplayer], &camera2);
-
 		// clear cmd building stuff
 		memset(gamekeydown, 0, sizeof (gamekeydown));
 		for (i = 0;i < JOYAXISSET; i++)
@@ -1866,6 +1869,14 @@ void G_DoLoadLevel(player_t *player, boolean addworld, boolean resetplayer)
 		mousex = mousey = 0;
 		mouse2x = mouse2y = 0;
 
+		if (splitscreen)
+			G_ResetCamera(1);
+	}
+
+	if (player == &players[consoleplayer])
+	{
+		G_ResetCamera(consoleplayer);
+
 		// clear hud messages remains (usually from game startup)
 		CON_ClearHUD();
 	}
@@ -2365,7 +2376,7 @@ void G_Ticker(boolean run)
 // G_PlayerFinishLevel
 // Called when a player completes a level.
 //
-static inline void G_PlayerFinishLevel(INT32 player)
+void G_PlayerFinishLevel(INT32 player)
 {
 	player_t *p;
 
@@ -2707,9 +2718,6 @@ void G_MovePlayerToSpawnOrStarpost(INT32 playernum)
 		P_MovePlayerToStarpost(playernum);
 	else
 		P_MovePlayerToSpawn(playernum, G_FindMapStart(playernum));
-
-	if (players[playernum].world)
-		P_MarkWorldVisited(&players[playernum], players[playernum].world);
 }
 
 mapthing_t *G_FindCTFStart(INT32 playernum)
@@ -3150,9 +3158,8 @@ void G_AddPlayer(INT32 playernum)
 		}
 	}
 
-	p->world = world;
-	if (p->world)
-		((world_t *)p->world)->players++;
+	if (worldlist) // Assume the player is on the first world
+		p->world = worldlist[0];
 
 	p->playerstate = PST_REBORN;
 
@@ -3779,34 +3786,9 @@ static void G_HandleSaveLevel(void)
 		G_SaveGame((UINT32)cursaveslot, lastmap+1); // not nextmap+1 to route around special stages
 }
 
-//
-// G_DoCompleted
-//
-static void G_DoCompleted(void)
+void G_SetNextMap(boolean usespec, boolean inspec)
 {
 	INT32 i;
-	boolean spec = G_IsSpecialStage(gamemap);
-
-	tokenlist = 0; // Reset the list
-
-	if (modeattacking && pausedelay)
-		pausedelay = 0;
-
-	gameaction = ga_nothing;
-
-	if (metalplayback)
-		G_StopMetalDemo();
-	if (metalrecording)
-		G_StopMetalRecording(false);
-
-	for (i = 0; i < MAXPLAYERS; i++)
-		if (playeringame[i])
-			G_PlayerFinishLevel(i); // take away cards and stuff
-
-	if (automapactive)
-		AM_Stop();
-
-	S_StopSounds();
 
 	prevmap = (INT16)(gamemap-1);
 
@@ -3827,7 +3809,7 @@ static void G_DoCompleted(void)
 	// a map of the proper gametype -- skip levels that don't support
 	// the current gametype. (Helps avoid playing boss levels in Race,
 	// for instance).
-	if (!spec)
+	if (!inspec)
 	{
 		if (nextmap >= 0 && nextmap < NUMMAPS)
 		{
@@ -3882,7 +3864,7 @@ static void G_DoCompleted(void)
 		lastmap = nextmap; // Remember last map for when you come out of the special stage.
 	}
 
-	if ((gottoken = ((gametyperules & GTR_SPECIALSTAGES) && token)))
+	if ((gottoken = (usespec && token)))
 	{
 		token--;
 
@@ -3900,8 +3882,39 @@ static void G_DoCompleted(void)
 		}
 	}
 
-	if (spec && !gottoken)
+	if (inspec && !gottoken)
 		nextmap = lastmap; // Exiting from a special stage? Go back to the game. Tails 08-11-2001
+}
+
+//
+// G_DoCompleted
+//
+static void G_DoCompleted(void)
+{
+	INT32 i;
+	boolean spec = G_IsSpecialStage(gamemap);
+
+	tokenlist = 0; // Reset the list
+
+	if (modeattacking && pausedelay)
+		pausedelay = 0;
+
+	gameaction = ga_nothing;
+
+	if (metalplayback)
+		G_StopMetalDemo();
+	if (metalrecording)
+		G_StopMetalRecording(false);
+
+	for (i = 0; i < MAXPLAYERS; i++)
+		if (playeringame[i])
+			G_PlayerFinishLevel(i); // take away cards and stuff
+
+	if (automapactive)
+		AM_Stop();
+
+	S_StopSounds();
+	G_SetNextMap(gametyperules & GTR_SPECIALSTAGES, spec);
 
 	automapactive = false;
 
@@ -4755,6 +4768,7 @@ void G_InitNew(player_t *player,
 		if (addworld)
 		{
 			P_DetachPlayerWorld(player);
+			P_UnloadWorldPlayer(player);
 			G_ResetPlayer(player, pultmode, FLS);
 		}
 		else
@@ -4789,7 +4803,6 @@ void G_InitNew(player_t *player,
 		P_AllocMapHeader(gamemap-1);
 
 	maptol = mapheaderinfo[gamemap-1]->typeoflevel;
-	globalweather = mapheaderinfo[gamemap-1]->weather;
 
 	// Don't carry over custom music change to another map.
 	mapmusflags |= MUSIC_RELOADRESET;
diff --git a/src/g_game.h b/src/g_game.h
index 3b47593c375ea4317325cbd7c3b8994921f1e708..f77630c4014cb0481a3600d8b5141a7032d576e2 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -198,6 +198,8 @@ boolean G_CompetitionGametype(void);
 boolean G_EnoughPlayersFinished(void);
 void G_ExitLevel(void);
 void G_NextLevel(void);
+void G_SetNextMap(boolean usespec, boolean inspec);
+void G_PlayerFinishLevel(INT32 player);
 void G_Continue(void);
 void G_UseContinue(void);
 void G_AfterIntermission(void);
diff --git a/src/g_state.h b/src/g_state.h
index e364c5a35b62c323464783d518bf266b2abe4185..6fb2bd872c88cf00e8592a590e1b9a4f7ccc247b 100644
--- a/src/g_state.h
+++ b/src/g_state.h
@@ -54,6 +54,7 @@ typedef enum
 extern gamestate_t gamestate;
 extern UINT8 titlemapinaction;
 extern UINT8 ultimatemode; // was sk_insane
+extern boolean roaming;
 extern gameaction_t gameaction;
 
 extern boolean botingame;
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index c4937a54d2e9b086d6e2d13cb02b21a43af9d6a1..ffa676e329e525119e0089f63332b3e02481ebff 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5061,8 +5061,8 @@ static void HWR_DrawSkyBackground(player_t *player)
 		}
 		dometransform.splitscreen = splitscreen;
 
-		HWR_GetTexture(texturetranslation[viewworld->skytexture]);
-		HWD.pfnRenderSkyDome(viewworld->skytexture, textures[viewworld->skytexture]->width, textures[viewworld->skytexture]->height, dometransform);
+		HWR_GetTexture(texturetranslation[skytexture]);
+		HWD.pfnRenderSkyDome(skytexture, textures[skytexture]->width, textures[skytexture]->height, dometransform);
 	}
 	else
 	{
@@ -5072,7 +5072,7 @@ static void HWR_DrawSkyBackground(player_t *player)
 		float aspectratio;
 		float angleturn;
 
-		HWR_GetTexture(texturetranslation[viewworld->skytexture]);
+		HWR_GetTexture(texturetranslation[skytexture]);
 		aspectratio = (float)vid.width/(float)vid.height;
 
 		//Hurdler: the sky is the only texture who need 4.0f instead of 1.0
@@ -5099,7 +5099,7 @@ static void HWR_DrawSkyBackground(player_t *player)
 
 		angle = (dup_viewangle + gr_xtoviewangle[0]);
 
-		dimensionmultiply = ((float)textures[texturetranslation[viewworld->skytexture]]->width/256.0f);
+		dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->width/256.0f);
 
 		v[0].s = v[3].s = (-1.0f * angle) / ((ANGLE_90-1)*dimensionmultiply); // left
 		v[2].s = v[1].s = v[0].s + (1.0f/dimensionmultiply); // right (or left + 1.0f)
@@ -5107,7 +5107,7 @@ static void HWR_DrawSkyBackground(player_t *player)
 
 		// Y
 		angle = aimingangle;
-		dimensionmultiply = ((float)textures[texturetranslation[viewworld->skytexture]]->height/(128.0f*aspectratio));
+		dimensionmultiply = ((float)textures[texturetranslation[skytexture]]->height/(128.0f*aspectratio));
 
 		if (splitscreen)
 		{
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index b1187f5437762f7ecf51e8ea1bcaed39444303f3..b537d47da4eecdc1f62e5469be7882445dfc10ae 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -2000,7 +2000,7 @@ static int lib_pSwitchWeather(lua_State *L)
 	if (!lua_isnone(L, 2) && lua_isuserdata(L, 2)) // if a player, setup weather for only the player, otherwise setup weather for all players
 		user = *((player_t **)luaL_checkudata(L, 2, META_PLAYER));
 	if (!user) // global
-		globalweather = weathernum;
+		world->weather = weathernum;
 	if (!user || P_IsLocalPlayer(user))
 		P_SwitchWeather(weathernum);
 	return 0;
diff --git a/src/lua_script.c b/src/lua_script.c
index ecd369e91290c43026429b6ab62e3e7975f57543..0fe26b1bee192ed4116083499a039d7b065d68fe 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -293,13 +293,13 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 		lua_pushinteger(L, curWeather);
 		return 1;
 	} else if (fastcmp(word,"globalweather")) {
-		lua_pushinteger(L, globalweather);
+		lua_pushinteger(L, (localworld ? localworld->weather : PRECIP_NONE));
 		return 1;
 	} else if (fastcmp(word,"levelskynum")) {
 		lua_pushinteger(L, levelskynum);
 		return 1;
 	} else if (fastcmp(word,"globallevelskynum")) {
-		lua_pushinteger(L, globallevelskynum);
+		lua_pushinteger(L, (localworld ? localworld->skynum : 0));
 		return 1;
 	} else if (fastcmp(word,"mapmusname")) {
 		lua_pushstring(L, mapmusname);
@@ -1338,40 +1338,40 @@ static UINT8 UnArchiveValue(int TABLESINDEX)
 		LUA_PushUserdata(gL, &states[READUINT16(save_p)], META_STATE);
 		break;
 	case ARCH_MOBJ:
-		LUA_PushUserdata(gL, P_FindNewPosition(READUINT32(save_p)), META_MOBJ);
+		LUA_PushUserdata(gL, P_FindNewPosition(archiveworld, READUINT32(save_p)), META_MOBJ);
 		break;
 	case ARCH_PLAYER:
 		LUA_PushUserdata(gL, &players[READUINT8(save_p)], META_PLAYER);
 		break;
 	case ARCH_MAPTHING:
-		LUA_PushUserdata(gL, &mapthings[READUINT16(save_p)], META_MAPTHING);
+		LUA_PushUserdata(gL, &archiveworld->mapthings[READUINT16(save_p)], META_MAPTHING);
 		break;
 	case ARCH_VERTEX:
-		LUA_PushUserdata(gL, &vertexes[READUINT16(save_p)], META_VERTEX);
+		LUA_PushUserdata(gL, &archiveworld->vertexes[READUINT16(save_p)], META_VERTEX);
 		break;
 	case ARCH_LINE:
-		LUA_PushUserdata(gL, &lines[READUINT16(save_p)], META_LINE);
+		LUA_PushUserdata(gL, &archiveworld->lines[READUINT16(save_p)], META_LINE);
 		break;
 	case ARCH_SIDE:
-		LUA_PushUserdata(gL, &sides[READUINT16(save_p)], META_SIDE);
+		LUA_PushUserdata(gL, &archiveworld->sides[READUINT16(save_p)], META_SIDE);
 		break;
 	case ARCH_SUBSECTOR:
-		LUA_PushUserdata(gL, &subsectors[READUINT16(save_p)], META_SUBSECTOR);
+		LUA_PushUserdata(gL, &archiveworld->subsectors[READUINT16(save_p)], META_SUBSECTOR);
 		break;
 	case ARCH_SECTOR:
-		LUA_PushUserdata(gL, &sectors[READUINT16(save_p)], META_SECTOR);
+		LUA_PushUserdata(gL, &archiveworld->sectors[READUINT16(save_p)], META_SECTOR);
 		break;
 #ifdef HAVE_LUA_SEGS
 	case ARCH_SEG:
-		LUA_PushUserdata(gL, &segs[READUINT16(save_p)], META_SEG);
+		LUA_PushUserdata(gL, &archiveworld->segs[READUINT16(save_p)], META_SEG);
 		break;
 	case ARCH_NODE:
-		LUA_PushUserdata(gL, &nodes[READUINT16(save_p)], META_NODE);
+		LUA_PushUserdata(gL, &archiveworld->nodes[READUINT16(save_p)], META_NODE);
 		break;
 #endif
 	case ARCH_FFLOOR:
 	{
-		sector_t *sector = &sectors[READUINT16(save_p)];
+		sector_t *sector = &archiveworld->sectors[READUINT16(save_p)];
 		UINT16 id = READUINT16(save_p);
 		ffloor_t *rover = P_GetFFloorByID(sector, id);
 		if (rover)
diff --git a/src/p_enemy.c b/src/p_enemy.c
index bb4e8eca97e854a5f318882a84769ead4aba2edf..6ec021ef08a45e29d31f59c555452650c26b3d65 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -749,6 +749,9 @@ boolean P_LookForPlayers(mobj_t *actor, boolean allaround, boolean tracer, fixed
 		if (player->quittime)
 			continue; // Ignore uncontrolled bodies
 
+		if (player->world != actor->world)
+			continue; // Different world
+
 		if (dist > 0
 			&& P_AproxDistance(P_AproxDistance(player->mo->x - actor->x, player->mo->y - actor->y), player->mo->z - actor->z) > dist)
 			continue; // Too far away
diff --git a/src/p_local.h b/src/p_local.h
index ce60cc66b5121ab8069d2c28a5a6211afef68d32..1f053800f0b98499ea9d5f83e643dc4995c3c58a 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -266,13 +266,6 @@ void P_PlayJingleMusic(player_t *player, const char *musname, UINT16 musflags, b
 #define ONFLOORZ INT32_MIN
 #define ONCEILINGZ INT32_MAX
 
-// Time interval for item respawning.
-// WARNING MUST be a power of 2
-#define ITEMQUESIZE 1024
-
-extern mapthing_t *itemrespawnque[ITEMQUESIZE];
-extern tic_t itemrespawntime[ITEMQUESIZE];
-extern size_t iquehead, iquetail;
 extern consvar_t cv_gravity, cv_movebob;
 
 void P_RespawnSpecials(void);
@@ -280,7 +273,7 @@ void P_RespawnSpecials(void);
 mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type);
 
 void P_RecalcPrecipInSector(sector_t *sector);
-void P_PrecipitationEffects(void);
+void P_PrecipitationEffects(world_t *w);
 
 void P_RemoveMobj(mobj_t *th);
 boolean P_MobjWasRemoved(mobj_t *th);
diff --git a/src/p_mobj.c b/src/p_mobj.c
index c6d65510bb6690b0737e4499fb372d21f7a81380..f9467708244b2d3d1388c9c126327c4ed01c5410 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -4205,6 +4205,9 @@ boolean P_SupermanLook4Players(mobj_t *actor)
 			if (players[c].mo->health <= 0)
 				continue; // dead
 
+			if (players[c].world != actor->world)
+				continue; // Different world
+
 			playersinthegame[stop] = &players[c];
 			stop++;
 		}
@@ -10028,13 +10031,13 @@ void P_MobjThinker(mobj_t *mobj)
 		return;
 
 	// Remove dead target/tracer.
-	if (mobj->target && P_MobjWasRemoved(mobj->target))
+	if (!P_MobjIsConnected(mobj, mobj->target))
 		P_SetTarget(&mobj->target, NULL);
-	if (mobj->tracer && P_MobjWasRemoved(mobj->tracer))
+	if (!P_MobjIsConnected(mobj, mobj->tracer))
 		P_SetTarget(&mobj->tracer, NULL);
-	if (mobj->hnext && P_MobjWasRemoved(mobj->hnext))
+	if (!P_MobjIsConnected(mobj, mobj->hnext))
 		P_SetTarget(&mobj->hnext, NULL);
-	if (mobj->hprev && P_MobjWasRemoved(mobj->hprev))
+	if (!P_MobjIsConnected(mobj, mobj->hprev))
 		P_SetTarget(&mobj->hprev, NULL);
 
 	mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG);
@@ -10906,9 +10909,6 @@ static inline precipmobj_t *P_SpawnSnowMobj(fixed_t x, fixed_t y, fixed_t z, mob
 //
 // P_RemoveMobj
 //
-mapthing_t *itemrespawnque[ITEMQUESIZE];
-tic_t itemrespawntime[ITEMQUESIZE];
-size_t iquehead, iquetail;
 
 #ifdef PARANOIA
 #define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed
@@ -10933,12 +10933,12 @@ void P_RemoveMobj(mobj_t *mobj)
 		|| P_WeaponOrPanel(mobj->type))
 		&& !(mobj->flags2 & MF2_DONTRESPAWN))
 	{
-		itemrespawnque[iquehead] = mobj->spawnpoint;
-		itemrespawntime[iquehead] = leveltime;
-		iquehead = (iquehead+1)&(ITEMQUESIZE-1);
+		world->itemrespawnque[world->iquehead] = mobj->spawnpoint;
+		world->itemrespawntime[world->iquehead] = leveltime;
+		world->iquehead = (world->iquehead+1)&(ITEMQUESIZE-1);
 		// lose one off the end?
-		if (iquehead == iquetail)
-			iquetail = (iquetail+1)&(ITEMQUESIZE-1);
+		if (world->iquehead == world->iquetail)
+			world->iquetail = (world->iquetail+1)&(ITEMQUESIZE-1);
 	}
 
 	if (mobj->type == MT_OVERLAY)
@@ -11127,7 +11127,7 @@ void P_SpawnPrecipitation(void)
 //
 // P_PrecipitationEffects
 //
-void P_PrecipitationEffects(void)
+void P_PrecipitationEffects(world_t *w)
 {
 	INT16 thunderchance = INT16_MAX;
 	INT32 volume;
@@ -11144,8 +11144,8 @@ void P_PrecipitationEffects(void)
 	// with global rain and switched players to anything else ...
 	// If the global weather has lightning strikes,
 	// EVERYONE gets them at the SAME time!
-	else if (globalweather == PRECIP_STORM
-	 || globalweather == PRECIP_STORM_NORAIN)
+	else if (w->weather == PRECIP_STORM
+	 || w->weather == PRECIP_STORM_NORAIN)
 		thunderchance = (P_RandomKey(8192));
 	// But on the other hand, if the global weather is ANYTHING ELSE,
 	// don't sync lightning strikes.
@@ -11180,8 +11180,8 @@ void P_PrecipitationEffects(void)
 	{
 		sector_t *ss = sectors;
 
-		for (i = 0; i < numsectors; i++, ss++)
-			if (ss->ceilingpic == world->skyflatnum) // Only for the sky.
+		for (i = 0; i < w->numsectors; i++, ss++)
+			if (ss->ceilingpic == w->skyflatnum) // Only for the sky.
 				P_SpawnLightningFlash(ss); // Spawn a quick flash thinker
 	}
 
@@ -11193,7 +11193,7 @@ void P_PrecipitationEffects(void)
 	if (sound_disabled)
 		return; // Sound off? D'aw, no fun.
 
-	if (players[displayplayer].mo->subsector->sector->ceilingpic == world->skyflatnum)
+	if (players[displayplayer].mo->subsector->sector->ceilingpic == w->skyflatnum)
 		volume = 255; // Sky above? We get it full blast.
 	else
 	{
@@ -11209,7 +11209,7 @@ void P_PrecipitationEffects(void)
 		for (y = yl; y <= yh; y += FRACUNIT*64)
 			for (x = xl; x <= xh; x += FRACUNIT*64)
 			{
-				if (R_PointInSubsector(x, y)->sector->ceilingpic == world->skyflatnum) // Found the outdoors!
+				if (R_PointInWorldSubsector(w, x, y)->sector->ceilingpic == w->skyflatnum) // Found the outdoors!
 				{
 					newdist = S_CalculateSoundDistance(players[displayplayer].mo->x, players[displayplayer].mo->y, 0, x, y, 0);
 					if (newdist < closedist)
@@ -11277,15 +11277,15 @@ void P_RespawnSpecials(void)
 		return;
 
 	// nothing left to respawn?
-	if (iquehead == iquetail)
+	if (world->iquehead == world->iquetail)
 		return;
 
 	// the first item in the queue is the first to respawn
 	// wait at least 30 seconds
-	if (leveltime - itemrespawntime[iquetail] < (tic_t)cv_itemrespawntime.value*TICRATE)
+	if (leveltime - world->itemrespawntime[world->iquetail] < (tic_t)cv_itemrespawntime.value*TICRATE)
 		return;
 
-	mthing = itemrespawnque[iquetail];
+	mthing = world->itemrespawnque[world->iquetail];
 
 #ifdef PARANOIA
 	if (!mthing)
@@ -11296,7 +11296,7 @@ void P_RespawnSpecials(void)
 		P_SpawnMapThing(mthing);
 
 	// pull it from the que
-	iquetail = (iquetail+1)&(ITEMQUESIZE-1);
+	world->iquetail = (world->iquetail+1)&(ITEMQUESIZE-1);
 }
 
 //
diff --git a/src/p_mobj.h b/src/p_mobj.h
index 6182d108401d74d6eff59af5ccf9c046de228d69..9e769ce4cc83bec4b5ba741d347e710ef81bd226 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -357,7 +357,9 @@ typedef struct mobj_s
 	fixed_t watertop; // top of the water FOF the mobj is in
 	fixed_t waterbottom; // bottom of the water FOF the mobj is in
 
-	UINT32 mobjnum; // A unique number for this mobj. Used for restoring pointers on save games.
+	// Used for restoring pointers on save games.
+	UINT32 mobjnum; // A unique number for this mobj.
+	UINT32 worldnum; // The world number for this mobj.
 
 	fixed_t scale;
 	fixed_t destscale;
diff --git a/src/p_saveg.c b/src/p_saveg.c
index ba0730da477d0bfe90248dd7b6ca27e61f5d82b3..9c13a242c65cc7fa4f6806543d1d7ac855bc2804 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -37,11 +37,15 @@
 savedata_t savedata;
 UINT8 *save_p;
 
+world_t *archiveworld;
+world_t *unarchiveworld;
+
 // Block UINT32s to attempt to ensure that the correct data is
 // being sent and received
 #define ARCHIVEBLOCK_MISC     0x7FEEDEED
 #define ARCHIVEBLOCK_PLAYERS  0x7F448008
-#define ARCHIVEBLOCK_WORLD    0x7F8C08C0
+#define ARCHIVEBLOCK_MAP      0x7F8C08C0
+#define ARCHIVEBLOCK_WORLD    0x7F9F47A3
 #define ARCHIVEBLOCK_POBJS    0x7F928546
 #define ARCHIVEBLOCK_THINKERS 0x7F37037C
 #define ARCHIVEBLOCK_SPECIALS 0x7F228378
@@ -104,6 +108,7 @@ static void P_NetArchivePlayers(void)
 
 		// no longer send ticcmds, player name, skin, or color
 
+		WRITEINT32(save_p, players[i].worldnum);
 		WRITEINT16(save_p, players[i].angleturn);
 		WRITEINT16(save_p, players[i].oldrelangleturn);
 		WRITEANGLE(save_p, players[i].aiming);
@@ -314,6 +319,7 @@ static void P_NetUnArchivePlayers(void)
 		// sending player names, skin and color should not be necessary at all!
 		// (that data is handled in the server config now)
 
+		players[i].worldnum = READINT32(save_p);
 		players[i].angleturn = READINT16(save_p);
 		players[i].oldrelangleturn = READINT16(save_p);
 		players[i].aiming = READANGLE(save_p);
@@ -728,9 +734,9 @@ static void P_NetArchiveWaypoints(void)
 
 	for (i = 0; i < NUMWAYPOINTSEQUENCES; i++)
 	{
-		WRITEUINT16(save_p, world->numwaypoints[i]);
-		for (j = 0; j < world->numwaypoints[i]; j++)
-			WRITEUINT32(save_p, world->waypoints[i][j] ? world->waypoints[i][j]->mobjnum : 0);
+		WRITEUINT16(save_p, archiveworld->numwaypoints[i]);
+		for (j = 0; j < archiveworld->numwaypoints[i]; j++)
+			WRITEUINT32(save_p, archiveworld->waypoints[i][j] ? archiveworld->waypoints[i][j]->mobjnum : 0);
 	}
 }
 
@@ -741,11 +747,11 @@ static void P_NetUnArchiveWaypoints(void)
 
 	for (i = 0; i < NUMWAYPOINTSEQUENCES; i++)
 	{
-		world->numwaypoints[i] = READUINT16(save_p);
-		for (j = 0; j < world->numwaypoints[i]; j++)
+		unarchiveworld->numwaypoints[i] = READUINT16(save_p);
+		for (j = 0; j < unarchiveworld->numwaypoints[i]; j++)
 		{
 			mobjnum = READUINT32(save_p);
-			world->waypoints[i][j] = (mobjnum == 0) ? NULL : P_FindNewPosition(mobjnum);
+			unarchiveworld->waypoints[i][j] = (mobjnum == 0) ? NULL : P_FindNewPosition(unarchiveworld, mobjnum);
 		}
 	}
 }
@@ -885,11 +891,11 @@ static void UnArchiveFFloors(const sector_t *ss)
 static void ArchiveSectors(void)
 {
 	size_t i;
-	const sector_t *ss = sectors;
-	const sector_t *spawnss = spawnsectors;
+	const sector_t *ss = archiveworld->sectors;
+	const sector_t *spawnss = archiveworld->spawnsectors;
 	UINT8 diff, diff2, diff3;
 
-	for (i = 0; i < numsectors; i++, ss++, spawnss++)
+	for (i = 0; i < archiveworld->numsectors; i++, ss++, spawnss++)
 	{
 		diff = diff2 = diff3 = 0;
 		if (ss->floorheight != spawnss->floorheight)
@@ -954,9 +960,9 @@ static void ArchiveSectors(void)
 			if (diff & SD_CEILHT)
 				WRITEFIXED(save_p, ss->ceilingheight);
 			if (diff & SD_FLOORPIC)
-				WRITEMEM(save_p, world->flats[ss->floorpic].name, 8);
+				WRITEMEM(save_p, archiveworld->flats[ss->floorpic].name, 8);
 			if (diff & SD_CEILPIC)
-				WRITEMEM(save_p, world->flats[ss->ceilingpic].name, 8);
+				WRITEMEM(save_p, archiveworld->flats[ss->ceilingpic].name, 8);
 			if (diff & SD_LIGHT)
 				WRITEINT16(save_p, ss->lightlevel);
 			if (diff & SD_SPECIAL)
@@ -1005,8 +1011,8 @@ static void UnArchiveSectors(void)
 		if (i == 0xffff)
 			break;
 
-		if (i > numsectors)
-			I_Error("Invalid sector number %u from server (expected end at %s)", i, sizeu1(numsectors));
+		if (i > unarchiveworld->numsectors)
+			I_Error("Invalid sector number %u from server (expected end at %s)", i, sizeu1(unarchiveworld->numsectors));
 
 		diff = READUINT8(save_p);
 		if (diff & SD_DIFF2)
@@ -1019,64 +1025,64 @@ static void UnArchiveSectors(void)
 			diff3 = 0;
 
 		if (diff & SD_FLOORHT)
-			sectors[i].floorheight = READFIXED(save_p);
+			unarchiveworld->sectors[i].floorheight = READFIXED(save_p);
 		if (diff & SD_CEILHT)
-			sectors[i].ceilingheight = READFIXED(save_p);
+			unarchiveworld->sectors[i].ceilingheight = READFIXED(save_p);
 		if (diff & SD_FLOORPIC)
 		{
-			sectors[i].floorpic = P_AddLevelFlatRuntime((char *)save_p);
+			unarchiveworld->sectors[i].floorpic = P_AddLevelFlatForWorld(unarchiveworld, (char *)save_p);
 			save_p += 8;
 		}
 		if (diff & SD_CEILPIC)
 		{
-			sectors[i].ceilingpic = P_AddLevelFlatRuntime((char *)save_p);
+			unarchiveworld->sectors[i].ceilingpic = P_AddLevelFlatForWorld(unarchiveworld, (char *)save_p);
 			save_p += 8;
 		}
 		if (diff & SD_LIGHT)
-			sectors[i].lightlevel = READINT16(save_p);
+			unarchiveworld->sectors[i].lightlevel = READINT16(save_p);
 		if (diff & SD_SPECIAL)
-			sectors[i].special = READINT16(save_p);
+			unarchiveworld->sectors[i].special = READINT16(save_p);
 
 		if (diff2 & SD_FXOFFS)
-			sectors[i].floor_xoffs = READFIXED(save_p);
+			unarchiveworld->sectors[i].floor_xoffs = READFIXED(save_p);
 		if (diff2 & SD_FYOFFS)
-			sectors[i].floor_yoffs = READFIXED(save_p);
+			unarchiveworld->sectors[i].floor_yoffs = READFIXED(save_p);
 		if (diff2 & SD_CXOFFS)
-			sectors[i].ceiling_xoffs = READFIXED(save_p);
+			unarchiveworld->sectors[i].ceiling_xoffs = READFIXED(save_p);
 		if (diff2 & SD_CYOFFS)
-			sectors[i].ceiling_yoffs = READFIXED(save_p);
+			unarchiveworld->sectors[i].ceiling_yoffs = READFIXED(save_p);
 		if (diff2 & SD_FLOORANG)
-			sectors[i].floorpic_angle  = READANGLE(save_p);
+			unarchiveworld->sectors[i].floorpic_angle  = READANGLE(save_p);
 		if (diff2 & SD_CEILANG)
-			sectors[i].ceilingpic_angle = READANGLE(save_p);
+			unarchiveworld->sectors[i].ceilingpic_angle = READANGLE(save_p);
 		if (diff2 & SD_TAG)
-			sectors[i].tag = READINT16(save_p); // DON'T use P_ChangeSectorTag
+			unarchiveworld->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);
+			unarchiveworld->sectors[i].firsttag = READINT32(save_p);
+			unarchiveworld->sectors[i].nexttag = READINT32(save_p);
 		}
 
 		if (diff3 & SD_COLORMAP)
-			sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(save_p));
+			unarchiveworld->sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(save_p));
 		if (diff3 & SD_CRUMBLESTATE)
-			sectors[i].crumblestate = READINT32(save_p);
+			unarchiveworld->sectors[i].crumblestate = READINT32(save_p);
 
 		if (diff & SD_FFLOORS)
-			UnArchiveFFloors(&sectors[i]);
+			UnArchiveFFloors(&unarchiveworld->sectors[i]);
 	}
 }
 
 static void ArchiveLines(void)
 {
 	size_t i;
-	const line_t *li = lines;
-	const line_t *spawnli = spawnlines;
+	const line_t *li = archiveworld->lines;
+	const line_t *spawnli = archiveworld->spawnlines;
 	const side_t *si;
 	const side_t *spawnsi;
 	UINT8 diff, diff2; // no diff3
 
-	for (i = 0; i < numlines; i++, spawnli++, li++)
+	for (i = 0; i < archiveworld->numlines; i++, spawnli++, li++)
 	{
 		diff = diff2 = 0;
 
@@ -1088,8 +1094,8 @@ static void ArchiveLines(void)
 
 		if (li->sidenum[0] != 0xffff)
 		{
-			si = &sides[li->sidenum[0]];
-			spawnsi = &spawnsides[li->sidenum[0]];
+			si = &archiveworld->sides[li->sidenum[0]];
+			spawnsi = &archiveworld->spawnsides[li->sidenum[0]];
 			if (si->textureoffset != spawnsi->textureoffset)
 				diff |= LD_S1TEXOFF;
 			//SoM: 4/1/2000: Some textures are colormaps. Don't worry about invalid textures.
@@ -1102,8 +1108,8 @@ static void ArchiveLines(void)
 		}
 		if (li->sidenum[1] != 0xffff)
 		{
-			si = &sides[li->sidenum[1]];
-			spawnsi = &spawnsides[li->sidenum[1]];
+			si = &archiveworld->sides[li->sidenum[1]];
+			spawnsi = &archiveworld->spawnsides[li->sidenum[1]];
 			if (si->textureoffset != spawnsi->textureoffset)
 				diff2 |= LD_S2TEXOFF;
 			if (si->toptexture != spawnsi->toptexture)
@@ -1129,7 +1135,7 @@ static void ArchiveLines(void)
 			if (diff & LD_CLLCOUNT)
 				WRITEINT16(save_p, li->callcount);
 
-			si = &sides[li->sidenum[0]];
+			si = &archiveworld->sides[li->sidenum[0]];
 			if (diff & LD_S1TEXOFF)
 				WRITEFIXED(save_p, si->textureoffset);
 			if (diff & LD_S1TOPTEX)
@@ -1139,7 +1145,7 @@ static void ArchiveLines(void)
 			if (diff & LD_S1MIDTEX)
 				WRITEINT32(save_p, si->midtexture);
 
-			si = &sides[li->sidenum[1]];
+			si = &archiveworld->sides[li->sidenum[1]];
 			if (diff2 & LD_S2TEXOFF)
 				WRITEFIXED(save_p, si->textureoffset);
 			if (diff2 & LD_S2TOPTEX)
@@ -1166,11 +1172,11 @@ static void UnArchiveLines(void)
 
 		if (i == 0xffff)
 			break;
-		if (i > numlines)
+		if (i > unarchiveworld->numlines)
 			I_Error("Invalid line number %u from server", i);
 
 		diff = READUINT8(save_p);
-		li = &lines[i];
+		li = &unarchiveworld->lines[i];
 
 		if (diff & LD_DIFF2)
 			diff2 = READUINT8(save_p);
@@ -1206,27 +1212,19 @@ static void UnArchiveLines(void)
 	}
 }
 
-static void P_NetArchiveWorld(void)
+static void P_NetArchiveMap(void)
 {
-	// initialize colormap vars because paranoia
-	ClearNetColormaps();
-
-	WRITEUINT32(save_p, ARCHIVEBLOCK_WORLD);
-
+	WRITEUINT32(save_p, ARCHIVEBLOCK_MAP);
 	ArchiveSectors();
 	ArchiveLines();
-	R_ClearTextureNumCache(false);
 }
 
-static void P_NetUnArchiveWorld(void)
+static void P_NetUnArchiveMap(void)
 {
 	UINT16 i;
 
-	if (READUINT32(save_p) != ARCHIVEBLOCK_WORLD)
-		I_Error("Bad $$$.sav at archive block World");
-
-	// initialize colormap vars because paranoia
-	ClearNetColormaps();
+	if (READUINT32(save_p) != ARCHIVEBLOCK_MAP)
+		I_Error("Bad $$$.sav at archive block Map");
 
 	// count the level's ffloors so that colormap loading can have an upper limit
 	for (i = 0; i < numsectors; i++)
@@ -1354,13 +1352,13 @@ static inline UINT32 SaveMobjnum(const mobj_t *mobj)
 
 static UINT32 SaveSector(const sector_t *sector)
 {
-	if (sector) return (UINT32)(sector - sectors);
+	if (sector) return (UINT32)(sector - archiveworld->sectors);
 	return 0xFFFFFFFF;
 }
 
 static UINT32 SaveLine(const line_t *line)
 {
-	if (line) return (UINT32)(line - lines);
+	if (line) return (UINT32)(line - archiveworld->lines);
 	return 0xFFFFFFFF;
 }
 
@@ -1543,8 +1541,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 	{
 		size_t z;
 
-		for (z = 0; z < nummapthings; z++)
-			if (&mapthings[z] == mobj->spawnpoint)
+		for (z = 0; z < archiveworld->nummapthings; z++)
+			if (&archiveworld->mapthings[z] == mobj->spawnpoint)
 				WRITEUINT16(save_p, z);
 		if (mobj->type == MT_HOOPCENTER)
 			return;
@@ -1650,6 +1648,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		WRITEFIXED(save_p, mobj->shadowscale);
 
 	WRITEUINT32(save_p, mobj->mobjnum);
+	WRITEUINT32(save_p, mobj->worldnum);
 }
 
 static void SaveNoEnemiesThinker(const thinker_t *th, const UINT8 type)
@@ -2142,7 +2141,7 @@ static void P_NetArchiveThinkers(void)
 	{
 		UINT32 numsaved = 0;
 		// save off the current thinkers
-		for (th = thlist[i].next; th != &thlist[i]; th = th->next)
+		for (th = archiveworld->thlist[i].next; th != &archiveworld->thlist[i]; th = th->next)
 		{
 			if (!(th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed
 			 || th->function.acp1 == (actionf_p1)P_NullPrecipThinker))
@@ -2372,12 +2371,12 @@ static void P_NetArchiveThinkers(void)
 // relink to this; the savegame contains the old position in the pointer
 // field copyed in the info field temporarily, but finally we just search
 // for the old position and relink to it.
-mobj_t *P_FindNewPosition(UINT32 oldposition)
+mobj_t *P_FindNewPosition(world_t *w, UINT32 oldposition)
 {
 	thinker_t *th;
 	mobj_t *mobj;
 
-	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+	for (th = w->thlist[THINK_MOBJ].next; th != &w->thlist[THINK_MOBJ]; th = th->next)
 	{
 		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
 			continue;
@@ -2400,14 +2399,14 @@ static inline mobj_t *LoadMobj(UINT32 mobjnum)
 
 static sector_t *LoadSector(UINT32 sector)
 {
-	if (sector >= numsectors) return NULL;
-	return &sectors[sector];
+	if (sector >= unarchiveworld->numsectors) return NULL;
+	return &unarchiveworld->sectors[sector];
 }
 
 static line_t *LoadLine(UINT32 line)
 {
-	if (line >= numlines) return NULL;
-	return &lines[line];
+	if (line >= unarchiveworld->numlines) return NULL;
+	return &unarchiveworld->lines[line];
 }
 
 static inline player_t *LoadPlayer(UINT32 player)
@@ -2418,8 +2417,8 @@ static inline player_t *LoadPlayer(UINT32 player)
 
 static inline pslope_t *LoadSlope(UINT32 slopeid)
 {
-	pslope_t *p = world->slopelist;
-	if (slopeid > world->slopecount) return NULL;
+	pslope_t *p = unarchiveworld->slopelist;
+	if (slopeid > unarchiveworld->slopecount) return NULL;
 	do
 	{
 		if (p->id == slopeid)
@@ -2468,16 +2467,17 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 	{
 		UINT16 spawnpointnum = READUINT16(save_p);
 
-		if (mapthings[spawnpointnum].type == 1705 || mapthings[spawnpointnum].type == 1713) // NiGHTS Hoop special case
+		if (unarchiveworld->mapthings[spawnpointnum].type == 1705
+		|| unarchiveworld->mapthings[spawnpointnum].type == 1713) // NiGHTS Hoop special case
 		{
-			P_SpawnHoop(&mapthings[spawnpointnum]);
+			P_SpawnHoop(&unarchiveworld->mapthings[spawnpointnum]);
 			return NULL;
 		}
 
 		mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
 
-		mobj->spawnpoint = &mapthings[spawnpointnum];
-		mapthings[spawnpointnum].mobj = mobj;
+		mobj->spawnpoint = &unarchiveworld->mapthings[spawnpointnum];
+		unarchiveworld->mapthings[spawnpointnum].mobj = mobj;
 	}
 	else
 		mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
@@ -2670,6 +2670,11 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 
 	mobj->mobjnum = READUINT32(save_p);
 
+	// Lactozilla: Unarchive world number
+	mobj->worldnum = READUINT32(save_p);
+	if (mobj->worldnum == 0xFFFFFFFF)
+		I_Error("Unknown world");
+
 	if (mobj->player)
 	{
 		if (mobj->eflags & MFE_VERTICALFLIP)
@@ -3284,8 +3289,8 @@ static void P_NetUnArchiveThinkers(void)
 	// remove all the current thinkers
 	for (i = 0; i < NUM_THINKERLISTS; i++)
 	{
-		currentthinker = thlist[i].next;
-		for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = next)
+		currentthinker = unarchiveworld->thlist[i].next;
+		for (currentthinker = unarchiveworld->thlist[i].next; currentthinker != &unarchiveworld->thlist[i]; currentthinker = next)
 		{
 			next = currentthinker->next;
 
@@ -3297,7 +3302,7 @@ static void P_NetUnArchiveThinkers(void)
 	}
 
 	// we don't want the removed mobjs to come back
-	iquetail = iquehead = 0;
+	unarchiveworld->iquetail = unarchiveworld->iquehead = 0;
 	P_InitThinkers();
 
 	// clear sector thinker pointers so they don't point to non-existant thinkers for all of eternity
@@ -3505,7 +3510,7 @@ static void P_NetUnArchiveThinkers(void)
 			delay = (void *)currentthinker;
 			if (!(mobjnum = (UINT32)(size_t)delay->caller))
 				continue;
-			delay->caller = P_FindNewPosition(mobjnum);
+			delay->caller = P_FindNewPosition(unarchiveworld, mobjnum);
 		}
 	}
 }
@@ -3574,20 +3579,20 @@ static inline void P_UnArchivePolyObj(polyobj_t *po)
 	Polyobj_MoveOnLoad(po, angle, x, y);
 }
 
-static inline void P_ArchivePolyObjects(void)
+static inline void P_NetArchivePolyObjects(void)
 {
 	INT32 i;
 
 	WRITEUINT32(save_p, ARCHIVEBLOCK_POBJS);
 
 	// save number of polyobjects
-	WRITEINT32(save_p, world->numPolyObjects);
+	WRITEINT32(save_p, archiveworld->numPolyObjects);
 
-	for (i = 0; i < world->numPolyObjects; ++i)
-		P_ArchivePolyObj(&world->PolyObjects[i]);
+	for (i = 0; i < archiveworld->numPolyObjects; ++i)
+		P_ArchivePolyObj(&archiveworld->PolyObjects[i]);
 }
 
-static inline void P_UnArchivePolyObjects(void)
+static inline void P_NetUnArchivePolyObjects(void)
 {
 	INT32 i, numSavedPolys;
 
@@ -3597,7 +3602,7 @@ static inline void P_UnArchivePolyObjects(void)
 	numSavedPolys = READINT32(save_p);
 
 	if (numSavedPolys != world->numPolyObjects)
-		I_Error("P_UnArchivePolyObjects: polyobj count inconsistency\n");
+		I_Error("P_NetUnArchivePolyObjects: polyobj count inconsistency\n");
 
 	for (i = 0; i < numSavedPolys; ++i)
 		P_UnArchivePolyObj(&world->PolyObjects[i]);
@@ -3627,7 +3632,8 @@ static void P_RelinkPointers(void)
 	UINT32 temp;
 
 	// use info field (value = oldposition) to relink mobjs
-	for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ];
+	for (currentthinker = unarchiveworld->thlist[THINK_MOBJ].next;
+		currentthinker != &unarchiveworld->thlist[THINK_MOBJ];
 		currentthinker = currentthinker->next)
 	{
 		if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
@@ -3642,70 +3648,70 @@ static void P_RelinkPointers(void)
 		{
 			temp = (UINT32)(size_t)mobj->tracer;
 			mobj->tracer = NULL;
-			if (!P_SetTarget(&mobj->tracer, P_FindNewPosition(temp)))
+			if (!P_SetTarget(&mobj->tracer, P_FindNewPosition(unarchiveworld, temp)))
 				CONS_Debug(DBG_GAMELOGIC, "tracer not found on %d\n", mobj->type);
 		}
 		if (mobj->target)
 		{
 			temp = (UINT32)(size_t)mobj->target;
 			mobj->target = NULL;
-			if (!P_SetTarget(&mobj->target, P_FindNewPosition(temp)))
+			if (!P_SetTarget(&mobj->target, P_FindNewPosition(unarchiveworld, temp)))
 				CONS_Debug(DBG_GAMELOGIC, "target not found on %d\n", mobj->type);
 		}
 		if (mobj->hnext)
 		{
 			temp = (UINT32)(size_t)mobj->hnext;
 			mobj->hnext = NULL;
-			if (!(mobj->hnext = P_FindNewPosition(temp)))
+			if (!(mobj->hnext = P_FindNewPosition(unarchiveworld, temp)))
 				CONS_Debug(DBG_GAMELOGIC, "hnext not found on %d\n", mobj->type);
 		}
 		if (mobj->hprev)
 		{
 			temp = (UINT32)(size_t)mobj->hprev;
 			mobj->hprev = NULL;
-			if (!(mobj->hprev = P_FindNewPosition(temp)))
+			if (!(mobj->hprev = P_FindNewPosition(unarchiveworld, temp)))
 				CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type);
 		}
 		if (mobj->player && mobj->player->capsule)
 		{
 			temp = (UINT32)(size_t)mobj->player->capsule;
 			mobj->player->capsule = NULL;
-			if (!P_SetTarget(&mobj->player->capsule, P_FindNewPosition(temp)))
+			if (!P_SetTarget(&mobj->player->capsule, P_FindNewPosition(unarchiveworld, temp)))
 				CONS_Debug(DBG_GAMELOGIC, "capsule not found on %d\n", mobj->type);
 		}
 		if (mobj->player && mobj->player->axis1)
 		{
 			temp = (UINT32)(size_t)mobj->player->axis1;
 			mobj->player->axis1 = NULL;
-			if (!P_SetTarget(&mobj->player->axis1, P_FindNewPosition(temp)))
+			if (!P_SetTarget(&mobj->player->axis1, P_FindNewPosition(unarchiveworld, temp)))
 				CONS_Debug(DBG_GAMELOGIC, "axis1 not found on %d\n", mobj->type);
 		}
 		if (mobj->player && mobj->player->axis2)
 		{
 			temp = (UINT32)(size_t)mobj->player->axis2;
 			mobj->player->axis2 = NULL;
-			if (!P_SetTarget(&mobj->player->axis2, P_FindNewPosition(temp)))
+			if (!P_SetTarget(&mobj->player->axis2, P_FindNewPosition(unarchiveworld, temp)))
 				CONS_Debug(DBG_GAMELOGIC, "axis2 not found on %d\n", mobj->type);
 		}
 		if (mobj->player && mobj->player->awayviewmobj)
 		{
 			temp = (UINT32)(size_t)mobj->player->awayviewmobj;
 			mobj->player->awayviewmobj = NULL;
-			if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp)))
+			if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(unarchiveworld, temp)))
 				CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type);
 		}
 		if (mobj->player && mobj->player->followmobj)
 		{
 			temp = (UINT32)(size_t)mobj->player->followmobj;
 			mobj->player->followmobj = NULL;
-			if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(temp)))
+			if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(unarchiveworld, temp)))
 				CONS_Debug(DBG_GAMELOGIC, "followmobj not found on %d\n", mobj->type);
 		}
 		if (mobj->player && mobj->player->drone)
 		{
 			temp = (UINT32)(size_t)mobj->player->drone;
 			mobj->player->drone = NULL;
-			if (!P_SetTarget(&mobj->player->drone, P_FindNewPosition(temp)))
+			if (!P_SetTarget(&mobj->player->drone, P_FindNewPosition(unarchiveworld, temp)))
 				CONS_Debug(DBG_GAMELOGIC, "drone not found on %d\n", mobj->type);
 		}
 	}
@@ -3718,18 +3724,18 @@ static inline void P_NetArchiveSpecials(void)
 	WRITEUINT32(save_p, ARCHIVEBLOCK_SPECIALS);
 
 	// itemrespawn queue for deathmatch
-	i = iquetail;
-	while (iquehead != i)
+	i = archiveworld->iquetail;
+	while (archiveworld->iquehead != i)
 	{
-		for (z = 0; z < nummapthings; z++)
+		for (z = 0; z < archiveworld->nummapthings; z++)
 		{
-			if (&mapthings[z] == itemrespawnque[i])
+			if (&archiveworld->mapthings[z] == archiveworld->itemrespawnque[i])
 			{
 				WRITEUINT32(save_p, z);
 				break;
 			}
 		}
-		WRITEUINT32(save_p, itemrespawntime[i]);
+		WRITEUINT32(save_p, archiveworld->itemrespawntime[i]);
 		i = (i + 1) & (ITEMQUESIZE-1);
 	}
 
@@ -3737,10 +3743,10 @@ static inline void P_NetArchiveSpecials(void)
 	WRITEUINT32(save_p, 0xffffffff);
 
 	// Sky number
-	WRITEINT32(save_p, globallevelskynum);
+	WRITEINT32(save_p, archiveworld->skynum);
 
 	// Current global weather type
-	WRITEUINT8(save_p, globalweather);
+	WRITEUINT8(save_p, archiveworld->weather);
 
 	if (metalplayback) // Is metal sonic running?
 	{
@@ -3760,30 +3766,30 @@ static void P_NetUnArchiveSpecials(void)
 		I_Error("Bad $$$.sav at archive block Specials");
 
 	// BP: added save itemrespawn queue for deathmatch
-	iquetail = iquehead = 0;
+	unarchiveworld->iquetail = unarchiveworld->iquehead = 0;
 	while ((i = READUINT32(save_p)) != 0xffffffff)
 	{
-		itemrespawnque[iquehead] = &mapthings[i];
-		itemrespawntime[iquehead++] = READINT32(save_p);
+		unarchiveworld->itemrespawnque[unarchiveworld->iquehead] = &unarchiveworld->mapthings[i];
+		unarchiveworld->itemrespawntime[unarchiveworld->iquehead++] = READINT32(save_p);
 	}
 
 	j = READINT32(save_p);
-	if (j != globallevelskynum)
-		P_SetupLevelSky(j, true);
+	P_SetupLevelSky(j, false); // Don't call P_SetupWorldSky from there
+	P_SetupWorldSky(j, unarchiveworld);
 
-	globalweather = READUINT8(save_p);
+	unarchiveworld->weather = READUINT8(save_p);
 
-	if (globalweather)
+	if (world->weather)
 	{
-		if (curWeather == globalweather)
+		if (curWeather == world->weather)
 			curWeather = PRECIP_NONE;
 
-		P_SwitchWeather(globalweather);
+		P_SwitchWeather(world->weather);
 	}
 	else // PRECIP_NONE
 	{
 		if (curWeather != PRECIP_NONE)
-			P_SwitchWeather(globalweather);
+			P_SwitchWeather(world->weather);
 	}
 
 	if (READUINT8(save_p) == 0x01) // metal sonic
@@ -3853,7 +3859,7 @@ static void P_NetArchiveMisc(void)
 
 	WRITEUINT32(save_p, ARCHIVEBLOCK_MISC);
 
-	WRITEINT16(save_p, gamemap);
+	WRITEINT16(save_p, baseworld->gamemap);
 	WRITEINT16(save_p, gamestate);
 
 	{
@@ -3903,7 +3909,7 @@ static void P_NetArchiveMisc(void)
 	WRITEUINT32(save_p, countdown);
 	WRITEUINT32(save_p, countdown2);
 
-	WRITEFIXED(save_p, gravity);
+	WRITEFIXED(save_p, baseworld->gravity);
 
 	WRITEUINT32(save_p, countdowntimer);
 	WRITEUINT8(save_p, countdowntimeup);
@@ -3924,6 +3930,8 @@ static inline boolean P_NetUnArchiveMisc(void)
 	if (READUINT32(save_p) != ARCHIVEBLOCK_MISC)
 		I_Error("Bad $$$.sav at archive block Misc");
 
+	P_UnloadWorldList();
+
 	gamemap = READINT16(save_p);
 
 	// gamemap changed; we assume that its map header is always valid,
@@ -4049,37 +4057,87 @@ void P_SaveGame(INT16 mapnum)
 	P_ArchiveLuabanksAndConsistency();
 }
 
+static void P_NetArchiveWorlds(void)
+{
+	INT32 i;
+
+	// initialize colormap vars because paranoia
+	ClearNetColormaps();
+	R_ClearTextureNumCache(false);
+
+	WRITEUINT32(save_p, ARCHIVEBLOCK_WORLD);
+	WRITEINT32(save_p, numworlds);
+
+	for (i = 0; i < numworlds; i++)
+	{
+		archiveworld = worldlist[i];
+
+		WRITEINT16(save_p, archiveworld->gamemap);
+		WRITEUINT8(save_p, archiveworld->players);
+
+		P_NetArchiveMap();
+		P_NetArchivePolyObjects();
+		P_NetArchiveThinkers();
+		P_NetArchiveSpecials();
+		P_NetArchiveWaypoints();
+	}
+
+	P_NetArchiveColormaps();
+}
+
 void P_SaveNetGame(void)
 {
 	thinker_t *th;
 	mobj_t *mobj;
-	INT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise
+	INT32 i;
 
 	CV_SaveNetVars(&save_p);
 	P_NetArchiveMisc();
 
 	// Assign the mobjnumber for pointer tracking
-	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
+	for (i = 0; i < numworlds; i++)
 	{
-		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
-			continue;
+		world_t *w = worldlist[i];
+		INT32 mobjnum = 1; // don't start from 0, it'd be confused with a blank pointer otherwise
 
-		mobj = (mobj_t *)th;
-		if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
+		for (th = w->thlist[THINK_MOBJ].next; th != &w->thlist[THINK_MOBJ]; th = th->next)
+		{
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				continue;
+
+			mobj = (mobj_t *)th;
+			if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
+				continue;
+			mobj->mobjnum = mobjnum++;
+			mobj->worldnum = (UINT32)i;
+		}
+	}
+
+	// Lactozilla: Assign world numbers to players
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		player_t *player;
+		INT32 w;
+
+		if (!playeringame[i])
 			continue;
-		mobj->mobjnum = i++;
+
+		player = &players[i];
+		player->worldnum = -1;
+
+		for (w = 0; w < numworlds; w++)
+		{
+			if (player->world == worldlist[w])
+			{
+				player->worldnum = w;
+				break;
+			}
+		}
 	}
 
 	P_NetArchivePlayers();
 	if (gamestate == GS_LEVEL)
-	{
-		P_NetArchiveWorld();
-		P_ArchivePolyObjects();
-		P_NetArchiveThinkers();
-		P_NetArchiveSpecials();
-		P_NetArchiveColormaps();
-		P_NetArchiveWaypoints();
-	}
+		P_NetArchiveWorlds();
 	LUA_Archive();
 
 	P_ArchiveLuabanksAndConsistency();
@@ -4104,6 +4162,108 @@ boolean P_LoadGame(INT16 mapoverride)
 	return true;
 }
 
+static void UnArchiveWorld(void)
+{
+	unarchiveworld->players = READUINT8(save_p);
+	P_NetUnArchiveMap();
+	P_NetUnArchivePolyObjects();
+	P_NetUnArchiveThinkers();
+	P_NetUnArchiveSpecials();
+	P_NetUnArchiveWaypoints();
+	P_RelinkPointers();
+	P_FinishMobjs();
+}
+
+static void RelinkWorldsToEntities(void)
+{
+	thinker_t *th;
+	mobj_t *mo;
+	INT32 i;
+
+	for (i = 0; i < numworlds; i++)
+	{
+		world_t *w = worldlist[i];
+
+		for (th = w->thlist[THINK_MOBJ].next; th != &w->thlist[THINK_MOBJ]; th = th->next)
+		{
+			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+				continue;
+
+			mo = (mobj_t *)th;
+
+			if ((INT32)mo->worldnum >= numworlds)
+				I_Error("RelinkWorldsToEntities: Mobj type %d has unknown world %d", mo->type, mo->worldnum);
+
+			if ((INT32)mo->worldnum != i)
+				I_Error("RelinkWorldsToEntities: Mobj type %d has a world mismatch (mobj: %d, set: %d)", mo->type, mo->worldnum, i);
+
+			mo->world = worldlist[mo->worldnum];
+		}
+	}
+
+	// Relink players to their worlds
+	// This is also done for their mobjs.
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		world_t *w;
+		player_t *player;
+
+		if (!playeringame[i])
+			continue;
+
+		player = &players[i];
+
+		if (player->worldnum == -1 || player->worldnum >= numworlds)
+			I_Error("RelinkWorldsToEntities: Player %d (%s) has unknown world %d", i, player_names[i], player->worldnum);
+
+		w = worldlist[player->worldnum];
+		player->world = w;
+		if (player->mo)
+			player->mo->world = w;
+	}
+}
+
+static void SetUnArchiveWorld(world_t *w)
+{
+	unarchiveworld = world = baseworld = localworld = w;
+}
+
+static void P_NetUnArchiveWorlds(void)
+{
+	player_t *player = &players[consoleplayer];
+	INT32 worldcount, i;
+
+	if (READUINT32(save_p) != ARCHIVEBLOCK_WORLD)
+		I_Error("Bad $$$.sav at archive block World");
+
+	worldcount = READINT32(save_p);
+
+	// initialize colormap vars because paranoia
+	ClearNetColormaps();
+
+	// Unarchive the first world
+	gamemap = READINT16(save_p);
+	SetUnArchiveWorld(worldlist[0]);
+	UnArchiveWorld();
+
+	for (i = 1; i < worldcount; i++)
+	{
+		gamemap = READINT16(save_p);
+		if (!P_LoadLevel(player, true, true))
+			I_Error("P_NetUnArchiveWorlds: failed loading world");
+		SetUnArchiveWorld(worldlist[i]);
+		UnArchiveWorld();
+	}
+
+	P_NetUnArchiveColormaps();
+	RelinkWorldsToEntities();
+
+	// Send a command to switch this player to the first world
+	// For every other client, the player is on that world, but not for the joiner
+	if (worldcount > 1)
+		SendWorldSwitch(0, true);
+}
+
 boolean P_LoadNetGame(void)
 {
 	CV_LoadNetVars(&save_p);
@@ -4111,16 +4271,7 @@ boolean P_LoadNetGame(void)
 		return false;
 	P_NetUnArchivePlayers();
 	if (gamestate == GS_LEVEL)
-	{
-		P_NetUnArchiveWorld();
-		P_UnArchivePolyObjects();
-		P_NetUnArchiveThinkers();
-		P_NetUnArchiveSpecials();
-		P_NetUnArchiveColormaps();
-		P_NetUnArchiveWaypoints();
-		P_RelinkPointers();
-		P_FinishMobjs();
-	}
+		P_NetUnArchiveWorlds();
 	LUA_UnArchive();
 
 	// This is stupid and hacky, but maybe it'll work!
diff --git a/src/p_saveg.h b/src/p_saveg.h
index d8756a7a9b955e4520e46849c3d76313329b1623..bf2f45175365bc1e770b0494bdc991f8403b8dae 100644
--- a/src/p_saveg.h
+++ b/src/p_saveg.h
@@ -18,6 +18,8 @@
 #pragma interface
 #endif
 
+#include "p_world.h"
+
 // Persistent storage/archiving.
 // These are the load / save game routines.
 
@@ -26,7 +28,7 @@ void P_SaveNetGame(void);
 boolean P_LoadGame(INT16 mapoverride);
 boolean P_LoadNetGame(void);
 
-mobj_t *P_FindNewPosition(UINT32 oldposition);
+mobj_t *P_FindNewPosition(world_t *w, UINT32 oldposition);
 
 typedef struct
 {
@@ -42,4 +44,7 @@ typedef struct
 extern savedata_t savedata;
 extern UINT8 *save_p;
 
+extern world_t *archiveworld;
+extern world_t *unarchiveworld;
+
 #endif
diff --git a/src/p_setup.c b/src/p_setup.c
index 02241bbf7c6d15ef60c1111b6cf1307e7fe3e0f3..6706f796c46807f134f10aed795c1e4daf6eefc4 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2832,27 +2832,50 @@ static boolean P_LoadMapFromFile(void)
 // LEVEL INITIALIZATION FUNCTIONS
 //
 
+static void P_InitLevelSky(INT32 skynum, player_t *player)
+{
+	if (player == &players[consoleplayer])
+		P_SetupSkyTexture(skynum);
+
+	P_SetupWorldSky(skynum, world);
+	levelskynum = skynum;
+
+	// scale up the old skies, if needed
+	if (!dedicated)
+		R_SetupSkyDraw();
+}
+
 /** Sets up a sky texture to use for the level.
   * The sky texture is used instead of F_SKY1.
   */
 void P_SetupLevelSky(INT32 skynum, boolean global)
 {
-	char skytexname[12];
-
-	sprintf(skytexname, "SKY%d", skynum);
-	world->skytexture = R_TextureNumForName(skytexname);
+	P_SetupSkyTexture(skynum);
 	levelskynum = skynum;
 
 	// Global change
 	if (global)
-		globallevelskynum = levelskynum;
+		P_SetupWorldSky(skynum, world);
 
-	// Don't go beyond for dedicated servers
-	if (dedicated)
-		return;
+	// scale up the old skies, if needed
+	if (!dedicated)
+		R_SetupSkyDraw();
+}
+
+void P_SetupWorldSky(INT32 skynum, world_t *w)
+{
+	w->skynum = skynum;
 
 	// scale up the old skies, if needed
-	R_SetupSkyDraw(world);
+	if (!dedicated)
+		R_SetupSkyDraw();
+}
+
+void P_SetupSkyTexture(INT32 skynum)
+{
+	char skytexname[12];
+	sprintf(skytexname, "SKY%d", skynum);
+	skytexture = R_TextureNumForName(skytexname);
 }
 
 static const char *maplumpname;
@@ -2934,7 +2957,7 @@ static void P_InitWorldSettings(void)
 	stagefailed = true; // assume failed unless proven otherwise - P_GiveEmerald or emerald touchspecial
 }
 
-static void P_InitLevelSettings(player_t *player, boolean addworld)
+static void P_InitLevelSettings(player_t *player, boolean addworld, boolean fromnetsave)
 {
 	INT32 i;
 	boolean canresetlives = true;
@@ -2963,10 +2986,18 @@ static void P_InitLevelSettings(player_t *player, boolean addworld)
 	if (!addworld)
 		countdown = countdown2 = exitfadestarted = 0;
 
-	if (!addworld)
+	if (!addworld || fromnetsave)
 	{
 		for (i = 0; i < MAXPLAYERS; i++)
-			P_InitPlayerSettings(i, canresetlives);
+		{
+			if (fromnetsave)
+			{
+				players[i].followmobj = NULL;
+				players[i].mo = NULL;
+			}
+			else
+				P_InitPlayerSettings(i, canresetlives);
+		}
 	}
 	else if (player)
 	{
@@ -3002,7 +3033,7 @@ void P_RespawnThings(void)
 		P_RemoveMobj((mobj_t *)think);
 	}
 
-	P_InitLevelSettings(NULL, false);
+	P_InitLevelSettings(NULL, false, false);
 
 	P_SpawnMapThings(true);
 
@@ -3476,11 +3507,11 @@ static void P_WriteLetter(void)
 	Z_Free(buf);
 }
 
-static void P_InitGametype(player_t *player, boolean addplayer)
+static void P_InitGametype(player_t *player, boolean addworld)
 {
 	UINT8 i;
 
-	if (!addplayer)
+	if (!addworld)
 	{
 		P_InitGametypePlayers();
 
@@ -3527,6 +3558,7 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	// 99% of the things already did, so.
 	// Map header should always be in place at this point
 	INT32 i, ranspecialwipe = 0;
+	boolean runforself = (!addworld || (addworld && player == &players[consoleplayer]));
 
 	levelloading = true;
 
@@ -3541,9 +3573,6 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	if (rendermode != render_none)
 		V_SetPaletteLump("PLAYPAL");
 
-	if (player && !titlemapinaction && player->world)
-		P_MarkWorldVisited(player, player->world);
-
 	// Initialize sector node list.
 	P_Initsecnode();
 
@@ -3562,7 +3591,7 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	if (cv_runscripts.value && mapheaderinfo[gamemap-1]->scriptname[0] != '#')
 		P_RunLevelScript(mapheaderinfo[gamemap-1]->scriptname);
 
-	P_InitLevelSettings(player, addworld);
+	P_InitLevelSettings(player, addworld, fromnetsave);
 
 	postimgtype = postimgtype2 = postimg_none;
 
@@ -3597,102 +3626,107 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	wipegamestate = FORCEWIPEOFF;
 	wipestyleflags = 0;
 
-	// Special stage fade to white
-	// This is handled BEFORE sounds are stopped.
-	if (modeattacking && !demoplayback && (pausedelay == INT32_MIN))
-		ranspecialwipe = 2;
-	else if (rendermode != render_none && G_IsSpecialStage(gamemap))
+	if (!addworld)
 	{
-		P_RunSpecialStageWipe();
-		ranspecialwipe = 1;
-	}
+		// Special stage fade to white
+		// This is handled BEFORE sounds are stopped.
+		if (modeattacking && !demoplayback && (pausedelay == INT32_MIN))
+			ranspecialwipe = 2;
+		else if (rendermode != render_none && G_IsSpecialStage(gamemap))
+		{
+			P_RunSpecialStageWipe();
+			ranspecialwipe = 1;
+		}
 
-	if (G_GetModeAttackRetryFlag())
-	{
-		if (modeattacking)
-			wipestyleflags |= (WSF_FADEOUT|WSF_TOWHITE);
-		G_ClearModeAttackRetryFlag();
-	}
+		if (G_GetModeAttackRetryFlag())
+		{
+			if (modeattacking)
+				wipestyleflags |= (WSF_FADEOUT|WSF_TOWHITE);
+			G_ClearModeAttackRetryFlag();
+		}
 
-	// Make sure all sounds are stopped before Z_FreeTags.
-	S_StopSounds();
-	S_ClearSfx();
+		// Make sure all sounds are stopped before Z_FreeTags.
+		S_StopSounds();
+		S_ClearSfx();
 
-	// Fade out music here. Deduct 2 tics so the fade volume actually reaches 0.
-	// But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug.
-	if (!titlemapinaction && (RESETMUSIC ||
-		strnicmp(S_MusicName(),
-			(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7)))
-		S_FadeMusic(0, FixedMul(
-			FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
+		// Fade out music here. Deduct 2 tics so the fade volume actually reaches 0.
+		// But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug.
+		if (!titlemapinaction && (RESETMUSIC ||
+			strnicmp(S_MusicName(),
+				(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7)))
+			S_FadeMusic(0, FixedMul(
+				FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
 
-	// Let's fade to black here
-	// But only if we didn't do the special stage wipe
-	if (rendermode != render_none && !ranspecialwipe)
-		P_RunLevelWipe();
+		// Let's fade to black here
+		// But only if we didn't do the special stage wipe
+		if (rendermode != render_none && !ranspecialwipe)
+			P_RunLevelWipe();
 
-	if (!titlemapinaction)
-	{
-		if (ranspecialwipe == 2)
+		if (!titlemapinaction)
 		{
-			pausedelay = -3; // preticker plus one
-			S_StartSound(NULL, sfx_s3k73);
-		}
+			if (ranspecialwipe == 2)
+			{
+				pausedelay = -3; // preticker plus one
+				S_StartSound(NULL, sfx_s3k73);
+			}
 
-		// Print "SPEEDING OFF TO [ZONE] [ACT 1]..."
-		if (rendermode != render_none)
-		{
-			// Don't include these in the fade!
-			char tx[64];
-			V_DrawSmallString(1, 191, V_ALLOWLOWERCASE|V_TRANSLUCENT|V_SNAPTOLEFT|V_SNAPTOBOTTOM, M_GetText("Speeding off to..."));
-			snprintf(tx, 63, "%s%s%s",
-				mapheaderinfo[gamemap-1]->lvlttl,
-				(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) ? "" : " Zone",
-				(mapheaderinfo[gamemap-1]->actnum > 0) ? va(" %d",mapheaderinfo[gamemap-1]->actnum) : "");
-			V_DrawSmallString(1, 195, V_ALLOWLOWERCASE|V_TRANSLUCENT|V_SNAPTOLEFT|V_SNAPTOBOTTOM, tx);
-			I_UpdateNoVsync();
-		}
+			// Print "SPEEDING OFF TO [ZONE] [ACT 1]..."
+			if (rendermode != render_none)
+			{
+				// Don't include these in the fade!
+				char tx[64];
+				V_DrawSmallString(1, 191, V_ALLOWLOWERCASE|V_TRANSLUCENT|V_SNAPTOLEFT|V_SNAPTOBOTTOM, M_GetText("Speeding off to..."));
+				snprintf(tx, 63, "%s%s%s",
+					mapheaderinfo[gamemap-1]->lvlttl,
+					(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) ? "" : " Zone",
+					(mapheaderinfo[gamemap-1]->actnum > 0) ? va(" %d",mapheaderinfo[gamemap-1]->actnum) : "");
+				V_DrawSmallString(1, 195, V_ALLOWLOWERCASE|V_TRANSLUCENT|V_SNAPTOLEFT|V_SNAPTOBOTTOM, tx);
+				I_UpdateNoVsync();
+			}
 
-		// As oddly named as this is, this handles music only.
-		// We should be fine starting it here.
-		// Don't do this during titlemap, because the menu code handles music by itself.
-		S_Start();
-	}
+			// As oddly named as this is, this handles music only.
+			// We should be fine starting it here.
+			// Don't do this during titlemap, because the menu code handles music by itself.
+			S_Start();
+		}
 
-	levelfadecol = (ranspecialwipe) ? 0 : 31;
+		levelfadecol = (ranspecialwipe) ? 0 : 31;
 
-	// Close text prompt before freeing the old level
-	F_EndTextPrompt(false, true);
+		// Close text prompt before freeing the old level
+		F_EndTextPrompt(false, true);
+	}
+	else
+		wipegamestate = gamestate;
 
 	if (player && (!titlemapinaction))
-	{
 		P_UnloadWorldPlayer(player);
-		//P_UnloadWorld(world);
-	}
 
 	world = P_InitNewWorld();
 	thlist = world->thlist;
 
-	if (addworld)
-		P_SwitchPlayerWorld(player, world);
-	else
+	if (!fromnetsave)
 	{
-		for (i = 0; i < MAXPLAYERS; i++)
+		if (addworld)
+			P_SwitchPlayerWorld(player, world);
+		else
 		{
-			player_t *p;
+			for (i = 0; i < MAXPLAYERS; i++)
+			{
+				player_t *p;
 
-			if (!playeringame[i])
-				continue;
+				if (!playeringame[i])
+					continue;
 
-			p = &players[i];
-			p->world = NULL;
-			P_SwitchPlayerWorld(p, world);
-		}
+				p = &players[i];
+				p->world = NULL;
+				P_SwitchPlayerWorld(p, world);
+			}
 
-		world->players = D_NumPlayers();
+			world->players = D_NumPlayers();
+		}
 	}
 
-	if (player == &players[consoleplayer])
+	if (!addworld || player == &players[consoleplayer])
 		localworld = world;
 
 #if defined (WALLSPLATS) || defined (FLOORSPLATS)
@@ -3722,10 +3756,15 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 		I_Error("Map %s not found.\n", maplumpname);
 
 	R_ReInitColormaps(mapheaderinfo[gamemap-1]->palette);
+	if (!addworld)
+	{
+		// Init Boom colormaps.
+		R_ClearColormaps();
+	}
 	CON_SetupBackColormap();
 
 	// SRB2 determines the sky texture to be used depending on the map header.
-	P_SetupLevelSky(mapheaderinfo[gamemap-1]->skynum, true);
+	P_InitLevelSky(mapheaderinfo[gamemap-1]->skynum, player);
 
 	P_ResetSpawnpoints();
 
@@ -3756,7 +3795,8 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	// set up world state
 	P_SpawnSpecials(fromnetsave);
 
-	if (!fromnetsave) //  ugly hack for P_NetUnArchiveMisc (and P_LoadNetGame)
+	//  ugly hack for P_NetUnArchiveMisc (and P_LoadNetGame)
+	if (!fromnetsave && runforself)
 		P_SpawnPrecipitation();
 
 #ifdef HWRENDER // not win32 only 19990829 by Kin
@@ -3778,10 +3818,11 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	if (!fromnetsave)
 		P_InitGametype(player, addworld);
 
-	P_InitCamera();
+	if (runforself)
+		P_InitCamera();
 
 	// clear special respawning que
-	iquehead = iquetail = 0;
+	world->iquehead = world->iquetail = 0;
 
 	// Fab : 19-07-98 : start cd music for this level (note: can be remapped)
 	I_PlayCD((UINT8)(gamemap), false);
@@ -3789,7 +3830,7 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	P_MapEnd();
 
 	// Remove the loading shit from the screen
-	if (rendermode != render_none && !titlemapinaction)
+	if (rendermode != render_none && !titlemapinaction && runforself)
 		F_WipeColorFill(levelfadecol);
 
 	if (precache || dedicated)
@@ -3837,6 +3878,10 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave)
 	if (rendermode == render_none)
 		return true;
 
+	//if (!runforself || (addworld && splitscreen))
+	if (addworld)
+		return true;
+
 	// Title card!
 	G_StartTitleCard();
 
diff --git a/src/p_setup.h b/src/p_setup.h
index 893b19f096c57caf294bac2017ccd2c3e00dc7ec..c3828aa3f63a60d67b1720c08de2b358ca23856c 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -86,6 +86,7 @@ extern size_t nummapthings;
 extern mapthing_t *mapthings;
 
 void P_SetupLevelSky(INT32 skynum, boolean global);
+void P_SetupSkyTexture(INT32 skynum);
 #ifdef SCANTHINGS
 void P_ScanThings(INT16 mapnum, INT16 wadnum, INT16 lumpnum);
 #endif
diff --git a/src/p_sight.c b/src/p_sight.c
index 8ddec3999bcf915aebd0f06ebd8e644716ddea78..7ce4d0357e799afdad3956cce609a4768567de2e 100644
--- a/src/p_sight.c
+++ b/src/p_sight.c
@@ -397,14 +397,17 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
 	s2 = t2->subsector->sector;
 
 	// Check in REJECT table.
-	//CONS_Printf("P_CheckSight: %d %p %d %p\n", t1->type, t1->world, t2->type, t2->world);
-	if (t1->world == t2->world)
+	if (s1->world == s2->world
+	&& t1->world == t2->world
+	&& t1->world == s1->world
+	&& t2->world == s2->world)
 	{
-		size_t pnum = (s1-sectors)*numsectors + (s2-sectors);
+		world_t *w = s1->world;
+		size_t pnum = (s1-w->sectors)*w->numsectors + (s2-w->sectors);
 
-		if (rejectmatrix != NULL)
+		if (w->rejectmatrix != NULL)
 		{
-			if (rejectmatrix[pnum>>3] & (1 << (pnum&7))) // can't possibly be connected
+			if (w->rejectmatrix[pnum>>3] & (1 << (pnum&7))) // can't possibly be connected
 				return false;
 		}
 	}
diff --git a/src/p_spec.c b/src/p_spec.c
index ba77c835cc3f235edf64f397398fbab59dec3f37..ee039ceab4c30fc22a6b4bd7b41b4d33207ca3ed 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -2987,8 +2987,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 		case 424: // Change Weather
 			if (line->flags & ML_NOCLIMB)
 			{
-				globalweather = (UINT8)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
-				P_SwitchWeather(globalweather);
+				world->weather = (UINT8)(sides[line->sidenum[0]].textureoffset>>FRACBITS);
+				P_SwitchWeather(world->weather);
 			}
 			else if (mo && mo->player && P_IsLocalPlayer(mo->player))
 				P_SwitchWeather(sides[line->sidenum[0]].textureoffset>>FRACBITS);
@@ -6136,7 +6136,7 @@ static void P_RunLevelLoadExecutors(void)
 void P_InitSpecials(void)
 {
 	// Set the default gravity. Custom gravity overrides this setting.
-	gravity = mapheaderinfo[gamemap-1]->gravity;
+	world->gravity = gravity = mapheaderinfo[gamemap-1]->gravity;
 
 	// Defaults in case levels don't have them set.
 	sstimer = mapheaderinfo[gamemap-1]->sstimer*TICRATE + 6;
@@ -6161,7 +6161,7 @@ void P_InitSpecials(void)
 	}
 
 	// Set globalweather
-	globalweather = mapheaderinfo[gamemap-1]->weather;
+	world->weather = mapheaderinfo[gamemap-1]->weather;
 }
 
 static void P_ApplyFlatAlignment(line_t *master, sector_t *sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs)
@@ -6236,7 +6236,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 11: // Custom global gravity!
-				gravity = sector->floorheight/1000;
+				world->gravity = gravity = sector->floorheight/1000;
 				break;
 		}
 
diff --git a/src/p_tick.c b/src/p_tick.c
index 74cb8255b3ca60e068605743d3acaf2d91415c77..0d71dd58f41856ebc388115c08bd4c196af07b09 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -651,6 +651,27 @@ static inline void P_RunWorldSpecials(void)
 	}
 }
 
+static inline void P_WorldPrecipitationEffects(void)
+{
+	INT32 i;
+
+	if (!(netgame || multiplayer) || (numworlds < 2))
+	{
+		P_PrecipitationEffects(baseworld);
+		return;
+	}
+
+	for (i = 0; i < numworlds; i++)
+	{
+		world_t *w = worldlist[i];
+
+		if (!w->players)
+			continue;
+
+		P_PrecipitationEffects(w);
+	}
+}
+
 static inline void P_WorldRunVoid(void (*func)(void))
 {
 	INT32 i;
@@ -781,7 +802,7 @@ void P_Ticker(boolean run)
 	P_RunWorldSpecials();
 
 	// Lightning, rain sounds, etc.
-	P_PrecipitationEffects();
+	P_WorldPrecipitationEffects();
 
 	if (run)
 		leveltime++;
diff --git a/src/p_user.c b/src/p_user.c
index c4a3ec0678bb8e86e2efc26e636226b5e855b890..8d8dcd3e7c0aa3dd8f90aca1092423eda0d75b07 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2213,7 +2213,9 @@ void P_DoPlayerExit(player_t *player)
 	if (player->exiting)
 		return;
 
-	if (cv_allowexitlevel.value == 0 && !G_PlatformGametype())
+	if (roaming)
+		player->exiting = (3*TICRATE) - 1;
+	else if (cv_allowexitlevel.value == 0 && !G_PlatformGametype())
 		return;
 	else if (gametyperules & GTR_RACE) // If in Race Mode, allow
 	{
@@ -9457,6 +9459,30 @@ void P_RestoreMultiMusic(player_t *player)
 	}
 }
 
+static void P_PlayerSetRaceRealTime(player_t *player)
+{
+	if (leveltime >= 4*TICRATE)
+	{
+		player->realtime = leveltime - 4*TICRATE;
+
+		if (roaming)
+		{
+			INT32 i;
+
+			for (i = 0; i < numworlds; i++)
+			{
+				if (player->world == worldlist[i])
+					break;
+			}
+
+			if (i > 0 && i < numworlds)
+				player->realtime -= (i * (3*TICRATE)-1);
+		}
+	}
+	else
+		player->realtime = 0;
+}
+
 //
 // P_DeathThink
 // Fall on your face when dying.
@@ -9584,12 +9610,7 @@ static void P_DeathThink(player_t *player)
 		if (!(countdown2 && !countdown) && !player->exiting && !(player->pflags & PF_GAMETYPEOVER) && !stoppedclock)
 		{
 			if (gametyperules & GTR_RACE)
-			{
-				if (leveltime >= 4*TICRATE)
-					player->realtime = leveltime - 4*TICRATE;
-				else
-					player->realtime = 0;
-			}
+				P_PlayerSetRaceRealTime(player);
 			else
 				player->realtime = leveltime;
 		}
@@ -11608,43 +11629,52 @@ void P_PlayerThink(player_t *player)
 
 	if (player->exiting == 2 || countdown2 == 2)
 	{
-		UINT8 numneeded = (G_IsSpecialStage(gamemap) ? 4 : cv_playersforexit.value);
-		if (numneeded) // Count to be sure everyone's exited
+		if (roaming)
 		{
-			INT32 i, total = 0, exiting = 0;
-
-			for (i = 0; i < MAXPLAYERS; i++)
+			G_PlayerFinishLevel(playeri);
+			G_SetNextMap(false, false);
+			P_RoamIntoWorld(player, nextmap+1);
+		}
+		else
+		{
+			UINT8 numneeded = (G_IsSpecialStage(gamemap) ? 4 : cv_playersforexit.value);
+			if (numneeded) // Count to be sure everyone's exited
 			{
-				if (!playeringame[i] || players[i].spectator || players[i].bot)
-					continue;
-				if (players[i].quittime > 30 * TICRATE)
-					continue;
-				if (players[i].lives <= 0)
-					continue;
+				INT32 i, total = 0, exiting = 0;
 
-				total++;
-				if (players[i].exiting && players[i].exiting < 4)
-					exiting++;
-			}
+				for (i = 0; i < MAXPLAYERS; i++)
+				{
+					if (!playeringame[i] || players[i].spectator || players[i].bot)
+						continue;
+					if (players[i].quittime > 30 * TICRATE)
+						continue;
+					if (players[i].lives <= 0)
+						continue;
+
+					total++;
+					if (players[i].exiting && players[i].exiting < 4)
+						exiting++;
+				}
 
-			if (!total || ((4*exiting)/total) >= numneeded)
+				if (!total || ((4*exiting)/total) >= numneeded)
+				{
+					if (server)
+						SendNetXCmd(XD_EXITLEVEL, NULL, 0);
+				}
+				else
+					player->exiting = 3;
+			}
+			else
 			{
 				if (server)
 					SendNetXCmd(XD_EXITLEVEL, NULL, 0);
 			}
-			else
-				player->exiting = 3;
-		}
-		else
-		{
-			if (server)
-				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
 		}
 	}
 
 	if (player->pflags & PF_FINISHED)
 	{
-		if (((gametyperules & GTR_FRIENDLY) && cv_exitmove.value) && !G_EnoughPlayersFinished())
+		if (((gametyperules & GTR_FRIENDLY) && cv_exitmove.value) && !G_EnoughPlayersFinished() && (!roaming))
 			player->exiting = 0;
 		else
 			P_DoPlayerExit(player);
@@ -11699,12 +11729,7 @@ void P_PlayerThink(player_t *player)
 	if (!player->exiting && !stoppedclock)
 	{
 		if (gametyperules & GTR_RACE)
-		{
-			if (leveltime >= 4*TICRATE)
-				player->realtime = leveltime - 4*TICRATE;
-			else
-				player->realtime = 0;
-		}
+			P_PlayerSetRaceRealTime(player);
 		else
 			player->realtime = leveltime;
 	}
diff --git a/src/p_world.c b/src/p_world.c
index 074214282912d658eb97b41725976edd7db55f3d..cd58d4124723b038f3b92dcad66cfd3db07d58ca 100644
--- a/src/p_world.c
+++ b/src/p_world.c
@@ -38,6 +38,7 @@
 #include "lua_hook.h"
 
 world_t *world = NULL;
+world_t *baseworld = NULL;
 world_t *localworld = NULL;
 world_t *viewworld = NULL;
 
@@ -66,13 +67,15 @@ world_t *P_InitNewWorld(void)
 	worldlist[numworlds] = P_InitWorld();
 	w = worldlist[numworlds];
 
+	if (!numworlds)
+		baseworld = w;
+
 	numworlds++;
 	return w;
 }
 
 //
 // Sets the current world structures for physics simulation.
-// (This was easier than referencing world-> in every part of gamelogic.)
 //
 void P_SetGameWorld(world_t *w)
 {
@@ -120,26 +123,6 @@ void P_SetViewWorld(world_t *w)
 	viewworld = w;
 }
 
-//
-// Sets a world as visited by a player.
-//
-void P_MarkWorldVisited(player_t *player, world_t *w)
-{
-	size_t playernum = (size_t)(player - players);
-	worldplayerinfo_t *playerinfo = &w->playerinfo[playernum];
-	vector3_t *pos = &playerinfo->pos;
-
-	if (!playeringame[playernum] || !player->mo || P_MobjWasRemoved(player->mo))
-		return;
-
-	pos->x = player->mo->x;
-	pos->y = player->mo->y;
-	pos->z = player->mo->z;
-	playerinfo->angle = player->mo->angle;
-
-	playerinfo->visited = true;
-}
-
 //
 // Sets the current world.
 //
@@ -150,10 +133,9 @@ void P_SetWorld(world_t *w)
 
 	P_SetGameWorld(w);
 
-	thlist = world->thlist;
+	thlist = w->thlist;
 	gamemap = w->gamemap;
-
-	P_InitSpecials();
+	gravity = w->gravity;
 }
 
 //
@@ -183,21 +165,94 @@ void P_SwitchPlayerWorld(player_t *player, world_t *newworld)
 	newworld->players++;
 }
 
+//
+// Loads a new world, or switches to one.
+//
+void P_RoamIntoWorld(player_t *player, INT32 mapnum)
+{
+	world_t *w = NULL;
+	INT32 i;
+
+	for (i = 0; i < numworlds; i++)
+	{
+		if (worldlist[i]->gamemap == mapnum)
+		{
+			w = worldlist[i];
+			break;
+		}
+	}
+
+	if (w == player->world)
+		return;
+	else if (w)
+		P_SwitchWorld(player, w);
+	else
+		D_MapChange(mapnum, gametype, true, false, false, 0, false, false);
+}
+
+boolean P_TransferCarriedPlayers(player_t *player, world_t *w)
+{
+	boolean anycarried = false;
+	INT32 i;
+
+	// Lactozilla: Transfer carried players
+	for (i = 0; i < MAXPLAYERS; i++)
+	{
+		player_t *carry;
+
+		if (!playeringame[i])
+			continue;
+
+		carry = &players[i];
+		if (carry == player)
+			continue;
+
+		if (carry->powers[pw_carry] == CR_PLAYER
+		&& carry->mo->tracer && !P_MobjWasRemoved(carry->mo->tracer)
+		&& carry->mo->tracer == player->mo)
+		{
+			mobj_t *tails = player->mo;
+
+			P_SwitchWorld(carry, w);
+
+			tails->z += tails->height*3*P_MobjFlip(tails);
+
+			// Set player position
+			P_UnsetThingPosition(carry->mo);
+			carry->mo->x = tails->x;
+			carry->mo->y = tails->y;
+			if (carry->mo->eflags & MFE_VERTICALFLIP)
+				carry->mo->z = tails->z + tails->height + 12*carry->mo->scale;
+			else
+				carry->mo->z = tails->z - carry->mo->height - 12*carry->mo->scale;
+			P_SetThingPosition(carry->mo);
+
+			anycarried = true;
+		}
+	}
+
+	return anycarried;
+}
+
 //
 // Switches a player to a world.
 //
 void P_SwitchWorld(player_t *player, world_t *w)
 {
 	size_t playernum = (size_t)(player - players);
-	worldplayerinfo_t *playerinfo = &w->playerinfo[playernum];
+	boolean local = ((INT32)playernum == consoleplayer);
+	boolean resetplayer = (player->powers[pw_carry] != CR_PLAYER);
 
+#if 0
 	if (w == player->world)
 		return;
+#endif
 
 	if (!playeringame[playernum] || !player->mo || P_MobjWasRemoved(player->mo))
 		return;
 
-	P_MarkWorldVisited(player, player->world);
+	if (player->world)
+		P_RemoveMobjConnections(player->mo, player->world);
 
 	if (player->followmobj)
 	{
@@ -206,32 +261,38 @@ void P_SwitchWorld(player_t *player, world_t *w)
 	}
 
 	P_SwitchPlayerWorld(player, w);
+
 	if (!splitscreen)
+	{
 		P_SetWorld(w);
+		if (local)
+			P_InitSpecials();
+	}
 
 	P_UnsetThingPosition(player->mo);
 	P_MoveThinkerToWorld(w, THINK_MAIN, (thinker_t *)(player->mo));
+	G_MovePlayerToSpawnOrStarpost(playernum);
 
-	if (playerinfo->visited)
-	{
-		vector3_t *pos = &playerinfo->pos;
-		P_TeleportMove(player->mo, pos->x, pos->y, pos->z);
-		P_SetPlayerAngle(player, playerinfo->angle);
-	}
-	else
-		G_MovePlayerToSpawnOrStarpost(playernum);
-
-	P_MapEnd();
-
-	if ((INT32)playernum == consoleplayer)
+	if (local)
 	{
 		localworld = world;
 		S_Start();
+		if (!dedicated)
+		{
+			P_SetupSkyTexture(w->skynum);
+			R_SetupSkyDraw();
+		}
 	}
 
-	if (!dedicated)
-		R_SetupSkyDraw(world);
-	P_ResetCamera(player, &camera);
+	if (player == &players[displayplayer])
+		P_ResetCamera(player, (splitscreen && playernum == 1) ? &camera2 : &camera);
+
+	if (P_TransferCarriedPlayers(player, w))
+		resetplayer = false;
+
+	if (resetplayer)
+		P_ResetPlayer(player);
+	P_MapEnd();
 }
 
 void Command_Switchworld_f(void)
@@ -248,32 +309,27 @@ void Command_Switchworld_f(void)
 
 	w = worldlist[worldnum];
 	CONS_Printf("Switching to world %d (%p)\n", worldnum, w);
-	P_SwitchWorld(&players[consoleplayer], w);
+
+	if (netgame)
+		SendWorldSwitch(worldnum, false);
+	else
+		P_SwitchWorld(&players[consoleplayer], w);
 }
 
 void Command_Listworlds_f(void)
 {
 	INT32 worldnum;
 	world_t *w;
-	worldplayerinfo_t *playerinfo;
 
     for (worldnum = 0; worldnum < numworlds; worldnum++)
 	{
 		w = worldlist[worldnum];
-		playerinfo = &w->playerinfo[consoleplayer];
 
 		CONS_Printf("World %d (%p)\n", worldnum, w);
 		CONS_Printf("Gamemap: %d\n", w->gamemap);
 		CONS_Printf("vt %d sg %d sc %d ss %d nd %d ld %d sd %d mt %d\n",
 			w->numvertexes, w->numsegs, w->numsectors, w->numsubsectors, w->numnodes, w->numlines, w->numsides, w->nummapthings);
-
-		CONS_Printf("Player has visited: %d\n", playerinfo->visited);
 		CONS_Printf("Player count: %d\n", w->players);
-		CONS_Printf("Player position: %d %d %d %d\n",
-					playerinfo->pos.x>>FRACBITS,
-					playerinfo->pos.y>>FRACBITS,
-					playerinfo->pos.z>>FRACBITS,
-					AngleFixed(playerinfo->angle)>>FRACBITS);
 	}
 }
 
@@ -380,3 +436,31 @@ void P_UnloadWorldPlayer(player_t *player)
 		P_SetTarget(&player->mo, NULL);
 	}
 }
+
+boolean P_MobjIsConnected(mobj_t *mobj1, mobj_t *mobj2)
+{
+	return (mobj2 && !P_MobjWasRemoved(mobj2) && mobj1->world == mobj2->world);
+}
+
+void P_RemoveMobjConnections(mobj_t *mobj, world_t *w)
+{
+	thinker_t *th;
+	mobj_t *check;
+
+	for (th = w->thlist[THINK_MOBJ].next; th != &w->thlist[THINK_MOBJ]; th = th->next)
+	{
+		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
+			continue;
+
+		check = (mobj_t *)th;
+
+		if (check->target == mobj)
+			P_SetTarget(&mobj->target, NULL);
+		if (check->tracer == mobj)
+			P_SetTarget(&mobj->tracer, NULL);
+		if (check->hnext == mobj)
+			P_SetTarget(&mobj->hnext, NULL);
+		if (check->hprev == mobj)
+			P_SetTarget(&mobj->hprev, NULL);
+	}
+}
diff --git a/src/p_world.h b/src/p_world.h
index afe2a541e6fa33f588995d5e8bb2ac8569ef40b3..a5a8f2787a6e5b7c84dab2fcd9394068991f8fba 100644
--- a/src/p_world.h
+++ b/src/p_world.h
@@ -25,13 +25,6 @@
 #define WAYPOINTSEQUENCESIZE 256
 #define NUMWAYPOINTSEQUENCES 256
 
-typedef struct
-{
-	boolean visited;
-	vector3_t pos;
-	angle_t angle;
-} worldplayerinfo_t;
-
 //
 // Lactozilla: A "world" is the environment that players interact with.
 // A "map" is what defines how the world is built (what you edit in a level editor.)
@@ -41,7 +34,6 @@ typedef struct
 typedef struct
 {
 	INT32 gamemap;
-	worldplayerinfo_t playerinfo[MAXPLAYERS];
 	thinker_t *thlist;
 
 	INT32 players;
@@ -71,9 +63,10 @@ typedef struct
 
 	mobj_t *overlaycap;
 
-	INT32 skytexture; // the lump number of the sky texture
-	INT32 skytexturemid; // the horizon line in a 256x128 sky texture
-	fixed_t skyscale; // the scale of the sky
+	fixed_t gravity;
+
+	INT32 skynum; // used for keeping track of the current sky
+	UINT8 weather;
 
 	// Needed to store the number of the dummy sky flat.
 	// Used for rendering, as well as tracking projectiles etc.
@@ -95,6 +88,10 @@ typedef struct
 	mobj_t *waypoints[NUMWAYPOINTSEQUENCES][WAYPOINTSEQUENCESIZE];
 	UINT16 numwaypoints[NUMWAYPOINTSEQUENCES];
 
+	mapthing_t *itemrespawnque[ITEMQUESIZE];
+	tic_t itemrespawntime[ITEMQUESIZE];
+	size_t iquehead, iquetail;
+
 	UINT8 *rejectmatrix; // for fast sight rejection
 	INT32 *blockmaplump; // offsets in blockmap are from here
 	INT32 *blockmap; // Big blockmap
@@ -112,6 +109,7 @@ typedef struct
 } world_t;
 
 extern world_t *world;
+extern world_t *baseworld;
 extern world_t *localworld;
 extern world_t *viewworld;
 
@@ -129,15 +127,21 @@ void P_SetGameWorld(world_t *w);
 void P_SetViewWorld(world_t *w);
 
 void P_SetWorld(world_t *w);
-void P_MarkWorldVisited(player_t *player, world_t *w);
 
+void P_RoamIntoWorld(player_t *player, INT32 mapnum);
 void P_SwitchWorld(player_t *player, world_t *w);
+
 void P_DetachPlayerWorld(player_t *player);
 void P_SwitchPlayerWorld(player_t *player, world_t *newworld);
 
+boolean P_TransferCarriedPlayers(player_t *player, world_t *w);
+boolean P_MobjIsConnected(mobj_t *mobj1, mobj_t *mobj2);
+void P_RemoveMobjConnections(mobj_t *mobj, world_t *w);
+
 void Command_Switchworld_f(void);
 void Command_Listworlds_f(void);
 
+void P_SetupWorldSky(INT32 skynum, world_t *w);
 INT32 P_AddLevelFlatForWorld(world_t *w, const char *flatname);
 
 #endif
diff --git a/src/r_data.c b/src/r_data.c
index 698015cd53578d3543ede65144dca36098a178ee..b48888a1fe027f2a7ef0334692a295f1bc02659a 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -1772,9 +1772,6 @@ void R_ReInitColormaps(UINT16 num)
 	if (fadecolormap)
 		Z_Free(fadecolormap);
 	R_CreateFadeColormaps();
-
-	// Init Boom colormaps.
-	R_ClearColormaps();
 }
 
 //
@@ -2694,7 +2691,7 @@ void R_PrecacheLevel(void)
 	// Sky texture is always present.
 	// Note that F_SKY1 is the name used to indicate a sky floor/ceiling as a flat,
 	// while the sky texture is stored like a wall texture, with a skynum dependent name.
-	texturepresent[world->skytexture] = 1;
+	texturepresent[skytexture] = 1;
 
 	texturememory = 0;
 	for (j = 0; j < (unsigned)numtextures; j++)
diff --git a/src/r_main.c b/src/r_main.c
index 39e7529a43f19456d947dff3e9f96fa7e43728cb..ed45514ce44fff72e5ae7d725e7be046ed391550 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -927,11 +927,7 @@ void R_ExecuteSetViewSize(void)
 		screenheightarray[i] = (INT16)viewheight;
 
 	// setup sky scaling
-	for (i = 0; i < numworlds; i++)
-	{
-		if (worldlist[i])
-			R_SetSkyScale(worldlist[i]);
-	}
+	R_SetSkyScale();
 
 	// planes
 	if (rendermode == render_soft)
@@ -1229,7 +1225,7 @@ void R_SkyboxFrame(player_t *player)
 		thiscam = &camera;
 
 	// cut-away view stuff
-	r_viewmobj = world->skyboxmo[0];
+	r_viewmobj = viewworld->skyboxmo[0];
 #ifdef PARANOIA
 	if (!r_viewmobj)
 	{
@@ -1298,18 +1294,18 @@ void R_SkyboxFrame(player_t *player)
 		campos.y += quake.y;
 		campos.z += quake.z;
 
-		if (world->skyboxmo[1]) // Is there a viewpoint?
+		if (viewworld->skyboxmo[1]) // Is there a viewpoint?
 		{
 			fixed_t x = 0, y = 0;
 			if (mh->skybox_scalex > 0)
-				x = (campos.x - world->skyboxmo[1]->x) / mh->skybox_scalex;
+				x = (campos.x - viewworld->skyboxmo[1]->x) / mh->skybox_scalex;
 			else if (mh->skybox_scalex < 0)
-				x = (campos.x - world->skyboxmo[1]->x) * -mh->skybox_scalex;
+				x = (campos.x - viewworld->skyboxmo[1]->x) * -mh->skybox_scalex;
 
 			if (mh->skybox_scaley > 0)
-				y = (campos.y - world->skyboxmo[1]->y) / mh->skybox_scaley;
+				y = (campos.y - viewworld->skyboxmo[1]->y) / mh->skybox_scaley;
 			else if (mh->skybox_scaley < 0)
-				y = (campos.y - world->skyboxmo[1]->y) * -mh->skybox_scaley;
+				y = (campos.y - viewworld->skyboxmo[1]->y) * -mh->skybox_scaley;
 
 			if (r_viewmobj->angle == 0)
 			{
@@ -1525,7 +1521,7 @@ void R_RenderPlayerView(player_t *player)
 
 
 	// Add skybox portals caused by sky visplanes.
-	if (cv_skybox.value && world->skyboxmo[0])
+	if (cv_skybox.value && viewworld->skyboxmo[0])
 		Portal_AddSkyboxPortals();
 
 	// Portal rendering. Hijacks the BSP traversal.
diff --git a/src/r_plane.c b/src/r_plane.c
index 4655500e385704103b70fb63b084b6fbf66d28dc..d7c861aa1fa73b7dd77fe401a02134a07401b74d 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -616,15 +616,15 @@ static void R_DrawSkyPlane(visplane_t *pl)
 	colfunc = colfuncs[BASEDRAWFUNC];
 
 	// use correct aspect ratio scale
-	dc_iscale = world->skyscale;
+	dc_iscale = skyscale;
 
 	// Sky is always drawn full bright,
 	//  i.e. colormaps[0] is used.
 	// Because of this hack, sky is not affected
 	//  by sector colormaps (INVUL inverse mapping is not implemented in SRB2 so is irrelevant).
 	dc_colormap = colormaps;
-	dc_texturemid = viewworld->skytexturemid;
-	dc_texheight = textureheight[viewworld->skytexture]
+	dc_texturemid = skytexturemid;
+	dc_texheight = textureheight[skytexture]
 		>>FRACBITS;
 	for (x = pl->minx; x <= pl->maxx; x++)
 	{
@@ -634,10 +634,10 @@ static void R_DrawSkyPlane(visplane_t *pl)
 		if (dc_yl <= dc_yh)
 		{
 			angle = (pl->viewangle + xtoviewangle[x])>>ANGLETOSKYSHIFT;
-			dc_iscale = FixedMul(viewworld->skyscale, FINECOSINE(xtoviewangle[x]>>ANGLETOFINESHIFT));
+			dc_iscale = FixedMul(skyscale, FINECOSINE(xtoviewangle[x]>>ANGLETOFINESHIFT));
 			dc_x = x;
 			dc_source =
-				R_GetColumn(texturetranslation[viewworld->skytexture],
+				R_GetColumn(texturetranslation[skytexture],
 					-angle); // get negative of angle for each column to display sky correct way round! --Monster Iestyn 27/01/18
 			colfunc();
 		}
diff --git a/src/r_sky.c b/src/r_sky.c
index 789882457cfb7aa3c302ec95ee314933db00bcc4..f559fbccb228cd2998fb78b3b9c8b5f20d88c594 100644
--- a/src/r_sky.c
+++ b/src/r_sky.c
@@ -31,7 +31,18 @@
 /** \brief used for keeping track of the current sky
 */
 INT32 levelskynum;
-INT32 globallevelskynum;
+
+/**	\brief the lump number of the sky texture
+*/
+INT32 skytexture;
+
+/**	\brief the horizon line in a 256x128 sky texture
+*/
+INT32 skytexturemid;
+
+/**	\brief the scale of the sky
+*/
+fixed_t skyscale;
 
 /**	\brief	The R_SetupSkyDraw function
 
@@ -42,11 +53,11 @@ INT32 globallevelskynum;
 
 	\return	void
 */
-void R_SetupSkyDraw(world_t *w)
+void R_SetupSkyDraw(void)
 {
 	// the horizon line in a 256x128 sky texture
-	w->skytexturemid = (textures[world->skytexture]->height/2)<<FRACBITS;
-	R_SetSkyScale(w);
+	skytexturemid = (textures[skytexture]->height/2)<<FRACBITS;
+	R_SetSkyScale();
 }
 
 /**	\brief	The R_SetSkyScale function
@@ -55,8 +66,8 @@ void R_SetupSkyDraw(world_t *w)
 
 	\return void
 */
-void R_SetSkyScale(world_t *w)
+void R_SetSkyScale(void)
 {
 	fixed_t difference = vid.fdupx-(vid.dupx<<FRACBITS);
-	w->skyscale = FixedDiv(fovtan, vid.fdupx+difference);
+	skyscale = FixedDiv(fovtan, vid.fdupx+difference);
 }
diff --git a/src/r_sky.h b/src/r_sky.h
index ab3923d91625b4b78762abb9d20a722238dad2f0..b74d9257dd2f2b0a6276ea5fd67bb0cd8ba06c3f 100644
--- a/src/r_sky.h
+++ b/src/r_sky.h
@@ -27,11 +27,13 @@
 /// \brief The sky map is 256*128*4 maps.
 #define ANGLETOSKYSHIFT 22
 
+extern INT32 skytexture, skytexturemid;
+extern fixed_t skyscale;
+
 extern INT32 levelskynum;
-extern INT32 globallevelskynum;
 
 // call after skytexture is set to adapt for old/new skies
-void R_SetupSkyDraw(world_t *w);
-void R_SetSkyScale(world_t *w);
+void R_SetupSkyDraw(void);
+void R_SetSkyScale(void);
 
 #endif