diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 61e50f9262ec01ca3ac37b26933f5c20721515d6..b4d8272f23636f576859918fcffe4122d6fbc7e8 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -4304,6 +4304,8 @@ static void HWR_DrawSkyBackground(player_t *player)
 
 	v[0].z = v[1].z = v[2].z = v[3].z = 4.0f;
 
+	// X
+
 	if (textures[skytexture]->width > 256)
 		angle = (angle_t)((float)(dup_viewangle + gr_xtoviewangle[0])
 						/((float)textures[skytexture]->width/256.0f))
@@ -4318,9 +4320,19 @@ static void HWR_DrawSkyBackground(player_t *player)
 	v[0].sow = v[3].sow = 0.22f+(f)/(textures[skytexture]->width/2);
 	v[2].sow = v[1].sow = 0.22f+(f+(127))/(textures[skytexture]->width/2);
 
+
+	// Y
+
+	if (textures[skytexture]->height > 256)
+		angle = (angle_t)((float)(aimingangle)
+						*(256.0f/(float)textures[skytexture]->height))
+							%(ANGLE_90-1); // Just so that looking up and down scales right
+	else
+		angle = (aimingangle);
+
 	f = (float)((textures[skytexture]->height/2)
 	            * FIXED_TO_FLOAT(FINETANGENT((2048
-	 - ((INT32)aimingangle>>(ANGLETOFINESHIFT + 1))) & FINEMASK)));
+	 - ((INT32)angle>>(ANGLETOFINESHIFT + 1))) & FINEMASK)));
 
 	v[3].tow = v[2].tow = 0.22f+(f)/(textures[skytexture]->height/2);
 	v[0].tow = v[1].tow = 0.22f+(f+(127))/(textures[skytexture]->height/2);
@@ -4386,6 +4398,205 @@ void HWR_SetViewSize(void)
 	gr_pspriteyscale = ((vid.height*gr_pspritexscale*BASEVIDWIDTH)/BASEVIDHEIGHT)/vid.width;
 }
 
+// ==========================================================================
+// Same as rendering the player view, but from the skybox object
+// ==========================================================================
+void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player)
+{
+	const float fpov = FIXED_TO_FLOAT(cv_grfov.value+player->fovadd);
+	FTransform stransform;
+
+	{
+		// do we really need to save player (is it not the same)?
+		player_t *saved_player = stplyr;
+		stplyr = player;
+		ST_doPaletteStuff();
+		stplyr = saved_player;
+#ifdef ALAM_LIGHTING
+		HWR_SetLights(viewnumber);
+#endif
+	}
+
+	// note: sets viewangle, viewx, viewy, viewz
+	R_SkyboxFrame(player);
+
+	// copy view cam position for local use
+	dup_viewx = viewx;
+	dup_viewy = viewy;
+	dup_viewz = viewz;
+	dup_viewangle = viewangle;
+
+	// set window position
+	gr_centery = gr_basecentery;
+	gr_viewwindowy = gr_baseviewwindowy;
+	gr_windowcentery = gr_basewindowcentery;
+	if (splitscreen && viewnumber == 1)
+	{
+		gr_viewwindowy += (vid.height/2);
+		gr_windowcentery += (vid.height/2);
+	}
+
+	// check for new console commands.
+	NetUpdate();
+
+	gr_viewx = FIXED_TO_FLOAT(dup_viewx);
+	gr_viewy = FIXED_TO_FLOAT(dup_viewy);
+	gr_viewz = FIXED_TO_FLOAT(dup_viewz);
+	gr_viewsin = FIXED_TO_FLOAT(viewsin);
+	gr_viewcos = FIXED_TO_FLOAT(viewcos);
+
+	gr_viewludsin = FIXED_TO_FLOAT(FINECOSINE(aimingangle>>ANGLETOFINESHIFT));
+	gr_viewludcos = FIXED_TO_FLOAT(-FINESINE(aimingangle>>ANGLETOFINESHIFT));
+
+	//04/01/2000: Hurdler: added for T&L
+	//                     It should replace all other gr_viewxxx when finished
+	atransform.anglex = (float)(aimingangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
+	atransform.angley = (float)(viewangle>>ANGLETOFINESHIFT)*(360.0f/(float)FINEANGLES);
+	atransform.x      = gr_viewx;  // FIXED_TO_FLOAT(viewx)
+	atransform.y      = gr_viewy;  // FIXED_TO_FLOAT(viewy)
+	atransform.z      = gr_viewz;  // FIXED_TO_FLOAT(viewz)
+	atransform.scalex = 1;
+	atransform.scaley = ORIGINAL_ASPECT;
+	atransform.scalez = 1;
+	atransform.fovxangle = fpov; // Tails
+	atransform.fovyangle = fpov; // Tails
+	atransform.splitscreen = splitscreen;
+
+	// Transform for sprites
+	stransform.anglex = 0.0f;
+	stransform.angley = -270.0f;
+	stransform.x      = 0.0f;
+	stransform.y      = 0.0f;
+	stransform.z      = 0.0f;
+	stransform.scalex = 1;
+	stransform.scaley = 1;
+	stransform.scalez = 1;
+	stransform.fovxangle = 90.0f;
+	stransform.fovyangle = 90.0f;
+	stransform.splitscreen = splitscreen;
+
+	gr_fovlud = (float)(1.0l/tan((double)(fpov*M_PIl/360l)));
+
+	//------------------------------------------------------------------------
+	HWR_ClearView();
+
+if (0)
+{ // I don't think this is ever used.
+	if (cv_grfog.value)
+		HWR_FoggingOn(); // First of all, turn it on, set the default user settings too
+	else
+		HWD.pfnSetSpecialState(HWD_SET_FOG_MODE, 0); // Turn it off
+}
+
+#ifndef _NDS
+	if (drawsky)
+		HWR_DrawSkyBackground(player);
+#else
+	(void)HWR_DrawSkyBackground;
+#endif
+
+	//Hurdler: it doesn't work in splitscreen mode
+	drawsky = splitscreen;
+
+	HWR_ClearSprites();
+
+#ifdef SORTING
+	drawcount = 0;
+#endif
+	HWR_ClearClipSegs();
+
+	//04/01/2000: Hurdler: added for T&L
+	//                     Actually it only works on Walls and Planes
+	HWD.pfnSetTransform(&atransform);
+
+	validcount++;
+
+	HWR_RenderBSPNode((INT32)numnodes-1);
+
+	// Make a viewangle int so we can render things based on mouselook
+	if (player == &players[consoleplayer])
+		viewangle = localaiming;
+	else if (splitscreen && player == &players[secondarydisplayplayer])
+		viewangle = localaiming2;
+
+	// Handle stuff when you are looking farther up or down.
+	if ((aimingangle || cv_grfov.value+player->fovadd > 90*FRACUNIT))
+	{
+		dup_viewangle += ANGLE_90;
+		HWR_ClearClipSegs();
+		HWR_RenderBSPNode((INT32)numnodes-1); //left
+
+		dup_viewangle += ANGLE_90;
+		if (((INT32)aimingangle > ANGLE_45 || (INT32)aimingangle<-ANGLE_45))
+		{
+			HWR_ClearClipSegs();
+			HWR_RenderBSPNode((INT32)numnodes-1); //back
+		}
+
+		dup_viewangle += ANGLE_90;
+		HWR_ClearClipSegs();
+		HWR_RenderBSPNode((INT32)numnodes-1); //right
+
+		dup_viewangle += ANGLE_90;
+	}
+
+	// Check for new console commands.
+	NetUpdate();
+
+#ifdef ALAM_LIGHTING
+	//14/11/99: Hurdler: moved here because it doesn't work with
+	// subsector, see other comments;
+	HWR_ResetLights();
+#endif
+
+	// Draw MD2 and sprites
+#ifdef SORTING
+	HWR_SortVisSprites();
+#endif
+	HWR_DrawMD2S();
+
+	// Draw the sprites with trivial transform
+	HWD.pfnSetTransform(&stransform);
+#ifdef SORTING
+	HWR_DrawSprites();
+#endif
+#ifdef NEWCORONAS
+	//Hurdler: they must be drawn before translucent planes, what about gl fog?
+	HWR_DrawCoronas();
+#endif
+
+#ifdef SORTING
+	if (numplanes || numwalls) //Hurdler: render 3D water and transparent walls after everything
+	{
+		HWR_CreateDrawNodes();
+	}
+#else
+	if (numfloors || numwalls)
+	{
+		HWD.pfnSetTransform(&atransform);
+		if (numfloors)
+			HWR_Render3DWater();
+		if (numwalls)
+			HWR_RenderTransparentWalls();
+	}
+#endif
+
+	HWD.pfnSetTransform(NULL);
+
+	// put it off for menus etc
+	if (cv_grfog.value)
+		HWD.pfnSetSpecialState(HWD_SET_FOG_MODE, 0);
+
+	HWR_DoPostProcessor(player);
+
+	// Check for new console commands.
+	NetUpdate();
+
+	// added by Hurdler for correct splitscreen
+	// moved here by hurdler so it works with the new near clipping plane
+	HWD.pfnGClipRect(0, 0, vid.width, vid.height, NZCLIP_PLANE);
+}
+
 // ==========================================================================
 //
 // ==========================================================================
@@ -4394,6 +4605,11 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	const float fpov = FIXED_TO_FLOAT(cv_grfov.value+player->fovadd);
 	FTransform stransform;
 
+	const boolean skybox = (skyboxmo[0] && cv_skybox.value); // True if there's a skybox object and skyboxes are on
+
+	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
+
 	{
 		// do we really need to save player (is it not the same)?
 		player_t *saved_player = stplyr;
@@ -4406,7 +4622,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 	}
 
 	// note: sets viewangle, viewx, viewy, viewz
-	R_SetupFrame(player, false);
+	R_SetupFrame(player, false); // This can stay false because it is only used to set viewsky in r_main.c, which isn't used here
 
 	// copy view cam position for local use
 	dup_viewx = viewx;
@@ -4477,7 +4693,7 @@ if (0)
 }
 
 #ifndef _NDS
-	if (drawsky)
+	if (!skybox && drawsky) // Don't draw the regular sky if there's a skybox
 		HWR_DrawSkyBackground(player);
 #else
 	(void)HWR_DrawSkyBackground;
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 2fc0ba8a0dac85bc5ae95fc8ce7157caee74aed3..a6cf3fb7f153ba1188afb48380717b129ea7a1fc 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -35,6 +35,7 @@ void HWR_clearAutomap(void);
 void HWR_drawAMline(const fline_t *fl, INT32 color);
 void HWR_FadeScreenMenuBack(UINT32 color, INT32 height);
 void HWR_DrawConsoleBack(UINT32 color, INT32 height);
+void HWR_RenderSkyboxView(INT32 viewnumber, player_t *player);
 void HWR_RenderPlayerView(INT32 viewnumber, player_t *player);
 void HWR_DrawViewBorder(INT32 clearlines);
 void HWR_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum);
diff --git a/src/r_main.c b/src/r_main.c
index 10228c29a4684c38175d4b0a42d6b27337e46a1b..c55de3940c9ee93945cbd2dba9272866b8051ae8 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -732,7 +732,7 @@ static mobj_t *viewmobj;
 // WARNING: a should be unsigned but to add with 2048, it isn't!
 #define AIMINGTODY(a) ((FINETANGENT((2048+(((INT32)a)>>ANGLETOFINESHIFT)) & FINEMASK)*160)>>FRACBITS)
 
-static void R_SkyboxFrame(player_t *player)
+void R_SkyboxFrame(player_t *player)
 {
 	INT32 dy = 0;
 	camera_t *thiscam;
diff --git a/src/r_main.h b/src/r_main.h
index a6387bbc237dfb88879db584c920306b75495c70..8a39b7d30e33c9c432a001d6fa49c176829eca17 100644
--- a/src/r_main.h
+++ b/src/r_main.h
@@ -101,6 +101,8 @@ void R_SetViewSize(void);
 // do it (sometimes explicitly called)
 void R_ExecuteSetViewSize(void);
 
+void R_SkyboxFrame(player_t *player);
+
 void R_SetupFrame(player_t *player, boolean skybox);
 // Called by G_Drawer.
 void R_RenderPlayerView(player_t *player);