diff --git a/src/dehacked.c b/src/dehacked.c
index 78120c5c44aefae1072668c99e5c916be2ea0e92..0b89b87a201971904c65f03f59e962b89f86ed92 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -9506,6 +9506,7 @@ struct {
 	{"FF_GLOBALANIM",FF_GLOBALANIM},
 	{"FF_FULLBRIGHT",FF_FULLBRIGHT},
 	{"FF_VERTICALFLIP",FF_VERTICALFLIP},
+	{"FF_HORIZONTALFLIP",FF_HORIZONTALFLIP},
 	{"FF_PAPERSPRITE",FF_PAPERSPRITE},
 	{"FF_TRANSMASK",FF_TRANSMASK},
 	{"FF_TRANSSHIFT",FF_TRANSSHIFT},
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 2e4ea10ccf9a714a32475ca96abd889899548e18..6f039cc3a0d0bad5791f5eeaabe4a79796e3f339 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -50,7 +50,7 @@ EXPORT void HWRAPI(ClearMipMapCache) (void);
 EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value);
 
 //Hurdler: added for new development
-EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, FSurfaceInfo *Surface);
+EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface);
 EXPORT void HWRAPI(CreateModelVBOs) (model_t *model);
 EXPORT void HWRAPI(SetTransform) (FTransform *ptransform);
 EXPORT INT32 HWRAPI(GetTextureUsed) (void);
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 455985ddfba7d92c36118e356a8323a335fce673..f07cb24d87cc5e9ffcdfa3f7d4bebc4b0a894ad5 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -4636,6 +4636,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	unsigned rot;
 	UINT16 flip;
 	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !(thing->frame & FF_VERTICALFLIP));
+	boolean mirrored = thing->mirrored;
+	boolean hflip = (!(thing->frame & FF_HORIZONTALFLIP) != !mirrored);
 
 	angle_t ang;
 	INT32 heightsec, phs;
@@ -4724,6 +4726,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 #endif
 
 	ang = R_PointToAngle (thing->x, thing->y) - mobjangle;
+	if (mirrored)
+		ang = InvAngle(ang);
 
 	if (sprframe->rotate == SRF_SINGLE)
 	{
@@ -4796,6 +4800,8 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		rightcos = FIXED_TO_FLOAT(FINECOSINE((viewangle + ANGLE_90)>>ANGLETOFINESHIFT));
 	}
 
+	flip = !flip != !hflip;
+
 	if (flip)
 	{
 		x1 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_scale);
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 68b426f0c7fea5e015f758b474c0b24f8384a898..f5df49bcdef2ffa5a76d15f3f6605129de1c9b4c 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1242,6 +1242,7 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 		//mdlframe_t *next = NULL;
 		const boolean papersprite = (spr->mobj->frame & FF_PAPERSPRITE);
 		const UINT8 flip = (UINT8)(!(spr->mobj->eflags & MFE_VERTICALFLIP) != !(spr->mobj->frame & FF_VERTICALFLIP));
+		const UINT8 hflip = (UINT8)(!(spr->mobj->mirrored) != !(spr->mobj->frame & FF_HORIZONTALFLIP));
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
 		spriteinfo_t *sprinfo;
@@ -1514,7 +1515,7 @@ boolean HWR_DrawModel(gr_vissprite_t *spr)
 #endif
 
 		HWD.pfnSetShader(4);	// model shader
-		HWD.pfnDrawModel(md2->model, frame, durs, tics, nextFrame, &p, finalscale, flip, &Surf);
+		HWD.pfnDrawModel(md2->model, frame, durs, tics, nextFrame, &p, finalscale, flip, hflip, &Surf);
 	}
 
 	return true;
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 41ec703bc46c203c0eb77b822442b4c8495a0a38..e5f6ff3cfcc1c65a1266ee08248c8f80d910bb06 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -2615,7 +2615,7 @@ EXPORT void HWRAPI(CreateModelVBOs) (model_t *model)
 
 #define BUFFER_OFFSET(i) ((void*)(i))
 
-static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, FSurfaceInfo *Surface)
+static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface)
 {
 	static GLRGBAFloat poly = {0,0,0,0};
 	static GLRGBAFloat tint = {0,0,0,0};
@@ -2708,12 +2708,13 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	pglEnable(GL_NORMALIZE);
 
 #ifdef USE_FTRANSFORM_MIRROR
-	// flipped is if the object is flipped
+	// flipped is if the object is vertically flipped
+	// hflipped is if the object is horizontally flipped
 	// pos->flip is if the screen is flipped vertically
 	// pos->mirror is if the screen is flipped horizontally
 	// XOR all the flips together to figure out what culling to use!
 	{
-		boolean reversecull = (flipped ^ pos->flip ^ pos->mirror);
+		boolean reversecull = (flipped ^ hflipped ^ pos->flip ^ pos->mirror);
 		if (reversecull)
 			pglCullFace(GL_FRONT);
 		else
@@ -2721,7 +2722,7 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	}
 #else
 	// pos->flip is if the screen is flipped too
-	if (flipped != pos->flip) // If either are active, but not both, invert the model's culling
+	if (flipped ^ hflipped ^ pos->flip) // If one or three of these are active, but not two, invert the model's culling
 	{
 		pglCullFace(GL_FRONT);
 	}
@@ -2736,6 +2737,8 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 	pglTranslatef(pos->x, pos->z, pos->y);
 	if (flipped)
 		scaley = -scaley;
+	if (hflipped)
+		scalez = -scalez;
 
 #ifdef USE_FTRANSFORM_ANGLEZ
 	pglRotatef(pos->anglez, 0.0f, 0.0f, -1.0f); // rotate by slope from Kart
@@ -2882,9 +2885,9 @@ static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32
 // -----------------+
 // HWRAPI DrawModel : Draw a model
 // -----------------+
-EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, FSurfaceInfo *Surface)
+EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 hflipped, FSurfaceInfo *Surface)
 {
-	DrawModelEx(model, frameIndex, duration, tics, nextFrameIndex, pos, scale, flipped, Surface);
+	DrawModelEx(model, frameIndex, duration, tics, nextFrameIndex, pos, scale, flipped, hflipped, Surface);
 }
 
 // -----------------+
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 4d0efa9e205fe401feed77e9102673d09e5c7ef9..129339b96fde68c6eab8b3878c91aacadfa529f5 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -86,6 +86,7 @@ enum mobj_e {
 	mobj_cvmem,
 	mobj_standingslope,
 	mobj_colorized,
+	mobj_mirrored,
 	mobj_shadowscale
 };
 
@@ -152,6 +153,7 @@ static const char *const mobj_opt[] = {
 	"cvmem",
 	"standingslope",
 	"colorized",
+	"mirrored",
 	"shadowscale",
 	NULL};
 
@@ -385,6 +387,9 @@ static int mobj_get(lua_State *L)
 	case mobj_colorized:
 		lua_pushboolean(L, mo->colorized);
 		break;
+	case mobj_mirrored:
+		lua_pushboolean(L, mo->mirrored);
+		break;
 	case mobj_shadowscale:
 		lua_pushfixed(L, mo->shadowscale);
 		break;
@@ -713,6 +718,9 @@ static int mobj_set(lua_State *L)
 	case mobj_colorized:
 		mo->colorized = luaL_checkboolean(L, 3);
 		break;
+	case mobj_mirrored:
+		mo->mirrored = luaL_checkboolean(L, 3);
+		break;
 	case mobj_shadowscale:
 		mo->shadowscale = luaL_checkfixed(L, 3);
 		break;
diff --git a/src/p_mobj.h b/src/p_mobj.h
index dae8c8a86079726aa17ebf45f4283761960d26d0..c94dbd650310a2393ae3129bf028f8e5757c48c3 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -373,6 +373,7 @@ typedef struct mobj_s
 	struct pslope_s *standingslope; // The slope that the object is standing on (shouldn't need synced in savegames, right?)
 
 	boolean colorized; // Whether the mobj uses the rainbow colormap
+	boolean mirrored; // The object's rotations will be mirrored left to right, e.g., see frame AL from the right and AR from the left
 	fixed_t shadowscale; // If this object casts a shadow, and the size relative to radius
 
 	// WARNING: New fields must be added separately to savegame and Lua.
diff --git a/src/p_pspr.h b/src/p_pspr.h
index 3c10e9be431aae095acc10c8873a6d936520047e..231262beb3aa204b331103a42342369efb624259 100644
--- a/src/p_pspr.h
+++ b/src/p_pspr.h
@@ -64,8 +64,10 @@
 #define FF_FULLBRIGHT 0x00100000
 /// \brief Frame flags: Flip sprite vertically (relative to what it should be for its gravity)
 #define FF_VERTICALFLIP 0x00200000
+/// \brief Frame flags: Flip sprite horizontally
+#define FF_HORIZONTALFLIP 0x00400000
 /// \brief Frame flags: Thin, paper-like sprite (for collision equivalent, see MF_PAPERCOLLISION)
-#define FF_PAPERSPRITE 0x00400000
+#define FF_PAPERSPRITE 0x00800000
 
 /// \brief Frame flags - Animate: Simple stateless animation
 #define FF_ANIMATE 0x01000000
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 6f54467950d961df3a00f8ecef3454bf66234091..dd4ade115f647e92b52b1e559b91428fa0052370 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1294,8 +1294,9 @@ typedef enum
 	MD2_CEILINGROVER = 1<<10,
 	MD2_SLOPE        = 1<<11,
 	MD2_COLORIZED    = 1<<12,
-	MD2_ROLLANGLE    = 1<<13,
-	MD2_SHADOWSCALE  = 1<<14,
+	MD2_MIRRORED     = 1<<13,
+	MD2_ROLLANGLE    = 1<<14,
+	MD2_SHADOWSCALE  = 1<<15,
 } mobj_diff2_t;
 
 typedef enum
@@ -1500,6 +1501,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		diff2 |= MD2_SLOPE;
 	if (mobj->colorized)
 		diff2 |= MD2_COLORIZED;
+	if (mobj->mirrored)
+		diff2 |= MD2_MIRRORED;
 	if (mobj->rollangle)
 		diff2 |= MD2_ROLLANGLE;
 	if (mobj->shadowscale)
@@ -1638,6 +1641,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 		WRITEUINT16(save_p, mobj->standingslope->id);
 	if (diff2 & MD2_COLORIZED)
 		WRITEUINT8(save_p, mobj->colorized);
+	if (diff2 & MD2_MIRRORED)
+		WRITEUINT8(save_p, mobj->mirrored);
 	if (diff2 & MD2_ROLLANGLE)
 		WRITEANGLE(save_p, mobj->rollangle);
 	if (diff2 & MD2_SHADOWSCALE)
@@ -2641,6 +2646,8 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 		mobj->standingslope = P_SlopeById(READUINT16(save_p));
 	if (diff2 & MD2_COLORIZED)
 		mobj->colorized = READUINT8(save_p);
+	if (diff2 & MD2_MIRRORED)
+		mobj->mirrored = READUINT8(save_p);
 	if (diff2 & MD2_ROLLANGLE)
 		mobj->rollangle = READANGLE(save_p);
 	if (diff2 & MD2_SHADOWSCALE)
diff --git a/src/r_things.c b/src/r_things.c
index de5eb208f49bdebf4c5041bde1dbc0b883e83b38..d2647b8111b7c86a8674025bad6730472784475b 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -1367,6 +1367,8 @@ static void R_ProjectSprite(mobj_t *thing)
 	size_t frame, rot;
 	UINT16 flip;
 	boolean vflip = (!(thing->eflags & MFE_VERTICALFLIP) != !(thing->frame & FF_VERTICALFLIP));
+	boolean mirrored = thing->mirrored;
+	boolean hflip = (!(thing->frame & FF_HORIZONTALFLIP) != !mirrored);
 
 	INT32 lindex;
 
@@ -1477,7 +1479,11 @@ static void R_ProjectSprite(mobj_t *thing)
 #endif
 
 	if (sprframe->rotate != SRF_SINGLE || papersprite)
+	{
 		ang = R_PointToAngle (thing->x, thing->y) - (thing->player ? thing->player->drawangle : thing->angle);
+		if (mirrored)
+			ang = InvAngle(ang);
+	}
 
 	if (sprframe->rotate == SRF_SINGLE)
 	{
@@ -1537,6 +1543,8 @@ static void R_ProjectSprite(mobj_t *thing)
 	}
 #endif
 
+	flip = !flip != !hflip;
+
 	// calculate edges of the shape
 	if (flip)
 		offset = spr_offset - spr_width;