diff --git a/src/dehacked.c b/src/dehacked.c
index 5bcb64073cec9239df8d58f3e98c3c871ae2678b..cf654dcd7216f309899b4d51a33434a0c9b66507 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -9364,6 +9364,13 @@ struct {
 	{"DI_SOUTHEAST",DI_SOUTHEAST},
 	{"NUMDIRS",NUMDIRS},
 
+#ifdef ROTSPRITE
+	// Sprite roll axis (rollaxis_t)
+	{"ROLLAXIS_X",ROLLAXIS_X},
+	{"ROLLAXIS_Y",ROLLAXIS_Y},
+	{"ROLLAXIS_Z",ROLLAXIS_Z},
+#endif
+
 	// Buttons (ticcmd_t)
 	{"BT_WEAPONMASK",BT_WEAPONMASK}, //our first four bits.
 	{"BT_WEAPONNEXT",BT_WEAPONNEXT},
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index eed96ddc04799769108520147f17f456b5eaef25..c1996ea4b0cbc242e207a580af35335310b528d7 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -117,7 +117,8 @@ typedef struct
 	boolean     flip;            // screenflip
 #ifdef ROTSPRITE
 	boolean     roll;
-	SINT8       nightsroll;
+	UINT8       rollaxis;
+	SINT8       rollflip;
 	FLOAT       rollangle; // done to not override USE_FTRANSFORM_ANGLEZ
 	FLOAT       centerx, centery;
 #endif
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index c4b28076baf7fcc03aed656dd39fce2c63f3b59c..2c5ab07dbbef7b83591c90f81c7c6b5914b7052c 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -961,6 +961,10 @@ void HWR_DrawModel(gr_vissprite_t *spr)
 		const UINT8 flip = (UINT8)(!(spr->mobj->eflags & MFE_VERTICALFLIP) != !(spr->mobj->frame & FF_VERTICALFLIP));
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
+#ifdef ROTSPRITE
+		spriteinfo_t *sprinfo;
+		angle_t ang;
+#endif
 		INT32 mod;
 		float finalscale;
 
@@ -984,9 +988,17 @@ void HWR_DrawModel(gr_vissprite_t *spr)
 		{
 			md2 = &md2_playermodels[(skin_t*)spr->mobj->skin-skins];
 			md2->skin = (skin_t*)spr->mobj->skin-skins;
+#ifdef ROTSPRITE
+			sprinfo = &((skin_t *)spr->mobj->skin)->sprinfo[spr->mobj->sprite2];
+#endif
 		}
 		else
+		{
 			md2 = &md2_models[spr->mobj->sprite];
+#ifdef ROTSPRITE
+			sprinfo = &spriteinfo[spr->mobj->sprite];
+#endif
+		}
 
 		if (md2->error)
 			return; // we already failed loading this before :(
@@ -1175,29 +1187,28 @@ void HWR_DrawModel(gr_vissprite_t *spr)
 
 #ifdef ROTSPRITE
 		p.rollangle = 0.0f;
-		p.nightsroll = 0;
+		p.rollaxis = 0;
+		p.rollflip = 0;
 		if (spr->mobj->rollangle)
 		{
-			// do i have to support ROTANGLES here??????
 			fixed_t anglef = AngleFixed(spr->mobj->rollangle);
 			p.rollangle = FIXED_TO_FLOAT(anglef);
-			// pivot
+			p.roll = true;
+
+			// rotation pivot
 			p.centerx = FIXED_TO_FLOAT(spr->mobj->radius/2);
 			p.centery = FIXED_TO_FLOAT(spr->mobj->height/2);
-			p.roll = true;
-			// NiGHTS-specific conditional
-			//if (spr->mobj->player)
-			{
-				statenum_t state = spr->mobj->state-states;
-				if ((state == S_PLAY_NIGHTS_FLY0) || (state == S_PLAY_NIGHTS_DRILL0))
-				{
-					angle_t ang = R_PointToAngle (spr->mobj->x, spr->mobj->y) - (spr->mobj->player ? spr->mobj->player->drawangle : spr->mobj->angle);
-					if ((sprframe->rotate & SRF_RIGHT) && (ang < ANGLE_180)) // See from right
-						p.nightsroll = 1;
-					else if ((sprframe->rotate & SRF_LEFT) && (ang >= ANGLE_180)) // See from left
-						p.nightsroll = -1;
-				}
-			}
+
+			// roll axis
+			if (sprinfo->available)
+				p.rollaxis = (UINT8)(sprinfo->pivot[(spr->mobj->frame & FF_FRAMEMASK)].rollaxis);
+
+			// for NiGHTS specifically but should work everywhere else
+			ang = R_PointToAngle (spr->mobj->x, spr->mobj->y) - (spr->mobj->player ? spr->mobj->player->drawangle : spr->mobj->angle);
+			if ((sprframe->rotate & SRF_RIGHT) && (ang < ANGLE_180)) // See from right
+				p.rollflip = 1;
+			else if ((sprframe->rotate & SRF_LEFT) && (ang >= ANGLE_180)) // See from left
+				p.rollflip = -1;
 		}
 #endif
 
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 4388214ec686d515ae61bb09bec158e9bb3bc28e..b05a5dd83a32b77a249144ffe336ca584119dc9e 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -2069,11 +2069,14 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 #ifdef ROTSPRITE
 	if (pos->roll)
 	{
+		float roll = (1.0f * pos->rollflip);
 		pglTranslatef(pos->centerx, pos->centery, 0);
-		if (pos->nightsroll != 0)
-			pglRotatef(pos->rollangle, 0.0f, 0.0f, (1.0f * pos->nightsroll));
-		else
-			pglRotatef(pos->rollangle, 1.0f, 0.0f, 0.0f);
+		if (pos->rollaxis == 2) // Z
+			pglRotatef(pos->rollangle, 0.0f, 0.0f, roll);
+		else if (pos->rollaxis == 1) // Y
+			pglRotatef(pos->rollangle, 0.0f, roll, 0.0f);
+		else // X
+			pglRotatef(pos->rollangle, roll, 0.0f, 0.0f);
 		pglTranslatef(-pos->centerx, -pos->centery, 0);
 	}
 #endif
diff --git a/src/lua_infolib.c b/src/lua_infolib.c
index 4cb851dcaa3d36eedea04db64cee5562b2cad4e5..2782e6b29a61a5bf530b72836548ba79dc5ebcb4 100644
--- a/src/lua_infolib.c
+++ b/src/lua_infolib.c
@@ -305,8 +305,10 @@ static int PopPivotSubTable(spriteframepivot_t *pivot, lua_State *L, int stk, in
 					pivot[idx].x = (INT32)value;
 				else if (ikey == 2 || (key && fastcmp(key, "y")))
 					pivot[idx].y = (INT32)value;
+				else if (ikey == 3 || (key && fastcmp(key, "rollaxis")))
+					pivot[idx].rollaxis = (UINT8)value;
 				else if (ikey == -1 && (key != NULL))
-					FIELDERROR("pivot key", va("x or y expected, got %s", key));
+					FIELDERROR("pivot key", va("x, y or roll axis expected, got %s", key));
 				ok = 1;
 				//info->available = true; // the pivot for this frame is available
 				lua_pop(L, 1);
@@ -328,7 +330,7 @@ static int PopPivotTable(spriteinfo_t *info, lua_State *L, int stk)
 	// stk = 0 has the pivot table
 	// stk = 1 has the frame key
 	// stk = 2 has the frame table
-	// stk = 3 has either "x" or "y" or a number as key
+	// stk = 3 has either "x" or "y" or "rollaxis" or a number as key
 	// stk = 4 has the value for the key mentioned above
 	while (lua_next(L, stk))
 	{
@@ -568,6 +570,8 @@ static int framepivot_get(lua_State *L)
 		lua_pushinteger(L, framepivot->x);
 	else if (fastcmp("y", field))
 		lua_pushinteger(L, framepivot->y);
+	else if (fastcmp("rollaxis", field))
+		lua_pushinteger(L, (UINT8)framepivot->rollaxis);
 	else
 		return luaL_error(L, va("Field %s does not exist in spriteframepivot_t", field));
 
@@ -590,6 +594,8 @@ static int framepivot_set(lua_State *L)
 		framepivot->x = luaL_checkinteger(L, 3);
 	else if (fastcmp("y", field))
 		framepivot->y = luaL_checkinteger(L, 3);
+	else if (fastcmp("rollaxis", field))
+		framepivot->rollaxis = (UINT8)luaL_checkinteger(L, 3);
 	else
 		return luaL_error(L, va("Field %s does not exist in spriteframepivot_t", field));
 
diff --git a/src/r_patch.c b/src/r_patch.c
index ce68b49aee3980a01d2e010bb13b878dc8117265..6f6050ce9be923ba4deaae9abe98dfd639afa3b3 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -791,6 +791,7 @@ static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
 #ifdef ROTSPRITE
 	INT16 frameXPivot = 0;
 	INT16 frameYPivot = 0;
+	rollaxis_t frameRollAxis = 0;
 #endif
 
 	// Sprite identifier
@@ -839,6 +840,17 @@ static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
 					sprinfoToken = M_GetToken(NULL);
 					frameYPivot = atoi(sprinfoToken);
 				}
+				else if (stricmp(sprinfoToken, "ROLLAXIS")==0)
+				{
+					Z_Free(sprinfoToken);
+					sprinfoToken = M_GetToken(NULL);
+					if ((stricmp(sprinfoToken, "X")==0) || (stricmp(sprinfoToken, "XAXIS")==0))
+						frameRollAxis = ROLLAXIS_X;
+					else if ((stricmp(sprinfoToken, "Y")==0) || (stricmp(sprinfoToken, "YAXIS")==0))
+						frameRollAxis = ROLLAXIS_Y;
+					else if ((stricmp(sprinfoToken, "Z")==0) || (stricmp(sprinfoToken, "ZAXIS")==0))
+						frameRollAxis = ROLLAXIS_Z;
+				}
 #endif
 				Z_Free(sprinfoToken);
 
@@ -856,6 +868,7 @@ static void R_ParseSpriteInfoFrame(spriteinfo_t *info)
 #ifdef ROTSPRITE
 	info->pivot[frameFrame].x = frameXPivot;
 	info->pivot[frameFrame].y = frameYPivot;
+	info->pivot[frameFrame].rollaxis = frameRollAxis;
 #endif
 }
 
diff --git a/src/r_patch.h b/src/r_patch.h
index b840823d8abaa367034a9b405ea6b7df3eb85833..fa6785792bfb9e551fc7eed39ca9b6ed25097d8f 100644
--- a/src/r_patch.h
+++ b/src/r_patch.h
@@ -19,9 +19,17 @@
 
 // structs
 #ifdef ROTSPRITE
+typedef enum
+{
+	ROLLAXIS_X, // the default
+	ROLLAXIS_Y,
+	ROLLAXIS_Z
+} rollaxis_t;
+
 typedef struct
 {
 	INT32 x, y;
+	rollaxis_t rollaxis;
 } spriteframepivot_t;
 #endif
 
diff --git a/src/r_things.c b/src/r_things.c
index 2df80b0c447967271207e5763adf255456214cac..3d2f2a846101e036a268f5ae251aed49a170217e 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1148,7 +1148,9 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	spritedef_t *sprdef;
 	spriteframe_t *sprframe;
+#ifdef ROTSPRITE
 	spriteinfo_t *sprinfo;
+#endif
 	size_t lump;
 
 	size_t rot;
@@ -1223,20 +1225,26 @@ static void R_ProjectSprite(mobj_t *thing)
 	if (thing->skin && thing->sprite == SPR_PLAY)
 	{
 		sprdef = &((skin_t *)thing->skin)->sprites[thing->sprite2];
+#ifdef ROTSPRITE
 		sprinfo = &((skin_t *)thing->skin)->sprinfo[thing->sprite2];
+#endif
 		if (rot >= sprdef->numframes) {
 			CONS_Alert(CONS_ERROR, M_GetText("R_ProjectSprite: invalid skins[\"%s\"].sprites[%sSPR2_%s] frame %s\n"), ((skin_t *)thing->skin)->name, ((thing->sprite2 & FF_SPR2SUPER) ? "FF_SPR2SUPER|": ""), spr2names[(thing->sprite2 & ~FF_SPR2SUPER)], sizeu5(rot));
 			thing->sprite = states[S_UNKNOWN].sprite;
 			thing->frame = states[S_UNKNOWN].frame;
 			sprdef = &sprites[thing->sprite];
+#ifdef ROTSPRITE
 			sprinfo = NULL;
+#endif
 			rot = thing->frame&FF_FRAMEMASK;
 		}
 	}
 	else
 	{
 		sprdef = &sprites[thing->sprite];
+#ifdef ROTSPRITE
 		sprinfo = NULL;
+#endif
 	}
 
 	if (rot >= sprdef->numframes)