From e486556f4bf9a15490059adcd1114f9a8d91be21 Mon Sep 17 00:00:00 2001
From: Lactozilla <jp6781615@gmail.com>
Date: Tue, 27 Jun 2023 01:28:21 -0300
Subject: [PATCH] Refactoring, fix subtle bugs

---
 src/b_bot.c            |   4 ++
 src/d_clisrv.c         |  20 +++---
 src/d_netcmd.c         |  15 +++--
 src/doomstat.h         |   8 ++-
 src/f_finale.c         |   6 +-
 src/g_demo.c           |   2 +
 src/g_game.c           |  57 ++++++++---------
 src/hardware/hw_main.c |   8 +++
 src/lua_baselib.c      |  10 +--
 src/lua_script.c       |   4 +-
 src/m_menu.c           |   6 +-
 src/m_misc.c           |   8 +--
 src/p_enemy.c          |  64 +++++++++----------
 src/p_floor.c          |   4 +-
 src/p_inter.c          |  34 ++++++-----
 src/p_local.h          |  28 +++++----
 src/p_mobj.c           |  53 ++++++++--------
 src/p_saveg.c          |  17 ++++--
 src/p_setup.c          |  83 +++++++++++++------------
 src/p_sight.c          |  23 +++----
 src/p_spec.c           |  63 +++++++++----------
 src/p_spec.h           |   3 +-
 src/p_tick.c           | 136 +++++++++++++++--------------------------
 src/p_user.c           |  62 +++++++++----------
 src/p_world.c          |  63 ++++++++++++-------
 src/p_world.h          |  11 ++++
 src/r_bsp.c            |  27 ++++----
 src/r_fps.c            |   4 +-
 src/r_main.c           |  26 ++++----
 src/r_portal.c         |   2 +-
 src/r_skins.c          |   2 +-
 src/r_things.c         |   3 -
 src/s_sound.c          |  51 +++++++++++-----
 src/s_sound.h          |  13 ++--
 src/sdl/mixer_sound.c  |   4 +-
 src/st_stuff.c         |  31 +++++-----
 src/v_video.c          |   2 +-
 src/y_inter.c          |  44 ++++++-------
 38 files changed, 521 insertions(+), 480 deletions(-)

diff --git a/src/b_bot.c b/src/b_bot.c
index d1465f891d..a62ddcbba3 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -529,6 +529,10 @@ boolean B_CheckRespawn(player_t *player)
 		return false;
 	}
 
+	// Not even in the same world?
+	if (P_GetMobjWorld(sonic) != P_GetMobjWorld(tails))
+		return true;
+
 	// If you can't see Sonic, I guess we should?
 	if (!P_CheckSight(sonic, tails) && P_AproxDistance(P_AproxDistance(tails->x-sonic->x, tails->y-sonic->y), tails->z-sonic->z) > FixedMul(1024*FRACUNIT, tails->scale))
 		return true;
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 73f3b8eeff..dca3f1e195 100755
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1394,9 +1394,9 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
 
 	memset(netbuffer->u.serverinfo.maptitle, 0, sizeof netbuffer->u.serverinfo.maptitle);
 
-	if (mapheaderinfo[gamemap-1] && *mapheaderinfo[gamemap-1]->lvlttl)
+	if (worldmapheader && *worldmapheader->lvlttl)
 	{
-		char *read = mapheaderinfo[gamemap-1]->lvlttl, *writ = netbuffer->u.serverinfo.maptitle;
+		char *read = worldmapheader->lvlttl, *writ = netbuffer->u.serverinfo.maptitle;
 		while (writ < (netbuffer->u.serverinfo.maptitle+32) && *read != '\0')
 		{
 			if (!(*read & 0x80))
@@ -1407,18 +1407,18 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
 			read++;
 		}
 		*writ = '\0';
-		//strncpy(netbuffer->u.serverinfo.maptitle, (char *)mapheaderinfo[gamemap-1]->lvlttl, 33);
+		//strncpy(netbuffer->u.serverinfo.maptitle, (char *)worldmapheader->lvlttl, 33);
 	}
 	else
 		strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 32);
 
-	if (mapheaderinfo[gamemap-1] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
+	if (worldmapheader && !(worldmapheader->levelflags & LF_NOZONE))
 		netbuffer->u.serverinfo.iszone = 1;
 	else
 		netbuffer->u.serverinfo.iszone = 0;
 
-	if (mapheaderinfo[gamemap-1])
-		netbuffer->u.serverinfo.actnum = mapheaderinfo[gamemap-1]->actnum;
+	if (worldmapheader)
+		netbuffer->u.serverinfo.actnum = worldmapheader->actnum;
 
 	p = PutFileNeeded(0);
 
@@ -1706,12 +1706,12 @@ static void CL_LoadReceivedSavegame(boolean reloading)
 	// load a base level
 	if (P_LoadNetGame(reloading))
 	{
-		const UINT8 actnum = mapheaderinfo[gamemap-1]->actnum;
+		const UINT8 actnum = worldmapheader->actnum;
 		CONS_Printf(M_GetText("Map is now \"%s"), G_BuildMapName(gamemap));
-		if (strcmp(mapheaderinfo[gamemap-1]->lvlttl, ""))
+		if (strcmp(worldmapheader->lvlttl, ""))
 		{
-			CONS_Printf(": %s", mapheaderinfo[gamemap-1]->lvlttl);
-			if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
+			CONS_Printf(": %s", worldmapheader->lvlttl);
+			if (!(worldmapheader->levelflags & LF_NOZONE))
 				CONS_Printf(M_GetText(" Zone"));
 			if (actnum > 0)
 				CONS_Printf(" %2d", actnum);
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 4889b43a4f..e933c733c2 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -1163,7 +1163,7 @@ UINT8 CanChangeSkin(INT32 playernum)
 		return true;
 
 	// Force skin in effect.
-	if ((cv_forceskin.value != -1) || (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0'))
+	if ((cv_forceskin.value != -1) || (worldmapheader && worldmapheader->forcecharacter[0] != '\0'))
 		return false;
 
 	// Can change skin in intermission and whatnot.
@@ -2200,10 +2200,8 @@ static void Got_Switchworld(UINT8 **cp, INT32 playernum)
 
 	if (nodetach) // Don't detach from the current world, if any
 	{
-#if 1
 		if (player->world)
-			((world_t *)player->world)->players--;
-#endif
+			P_GetPlayerWorld(player)->players--;
 		player->world = NULL;
 	}
 
@@ -4385,7 +4383,8 @@ static void Gravity_OnChange(void)
 
 	if (!CV_IsSetToDefault(&cv_gravity))
 		G_SetUsedCheats(false);
-	gravity = cv_gravity.value;
+	if (world)
+		world->gravity = cv_gravity.value;
 }
 
 static void SoundTest_OnChange(void)
@@ -4578,10 +4577,10 @@ static void Command_Showmap_f(void)
 {
 	if (gamestate == GS_LEVEL)
 	{
-		if (mapheaderinfo[gamemap-1]->actnum)
-			CONS_Printf("%s (%d): %s %d\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->actnum);
+		if (worldmapheader->actnum)
+			CONS_Printf("%s (%d): %s %d\n", G_BuildMapName(gamemap), gamemap, worldmapheader->lvlttl, worldmapheader->actnum);
 		else
-			CONS_Printf("%s (%d): %s\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl);
+			CONS_Printf("%s (%d): %s\n", G_BuildMapName(gamemap), gamemap, worldmapheader->lvlttl);
 	}
 	else
 		CONS_Printf(M_GetText("You must be in a level to use this.\n"));
diff --git a/src/doomstat.h b/src/doomstat.h
index 9487d41462..5ffd346766 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -388,6 +388,10 @@ typedef struct
 
 extern mapheader_t* mapheaderinfo[NUMMAPS];
 
+extern mapheader_t* curmapheader;
+extern mapheader_t* nextmapheader;
+extern mapheader_t* worldmapheader;
+
 // Gametypes
 #define NUMGAMETYPEFREESLOTS 128
 enum GameType
@@ -548,14 +552,12 @@ extern UINT8 useContinues;
 
 extern UINT8 shareEmblems;
 
-extern mobj_t *hunt1, *hunt2, *hunt3; // Emerald hunt locations
+#define NUM_EMERALD_HUNT_LOCATIONS 3
 
 // For racing
 extern UINT32 countdown;
 extern UINT32 countdown2;
 
-extern fixed_t gravity;
-
 //for CTF balancing
 extern INT16 autobalance;
 extern INT16 teamscramble;
diff --git a/src/f_finale.c b/src/f_finale.c
index 1a45225784..ccc1dfb84a 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -2431,7 +2431,9 @@ void F_StartTitleScreen(void)
 
 		P_UnloadWorldList();
 
-		maptol = mapheaderinfo[gamemap-1]->typeoflevel;
+		curmapheader = nextmapheader = mapheaderinfo[gamemap-1];
+		worldmapheader = curmapheader;
+		maptol = curmapheader->typeoflevel;
 
 		G_DoLoadLevel(&players[displayplayer], false, true);
 		if (!titlemap)
@@ -2477,6 +2479,8 @@ void F_StartTitleScreen(void)
 		gamemap = 1; // g_game.c
 		if (!mapheaderinfo[gamemap-1])
 			P_AllocMapHeader(gamemap-1);
+		curmapheader = nextmapheader = mapheaderinfo[gamemap-1];
+		worldmapheader = curmapheader;
 		CON_ClearHUD();
 	}
 
diff --git a/src/g_demo.c b/src/g_demo.c
index f6ba1561c8..470485a3a2 100644
--- a/src/g_demo.c
+++ b/src/g_demo.c
@@ -1868,6 +1868,8 @@ void G_DoPlayDemo(char *defdemoname)
 	}
 	demo_p += 4; // "PLAY"
 	gamemap = READINT16(demo_p);
+	curmapheader = nextmapheader = mapheaderinfo[gamemap-1];
+	worldmapheader = curmapheader;
 	demo_p += 16; // mapmd5
 
 	demoflags = READUINT8(demo_p);
diff --git a/src/g_game.c b/src/g_game.c
index 82615a0174..837d4afba6 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -164,11 +164,13 @@ mobj_t *blueflag;
 mapthing_t *rflagpoint;
 mapthing_t *bflagpoint;
 
-struct quake quake;
-
 // Map Header Information
 mapheader_t* mapheaderinfo[NUMMAPS] = {NULL};
 
+mapheader_t* curmapheader = NULL;
+mapheader_t* nextmapheader = NULL;
+mapheader_t* worldmapheader = NULL;
+
 static boolean exitgame = false;
 static boolean retrying = false;
 static boolean retryingmodeattack = false;
@@ -221,15 +223,8 @@ UINT8 introtoplay;
 UINT8 creditscutscene;
 UINT8 useBlackRock = 1;
 
-// Emerald locations
-mobj_t *hunt1;
-mobj_t *hunt2;
-mobj_t *hunt3;
-
 UINT32 countdown, countdown2; // for racing
 
-fixed_t gravity;
-
 INT16 autobalance; //for CTF team balance
 INT16 teamscramble; //for CTF team scramble
 INT16 scrambleplayers[MAXPLAYERS]; //for CTF team scramble
@@ -1855,7 +1850,7 @@ void G_DoLoadLevel(player_t *player, boolean addworld, boolean resetplayer)
 		return;
 	}
 
-	P_FindEmerald();
+	P_FindEmerald(world);
 
 	displayplayer = consoleplayer; // view the guy you are playing
 	if (!splitscreen && !botingame)
@@ -1965,7 +1960,7 @@ boolean G_IsTitleCardAvailable(void)
 	else if (titlecardforreload)
 		titleflag = LF_NOTITLECARDRESPAWN;
 
-	if (mapheaderinfo[gamemap-1]->levelflags & titleflag)
+	if (worldmapheader->levelflags & titleflag)
 		return false;
 
 	// The current gametype doesn't have a title card.
@@ -1973,7 +1968,7 @@ boolean G_IsTitleCardAvailable(void)
 		return false;
 
 	// The current level has no name.
-	if (!mapheaderinfo[gamemap-1]->lvlttl[0])
+	if (!worldmapheader->lvlttl[0])
 		return false;
 
 	// The title card is available.
@@ -2654,7 +2649,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
 
 	if (betweenmaps || !G_IsSpecialStage(gamemap))
 	{
-		rings = (ultimatemode ? 0 : mapheaderinfo[gamemap-1]->startrings);
+		rings = (ultimatemode ? 0 : curmapheader->startrings);
 		spheres = 0;
 	}
 	else
@@ -2769,23 +2764,23 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
 	{
 		if (mapmusflags & MUSIC_RELOADRESET)
 		{
-			strncpy(mapmusname, mapheaderinfo[gamemap-1]->musname, 7);
+			strncpy(mapmusname, worldmapheader->musname, 7);
 			mapmusname[6] = 0;
-			mapmusflags = (mapheaderinfo[gamemap-1]->mustrack & MUSIC_TRACKMASK);
-			mapmusposition = mapheaderinfo[gamemap-1]->muspos;
+			mapmusflags = (worldmapheader->mustrack & MUSIC_TRACKMASK);
+			mapmusposition = worldmapheader->muspos;
 		}
 
 		// This is in S_Start, but this was not here previously.
-		// if (RESETMUSIC)
+		// if (S_ShouldResetMusic(worldmapheader))
 		// 	S_StopMusic();
 		S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
 	}
 
 	if (gametyperules & GTR_EMERALDHUNT)
-		P_FindEmerald(); // scan for emeralds to hunt for
+		P_FindEmerald(p->world); // scan for emeralds to hunt for
 
 	// If NiGHTS, find lowest mare to start with.
-	p->mare = P_FindLowestMare();
+	p->mare = P_FindLowestMare(p->world);
 
 	CONS_Debug(DBG_NIGHTS, M_GetText("Current mare is %d\n"), p->mare);
 
@@ -3202,7 +3197,7 @@ void G_DoReborn(INT32 playernum)
 				players[i].starpostnum = 0;
 			}
 		}
-		if (!countdowntimeup && (mapheaderinfo[gamemap-1]->levelflags & LF_NORELOAD) && !(marathonmode & MA_INIT))
+		if (!countdowntimeup && (curmapheader->levelflags & LF_NORELOAD) && !(marathonmode & MA_INIT))
 		{
 			P_RespawnThings();
 
@@ -3983,11 +3978,11 @@ void G_SetNextMap(boolean usespec, boolean inspec)
 	// nextmap is 0-based, unlike gamemap
 	if (nextmapoverride != 0)
 		nextmap = (INT16)(nextmapoverride-1);
-	else if (marathonmode && mapheaderinfo[gamemap-1]->marathonnext)
-		nextmap = (INT16)(mapheaderinfo[gamemap-1]->marathonnext-1);
+	else if (marathonmode && curmapheader->marathonnext)
+		nextmap = (INT16)(curmapheader->marathonnext-1);
 	else
 	{
-		nextmap = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1);
+		nextmap = (INT16)(curmapheader->nextlevel-1);
 		if (marathonmode && nextmap == spmarathon_start-1)
 			nextmap = 1100-1; // No infinite loop for you
 	}
@@ -4161,14 +4156,14 @@ void G_AfterIntermission(void)
 
 	HU_ClearCEcho();
 
-	if ((gametyperules & GTR_CUTSCENES) && mapheaderinfo[gamemap-1]->cutscenenum
+	if ((gametyperules & GTR_CUTSCENES) && curmapheader->cutscenenum
 		&& !modeattacking
 		&& skipstats <= 1
 		&& (gamecomplete || !(marathonmode & MA_NOCUTSCENES))
 		&& stagefailed == false)
 	{
 		// Start a custom cutscene.
-		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false, false);
+		F_StartCustomCutscene(curmapheader->cutscenenum-1, false, false, false);
 	}
 	else
 	{
@@ -5068,7 +5063,12 @@ void G_InitNew(player_t *player,
 	if(!mapheaderinfo[gamemap-1])
 		P_AllocMapHeader(gamemap-1);
 
-	maptol = mapheaderinfo[gamemap-1]->typeoflevel;
+	nextmapheader = mapheaderinfo[gamemap-1];
+	maptol = curmapheader->typeoflevel;
+
+	if (!addworld)
+		curmapheader = nextmapheader;
+	worldmapheader = nextmapheader;
 
 	// Don't carry over custom music change to another map.
 	mapmusflags |= MUSIC_RELOADRESET;
@@ -5077,14 +5077,15 @@ void G_InitNew(player_t *player,
 	automapactive = false;
 	imcontinuing = false;
 
-	if ((gametyperules & GTR_CUTSCENES) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene.
-		F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer, FLS);
+	if (!addworld && ((gametyperules & GTR_CUTSCENES) && !skipprecutscene && nextmapheader->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene.
+		F_StartCustomCutscene(nextmapheader->precutscenenum-1, true, resetplayer, FLS);
 	else
 		G_DoLoadLevel(player, addworld, resetplayer);
 
 	if (addworld && player != &players[consoleplayer])
 		return;
 
+	// current world is hopefully the newly loaded world at this point
 	if (netgame)
 	{
 		char *title = G_BuildMapTitle(gamemap);
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 1aa9486f4c..3b51fc4124 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -5371,6 +5371,14 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	if (viewnumber == 0) // Only do it if it's the first screen being rendered
 		HWD.pfnClearBuffer(true, false, &ClearColor); // Clear the Color Buffer, stops HOMs. Also seems to fix the skybox issue on Intel GPUs.
 
+	// Uh, double check? I don't know, I'm not paid for this.
+	if (viewworld->extrasubsectors == NULL)
+		HWR_CreatePlanePolygons((INT32)viewworld->numnodes - 1);
+
+	// Same
+	if (viewworld->sky_dome == NULL)
+		HWR_BuildSkyDome(viewworld);
+
 	PS_START_TIMING(ps_hw_skyboxtime);
 	if (skybox && drawsky) // If there's a skybox and we should be drawing the sky, draw the skybox
 		HWR_RenderSkyboxView(viewnumber, player); // This is drawn before everything else so it is placed behind
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index add40a3763..0a920d63a0 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -2577,15 +2577,15 @@ static int lib_pStartQuake(lua_State *L)
 			q_epicenter.z = 0;
 		lua_pop(L, 1);
 
-		quake.epicenter = &q_epicenter;
+		world->quake.epicenter = &q_epicenter;
 	}
 	else
-		quake.epicenter = NULL;
-	quake.radius = luaL_optinteger(L, 4, 512*FRACUNIT);
+		world->quake.epicenter = NULL;
+	world->quake.radius = luaL_optinteger(L, 4, 512*FRACUNIT);
 
 	// These things are actually used in 2.1.
-	quake.intensity = q_intensity;
-	quake.time = q_time;
+	world->quake.intensity = q_intensity;
+	world->quake.time = q_time;
 	return 0;
 }
 
diff --git a/src/lua_script.c b/src/lua_script.c
index 433078eb2e..715f16e0c3 100644
--- a/src/lua_script.c
+++ b/src/lua_script.c
@@ -383,7 +383,7 @@ int LUA_PushGlobals(lua_State *L, const char *word)
 		lua_pushinteger(L, emeralds);
 		return 1;
 	} else if (fastcmp(word,"gravity")) {
-		lua_pushinteger(L, gravity);
+		lua_pushinteger(L, world->gravity);
 		return 1;
 	} else if (fastcmp(word,"VERSION")) {
 		lua_pushinteger(L, VERSION);
@@ -441,7 +441,7 @@ int LUA_CheckGlobals(lua_State *L, const char *word)
 	else if (fastcmp(word, "token"))
 		token = (UINT32)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "gravity"))
-		gravity = (fixed_t)luaL_checkinteger(L, 2);
+		world->gravity = (fixed_t)luaL_checkinteger(L, 2);
 	else if (fastcmp(word, "stoppedclock"))
 		stoppedclock = luaL_checkboolean(L, 2);
 	else if (fastcmp(word, "displayplayer"))
diff --git a/src/m_menu.c b/src/m_menu.c
index b142abf21a..cae75902b4 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -4716,10 +4716,10 @@ static void M_DrawPauseMenu(void)
 		// Draw any and all emblems at the top.
 		M_DrawMapEmblems(gamemap, 272, 28, true);
 
-		if (mapheaderinfo[gamemap-1]->actnum != 0)
-			V_DrawString(40, 28, V_YELLOWMAP, va("%s %d", mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->actnum));
+		if (curmapheader->actnum != 0)
+			V_DrawString(40, 28, V_YELLOWMAP, va("%s %d", curmapheader->lvlttl, curmapheader->actnum));
 		else
-			V_DrawString(40, 28, V_YELLOWMAP, mapheaderinfo[gamemap-1]->lvlttl);
+			V_DrawString(40, 28, V_YELLOWMAP, curmapheader->lvlttl);
 
 		// Set up the detail boxes.
 		{
diff --git a/src/m_misc.c b/src/m_misc.c
index c24896600e..52c4cfac82 100644
--- a/src/m_misc.c
+++ b/src/m_misc.c
@@ -830,11 +830,11 @@ static void M_PNGText(png_structp png_ptr, png_infop png_info_ptr, PNG_CONST png
 	else
 		snprintf(maptext, 8, "Unknown");
 
-	if (gamestate == GS_LEVEL && mapheaderinfo[gamemap-1]->lvlttl[0] != '\0')
+	if (gamestate == GS_LEVEL && worldmapheader->lvlttl[0] != '\0')
 		snprintf(lvlttltext, 48, "%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) : "");
+			worldmapheader->lvlttl,
+			(worldmapheader->levelflags & LF_NOZONE) ? "" : " Zone",
+			(worldmapheader->actnum > 0) ? va(" %d", worldmapheader->actnum) : "");
 	else
 		snprintf(lvlttltext, 48, "Unknown");
 
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 7a4ed60d85..66c5a1f343 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -756,7 +756,7 @@ boolean P_LookForPlayers(mobj_t *actor, boolean allaround, boolean tracer, fixed
 		if (player->quittime)
 			continue; // Ignore uncontrolled bodies
 
-		if (player->world != actor->world)
+		if (P_GetPlayerWorld(player) != actor->world)
 			continue; // Different world
 
 		if (dist > 0
@@ -1664,10 +1664,10 @@ static void P_ParabolicMove(mobj_t *actor, fixed_t x, fixed_t y, fixed_t z, fixe
 	actor->momx = FixedMul(FixedDiv(x, dh), speed);
 	actor->momy = FixedMul(FixedDiv(y, dh), speed);
 
-	if (!gravity)
+	if (!world->gravity)
 		return;
 
-	dh = FixedDiv(FixedMul(dh, gravity), speed);
+	dh = FixedDiv(FixedMul(dh, world->gravity), speed);
 	actor->momz = (dh>>1) + FixedDiv(z, dh<<1);
 }
 
@@ -2731,7 +2731,7 @@ void A_LobShot(mobj_t *actor)
 	dist = P_AproxDistance(actor->target->x - shot->x, actor->target->y - shot->y);
 
 	horizontal = dist / airtime;
-	vertical = FixedMul((gravity*airtime)/2, shot->scale);
+	vertical = FixedMul((world->gravity*airtime)/2, shot->scale);
 
 	shot->momx = FixedMul(horizontal, FINECOSINE(an));
 	shot->momy = FixedMul(horizontal, FINESINE(an));
@@ -2753,7 +2753,7 @@ void A_LobShot(mobj_t *actor)
 			CONS_Debug(DBG_GAMELOGIC, "orig: %d\n", (orig)>>FRACBITS);
 
 			horizontal = dist / airtime;
-			vertical = (gravity*airtime)/2;
+			vertical = (world->gravity*airtime)/2;
 		}
 		dist -= orig;
 		shot->momx = FixedMul(horizontal, FINECOSINE(an));
@@ -3967,26 +3967,26 @@ static void P_DoBossVictory(mobj_t *mo)
 			EV_DoElevator(LE_CAPSULE2, NULL, elevateHighest);
 		}
 
-		if (mapheaderinfo[gamemap-1]->muspostbossname[0] &&
-			S_MusicExists(mapheaderinfo[gamemap-1]->muspostbossname, !midi_disabled, !digital_disabled))
+		if (worldmapheader->muspostbossname[0] &&
+			S_MusicExists(worldmapheader->muspostbossname, !midi_disabled, !digital_disabled))
 		{
 			// Touching the egg trap button calls P_DoPlayerExit, which calls P_RestoreMusic.
 			// So just park ourselves in the mapmus variables.
 			// But don't change the mapmus variables if they were modified from their level header values (e.g., TUNES).
-			boolean changed = strnicmp(mapheaderinfo[gamemap-1]->musname, S_MusicName(), 7);
-			if (!strnicmp(mapheaderinfo[gamemap-1]->musname, mapmusname, 7))
+			boolean changed = strnicmp(worldmapheader->musname, S_MusicName(), 7);
+			if (!strnicmp(worldmapheader->musname, mapmusname, 7))
 			{
-				strncpy(mapmusname, mapheaderinfo[gamemap-1]->muspostbossname, 7);
+				strncpy(mapmusname, worldmapheader->muspostbossname, 7);
 				mapmusname[6] = 0;
-				mapmusflags = (mapheaderinfo[gamemap-1]->muspostbosstrack & MUSIC_TRACKMASK) | MUSIC_RELOADRESET;
-				mapmusposition = mapheaderinfo[gamemap-1]->muspostbosspos;
+				mapmusflags = (worldmapheader->muspostbosstrack & MUSIC_TRACKMASK) | MUSIC_RELOADRESET;
+				mapmusposition = worldmapheader->muspostbosspos;
 			}
 
 			// don't change if we're in another tune
 			// but in case we're in jingle, use our parked mapmus variables so the correct track restores
 			if (!changed)
 				S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, (1*MUSICRATE)+(MUSICRATE/2),
-					mapheaderinfo[gamemap-1]->muspostbossfadein);
+					worldmapheader->muspostbossfadein);
 		}
 	}
 }
@@ -4412,7 +4412,7 @@ void A_SuperSneakers(mobj_t *actor)
 
 	if (P_IsLocalPlayer(player) && !player->powers[pw_super])
 	{
-		if (S_SpeedMusic(0.0f) && (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC))
+		if (S_SpeedMusic(0.0f) && (worldmapheader->levelflags & LF_SPEEDMUSIC))
 			S_SpeedMusic(1.4f);
 		else
 			P_PlayJingle(player, JT_SHOES);
@@ -11591,7 +11591,7 @@ void A_BrakLobShot(mobj_t *actor)
 		return; // Don't even bother if we've got nothing to aim at.
 
 	// Look up actor's current gravity situation
-	g = FixedMul(gravity, P_GetSectorGravityFactor(actor->subsector->sector));
+	g = FixedMul(world->gravity, P_GetSectorGravityFactor(actor->subsector->sector));
 
 	// Look up distance between actor and its target
 	x = P_AproxDistance(actor->target->x - actor->x, actor->target->y - actor->y);
@@ -11706,7 +11706,7 @@ void A_NapalmScatter(mobj_t *actor)
 		airtime = 16<<FRACBITS;
 
 	// Look up actor's current gravity situation
-	g = FixedMul(gravity, P_GetSectorGravityFactor(actor->subsector->sector));
+	g = FixedMul(world->gravity, P_GetSectorGravityFactor(actor->subsector->sector));
 
 	// vy = (g*(airtime-1))/2
 	vy = FixedMul(g,(airtime-(1<<FRACBITS)))>>1;
@@ -11761,12 +11761,12 @@ mobj_t *P_InternalFlickySpawn(mobj_t *actor, mobjtype_t flickytype, fixed_t momz
 
 	if (!flickytype)
 	{
-		if (!mapheaderinfo[gamemap-1] || !mapheaderinfo[gamemap-1]->numFlickies) // No mapheader, no shoes, no service.
+		if (!worldmapheader || !worldmapheader->numFlickies) // No mapheader, no shoes, no service.
 			return NULL;
 		else
 		{
-			INT32 prandom = P_RandomKey(mapheaderinfo[gamemap-1]->numFlickies);
-			flickytype = mapheaderinfo[gamemap-1]->flickies[prandom];
+			INT32 prandom = P_RandomKey(worldmapheader->numFlickies);
+			flickytype = worldmapheader->flickies[prandom];
 		}
 	}
 
@@ -12416,7 +12416,7 @@ void A_Boss5Jump(mobj_t *actor)
 		return; // Don't even bother if we've got nothing to aim at.
 
 	// Look up actor's current gravity situation
-	g = FixedMul(gravity, P_GetSectorGravityFactor(actor->subsector->sector));
+	g = FixedMul(world->gravity, P_GetSectorGravityFactor(actor->subsector->sector));
 
 	// Look up distance between actor and its tracer
 	x = P_AproxDistance(actor->tracer->x - actor->x, actor->tracer->y - actor->y);
@@ -12519,10 +12519,10 @@ void A_MineExplode(mobj_t *actor)
 	A_Scream(actor);
 	actor->flags = MF_NOGRAVITY|MF_NOCLIP;
 
-	quake.epicenter = NULL;
-	quake.radius = 512*FRACUNIT;
-	quake.intensity = 8*FRACUNIT;
-	quake.time = TICRATE/3;
+	world->quake.epicenter = NULL;
+	world->quake.radius = 512*FRACUNIT;
+	world->quake.intensity = 8*FRACUNIT;
+	world->quake.time = TICRATE/3;
 
 	P_RadiusAttack(actor, actor->tracer, 192*FRACUNIT, DMG_CANHURTSELF, true);
 	P_MobjCheckWater(actor);
@@ -13505,14 +13505,14 @@ void A_Boss5BombExplode(mobj_t *actor)
 	P_DustRing(locvar1, 6, actor->x, actor->y, actor->z+actor->height/2, 3*actor->radius, FRACUNIT, FRACUNIT, actor->scale);
 	//P_StartQuake(9*actor->scale, TICRATE/6, {actor->x, actor->y, actor->z}, 20*actor->radius);
 	// the above does not exist, so we set the quake values directly instead
-	quake.intensity = 9*actor->scale;
-	quake.time = TICRATE/6;
+	world->quake.intensity = 9*actor->scale;
+	world->quake.time = TICRATE/6;
 	// the following quake values have no effect atm? ah well, may as well set them anyway
 	{
 		mappoint_t q_epicenter = {actor->x, actor->y, actor->z};
-		quake.epicenter = &q_epicenter;
+		world->quake.epicenter = &q_epicenter;
 	}
-	quake.radius = 20*actor->radius;
+	world->quake.radius = 20*actor->radius;
 }
 
 static mobj_t *dustdevil;
@@ -13785,10 +13785,10 @@ void A_TNTExplode(mobj_t *actor)
 	epicenter.x = actor->x;
 	epicenter.y = actor->y;
 	epicenter.z = actor->z;
-	quake.intensity = 9*FRACUNIT;
-	quake.time = TICRATE/6;
-	quake.epicenter = &epicenter;
-	quake.radius = 512*FRACUNIT;
+	world->quake.intensity = 9*FRACUNIT;
+	world->quake.time = TICRATE/6;
+	world->quake.epicenter = &epicenter;
+	world->quake.radius = 512*FRACUNIT;
 
 	if (locvar1)
 	{
diff --git a/src/p_floor.c b/src/p_floor.c
index ce983095d0..d7753ef108 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -689,7 +689,7 @@ void T_BounceCheese(bouncecheese_t *bouncer)
 		}
 		else if (bouncer->sector->ceilingheight > bouncer->ceilingwasheight) // Up
 		{
-			bouncer->speed += gravity;
+			bouncer->speed += world->gravity;
 		}
 
 		if (abs(bouncer->speed) < 2*FRACUNIT
@@ -838,7 +838,7 @@ void T_StartCrumble(crumble_t *crumble)
 		// Only fall like this if it isn't meant to float on water
 		if (!(crumble->flags & CF_FLOATBOB))
 		{
-			crumble->speed += gravity; // Gain more and more speed
+			crumble->speed += world->gravity; // Gain more and more speed
 
 			if ((!(crumble->flags & CF_REVERSE) && crumble->sector->ceilingheight >= -16384*FRACUNIT)
 				|| ((crumble->flags & CF_REVERSE) && crumble->sector->ceilingheight <= 16384*FRACUNIT))
diff --git a/src/p_inter.c b/src/p_inter.c
index 046a0a198e..d6cd42fec1 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -725,18 +725,24 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 			break;
 
 		// Emerald Hunt
-		case MT_EMERHUNT:
+		case MT_EMERHUNT: {
+			unsigned remaining_emeralds = 0;
+
 			if (player->bot && player->bot != BOT_MPAI)
 				return;
 
-			if (hunt1 == special)
-				hunt1 = NULL;
-			else if (hunt2 == special)
-				hunt2 = NULL;
-			else if (hunt3 == special)
-				hunt3 = NULL;
+			for (i = 0; i < NUM_EMERALD_HUNT_LOCATIONS; i++)
+			{
+				if (world->emerald_hunt_locations[i])
+				{
+					if (world->emerald_hunt_locations[i] == special)
+						P_SetTarget(&world->emerald_hunt_locations[i], NULL);
+					else
+						remaining_emeralds++;
+				}
+			}
 
-			if (!hunt1 && !hunt2 && !hunt3)
+			if (!remaining_emeralds)
 			{
 				for (i = 0; i < MAXPLAYERS; i++)
 				{
@@ -748,7 +754,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				//S_StartSound(NULL, sfx_lvpass);
 			}
 			break;
-
+		}
 		// Collectible emeralds
 		case MT_EMERALD1:
 		case MT_EMERALD2:
@@ -1678,9 +1684,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				P_SetObjectMomZ(toucher, FixedDiv(69*FRACUNIT,10*FRACUNIT), false);
 			if (P_IsLocalPlayer(player))
 			{
-				quake.intensity = 9*FRACUNIT;
-				quake.time = TICRATE/2;
-				quake.epicenter = NULL;
+				world->quake.intensity = 9*FRACUNIT;
+				world->quake.time = TICRATE/2;
+				world->quake.epicenter = NULL;
 			}
 
 #if 0 // camera redirection - deemed unnecessary
@@ -3149,7 +3155,7 @@ static void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
 		if (oldnightstime > 10*TICRATE
 			&& player->nightstime < 10*TICRATE)
 		{
-			if ((mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+			if ((worldmapheader->levelflags & LF_MIXNIGHTSCOUNTDOWN)
 #ifdef _WIN32
 				// win32 MIDI volume hack means we cannot fade down the music
 				&& S_MusicType() != MU_MID
@@ -3605,7 +3611,7 @@ void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
 	if (oldnightstime > 10*TICRATE
 		&& player->nightstime < 10*TICRATE)
 	{
-		if (mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+		if (worldmapheader->levelflags & LF_MIXNIGHTSCOUNTDOWN)
 		{
 			S_FadeMusic(0, 10*MUSICRATE);
 			S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS.
diff --git a/src/p_local.h b/src/p_local.h
index 6ea57e122f..8f3bc9edc3 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -208,8 +208,8 @@ boolean P_SuperReady(player_t *player);
 void P_DoJump(player_t *player, boolean soundandstate);
 #define P_AnalogMove(player) (P_ControlStyle(player) == CS_LMAOGALOG)
 boolean P_TransferToNextMare(player_t *player);
-UINT8 P_FindLowestMare(void);
-void P_FindEmerald(void);
+UINT8 P_FindLowestMare(world_t *w);
+void P_FindEmerald(world_t *w);
 void P_TransferToAxis(player_t *player, INT32 axisnum);
 boolean P_PlayerMoving(INT32 pnum);
 void P_SpawnThokMobj(player_t *player);
@@ -276,7 +276,7 @@ extern consvar_t cv_gravity, cv_movebob;
 
 mobjtype_t P_GetMobjtype(UINT16 mthingtype);
 
-void P_RespawnSpecials(void);
+void P_RespawnSpecials(world_t *w);
 
 mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type);
 
@@ -288,8 +288,8 @@ boolean P_MobjWasRemoved(mobj_t *th);
 void P_RemoveSavegameMobj(mobj_t *th);
 boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state);
 boolean P_SetMobjState(mobj_t *mobj, statenum_t state);
-void P_RunShields(void);
-void P_RunOverlays(void);
+void P_RunShields(world_t *w);
+void P_RunOverlays(world_t *w);
 void P_HandleMinecartSegments(mobj_t *mobj);
 void P_MobjThinker(mobj_t *mobj);
 boolean P_RailThinker(mobj_t *mobj);
@@ -301,17 +301,17 @@ fixed_t P_MobjFloorZ(mobj_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t
 fixed_t P_MobjCeilingZ(mobj_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect);
 #define P_GetFloorZ(mobj, sector, x, y, line) P_MobjFloorZ(mobj, sector, NULL, x, y, line, false, false)
 #define P_GetCeilingZ(mobj, sector, x, y, line) P_MobjCeilingZ(mobj, sector, NULL, x, y, line, true, false)
-#define P_GetFOFTopZ(mobj, sector, fof, x, y, line) P_MobjCeilingZ(mobj, sectors + fof->secnum, sector, x, y, line, false, false)
-#define P_GetFOFBottomZ(mobj, sector, fof, x, y, line) P_MobjFloorZ(mobj, sectors + fof->secnum, sector, x, y, line, true, false)
+#define P_GetFOFTopZ(mobj, sector, fof, x, y, line) P_MobjCeilingZ(mobj, P_GetMobjWorld(mobj)->sectors + fof->secnum, sector, x, y, line, false, false)
+#define P_GetFOFBottomZ(mobj, sector, fof, x, y, line) P_MobjFloorZ(mobj, P_GetMobjWorld(mobj)->sectors + fof->secnum, sector, x, y, line, true, false)
 #define P_GetSpecialBottomZ(mobj, src, bound) P_MobjFloorZ(mobj, src, bound, mobj->x, mobj->y, NULL, src != bound, true)
 #define P_GetSpecialTopZ(mobj, src, bound) P_MobjCeilingZ(mobj, src, bound, mobj->x, mobj->y, NULL, src == bound, true)
 
-fixed_t P_CameraFloorZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect);
-fixed_t P_CameraCeilingZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect);
-#define P_CameraGetFloorZ(mobj, sector, x, y, line) P_CameraFloorZ(mobj, sector, NULL, x, y, line, false, false)
-#define P_CameraGetCeilingZ(mobj, sector, x, y, line) P_CameraCeilingZ(mobj, sector, NULL, x, y, line, true, false)
-#define P_CameraGetFOFTopZ(mobj, sector, fof, x, y, line) P_CameraCeilingZ(mobj, sectors + fof->secnum, sector, x, y, line, false, false)
-#define P_CameraGetFOFBottomZ(mobj, sector, fof, x, y, line) P_CameraFloorZ(mobj, sectors + fof->secnum, sector, x, y, line, true, false)
+fixed_t P_CameraFloorZ(camera_t *cam, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect);
+fixed_t P_CameraCeilingZ(camera_t *cam, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect);
+#define P_CameraGetFloorZ(cam, sector, x, y, line) P_CameraFloorZ(cam, sector, NULL, x, y, line, false, false)
+#define P_CameraGetCeilingZ(cam, sector, x, y, line) P_CameraCeilingZ(cam, sector, NULL, x, y, line, true, false)
+#define P_CameraGetFOFTopZ(cam, sector, fof, x, y, line) P_CameraCeilingZ(cam, P_GetCameraWorld(cam)->sectors + fof->secnum, sector, x, y, line, false, false)
+#define P_CameraGetFOFBottomZ(cam, sector, fof, x, y, line) P_CameraFloorZ(cam, P_GetCameraWorld(cam)->sectors + fof->secnum, sector, x, y, line, true, false)
 
 boolean P_InsideANonSolidFFloor(mobj_t *mobj, ffloor_t *rover);
 boolean P_CheckDeathPitCollide(mobj_t *mo);
@@ -335,6 +335,8 @@ FUNCMATH boolean P_WeaponOrPanel(mobjtype_t type);
 void P_CalcChasePostImg(player_t *player, camera_t *thiscam);
 boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled);
 
+world_t *P_GetCameraWorld(camera_t *cam);
+
 void P_Attract(mobj_t *source, mobj_t *enemy, boolean nightsgrab);
 mobj_t *P_GetClosestAxis(mobj_t *source);
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 6ab222c68d..02308680e2 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1385,6 +1385,14 @@ fixed_t P_CameraCeilingZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, f
 	} else // Well, that makes it easy. Just get the ceiling height
 		return sector->ceilingheight;
 }
+
+world_t *P_GetCameraWorld(camera_t *cam)
+{
+	if (cam->subsector == NULL)
+		return NULL;
+	return (world_t *)cam->subsector->sector->world;
+}
+
 static void P_PlayerFlip(mobj_t *mo)
 {
 	if (!mo->player)
@@ -1463,7 +1471,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
 	if (!gravsector) // If there is no 3D floor gravity, check sector's gravity
 		gravsector = mo->subsector->sector;
 
-	gravityadd = -FixedMul(gravity, P_GetSectorGravityFactor(gravsector));
+	gravityadd = -FixedMul(world->gravity, P_GetSectorGravityFactor(gravsector));
 
 	if ((gravsector->flags & MSF_GRAVITYFLIP) && gravityadd > 0)
 	{
@@ -3238,8 +3246,8 @@ void P_MobjCheckWater(mobj_t *mobj)
 		 || ((rover->fofflags & FOF_BLOCKOTHERS) && !mobj->player)))
 			continue;
 
-		topheight = P_GetSpecialTopZ(mobj, sectors + rover->secnum, sector);
-		bottomheight = P_GetSpecialBottomZ(mobj, sectors + rover->secnum, sector);
+		topheight = P_GetSpecialTopZ(mobj, P_GetMobjWorld(mobj)->sectors + rover->secnum, sector);
+		bottomheight = P_GetSpecialBottomZ(mobj, P_GetMobjWorld(mobj)->sectors + rover->secnum, sector);
 
 		if (mobj->eflags & MFE_VERTICALFLIP)
 		{
@@ -3760,12 +3768,12 @@ static void P_CheckCrumblingPlatforms(mobj_t *mobj)
 
 			if (mobj->eflags & MFE_VERTICALFLIP)
 			{
-				if (P_GetSpecialBottomZ(mobj, sectors + rover->secnum, node->m_sector) != mobj->z + mobj->height)
+				if (P_GetSpecialBottomZ(mobj, P_GetMobjWorld(mobj)->sectors + rover->secnum, node->m_sector) != mobj->z + mobj->height)
 					continue;
 			}
 			else
 			{
-				if (P_GetSpecialTopZ(mobj, sectors + rover->secnum, node->m_sector) != mobj->z)
+				if (P_GetSpecialTopZ(mobj, P_GetMobjWorld(mobj)->sectors + rover->secnum, node->m_sector) != mobj->z)
 					continue;
 			}
 
@@ -5354,7 +5362,7 @@ static void P_Boss7Thinker(mobj_t *mobj)
 		dist = P_AproxDistance(hitspot->x - mobj->x, hitspot->y - mobj->y);
 
 		horizontal = dist / airtime;
-		vertical = (gravity*airtime)/2;
+		vertical = (world->gravity*airtime)/2;
 
 		mobj->momx = FixedMul(horizontal, FINECOSINE(an));
 		mobj->momy = FixedMul(horizontal, FINESINE(an));
@@ -5482,7 +5490,7 @@ static void P_Boss9Thinker(mobj_t *mobj)
 	{
 		P_InstaThrust(mobj, mobj->angle, -4*FRACUNIT);
 		P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true);
-		mobj->momz -= gravity;
+		mobj->momz -= world->gravity;
 		if (mobj->z < mobj->watertop || mobj->z < (mobj->floorz + 16*FRACUNIT))
 		{
 			mobj->watertop = mobj->floorz + 32*FRACUNIT;
@@ -6758,19 +6766,16 @@ static boolean P_ShieldLook(mobj_t *thing, shieldtype_t shield)
 	return true;
 }
 
-void P_RunShields(void)
+void P_RunShields(world_t *w)
 {
-	INT32 i;
-
-	// run shields
-	for (i = 0; i < world->numshields; i++)
+	for (INT32 i = 0; i < w->numshields; i++)
 	{
-		if (!P_MobjWasRemoved(world->shields[i]))
-			P_ShieldLook(world->shields[i], world->shields[i]->threshold);
-		P_SetTarget(&world->shields[i], NULL);
+		if (!P_MobjWasRemoved(w->shields[i]))
+			P_ShieldLook(w->shields[i], w->shields[i]->threshold);
+		P_SetTarget(&w->shields[i], NULL);
 	}
 
-	world->numshields = 0;
+	w->numshields = 0;
 }
 
 static boolean P_AddShield(mobj_t *thing)
@@ -6807,13 +6812,13 @@ static boolean P_AddShield(mobj_t *thing)
 	return true;
 }
 
-void P_RunOverlays(void)
+void P_RunOverlays(world_t *w)
 {
 	// run overlays
 	mobj_t *mo, *next = NULL;
 	fixed_t destx,desty,zoffs;
 
-	for (mo = world->overlaycap; mo; mo = next)
+	for (mo = w->overlaycap; mo; mo = next)
 	{
 		I_Assert(!P_MobjWasRemoved(mo));
 
@@ -6878,7 +6883,7 @@ void P_RunOverlays(void)
 			P_SetThingPosition(mo);
 		P_CheckPosition(mo, mo->x, mo->y);
 	}
-	P_SetTarget(&world->overlaycap, NULL);
+	P_SetTarget(&w->overlaycap, NULL);
 }
 
 // Called only when MT_OVERLAY thinks.
@@ -11456,7 +11461,7 @@ mobjtype_t P_GetMobjtype(UINT16 mthingtype)
 //
 // P_RespawnSpecials
 //
-void P_RespawnSpecials(void)
+void P_RespawnSpecials(world_t *w)
 {
 	mapthing_t *mthing = NULL;
 
@@ -11471,15 +11476,15 @@ void P_RespawnSpecials(void)
 		return;
 
 	// nothing left to respawn?
-	if (world->iquehead == world->iquetail)
+	if (w->iquehead == w->iquetail)
 		return;
 
 	// the first item in the queue is the first to respawn
 	// wait at least 30 seconds
-	if (leveltime - world->itemrespawntime[world->iquetail] < (tic_t)cv_itemrespawntime.value*TICRATE)
+	if (leveltime - w->itemrespawntime[w->iquetail] < (tic_t)cv_itemrespawntime.value*TICRATE)
 		return;
 
-	mthing = world->itemrespawnque[world->iquetail];
+	mthing = w->itemrespawnque[w->iquetail];
 
 #ifdef PARANOIA
 	if (!mthing)
@@ -11490,7 +11495,7 @@ void P_RespawnSpecials(void)
 		P_SpawnMapThing(mthing);
 
 	// pull it from the que
-	world->iquetail = (world->iquetail+1)&(ITEMQUESIZE-1);
+	w->iquetail = (w->iquetail+1)&(ITEMQUESIZE-1);
 }
 
 //
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 565783cff0..11185d309b 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -4164,6 +4164,9 @@ static inline void P_NetArchiveSpecials(void)
 	// Sky number
 	WRITEINT32(save_p, archiveworld->skynum);
 
+	// Gravity
+	WRITEFIXED(save_p, archiveworld->gravity);
+
 	// Current global weather type
 	WRITEUINT8(save_p, archiveworld->weather);
 
@@ -4196,6 +4199,7 @@ static void P_NetUnArchiveSpecials(void)
 	P_SetupLevelSky(j, false); // Don't call P_SetupWorldSky from there
 	P_SetupWorldSky(j, unarchiveworld);
 
+	unarchiveworld->gravity = READFIXED(save_p);
 	unarchiveworld->weather = READUINT8(save_p);
 
 	if (world->weather)
@@ -4250,7 +4254,8 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
 	if(!mapheaderinfo[gamemap-1])
 		P_AllocMapHeader(gamemap-1);
 
-	//lastmapsaved = gamemap;
+	curmapheader = nextmapheader = mapheaderinfo[gamemap-1];
+	worldmapheader = curmapheader;
 	lastmaploaded = gamemap;
 
 	tokenlist = 0;
@@ -4337,8 +4342,6 @@ static void P_NetArchiveMisc(boolean resending)
 	WRITEUINT32(save_p, countdown);
 	WRITEUINT32(save_p, countdown2);
 
-	WRITEFIXED(save_p, baseworld->gravity);
-
 	WRITEUINT32(save_p, countdowntimer);
 	WRITEUINT8(save_p, countdowntimeup);
 
@@ -4438,8 +4441,6 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
 	countdown = READUINT32(save_p);
 	countdown2 = READUINT32(save_p);
 
-	gravity = READFIXED(save_p);
-
 	countdowntimer = (tic_t)READUINT32(save_p);
 	countdowntimeup = (boolean)READUINT8(save_p);
 
@@ -4798,7 +4799,7 @@ void P_SaveNetGame(boolean resending)
 
 		for (w = 0; w < numworlds; w++)
 		{
-			if (player->world == worldlist[w])
+			if (P_GetPlayerWorld(player) == worldlist[w])
 			{
 				player->worldnum = w;
 				break;
@@ -4931,6 +4932,10 @@ static void P_NetUnArchiveWorlds(boolean reloading)
 		UnArchiveWorld();
 	}
 
+	gamemap = worldlist[0]->gamemap;
+	curmapheader = nextmapheader = mapheaderinfo[gamemap-1];
+	worldmapheader = curmapheader;
+
 	P_NetUnArchiveColormaps();
 	RelinkWorldsToEntities();
 
diff --git a/src/p_setup.c b/src/p_setup.c
index ae18386f9d..6dbb53c73d 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -6881,9 +6881,7 @@ static boolean P_LoadMapFromFile(void)
 
 static void P_InitLevelSky(INT32 skynum, player_t *player)
 {
-	if (player == &players[consoleplayer])
-		P_SetupSkyTexture(skynum);
-
+	P_SetupSkyTexture(skynum);
 	P_SetupWorldSky(skynum, world);
 	levelskynum = skynum;
 }
@@ -6940,7 +6938,7 @@ static void P_InitPlayerSettings(INT32 i, boolean canresetlives)
 	players[i].pflags &= ~(PF_GAMETYPEOVER);
 }
 
-static void P_InitWorldSettings(void)
+static void P_InitWorldSettings(mapheader_t *mapheader)
 {
 	leveltime = 0;
 
@@ -6953,17 +6951,14 @@ static void P_InitWorldSettings(void)
 	if ((netgame || multiplayer) && !G_IsSpecialStage(gamemap))
 		nummaprings = -1;
 	else
-		nummaprings = mapheaderinfo[gamemap-1]->startrings;
-
-	// emerald hunt
-	hunt1 = hunt2 = hunt3 = NULL;
+		nummaprings = mapheader->startrings;
 
 	// map time limit
-	if (mapheaderinfo[gamemap-1]->countdown)
+	if (mapheader->countdown)
 	{
 		INT32 i;
 		tic_t maxtime = 0;
-		countdowntimer = mapheaderinfo[gamemap-1]->countdown * TICRATE;
+		countdowntimer = mapheader->countdown * TICRATE;
 		for (i = 0; i < MAXPLAYERS; i++)
 		{
 			if (!playeringame[i])
@@ -6991,20 +6986,17 @@ static void P_InitWorldSettings(void)
 	stagefailed = G_IsSpecialStage(gamemap);
 }
 
-static void P_InitLevelSettings(player_t *player, boolean addworld, boolean fromnetsave)
+static void P_InitLevelSettings(mapheader_t *mapheader, player_t *player, boolean addworld, boolean fromnetsave)
 {
 	INT32 i;
 	boolean canresetlives = true;
 
 	if (!addworld)
-		P_InitWorldSettings();
+		P_InitWorldSettings(mapheader);
 
 	// Reset temporary record data
 	memset(&ntemprecords, 0, sizeof(nightsdata_t));
 
-	// earthquake camera
-	memset(&quake,0,sizeof(struct quake));
-
 	if ((netgame || multiplayer) && G_GametypeUsesCoopStarposts() && cv_coopstarposts.value == 2)
 	{
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -7067,7 +7059,7 @@ void P_RespawnThings(void)
 		P_RemoveMobj((mobj_t *)think);
 	}
 
-	P_InitLevelSettings(NULL, false, false);
+	P_InitLevelSettings(worldmapheader, NULL, false, false);
 
 	localaiming = 0;
 	localaiming2 = 0;
@@ -7081,7 +7073,7 @@ void P_RespawnThings(void)
 
 static void P_RunLevelScript(const char *scriptname)
 {
-	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_SCRIPTISFILE))
+	if (!(worldmapheader->levelflags & LF_SCRIPTISFILE))
 	{
 		lumpnum_t lumpnum;
 		char newname[9];
@@ -7414,9 +7406,9 @@ static void P_RunSpecialStageWipe(void)
 	S_StartSound(NULL, sfx_s3kaf);
 
 	// Fade music! Time it to S3KAF: 0.25 seconds is snappy.
-	if (RESETMUSIC ||
+	if (S_ShouldResetMusic(nextmapheader) ||
 		strnicmp(S_MusicName(),
-		(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap - 1]->musname : mapmusname, 7))
+		(mapmusflags & MUSIC_RELOADRESET) ? nextmapheader->musname : mapmusname, 7))
 		S_FadeOutStopMusic(MUSICRATE/4); //FixedMul(FixedDiv(F_GetWipeLength(wipedefs[wipe_speclevel_towhite])*NEWTICRATERATIO, NEWTICRATE), MUSICRATE)
 
 	F_WipeStartScreen();
@@ -7584,7 +7576,7 @@ static void P_InitGametype(player_t *player, boolean addworld)
 		CV_StealthSetValue(&cv_numlaps,
 		(cv_basenumlaps.value)
 			? cv_basenumlaps.value
-			: mapheaderinfo[gamemap - 1]->numlaps);
+			: nextmapheader->numlaps);
 }
 
 /** Loads a level from a lump or external wad.
@@ -7603,7 +7595,7 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boo
 	levelloading = true;
 
 	// This is needed. Don't touch.
-	maptol = mapheaderinfo[gamemap-1]->typeoflevel;
+	maptol = nextmapheader->typeoflevel;
 	gametyperules = gametypedefaultrules[gametype];
 
 	CON_Drawer(); // let the user know what we are going to do
@@ -7625,18 +7617,18 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boo
 	// Clear CECHO messages
 	HU_ClearCEcho();
 
-	if (mapheaderinfo[gamemap-1]->runsoc[0] != '#')
-		P_RunSOC(mapheaderinfo[gamemap-1]->runsoc);
+	if (nextmapheader->runsoc[0] != '#')
+		P_RunSOC(nextmapheader->runsoc);
 
-	if (cv_runscripts.value && mapheaderinfo[gamemap-1]->scriptname[0] != '#')
-		P_RunLevelScript(mapheaderinfo[gamemap-1]->scriptname);
+	if (cv_runscripts.value && nextmapheader->scriptname[0] != '#')
+		P_RunLevelScript(nextmapheader->scriptname);
 
-	P_InitLevelSettings(player, addworld, fromnetsave);
+	P_InitLevelSettings(nextmapheader, player, addworld, fromnetsave);
 
 	postimgtype = postimgtype2 = postimg_none;
 
-	if (mapheaderinfo[gamemap-1]->forcecharacter[0] != '\0')
-		P_ForceCharacter(mapheaderinfo[gamemap-1]->forcecharacter);
+	if (nextmapheader->forcecharacter[0] != '\0')
+		P_ForceCharacter(nextmapheader->forcecharacter);
 
 	if (!dedicated)
 	{
@@ -7694,9 +7686,9 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boo
 
 		// 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 (!(reloadinggamestate || titlemapinaction) && (RESETMUSIC ||
+		if (!(reloadinggamestate || titlemapinaction) && (S_ShouldResetMusic(nextmapheader) ||
 			strnicmp(S_MusicName(),
-				(mapmusflags & MUSIC_RELOADRESET) ? mapheaderinfo[gamemap-1]->musname : mapmusname, 7)))
+				(mapmusflags & MUSIC_RELOADRESET) ? nextmapheader->musname : mapmusname, 7)))
 		{
 			S_FadeMusic(0, FixedMul(
 				FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
@@ -7722,9 +7714,9 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boo
 				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) : "");
+					nextmapheader->lvlttl,
+					(nextmapheader->levelflags & LF_NOZONE) ? "" : " Zone",
+					(nextmapheader->actnum > 0) ? va(" %d",nextmapheader->actnum) : "");
 				V_DrawSmallString(1, 195, V_ALLOWLOWERCASE|V_TRANSLUCENT|V_SNAPTOLEFT|V_SNAPTOBOTTOM, tx);
 				I_UpdateNoVsync();
 			}
@@ -7732,7 +7724,7 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boo
 			// 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();
+			S_Start(nextmapheader);
 
 			levelfadecol = (ranspecialwipe) ? 0 : 31;
 
@@ -7740,10 +7732,17 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boo
 			F_EndTextPrompt(false, true);
 		}
 	}
+	else
+	{
+		S_SetMapMusic(nextmapheader);
+		S_StopMusic();
+		S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
+	}
 
 	if (player && (!titlemapinaction))
 		P_UnloadWorldPlayer(player);
 
+	// Initialize the world
 	world = P_InitNewWorld();
 	thlist = world->thlist;
 
@@ -7796,7 +7795,7 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boo
 	if (lastloadedmaplumpnum == LUMPERROR)
 		I_Error("Map %s not found.\n", maplumpname);
 
-	R_ReInitColormaps(mapheaderinfo[gamemap-1]->palette);
+	R_ReInitColormaps(worldmapheader->palette);
 	if (!addworld)
 	{
 		// Init Boom colormaps.
@@ -7805,7 +7804,7 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boo
 	CON_SetupBackColormap();
 
 	// SRB2 determines the sky texture to be used depending on the map header.
-	P_InitLevelSky(mapheaderinfo[gamemap-1]->skynum, player);
+	P_InitLevelSky(worldmapheader->skynum, player);
 
 	P_ResetSpawnpoints();
 
@@ -7821,6 +7820,10 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boo
 	// init anything that P_SpawnSlopes/P_LoadThings needs to know
 	P_InitSpecials();
 
+	// Defaults in case levels don't have them set.
+	sstimer = worldmapheader->sstimer*TICRATE + 6;
+	ssspheres = worldmapheader->ssspheres;
+
 	P_SpawnSlopes(fromnetsave);
 
 	P_SpawnMapThings(!fromnetsave);
@@ -7916,12 +7919,12 @@ boolean P_LoadLevel(player_t *player, boolean addworld, boolean fromnetsave, boo
 		P_MapEnd(); // just in case MapLoad modifies tmthing
 	}
 
-	// No render mode or reloading gamestate, stop here.
-	if (rendermode == render_none || reloadinggamestate)
+	// Done here
+	if (addworld)
 		return true;
 
-	//if (!runforself || (addworld && splitscreen))
-	if (addworld)
+	// No render mode or reloading gamestate, stop here.
+	if (rendermode == render_none || reloadinggamestate)
 		return true;
 
 	R_ResetViewInterpolation(0);
diff --git a/src/p_sight.c b/src/p_sight.c
index ec2e23561a..bbca93b041 100644
--- a/src/p_sight.c
+++ b/src/p_sight.c
@@ -396,23 +396,18 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2)
 	s1 = t1->subsector->sector;
 	s2 = t2->subsector->sector;
 
-	// Check in REJECT table.
-	if (s1->world == s2->world
-	&& t1->world == t2->world
-	&& t1->world == s1->world
-	&& t2->world == s2->world)
+	world_t *w = P_GetMobjWorld(t1);
+
+	// Cannot possibly be seen if the worlds don't match
+	if (w != P_GetMobjWorld(t2))
+		return false;
+	else if (w->rejectmatrix != NULL)
 	{
-		world_t *w = s1->world;
+		// Check in REJECT table.
 		size_t pnum = (s1-w->sectors)*w->numsectors + (s2-w->sectors);
-
-		if (w->rejectmatrix != NULL)
-		{
-			if (w->rejectmatrix[pnum>>3] & (1 << (pnum&7))) // can't possibly be connected
-				return false;
-		}
+		if (w->rejectmatrix[pnum>>3] & (1 << (pnum&7))) // can't possibly be connected
+			return false;
 	}
-	else
-		return false;
 
 	// killough 11/98: shortcut for melee situations
 	// same subsector? obviously visible
diff --git a/src/p_spec.c b/src/p_spec.c
index d9cc23d99f..4d44be1acc 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -1391,11 +1391,11 @@ static boolean P_CheckNightsTriggerLine(line_t *triggerline, mobj_t *actor)
 	if (specialtype == 323)
 	{
 		// run only when no mares are found
-		if (donomares && P_FindLowestMare() != UINT8_MAX)
+		if (donomares && P_FindLowestMare(world) != UINT8_MAX)
 			return false;
 
 		// run only if there is a mare present
-		if (!donomares && P_FindLowestMare() == UINT8_MAX)
+		if (!donomares && P_FindLowestMare(world) == UINT8_MAX)
 			return false;
 
 		// run only if player is nightserizing from non-nights
@@ -1505,7 +1505,7 @@ static boolean P_CheckPlayerMareOld(line_t *triggerline)
 	if (!(maptol & TOL_NIGHTS))
 		return false;
 
-	mare = P_FindLowestMare();
+	mare = P_FindLowestMare(world);
 
 	if (triggerline->flags & ML_NOCLIMB)
 		return mare <= targetmare;
@@ -1524,7 +1524,7 @@ static boolean P_CheckPlayerMare(line_t *triggerline)
 	if (!(maptol & TOL_NIGHTS))
 		return false;
 
-	mare = P_FindLowestMare();
+	mare = P_FindLowestMare(world);
 
 	switch (triggerline->args[2])
 	{
@@ -2009,10 +2009,10 @@ static void P_PlaySFX(INT32 sfxnum, mobj_t *mo, sector_t *callsec, INT16 tag, te
 					if (!Tag_Find(&rover->master->frontsector->tags, tag))
 						continue;
 
-					if (camobj->z > P_GetSpecialTopZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
+					if (camobj->z > P_GetSpecialTopZ(camobj, P_GetMobjWorld(camobj)->sectors + rover->secnum, camobj->subsector->sector))
 						continue;
 
-					if (camobj->z + camobj->height < P_GetSpecialBottomZ(camobj, sectors + rover->secnum, camobj->subsector->sector))
+					if (camobj->z + camobj->height < P_GetSpecialBottomZ(camobj, P_GetMobjWorld(camobj)->sectors + rover->secnum, camobj->subsector->sector))
 						continue;
 
 					foundit = true;
@@ -3000,17 +3000,17 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 
 		case 444: // Earthquake camera
 		{
-			quake.intensity = line->args[1] << FRACBITS;
-			quake.radius = line->args[2] << FRACBITS;
-			quake.time = line->args[0];
+			world->quake.intensity = line->args[1] << FRACBITS;
+			world->quake.radius = line->args[2] << FRACBITS;
+			world->quake.time = line->args[0];
 
-			quake.epicenter = NULL; /// \todo
+			world->quake.epicenter = NULL; /// \todo
 
 			// reasonable defaults.
-			if (!quake.intensity)
-				quake.intensity = 8<<FRACBITS;
-			if (!quake.radius)
-				quake.radius = 512<<FRACBITS;
+			if (!world->quake.intensity)
+				world->quake.intensity = 8<<FRACBITS;
+			if (!world->quake.radius)
+				world->quake.radius = 512<<FRACBITS;
 			break;
 		}
 
@@ -3968,8 +3968,8 @@ boolean P_IsFlagAtBase(mobjtype_t flag)
 				if (!(rover->master->frontsector->specialflags & specialflag))
 					continue;
 
-				if (!(mo->z <= P_GetSpecialTopZ(mo, sectors + rover->secnum, mo->subsector->sector)
-					&& mo->z >= P_GetSpecialBottomZ(mo, sectors + rover->secnum, mo->subsector->sector)))
+				if (!(mo->z <= P_GetSpecialTopZ(mo, P_GetMobjWorld(mo)->sectors + rover->secnum, mo->subsector->sector)
+					&& mo->z >= P_GetSpecialBottomZ(mo, P_GetMobjWorld(mo)->sectors + rover->secnum, mo->subsector->sector)))
 					continue;
 
 				return true;
@@ -3993,8 +3993,8 @@ boolean P_IsMobjTouchingSectorPlane(mobj_t *mo, sector_t *sec)
 
 boolean P_IsMobjTouching3DFloor(mobj_t *mo, ffloor_t *ffloor, sector_t *sec)
 {
-	fixed_t topheight = P_GetSpecialTopZ(mo, sectors + ffloor->secnum, sec);
-	fixed_t bottomheight = P_GetSpecialBottomZ(mo, sectors + ffloor->secnum, sec);
+	fixed_t topheight = P_GetSpecialTopZ(mo, P_GetMobjWorld(mo)->sectors + ffloor->secnum, sec);
+	fixed_t bottomheight = P_GetSpecialBottomZ(mo, P_GetMobjWorld(mo)->sectors + ffloor->secnum, sec);
 
 	if (((ffloor->fofflags & FOF_BLOCKPLAYER) && mo->player)
 		|| ((ffloor->fofflags & FOF_BLOCKOTHERS) && !mo->player))
@@ -5385,7 +5385,7 @@ void P_CheckMobjTrigger(mobj_t *mobj, boolean pushable)
   *
   * \sa P_CheckTimeLimit, P_CheckPointLimit
   */
-void P_UpdateSpecials(void)
+void P_UpdateSpecials(world_t *w)
 {
 	anim_t *anim;
 	INT32 i;
@@ -5415,8 +5415,8 @@ void P_UpdateSpecials(void)
 	/// \todo do not check the non-animate flat.. link the animated ones?
 	/// \note its faster than the original anywaysince it animates only
 	///    flats used in the level, and there's usually very few of them
-	foundflats = world->flats;
-	for (j = 0; j < world->numflats; j++, foundflats++)
+	foundflats = w->flats;
+	for (j = 0; j < w->numflats; j++, foundflats++)
 	{
 		if (foundflats->speed) // it is an animated flat
 		{
@@ -6085,32 +6085,33 @@ static void P_RunLevelLoadExecutors(void)
 void P_InitSpecials(void)
 {
 	// Set the default gravity. Custom gravity overrides this setting.
-	world->gravity = gravity = mapheaderinfo[gamemap-1]->gravity;
+	world->gravity = worldmapheader->gravity;
+
+	// Set globalweather
+	world->weather = worldmapheader->weather;
 
-	// Defaults in case levels don't have them set.
-	sstimer = mapheaderinfo[gamemap-1]->sstimer*TICRATE + 6;
-	ssspheres = mapheaderinfo[gamemap-1]->ssspheres;
+	P_InitLocalSpecials();
+}
 
+void P_InitLocalSpecials(void)
+{
 	if (numworlds < 2)
 		CheckForBustableBlocks = CheckForBouncySector = CheckForQuicksand = CheckForMarioBlocks = CheckForFloatBob = CheckForReverseGravity = false;
 
 	// Set curWeather
-	switch (mapheaderinfo[gamemap-1]->weather)
+	switch (worldmapheader->weather)
 	{
 		case PRECIP_SNOW: // snow
 		case PRECIP_RAIN: // rain
 		case PRECIP_STORM: // storm
 		case PRECIP_STORM_NORAIN: // storm w/o rain
 		case PRECIP_STORM_NOSTRIKES: // storm w/o lightning
-			curWeather = mapheaderinfo[gamemap-1]->weather;
+			curWeather = worldmapheader->weather;
 			break;
 		default: // blank/none
 			curWeather = PRECIP_NONE;
 			break;
 	}
-
-	// Set globalweather
-	world->weather = mapheaderinfo[gamemap-1]->weather;
 }
 
 void P_ApplyFlatAlignment(sector_t *sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs, boolean floor, boolean ceiling)
@@ -6247,7 +6248,7 @@ void P_SpawnSpecials(boolean fromnetsave)
 				break;
 
 			case 11: // Custom global gravity!
-				world->gravity = gravity = sector->floorheight/1000;
+				world->gravity = sector->floorheight/1000;
 				break;
 		}
 	}
diff --git a/src/p_spec.h b/src/p_spec.h
index b9e52b07be..baa4eeec4f 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -485,12 +485,13 @@ void P_SetupLevelFlatAnims(void);
 
 // at map load
 void P_InitSpecials(void);
+void P_InitLocalSpecials(void);
 void P_ApplyFlatAlignment(sector_t* sector, angle_t flatangle, fixed_t xoffs, fixed_t yoffs, boolean floor, boolean ceiling);
 fixed_t P_GetSectorGravityFactor(sector_t *sec);
 void P_SpawnSpecials(boolean fromnetsave);
 
 // every tic
-void P_UpdateSpecials(void);
+void P_UpdateSpecials(world_t *w);
 sector_t *P_MobjTouchingSectorSpecial(mobj_t *mo, INT32 section, INT32 number);
 sector_t *P_ThingOnSpecial3DFloor(mobj_t *mo);
 sector_t *P_MobjTouchingSectorSpecialFlag(mobj_t *mo, sectorspecialflags_t flag);
diff --git a/src/p_tick.c b/src/p_tick.c
index ef205e2235..ae52eb9d1f 100644
--- a/src/p_tick.c
+++ b/src/p_tick.c
@@ -361,18 +361,9 @@ static inline void P_RunThinkers(void)
 
 static inline void P_RunWorldThinkers(void)
 {
-	INT32 i;
-
 	PS_START_TIMING(ps_thinkertime);
 
-	if (!(netgame || multiplayer) || (numworlds < 2))
-	{
-		P_RunThinkers();
-		PS_STOP_TIMING(ps_thinkertime);
-		return;
-	}
-
-	for (i = 0; i < numworlds; i++)
+	for (INT32 i = 0; i < numworlds; i++)
 	{
 		world_t *w = worldlist[i];
 
@@ -519,7 +510,7 @@ static inline void P_DoSpecialStageStuff(void)
 		players[i].powers[pw_underwater] = players[i].powers[pw_spacetime] = 0;
 	}
 
-	//if (sstimer < 15*TICRATE+6 && sstimer > 7 && (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC))
+	//if (sstimer < 15*TICRATE+6 && sstimer > 7 && (worldmapheader->levelflags & LF_SPEEDMUSIC))
 		//S_SpeedMusic(1.4f);
 
 	if (sstimer && !objectplacing)
@@ -637,65 +628,42 @@ static inline void P_DoCTFStuff(void)
 
 static inline void P_RunWorldSpecials(void)
 {
-	INT32 i;
-
-	if (!(netgame || multiplayer) || (numworlds < 2))
-	{
-		P_RunShields();
-		P_RunOverlays();
-		P_UpdateSpecials();
-		P_RespawnSpecials();
-		return;
-	}
-
-	for (i = 0; i < numworlds; i++)
+	for (INT32 i = 0; i < numworlds; i++)
 	{
 		world_t *w = worldlist[i];
 
 		if (!w->players)
 			continue;
 
+		// Just in case.
 		P_SetWorld(w);
 
-		P_RunShields();
-		P_RunOverlays();
-		P_UpdateSpecials();
-		P_RespawnSpecials();
+		P_RunShields(w);
+		P_RunOverlays(w);
+		P_UpdateSpecials(w);
+		P_RespawnSpecials(w);
+		P_PrecipitationEffects(w); // Lightning, rain sounds, etc.
 	}
 }
 
-static inline void P_WorldPrecipitationEffects(void)
+static void RunLuaHook(int hook)
 {
-	INT32 i;
-
-	if (!(netgame || multiplayer) || (numworlds < 2))
-	{
-		P_PrecipitationEffects(baseworld);
-		return;
-	}
-
-	for (i = 0; i < numworlds; i++)
+	for (INT32 i = 0; i < numworlds; i++)
 	{
 		world_t *w = worldlist[i];
 
 		if (!w->players)
 			continue;
 
-		P_PrecipitationEffects(w);
-	}
-}
-
-static inline void RunLuaHookForWorld(int hook)
-{
-	INT32 i;
+		P_SetWorld(w);
 
-	if (!(netgame || multiplayer) || (numworlds < 2))
-	{
 		LUA_HookVoid(hook);
-		return;
 	}
+}
 
-	for (i = 0; i < numworlds; i++)
+static inline void RunLuaThinkFrame(void)
+{
+	for (INT32 i = 0; i < numworlds; i++)
 	{
 		world_t *w = worldlist[i];
 
@@ -704,31 +672,23 @@ static inline void RunLuaHookForWorld(int hook)
 
 		P_SetWorld(w);
 
-		LUA_HookVoid(hook);
+		LUA_HookThinkFrame();
 	}
 }
 
-static inline void RunLuaThinkFrameForWorld(void)
+static void P_WorldPostUpdate(world_t *w)
 {
-	INT32 i;
-
-	if (!(netgame || multiplayer) || (numworlds < 2))
+	if (w->quake.time)
 	{
-		LUA_HookThinkFrame();
-		return;
-	}
-
-	for (i = 0; i < numworlds; i++)
-	{
-		world_t *w = worldlist[i];
-
-		if (!w->players)
-			continue;
-
-		P_SetWorld(w);
-
-		LUA_HookThinkFrame();
+		fixed_t ir = w->quake.intensity>>1;
+		/// \todo Calculate distance from epicenter if set and modulate the intensity accordingly based on radius.
+		w->quake.x = M_RandomRange(-ir,ir);
+		w->quake.y = M_RandomRange(-ir,ir);
+		w->quake.z = M_RandomRange(-ir,ir);
+		--w->quake.time;
 	}
+	else
+		w->quake.x = w->quake.y = w->quake.z = 0;
 }
 
 //
@@ -738,6 +698,8 @@ void P_Ticker(boolean run)
 {
 	INT32 i;
 
+	P_SetWorld(localworld);
+
 	// Increment jointime and quittime even if paused
 	for (i = 0; i < MAXPLAYERS; i++)
 		if (playeringame[i])
@@ -815,7 +777,7 @@ void P_Ticker(boolean run)
 				player_t *player = &players[i];
 
 				if (numworlds > 1 && !titlemapinaction && !player->bot)
-					P_SetWorld(player->world);
+					P_SetWorld(P_GetPlayerWorld(player));
 
 				P_PlayerThink(player);
 			}
@@ -846,22 +808,19 @@ void P_Ticker(boolean run)
 				player_t *player = &players[i];
 
 				if (numworlds > 1 && !titlemapinaction && !player->bot)
-					P_SetWorld(player->world);
+					P_SetWorld(P_GetPlayerWorld(player));
 
 				P_PlayerAfterThink(player);
 			}
 
 		PS_START_TIMING(ps_lua_thinkframe_time);
-		RunLuaThinkFrameForWorld();
+		RunLuaThinkFrame();
 		PS_STOP_TIMING(ps_lua_thinkframe_time);
 	}
 
 	// Run shield positioning
 	P_RunWorldSpecials();
 
-	// Lightning, rain sounds, etc.
-	P_WorldPrecipitationEffects();
-
 	if (run)
 		leveltime++;
 	timeinmap++;
@@ -898,17 +857,16 @@ void P_Ticker(boolean run)
 		if (countdown2)
 			countdown2--;
 
-		if (quake.time)
+		for (i = 0; i < numworlds; i++)
 		{
-			fixed_t ir = quake.intensity>>1;
-			/// \todo Calculate distance from epicenter if set and modulate the intensity accordingly based on radius.
-			quake.x = M_RandomRange(-ir,ir);
-			quake.y = M_RandomRange(-ir,ir);
-			quake.z = M_RandomRange(-ir,ir);
-			--quake.time;
+			world_t *w = worldlist[i];
+
+			if (!w->players)
+				continue;
+
+			P_SetWorld(w);
+			P_WorldPostUpdate(w);
 		}
-		else
-			quake.x = quake.y = quake.z = 0;
 
 		if (metalplayback)
 			G_ReadMetalTic(metalplayback);
@@ -921,7 +879,7 @@ void P_Ticker(boolean run)
 		if (modeattacking)
 			G_GhostTicker();
 
-		RunLuaHookForWorld(HOOK(PostThinkFrame));
+		RunLuaHook(HOOK(PostThinkFrame));
 	}
 
 	if (run)
@@ -962,6 +920,8 @@ void P_Ticker(boolean run)
 	}
 
 	P_MapEnd();
+
+	P_SetWorld(localworld);
 }
 
 // Abbreviated ticker for pre-loading, calls thinkers and assorted things
@@ -981,7 +941,7 @@ void P_PreTicker(INT32 frames)
 
 		R_UpdateAllMobjInterpolators();
 
-		RunLuaHookForWorld(HOOK(PreThinkFrame));
+		RunLuaHook(HOOK(PreThinkFrame));
 
 		for (i = 0; i < MAXPLAYERS; i++)
 			if (playeringame[i] && players[i].mo && !P_MobjWasRemoved(players[i].mo))
@@ -999,7 +959,7 @@ void P_PreTicker(INT32 frames)
 				players[i].cmd.angleturn = players[i].angleturn;
 
 				if (numworlds > 1 && !titlemapinaction && !player->bot)
-					P_SetWorld(player->world);
+					P_SetWorld(P_GetPlayerWorld(player));
 				P_PlayerThink(player);
 
 				memcpy(&players[i].cmd, &temptic, sizeof(ticcmd_t));
@@ -1014,17 +974,17 @@ void P_PreTicker(INT32 frames)
 				player_t *player = &players[i];
 
 				if (numworlds > 1 && !titlemapinaction && !player->bot)
-					P_SetWorld(player->world);
+					P_SetWorld(P_GetPlayerWorld(player));
 
 				P_PlayerAfterThink(&players[i]);
 			}
 
-		RunLuaThinkFrameForWorld();
+		RunLuaThinkFrame();
 
 		// Run shield positioning
 		P_RunWorldSpecials();
 
-		RunLuaHookForWorld(HOOK(PostThinkFrame));
+		RunLuaHook(HOOK(PostThinkFrame));
 
 		R_UpdateLevelInterpolators();
 		R_UpdateViewInterpolation();
diff --git a/src/p_user.c b/src/p_user.c
index fc5fd088fe..b3610a2f49 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -419,7 +419,7 @@ void P_ResetScore(player_t *player)
 //
 // Returns the lowest open mare available
 //
-UINT8 P_FindLowestMare(void)
+UINT8 P_FindLowestMare(world_t *w)
 {
 	thinker_t *th;
 	mobj_t *mo2;
@@ -430,7 +430,7 @@ UINT8 P_FindLowestMare(void)
 
 	// scan the thinkers
 	// to find the egg capsule with the lowest mare
-	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;
@@ -469,7 +469,7 @@ boolean P_TransferToNextMare(player_t *player)
 	mobj_t *mo2;
 	mobj_t *closestaxis = NULL;
 	INT32 lowestaxisnum = -1;
-	UINT8 mare = P_FindLowestMare();
+	UINT8 mare = P_FindLowestMare(P_GetPlayerWorld(player));
 	fixed_t dist1, dist2 = 0;
 
 	if (mare == 255)
@@ -726,7 +726,7 @@ static void P_DeNightserizePlayer(player_t *player)
 			P_DamageMobj(player->mo, NULL, NULL, 1, DMG_INSTAKILL);
 
 			// Reset music to beginning if MIXNIGHTSCOUNTDOWN
-			if ((mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+			if ((worldmapheader->levelflags & LF_MIXNIGHTSCOUNTDOWN)
 #ifdef _WIN32
 				&& S_MusicType() != MU_MID
 #endif
@@ -742,7 +742,7 @@ static void P_DeNightserizePlayer(player_t *player)
 	player->oldscale = 0;
 
 	// Restore from drowning music
-	if ((mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+	if ((worldmapheader->levelflags & LF_MIXNIGHTSCOUNTDOWN)
 #ifdef _WIN32
 		&& S_MusicType() != MU_MID
 #endif
@@ -755,7 +755,7 @@ static void P_DeNightserizePlayer(player_t *player)
 		// Reset the music if you did not destroy all the capsules, because you failed.
 		// Why make the all-capsules exception: because it's your reward for nearly finishing the level!
 		// (unless the player auto-loses upon denightserizing; for death case, see above.)
-		if (P_FindLowestMare() != UINT8_MAX || G_IsSpecialStage(gamemap))
+		if (P_FindLowestMare(P_GetPlayerWorld(player)) != UINT8_MAX || G_IsSpecialStage(gamemap))
 			S_SetMusicPosition(0);
 	}
 	else
@@ -813,7 +813,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
 	player->bonustime = false;
 
 	// Restore from drowning music
-	if (mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+	if (worldmapheader->levelflags & LF_MIXNIGHTSCOUNTDOWN)
 	{
 		S_StopSoundByNum(sfx_timeup);
 		S_StopFadingMusic();
@@ -1331,7 +1331,7 @@ void P_GiveCoopLives(player_t *player, INT32 numlives, boolean sound)
 void P_DoSuperTransformation(player_t *player, boolean giverings)
 {
 	player->powers[pw_super] = 1;
-	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC) && P_IsLocalPlayer(player))
+	if (!(worldmapheader->levelflags & LF_NOSSMUSIC) && P_IsLocalPlayer(player))
 		P_PlayJingle(player, JT_SUPER);
 
 	S_StartSound(NULL, sfx_supert); //let all players hear it -mattw_cfi
@@ -1345,7 +1345,7 @@ void P_DoSuperTransformation(player_t *player, boolean giverings)
 		player->rings = 50;
 
 	// Just in case.
-	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC))
+	if (!(worldmapheader->levelflags & LF_NOSSMUSIC))
 	{
 		player->powers[pw_extralife] = 0;
 		player->powers[pw_invulnerability] = 0;
@@ -1371,7 +1371,7 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
 		player = player->botleader;
 
 	// NiGHTS does it different!
-	if (gamestate == GS_LEVEL && mapheaderinfo[gamemap-1]->typeoflevel & TOL_NIGHTS)
+	if (gamestate == GS_LEVEL && worldmapheader->typeoflevel & TOL_NIGHTS)
 	{
 		if ((netgame || multiplayer) && G_IsSpecialStage(gamemap))
 		{ // Pseudo-shared score for multiplayer special stages.
@@ -1581,7 +1581,7 @@ boolean P_EvaluateMusicStatus(UINT16 status, const char *musname)
 				break;
 
 			case JT_SUPER:  // Super Sonic
-				result = (players[i].powers[pw_super] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC));
+				result = (players[i].powers[pw_super] && !(worldmapheader->levelflags & LF_NOSSMUSIC));
 				break;
 
 			case JT_GOVER: // Game Over
@@ -1630,7 +1630,7 @@ void P_RestoreMusic(player_t *player)
 		return;
 
 	// Super
-	else if (player->powers[pw_super] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC)
+	else if (player->powers[pw_super] && !(worldmapheader->levelflags & LF_NOSSMUSIC)
 		&& !S_RecallMusic(JT_SUPER, false))
 		P_PlayJingle(player, JT_SUPER);
 
@@ -1648,7 +1648,7 @@ void P_RestoreMusic(player_t *player)
 	{
 		strlcpy(S_sfx[sfx_None].caption, "Speed shoes", 12);
 		S_StartCaption(sfx_None, -1, player->powers[pw_sneakers]);
-		if (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC)
+		if (worldmapheader->levelflags & LF_SPEEDMUSIC)
 		{
 			S_SpeedMusic(1.4f);
 			if (!S_RecallMusic(JT_MASTER, true))
@@ -3017,7 +3017,7 @@ static void P_CheckInvincibilityTimer(player_t *player)
 			P_SpawnShieldOrb(player);
 		}
 
-		if (!player->powers[pw_super] || (mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC))
+		if (!player->powers[pw_super] || (worldmapheader->levelflags & LF_NOSSMUSIC))
 			P_RestoreMusic(player);
 	}
 }
@@ -5579,7 +5579,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 			{
 				fixed_t potentialmomz;
 				if (player->charability == CA_SLOWFALL)
-					potentialmomz = FixedMul(gravity, -4*player->mo->scale);
+					potentialmomz = FixedMul(world->gravity, -4*player->mo->scale);
 				else
 					potentialmomz = ((player->speed < 10*player->mo->scale)
 					? (player->speed - 10*player->mo->scale)/5
@@ -7098,7 +7098,7 @@ static void P_NiGHTSMovement(player_t *player)
 	}
 	else if (P_IsLocalPlayer(player) && player->nightstime == 10*TICRATE)
 	{
-		if (mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
+		if (worldmapheader->levelflags & LF_MIXNIGHTSCOUNTDOWN)
 		{
 			S_FadeMusic(0, 10*MUSICRATE);
 			S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS.
@@ -9044,10 +9044,10 @@ void P_Earthquake(mobj_t *inflictor, mobj_t *source, fixed_t radius)
 
 	if (inflictor->player && P_IsLocalPlayer(inflictor->player))
 	{
-		quake.epicenter = NULL;
-		quake.intensity = 8*inflictor->scale;
-		quake.time = 8;
-		quake.radius = scaledradius;
+		world->quake.epicenter = NULL;
+		world->quake.intensity = 8*inflictor->scale;
+		world->quake.time = 8;
+		world->quake.radius = scaledradius;
 	}
 
 	P_RadiusAttack(inflictor, source, radius, 0, false);
@@ -9326,12 +9326,13 @@ boolean P_HomingAttack(mobj_t *source, mobj_t *enemy) // Home in on your target
 }
 
 // Search for emeralds
-void P_FindEmerald(void)
+void P_FindEmerald(world_t *w)
 {
 	thinker_t *th;
 	mobj_t *mo2;
 
-	hunt1 = hunt2 = hunt3 = NULL;
+	for (unsigned i = 0; i < NUM_EMERALD_HUNT_LOCATIONS; i++)
+		w->emerald_hunt_locations[i] = NULL;
 
 	// scan the remaining thinkers
 	// to find all emeralds
@@ -9343,12 +9344,11 @@ void P_FindEmerald(void)
 		mo2 = (mobj_t *)th;
 		if (mo2->type == MT_EMERHUNT)
 		{
-			if (!hunt1)
-				hunt1 = mo2;
-			else if (!hunt2)
-				hunt2 = mo2;
-			else if (!hunt3)
-				hunt3 = mo2;
+			for (unsigned i = 0; i < NUM_EMERALD_HUNT_LOCATIONS; i++)
+			{
+				if (w->emerald_hunt_locations[i] == NULL)
+					P_SetTarget(&w->emerald_hunt_locations[i], mo2);
+			}
 		}
 	}
 	return;
@@ -9476,7 +9476,7 @@ static void P_PlayerSetRaceRealTime(player_t *player)
 
 			for (i = 0; i < numworlds; i++)
 			{
-				if (player->world == worldlist[i])
+				if (P_GetPlayerWorld(player) == worldlist[i])
 					break;
 			}
 
@@ -11043,7 +11043,7 @@ static void P_MinecartThink(player_t *player)
 					minecart->eflags &= ~MFE_ONGROUND;
 				minecart->z += P_MobjFlip(minecart);
 				if (sidelock)
-					P_ParabolicMove(minecart, sidelock->x, sidelock->y, sidelock->z, gravity, max(currentSpeed, 10 * FRACUNIT));
+					P_ParabolicMove(minecart, sidelock->x, sidelock->y, sidelock->z, world->gravity, max(currentSpeed, 10 * FRACUNIT));
 				else
 					minecart->momz = 10 * FRACUNIT;
 
@@ -11618,7 +11618,7 @@ void P_PlayerThink(player_t *player)
 	// so we can fade music
 	if (!exitfadestarted &&
 		player->exiting > 0 && player->exiting <= 1*TICRATE &&
-		(!multiplayer || G_CoopGametype() ? !mapheaderinfo[gamemap-1]->musinterfadeout : true) &&
+		(!multiplayer || G_CoopGametype() ? !worldmapheader->musinterfadeout : true) &&
 			// don't fade if we're fading during intermission. follows Y_StartIntermission intertype = int_coop
 		((gametyperules & GTR_RACE) ? countdown2 == 0 : true) && // don't fade on timeout
 		player->lives > 0 && // don't fade on game over (competition)
diff --git a/src/p_world.c b/src/p_world.c
index 07226fd0a1..f6bc074ce4 100644
--- a/src/p_world.c
+++ b/src/p_world.c
@@ -59,6 +59,9 @@ world_t *P_InitWorld(void)
 {
 	world_t *w = Z_Calloc(sizeof(world_t), PU_STATIC, NULL);
 	w->gamemap = gamemap;
+	if (!mapheaderinfo[w->gamemap-1])
+		P_AllocMapHeader(w->gamemap-1);
+	w->header = mapheaderinfo[w->gamemap-1];
 	w->thlist = Z_Calloc(sizeof(thinker_t) * NUM_THINKERLISTS, PU_STATIC, NULL);
 	return w;
 }
@@ -144,9 +147,18 @@ void P_SetWorld(world_t *w)
 
 	P_SetGameWorld(w);
 
+	worldmapheader = w->header;
+
 	thlist = w->thlist;
 	gamemap = w->gamemap;
-	gravity = w->gravity;
+}
+
+//
+// Gets a player's current world.
+//
+world_t *P_GetPlayerWorld(player_t *player)
+{
+	return (world_t *)player->world;
 }
 
 //
@@ -154,8 +166,8 @@ void P_SetWorld(world_t *w)
 //
 void P_DetachPlayerWorld(player_t *player)
 {
-	if (player->world != NULL)
-		((world_t *)player->world)->players--;
+	if (P_GetPlayerWorld(player) != NULL)
+		P_GetPlayerWorld(player)->players--;
 
 	player->world = NULL;
 	if (player->mo && !P_MobjWasRemoved(player->mo))
@@ -199,7 +211,7 @@ void P_RoamIntoWorld(player_t *player, INT32 mapnum)
 		}
 	}
 
-	if (w == player->world)
+	if (w == P_GetPlayerWorld(player))
 		return;
 	else if (w)
 		P_SwitchWorld(player, w);
@@ -263,8 +275,8 @@ void P_SwitchWorld(player_t *player, world_t *w)
 	if (!playeringame[playernum] || !player->mo || P_MobjWasRemoved(player->mo))
 		return;
 
-	if (player->world)
-		P_RemoveMobjConnections(player->mo, player->world);
+	if (P_GetPlayerWorld(player))
+		P_RemoveMobjConnections(player->mo, P_GetPlayerWorld(player));
 
 	if (player->followmobj)
 	{
@@ -275,19 +287,18 @@ void P_SwitchWorld(player_t *player, world_t *w)
 	P_SwitchPlayerWorld(player, w);
 
 	P_SetWorld(w);
+
 	if (local || splitscreen)
-		P_InitSpecials();
+		P_InitLocalSpecials();
 
 	P_UnsetThingPosition(player->mo);
 	P_MoveThinkerToWorld(w, THINK_MOBJ, (thinker_t *)(player->mo));
 	G_MovePlayerToSpawnOrStarpost(playernum);
 
 	if (local && !splitscreen)
-	{
 		localworld = world;
-		S_Start();
-		P_SetupSkyTexture(w->skynum);
-	}
+
+	P_SetupSkyTexture(w->skynum);
 
 	if (player == &players[displayplayer])
 		P_ResetCamera(player, (splitscreen && playernum == 1) ? &camera2 : &camera);
@@ -303,6 +314,13 @@ void P_SwitchWorld(player_t *player, world_t *w)
 	if (rendermode == render_opengl)
 		HWR_LoadLevel();
 #endif
+
+	if (local || splitscreen)
+	{
+		S_SetMapMusic(worldmapheader);
+		S_StopMusic();
+		S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
+	}
 }
 
 void Command_Switchworld_f(void)
@@ -328,18 +346,12 @@ void Command_Switchworld_f(void)
 
 void Command_Listworlds_f(void)
 {
-	INT32 worldnum;
-	world_t *w;
-
-    for (worldnum = 0; worldnum < numworlds; worldnum++)
+    for (INT32 i = 0; i < numworlds; i++)
 	{
-		w = worldlist[worldnum];
-
-		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 count: %d\n", w->players);
+		world_t *w = worldlist[i];
+		CONS_Printf("World %d\n", i);
+		CONS_Printf("  Gamemap: %d\n", w->gamemap);
+		CONS_Printf("  Player count: %d\n", w->players);
 	}
 }
 
@@ -466,7 +478,7 @@ void P_UnloadWorldPlayer(player_t *player)
 
 boolean P_MobjIsConnected(mobj_t *mobj1, mobj_t *mobj2)
 {
-	return (mobj2 && !P_MobjWasRemoved(mobj2) && mobj1->world == mobj2->world);
+	return mobj2 && !P_MobjWasRemoved(mobj2) && P_GetMobjWorld(mobj1) == P_GetMobjWorld(mobj2);
 }
 
 void P_RemoveMobjConnections(mobj_t *mobj, world_t *w)
@@ -491,3 +503,8 @@ void P_RemoveMobjConnections(mobj_t *mobj, world_t *w)
 			P_SetTarget(&mobj->hprev, NULL);
 	}
 }
+
+world_t *P_GetMobjWorld(mobj_t *mobj)
+{
+	return (world_t *)mobj->world;
+}
diff --git a/src/p_world.h b/src/p_world.h
index 275e3a6fc8..151c279ee9 100644
--- a/src/p_world.h
+++ b/src/p_world.h
@@ -65,6 +65,8 @@ typedef struct
 
 	mobj_t *overlaycap;
 
+	mapheader_t *header;
+
 	fixed_t gravity;
 
 	INT32 skynum; // used for keeping track of the current sky
@@ -97,6 +99,12 @@ typedef struct
 	tic_t itemrespawntime[ITEMQUESIZE];
 	size_t iquehead, iquetail;
 
+	struct quake quake;
+
+	// Emerald locations
+	mobj_t *emerald_hunt_locations[NUM_EMERALD_HUNT_LOCATIONS];
+
+	// All that boring blockmap stuff
 	UINT8 *rejectmatrix; // for fast sight rejection
 	INT32 *blockmaplump; // offsets in blockmap are from here
 	INT32 *blockmap; // Big blockmap
@@ -160,12 +168,15 @@ void P_SetWorld(world_t *w);
 void P_RoamIntoWorld(player_t *player, INT32 mapnum);
 void P_SwitchWorld(player_t *player, world_t *w);
 
+world_t *P_GetPlayerWorld(player_t *player);
+
 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);
+world_t *P_GetMobjWorld(mobj_t *mobj);
 
 void Command_Switchworld_f(void);
 void Command_Listworlds_f(void);
diff --git a/src/r_bsp.c b/src/r_bsp.c
index 0369cf60a7..aeb6fe7e54 100644
--- a/src/r_bsp.c
+++ b/src/r_bsp.c
@@ -244,9 +244,6 @@ sector_t *R_FakeFlat(sector_t *sec, sector_t *tempsec, INT32 *floorlightlevel,
 		*ceilinglightlevel = sec->ceilinglightsec == -1 ?
 			(sec->ceilinglightabsolute ? sec->ceilinglightlevel : max(0, min(255, sec->lightlevel + sec->ceilinglightlevel))) : viewworld->sectors[sec->ceilinglightsec].lightlevel;
 
-	// if (sec->midmap != -1)
-	//	mapnum = sec->midmap;
-	// In original colormap code, this block did not run if sec->midmap was set
 	if (!sec->extra_colormap && sec->heightsec != -1)
 	{
 		const sector_t *s = &viewworld->sectors[sec->heightsec];
@@ -1073,18 +1070,18 @@ static void R_Subsector(size_t num)
 		}
 	}
 
-   // killough 9/18/98: Fix underwater slowdown, by passing real sector
-   // instead of fake one. Improve sprite lighting by basing sprite
-   // lightlevels on floor & ceiling lightlevels in the surrounding area.
-   //
-   // 10/98 killough:
-   //
-   // NOTE: TeamTNT fixed this bug incorrectly, messing up sprite lighting!!!
-   // That is part of the 242 effect!!!  If you simply pass sub->sector to
-   // the old code you will not get correct lighting for underwater sprites!!!
-   // Either you must pass the fake sector and handle validcount here, on the
-   // real sector, or you must account for the lighting in some other way,
-   // like passing it as an argument.
+	// killough 9/18/98: Fix underwater slowdown, by passing real sector
+	// instead of fake one. Improve sprite lighting by basing sprite
+	// lightlevels on floor & ceiling lightlevels in the surrounding area.
+	//
+	// 10/98 killough:
+	//
+	// NOTE: TeamTNT fixed this bug incorrectly, messing up sprite lighting!!!
+	// That is part of the 242 effect!!!  If you simply pass sub->sector to
+	// the old code you will not get correct lighting for underwater sprites!!!
+	// Either you must pass the fake sector and handle validcount here, on the
+	// real sector, or you must account for the lighting in some other way,
+	// like passing it as an argument.
 	R_AddSprites(sub->sector, (floorlightlevel+ceilinglightlevel)/2);
 
 	firstseg = NULL;
diff --git a/src/r_fps.c b/src/r_fps.c
index 35c0f342fa..32d8718ed8 100644
--- a/src/r_fps.c
+++ b/src/r_fps.c
@@ -696,7 +696,7 @@ void R_DestroyLevelInterpolators(void *wptr, thinker_t *thinker)
 // reasons.
 void R_AddMobjInterpolator(mobj_t *mobj)
 {
-	world_t *w = (world_t *)mobj->world;
+	world_t *w = P_GetMobjWorld(mobj);
 	if (!w)
 		return;
 
@@ -729,7 +729,7 @@ void R_AddMobjInterpolator(mobj_t *mobj)
 
 void R_RemoveMobjInterpolator(mobj_t *mobj)
 {
-	world_t *w = (world_t *)mobj->world;
+	world_t *w = P_GetMobjWorld(mobj);
 	if (!w)
 		return;
 
diff --git a/src/r_main.c b/src/r_main.c
index 0060ef6f33..bdd4ea192d 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1187,7 +1187,7 @@ void R_SetupFrame(player_t *player)
 			}
 		}
 	}
-	newview->z += quake.z;
+	newview->z += viewworld->quake.z;
 
 	newview->player = player;
 
@@ -1195,8 +1195,8 @@ void R_SetupFrame(player_t *player)
 	{
 		newview->x = thiscam->x;
 		newview->y = thiscam->y;
-		newview->x += quake.x;
-		newview->y += quake.y;
+		newview->x += viewworld->quake.x;
+		newview->y += viewworld->quake.y;
 
 		if (thiscam->subsector)
 			newview->sector = thiscam->subsector->sector;
@@ -1207,8 +1207,8 @@ void R_SetupFrame(player_t *player)
 	{
 		newview->x = r_viewmobj->x;
 		newview->y = r_viewmobj->y;
-		newview->x += quake.x;
-		newview->y += quake.y;
+		newview->x += viewworld->quake.x;
+		newview->y += viewworld->quake.y;
 
 		if (r_viewmobj->subsector)
 			newview->sector = r_viewmobj->subsector->sector;
@@ -1286,9 +1286,9 @@ void R_SkyboxFrame(player_t *player)
 	newview->y = r_viewmobj->y;
 	newview->z = r_viewmobj->z; // 26/04/17: use actual Z position instead of spawnpoint angle!
 
-	if (mapheaderinfo[gamemap-1])
+	if (viewworld->header)
 	{
-		mapheader_t *mh = mapheaderinfo[gamemap-1];
+		mapheader_t *mh = viewworld->header;
 		vector3_t campos = {0,0,0}; // Position of player's actual view point
 
 		if (player->awayviewtics) {
@@ -1307,9 +1307,9 @@ void R_SkyboxFrame(player_t *player)
 
 		// Earthquake effects should be scaled in the skybox
 		// (if an axis isn't used, the skybox won't shake in that direction)
-		campos.x += quake.x;
-		campos.y += quake.y;
-		campos.z += quake.z;
+		campos.x += viewworld->quake.x;
+		campos.y += viewworld->quake.y;
+		campos.z += viewworld->quake.z;
 
 		if (viewworld->skyboxmo[1]) // Is there a viewpoint?
 		{
@@ -1509,9 +1509,9 @@ void R_PrepareViewWorld(player_t *player)
 	R_SetViewMobj(player);
 
 	if (r_viewmobj)
-		P_SetViewWorld(r_viewmobj->world);
-	else if (player->world)
-		P_SetViewWorld(player->world);
+		P_SetViewWorld(P_GetMobjWorld(r_viewmobj));
+	else if (P_GetPlayerWorld(player))
+		P_SetViewWorld(P_GetPlayerWorld(player));
 	else if (localworld && !splitscreen) // Yes?
 		P_SetViewWorld(localworld);
 	else
diff --git a/src/r_portal.c b/src/r_portal.c
index 285f296891..30658a50ce 100644
--- a/src/r_portal.c
+++ b/src/r_portal.c
@@ -275,7 +275,7 @@ void Portal_AddSkybox (const visplane_t* plane)
 	portal->viewz = viewworld->skyboxmo[0]->z;
 	portal->viewangle = viewangle + viewworld->skyboxmo[0]->angle;
 
-	mh = mapheaderinfo[gamemap-1];
+	mh = viewworld->header;
 
 	// If a relative viewpoint exists, offset the viewpoint.
 	if (viewworld->skyboxmo[1])
diff --git a/src/r_skins.c b/src/r_skins.c
index 2c031ee851..a875e19153 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -225,7 +225,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
 		return true;
 	}
 
-	if (Playing() && mapheaderinfo[gamemap-1] && (R_SkinAvailable(mapheaderinfo[gamemap-1]->forcecharacter) == skinnum))
+	if (Playing() && worldmapheader && (R_SkinAvailable(worldmapheader->forcecharacter) == skinnum))
 	{
 		// Force 1.
 		return true;
diff --git a/src/r_things.c b/src/r_things.c
index fce5845ab4..e63001fe17 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -2380,9 +2380,6 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel)
 	INT32 lightnum;
 	fixed_t limit_dist, hoop_limit_dist;
 
-	if (rendermode != render_soft)
-		return;
-
 	// BSP is traversed by subsector.
 	// A sector might have been split into several
 	//  subsectors during BSP building.
diff --git a/src/s_sound.c b/src/s_sound.c
index 81fbda12ac..10f86da882 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1783,6 +1783,20 @@ boolean S_MusicExists(const char *mname, boolean checkMIDI, boolean checkDigi)
 	);
 }
 
+INT32 S_ShouldResetMusic(mapheader_t *mapheader)
+{
+	if (modeattacking)
+		return 0;
+
+	if (cv_resetmusicbyheader.value)
+	{
+		if (mapheader->musforcereset != -1)
+			return mapheader->musforcereset;
+	}
+
+	return cv_resetmusic.value;
+}
+
 /// ------------------------
 /// Music Effects
 /// ------------------------
@@ -2424,25 +2438,32 @@ boolean S_FadeOutStopMusic(UINT32 ms)
 /// Init & Others
 /// ------------------------
 
-//
-// Per level startup code.
-// Kills playing sounds at start of level,
-//  determines music if any, changes music.
-//
-void S_StartEx(boolean reset)
+void S_SetMapMusic(mapheader_t *mapheader)
+{
+	strncpy(mapmusname, mapheader->musname, 7);
+	mapmusname[6] = 0;
+	mapmusflags = (mapheader->mustrack & MUSIC_TRACKMASK);
+	mapmusposition = mapheader->muspos;
+}
+
+void S_PlayMapMusic(mapheader_t *mapheader, boolean reset)
 {
 	if (mapmusflags & MUSIC_RELOADRESET)
-	{
-		strncpy(mapmusname, mapheaderinfo[gamemap-1]->musname, 7);
-		mapmusname[6] = 0;
-		mapmusflags = (mapheaderinfo[gamemap-1]->mustrack & MUSIC_TRACKMASK);
-		mapmusposition = mapheaderinfo[gamemap-1]->muspos;
-	}
+		S_SetMapMusic(mapheader);
 
-	if (RESETMUSIC || reset)
+	if (S_ShouldResetMusic(mapheader) || reset)
 		S_StopMusic();
 	S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
+}
 
+//
+// Per level startup code.
+// Kills playing sounds at start of level,
+//  determines music if any, changes music.
+//
+void S_StartEx(mapheader_t *mapheader, boolean reset)
+{
+	S_PlayMapMusic(mapheader, reset);
 	S_ResetMusicStack();
 	music_stack_noposition = false;
 	music_stack_fadeout = 0;
@@ -2483,8 +2504,8 @@ static void Command_Tunes_f(void)
 	}
 	else if (!strcasecmp(tunearg, "-default"))
 	{
-		tunearg = mapheaderinfo[gamemap-1]->musname;
-		track = mapheaderinfo[gamemap-1]->mustrack;
+		tunearg = worldmapheader->musname;
+		track = worldmapheader->mustrack;
 	}
 
 	if (strlen(tunearg) > 6) // This is automatic -- just show the error just in case
diff --git a/src/s_sound.h b/src/s_sound.h
index 288859c8d2..b49ffe49e9 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -18,6 +18,7 @@
 #include "sounds.h"
 #include "m_fixed.h"
 #include "command.h"
+#include "doomstat.h"
 #include "tables.h" // angle_t
 
 #ifdef HAVE_OPENMPT
@@ -37,11 +38,7 @@ extern consvar_t cv_resetmusicbyheader;
 
 extern consvar_t cv_1upsound;
 
-#define RESETMUSIC (!modeattacking && \
-	(cv_resetmusicbyheader.value ? \
-		(mapheaderinfo[gamemap-1]->musforcereset != -1 ? mapheaderinfo[gamemap-1]->musforcereset : cv_resetmusic.value) \
-		: cv_resetmusic.value) \
-	)
+INT32 S_ShouldResetMusic(mapheader_t *mapheader);
 
 extern consvar_t cv_gamedigimusic;
 extern consvar_t cv_gamemidimusic;
@@ -125,8 +122,10 @@ void S_InitSfxChannels(INT32 sfxVolume);
 //
 void S_StopSounds(void);
 void S_ClearSfx(void);
-void S_StartEx(boolean reset);
-#define S_Start() S_StartEx(false)
+void S_SetMapMusic(mapheader_t *mapheader);
+void S_PlayMapMusic(mapheader_t *mapheader, boolean reset);
+void S_StartEx(mapheader_t *mapheader, boolean reset);
+#define S_Start(mapheader) S_StartEx(mapheader, false)
 
 //
 // Basically a W_GetNumForName that adds "ds" at the beginning of the string. Returns a lumpnum.
diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c
index f13aaef5d6..dd0d97053b 100644
--- a/src/sdl/mixer_sound.c
+++ b/src/sdl/mixer_sound.c
@@ -150,7 +150,7 @@ static void Midiplayer_Onchange(void)
 	Mix_Timidity_addToPathList(cv_miditimiditypath.string);
 
 	if (restart)
-		S_StartEx(true);
+		S_StartEx(worldmapheader, true);
 }
 
 static void MidiSoundfontPath_Onchange(void)
@@ -188,7 +188,7 @@ static void MidiSoundfontPath_Onchange(void)
 			if (!Mix_SetSoundFonts(cv_midisoundfontpath.string))
 				CONS_Alert(CONS_ERROR, "Sound font error: %s", Mix_GetError());
 			else
-				S_StartEx(true);
+				S_StartEx(worldmapheader, true);
 		}
 	}
 }
diff --git a/src/st_stuff.c b/src/st_stuff.c
index dcab2bb9c5..9312a86784 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -739,7 +739,7 @@ static void ST_drawTime(void)
 			// right now before the island blows up with you on it!"
 			// "Blows up??" *awkward silence* "I've got to get outta
 			// here and find Amy and Tails right away!"
-			else if (mapheaderinfo[gamemap-1]->countdown)
+			else if (worldmapheader->countdown)
 			{
 				tics = countdowntimer;
 				downwards = true;
@@ -1232,15 +1232,15 @@ static void ST_cacheLevelTitle(void)
 #define SETPATCH(default, warning, custom, idx) \
 { \
 	lumpnum_t patlumpnum = LUMPERROR; \
-	if (mapheaderinfo[gamemap-1]->custom[0] != '\0') \
+	if (worldmapheader->custom[0] != '\0') \
 	{ \
-		patlumpnum = W_CheckNumForName(mapheaderinfo[gamemap-1]->custom); \
+		patlumpnum = W_CheckNumForName(worldmapheader->custom); \
 		if (patlumpnum != LUMPERROR) \
 			lt_patches[idx] = (patch_t *)W_CachePatchNum(patlumpnum, PU_HUDGFX); \
 	} \
 	if (patlumpnum == LUMPERROR) \
 	{ \
-		if (!(mapheaderinfo[gamemap-1]->levelflags & LF_WARNINGTITLE)) \
+		if (!(worldmapheader->levelflags & LF_WARNINGTITLE)) \
 			lt_patches[idx] = (patch_t *)W_CachePatchName(default, PU_HUDGFX); \
 		else \
 			lt_patches[idx] = (patch_t *)W_CachePatchName(warning, PU_HUDGFX); \
@@ -1343,9 +1343,9 @@ void ST_runTitleCard(void)
 //
 void ST_drawTitleCard(void)
 {
-	char *lvlttl = mapheaderinfo[gamemap-1]->lvlttl;
-	char *subttl = mapheaderinfo[gamemap-1]->subttl;
-	UINT8 actnum = mapheaderinfo[gamemap-1]->actnum;
+	char *lvlttl = worldmapheader->lvlttl;
+	char *subttl = worldmapheader->subttl;
+	UINT8 actnum = worldmapheader->actnum;
 	INT32 lvlttlxpos, ttlnumxpos, zonexpos;
 	INT32 subttlxpos = BASEVIDWIDTH/2;
 	INT32 ttlscroll = FixedInt(lt_scroll);
@@ -1412,7 +1412,7 @@ void ST_drawTitleCard(void)
 	}
 
 	V_DrawLevelTitle(lvlttlxpos - ttlscroll, 80, V_PERPLAYER, lvlttl);
-	if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
+	if (!(worldmapheader->levelflags & LF_NOZONE))
 		V_DrawLevelTitle(zonexpos + ttlscroll, 104, V_PERPLAYER, M_GetText("Zone"));
 	V_DrawCenteredString(subttlxpos - ttlscroll, 135, V_PERPLAYER|V_ALLOWLOWERCASE, subttl);
 
@@ -2558,20 +2558,21 @@ static INT32 ST_drawEmeraldHuntIcon(mobj_t *hunt, patch_t **patches, INT32 offse
 static void ST_doHuntIconsAndSound(void)
 {
 	INT32 interval = 0, newinterval = 0;
+	mobj_t **hunt = world->emerald_hunt_locations;
 
-	if (hunt1 && hunt1->health)
-		interval = ST_drawEmeraldHuntIcon(hunt1, hunthoming, -20);
+	if (hunt[0] && hunt[0]->health)
+		interval = ST_drawEmeraldHuntIcon(hunt[0], hunthoming, -20);
 
-	if (hunt2 && hunt2->health)
+	if (hunt[1] && hunt[1]->health)
 	{
-		newinterval = ST_drawEmeraldHuntIcon(hunt2, hunthoming, 0);
+		newinterval = ST_drawEmeraldHuntIcon(hunt[1], hunthoming, 0);
 		if (newinterval && (!interval || newinterval < interval))
 			interval = newinterval;
 	}
 
-	if (hunt3 && hunt3->health)
+	if (hunt[2] && hunt[2]->health)
 	{
-		newinterval = ST_drawEmeraldHuntIcon(hunt3, hunthoming, 20);
+		newinterval = ST_drawEmeraldHuntIcon(hunt[2], hunthoming, 20);
 		if (newinterval && (!interval || newinterval < interval))
 			interval = newinterval;
 	}
@@ -2671,7 +2672,7 @@ static void ST_overlayDrawer(void)
 	// Check for a valid level title
 	// If the HUD is enabled
 	// And, if Lua is running, if the HUD library has the stage title enabled
-	if (G_IsTitleCardAvailable() && *mapheaderinfo[gamemap-1]->lvlttl != '\0' && !(hu_showscores && (netgame || multiplayer)))
+	if (G_IsTitleCardAvailable() && *worldmapheader->lvlttl != '\0' && !(hu_showscores && (netgame || multiplayer)))
 	{
 		stagetitle = true;
 		ST_preDrawTitleCard();
diff --git a/src/v_video.c b/src/v_video.c
index 5674603ebb..8d53f06f46 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -399,7 +399,7 @@ const char *R_GetPalname(UINT16 num)
 const char *GetPalette(void)
 {
 	if (gamestate == GS_LEVEL)
-		return R_GetPalname(mapheaderinfo[gamemap-1]->palette);
+		return R_GetPalname(worldmapheader->palette);
 	return "PLAYPAL";
 }
 
diff --git a/src/y_inter.c b/src/y_inter.c
index 6e7d362a77..7476803089 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -248,8 +248,8 @@ void Y_LoadIntermissionData(void)
 
 
 			// grab an interscreen if appropriate
-			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
-				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
+			if (worldmapheader->interscreen[0] != '#')
+				interpic = W_CachePatchName(worldmapheader->interscreen, PU_PATCH);
 			else // no interscreen? use default background
 				bgpatch = W_CachePatchName("INTERSCR", PU_PATCH);
 			break;
@@ -263,8 +263,8 @@ void Y_LoadIntermissionData(void)
 			data.spec.pcontinues = W_CachePatchName("YB_CONTI", PU_PATCH);
 
 			// grab an interscreen if appropriate
-			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
-				interpic = W_CachePatchName(mapheaderinfo[gamemap-1]->interscreen, PU_PATCH);
+			if (worldmapheader->interscreen[0] != '#')
+				interpic = W_CachePatchName(worldmapheader->interscreen, PU_PATCH);
 			else // no interscreen? use default background
 				bgtile = W_CachePatchName("SPECTILE", PU_PATCH);
 			break;
@@ -1043,15 +1043,15 @@ void Y_Ticker(void)
 
 		if (!intertic) // first time only
 		{
-			if (mapheaderinfo[gamemap-1]->musinterfadeout
+			if (worldmapheader->musinterfadeout
 #ifdef _WIN32
 				// can't fade midi due to win32 volume hack
 				&& S_MusicType() != MU_MID
 #endif
 			)
-				S_FadeOutStopMusic(mapheaderinfo[gamemap-1]->musinterfadeout);
-			else if (mapheaderinfo[gamemap-1]->musintername[0] && S_MusicExists(mapheaderinfo[gamemap-1]->musintername, !midi_disabled, !digital_disabled))
-				S_ChangeMusicInternal(mapheaderinfo[gamemap-1]->musintername, false); // don't loop it
+				S_FadeOutStopMusic(worldmapheader->musinterfadeout);
+			else if (worldmapheader->musintername[0] && S_MusicExists(worldmapheader->musintername, !midi_disabled, !digital_disabled))
+				S_ChangeMusicInternal(worldmapheader->musintername, false); // don't loop it
 			else
 				S_ChangeMusicInternal("_clear", false); // don't loop it
 			tallydonetic = -1;
@@ -1120,15 +1120,15 @@ void Y_Ticker(void)
 
 		if (!intertic) // first time only
 		{
-			if (mapheaderinfo[gamemap-1]->musinterfadeout
+			if (worldmapheader->musinterfadeout
 #ifdef _WIN32
 				// can't fade midi due to win32 volume hack
 				&& S_MusicType() != MU_MID
 #endif
 			)
-				S_FadeOutStopMusic(mapheaderinfo[gamemap-1]->musinterfadeout);
-			else if (mapheaderinfo[gamemap-1]->musintername[0] && S_MusicExists(mapheaderinfo[gamemap-1]->musintername, !midi_disabled, !digital_disabled))
-				S_ChangeMusicInternal(mapheaderinfo[gamemap-1]->musintername, false); // don't loop it
+				S_FadeOutStopMusic(worldmapheader->musinterfadeout);
+			else if (worldmapheader->musintername[0] && S_MusicExists(worldmapheader->musintername, !midi_disabled, !digital_disabled))
+				S_ChangeMusicInternal(worldmapheader->musintername, false); // don't loop it
 			else
 				S_ChangeMusicInternal("_clear", false); // don't loop it
 			tallydonetic = -1;
@@ -1348,10 +1348,10 @@ void Y_StartIntermission(void)
 			data.coop.tics = players[consoleplayer].realtime;
 
 			// get act number
-			data.coop.actnum = mapheaderinfo[gamemap-1]->actnum;
+			data.coop.actnum = worldmapheader->actnum;
 
 			// grab an interscreen if appropriate
-			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
+			if (worldmapheader->interscreen[0] != '#')
 			{
 				useinterpic = true;
 				usebuffer = false;
@@ -1369,9 +1369,9 @@ void Y_StartIntermission(void)
 			// set up the "got through act" message according to skin name
 			if (stagefailed)
 			{
-				strcpy(data.coop.passed1, mapheaderinfo[gamemap-1]->lvlttl);
+				strcpy(data.coop.passed1, worldmapheader->lvlttl);
 
-				if (mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE)
+				if (worldmapheader->levelflags & LF_NOZONE)
 				{
 					data.spec.passed2[0] = '\0';
 				}
@@ -1386,25 +1386,25 @@ void Y_StartIntermission(void)
 				if (strlen(skins[players[consoleplayer].skin].realname) > 13)
 				{
 					strcpy(data.coop.passed1, "you got");
-					strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "through act" : "through the act");
+					strcpy(data.coop.passed2, (worldmapheader->actnum) ? "through act" : "through the act");
 				}
 				// long enough that "X GOT" won't fit so use "X PASSED THE ACT"
 				else if (strlen(skins[players[consoleplayer].skin].realname) > 8)
 				{
 					strcpy(data.coop.passed1, skins[players[consoleplayer].skin].realname);
-					strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "passed act" : "passed the act");
+					strcpy(data.coop.passed2, (worldmapheader->actnum) ? "passed act" : "passed the act");
 				}
 				// length is okay for normal use
 				else
 				{
 					snprintf(data.coop.passed1, sizeof data.coop.passed1, "%s got",
 						skins[players[consoleplayer].skin].realname);
-					strcpy(data.coop.passed2, (mapheaderinfo[gamemap-1]->actnum) ? "through act" : "through the act");
+					strcpy(data.coop.passed2, (worldmapheader->actnum) ? "through act" : "through the act");
 				}
 			}
 
 			// set X positions
-			if (mapheaderinfo[gamemap-1]->actnum)
+			if (worldmapheader->actnum)
 			{
 				data.coop.passedx1 = 62 + (176 - V_LevelNameWidth(data.coop.passed1))/2;
 				data.coop.passedx2 = 62 + (176 - V_LevelNameWidth(data.coop.passed2))/2;
@@ -1418,7 +1418,7 @@ void Y_StartIntermission(void)
 			// at the start of intermission, and precalculating it would preclude mods
 			// changing the font to one of a slightly different width.
 
-			if ((stagefailed) && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
+			if ((stagefailed) && !(worldmapheader->levelflags & LF_NOZONE))
 			{
 				// Bit of a hack, offset so that the "Zone" text is right aligned like title cards.
 				data.coop.passedx2 = (data.coop.passedx1 + V_LevelNameWidth(data.coop.passed1)) - V_LevelNameWidth(data.coop.passed2);
@@ -1433,7 +1433,7 @@ void Y_StartIntermission(void)
 			Y_AwardSpecialStageBonus();
 
 			// grab an interscreen if appropriate
-			if (mapheaderinfo[gamemap-1]->interscreen[0] != '#')
+			if (worldmapheader->interscreen[0] != '#')
 				useinterpic = true;
 			else
 				useinterpic = false;
-- 
GitLab