diff --git a/src/dehacked.c b/src/dehacked.c
index 2f28a74cf8e7787db06cddb7b6882f1278b8a02c..0df90c7019395090392d644d7d7d406adc8aae9d 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -2319,6 +2319,11 @@ static actionpointer_t actionpointers[] =
 	{{A_SpawnObjectRelative},    "A_SPAWNOBJECTRELATIVE"},
 	{{A_ChangeAngleRelative},    "A_CHANGEANGLERELATIVE"},
 	{{A_ChangeAngleAbsolute},    "A_CHANGEANGLEABSOLUTE"},
+#ifdef ROTSPRITE
+	{{A_RollAngle},              "A_ROLLANGLE"},
+	{{A_ChangeRollAngleRelative},"A_CHANGEROLLANGLERELATIVE"},
+	{{A_ChangeRollAngleAbsolute},"A_CHANGEROLLANGLEABSOLUTE"},
+#endif
 	{{A_PlaySound},              "A_PLAYSOUND"},
 	{{A_FindTarget},             "A_FINDTARGET"},
 	{{A_FindTracer},             "A_FINDTRACER"},
diff --git a/src/doomdef.h b/src/doomdef.h
index c948d780562961f0593cb3f4f4e672cb718b2a1f..767c4502017123691461863fbc99dbe207f377ec 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -489,6 +489,8 @@ extern INT32 cv_debug;
 // Misc stuff for later...
 // =======================
 
+#define ANG2RAD(angle) ((float)((angle)*M_PI)/ANGLE_180)
+
 // Modifier key variables, accessible anywhere
 extern UINT8 shiftdown, ctrldown, altdown;
 extern boolean capslock;
@@ -616,4 +618,9 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 ///      	SRB2CB itself ported this from PrBoom+
 #define NEWCLIP
 
+/// Sprite rotation
+#define ROTSPRITE
+#define ROTANGLES 24	// Needs to be a divisor of 360 (45, 60, 90, 120...)
+#define ROTANGDIFF (360 / ROTANGLES)
+
 #endif // __DOOMDEF__
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 6bc2c712e3f5c593d24aed10dd643c7134d76d50..8dbf165bb2772a6e0a287552cadea9c372b98e01 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -933,10 +933,14 @@ static void HWR_LoadMappedPatch(GLMipmap_t *grmip, GLPatch_t *gpatch)
 {
 	if (!grmip->downloaded && !grmip->grInfo.data)
 	{
-		patch_t *patch = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
+		patch_t *patch = gpatch->rawpatch;
+		if (!patch)
+			patch = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
 		HWR_MakePatch(patch, gpatch, grmip, true);
 
-		Z_Free(patch);
+		// You can't free rawpatch for some reason?
+		if (!gpatch->rawpatch)
+			Z_Free(patch);
 	}
 
 	HWD.pfnSetTexture(grmip);
@@ -955,12 +959,16 @@ void HWR_GetPatch(GLPatch_t *gpatch)
 	{
 		// load the software patch, PU_STATIC or the Z_Malloc for hardware patch will
 		// flush the software patch before the conversion! oh yeah I suffered
-		patch_t *ptr = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
+		patch_t *ptr = gpatch->rawpatch;
+		if (!ptr)
+			ptr = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
 		HWR_MakePatch(ptr, gpatch, &gpatch->mipmap, true);
 
 		// this is inefficient.. but the hardware patch in heap is purgeable so it should
 		// not fragment memory, and besides the REAL cache here is the hardware memory
-		Z_Free(ptr);
+		// You can't free rawpatch for some reason?
+		if (!gpatch->rawpatch)
+			Z_Free(ptr);
 	}
 
 	HWD.pfnSetTexture(&gpatch->mipmap);
@@ -1207,6 +1215,22 @@ GLPatch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum)
 	return HWR_GetCachedGLPatchPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
 }
 
+#ifdef ROTSPRITE
+GLPatch_t *HWR_GetCachedGLRotSprite(aatree_t *hwrcache, UINT16 rollangle, patch_t *rawpatch)
+{
+	GLPatch_t *grpatch;
+
+	if (!(grpatch = M_AATreeGet(hwrcache, rollangle)))
+	{
+		grpatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, NULL);
+		grpatch->rawpatch = rawpatch;
+		M_AATreeSet(hwrcache, rollangle, grpatch);
+	}
+
+	return grpatch;
+}
+#endif
+
 // Need to do this because they aren't powers of 2
 static void HWR_DrawFadeMaskInCache(GLMipmap_t *mipmap, INT32 pblockwidth, INT32 pblockheight,
 	lumpnum_t fademasklumpnum, UINT16 fmwidth, UINT16 fmheight)
diff --git a/src/hardware/hw_data.h b/src/hardware/hw_data.h
index 44929dd67ba00267173afde6f4fa55a50440c107..a8f030f73950171bfefb0514562983bf16211d71 100644
--- a/src/hardware/hw_data.h
+++ b/src/hardware/hw_data.h
@@ -83,6 +83,7 @@ struct GLPatch_s
 	float               max_s,max_t;
 	UINT16              wadnum;      // the software patch lump num for when the hardware patch
 	UINT16              lumpnum;     // was flushed, and we need to re-create it
+	void                *rawpatch;   // :^)
 	GLMipmap_t          mipmap;
 };
 typedef struct GLPatch_s GLPatch_t;
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 5dcead77cb9c59ed4d192feadf64ecde878802ad..b8354c82d14b04291e30826e7a63db670149bc6d 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -103,6 +103,11 @@ typedef struct
 	FLOAT       fovxangle, fovyangle;
 	INT32       splitscreen;
 	boolean     flip;            // screenflip
+#ifdef ROTSPRITE
+	// rotsprite
+	boolean     roll;
+	FLOAT       centerx,centery;
+#endif
 } FTransform;
 
 // Transformed vector, as passed to HWR API
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index 9656e54e9b1d44df9bc921cbb544a03e8101a26d..015890f612253e946d51bfc1223768852523f501 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -72,7 +72,8 @@ typedef struct gr_vissprite_s
 	struct gr_vissprite_s *next;
 	float x1, x2;
 	float tz, ty;
-	lumpnum_t patchlumpnum;
+	//lumpnum_t patchlumpnum;
+	GLPatch_t *gpatch;
 	boolean flip;
 	UINT8 translucency;       //alpha level 0-255
 	mobj_t *mobj;
@@ -109,6 +110,9 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum);
 void HWR_SetPalette(RGBA_t *palette);
 GLPatch_t *HWR_GetCachedGLPatchPwad(UINT16 wad, UINT16 lump);
 GLPatch_t *HWR_GetCachedGLPatch(lumpnum_t lumpnum);
+#ifdef ROTSPRITE
+GLPatch_t *HWR_GetCachedGLRotSprite(aatree_t *hwrcache, UINT16 rollangle, patch_t *rawpatch);
+#endif
 void HWR_GetFadeMask(lumpnum_t fademasklumpnum);
 
 // --------
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index c600800fd9ac9e0710a0eecdf44d7e7bf8a68753..ade62bfcfb81677e4fa64dfbf2a7cefef69b5354 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -4332,7 +4332,7 @@ static void HWR_SplitSprite(gr_vissprite_t *spr)
 	if (hires)
 		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)spr->mobj->skin)->highresscale);
 
-	gpatch = W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+	gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
 
 	// cache the patch in the graphics card memory
 	//12/12/99: Hurdler: same comment as above (for md2)
@@ -4688,7 +4688,7 @@ static void HWR_DrawSprite(gr_vissprite_t *spr)
 	//          sure to do it the right way. So actually, we keep normal sprite
 	//          in memory and we add the md2 model if it exists for that sprite
 
-	gpatch = W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+	gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
 
 #ifdef ALAM_LIGHTING
 	if (!(spr->mobj->flags2 & MF2_DEBRIS) && (spr->mobj->sprite != SPR_PLAY ||
@@ -4833,7 +4833,7 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr)
 		return;
 
 	// cache sprite graphics
-	gpatch = W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+	gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
 
 	// create the sprite billboard
 	//
@@ -5477,10 +5477,22 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	angle_t mobjangle = (thing->player ? thing->player->drawangle : thing->angle);
 	float z1, z2;
 
+	fixed_t spr_width, spr_height;
+	fixed_t spr_offset, spr_topoffset;
+#ifdef ROTSPRITE
+	patch_t *rotsprite = NULL;
+	angle_t arollangle;
+	UINT32 rollangle;
+#endif
+
 	if (!thing)
 		return;
-	else
-		this_scale = FIXED_TO_FLOAT(thing->scale);
+
+#ifdef ROTSPRITE
+	arollangle = thing->rollangle;
+	rollangle = AngleFixed(arollangle)>>FRACBITS;
+#endif
+	this_scale = FIXED_TO_FLOAT(thing->scale);
 
 	// transform the origin point
 	tr_x = FIXED_TO_FLOAT(thing->x) - gr_viewx;
@@ -5573,6 +5585,30 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
 		this_scale = this_scale * FIXED_TO_FLOAT(((skin_t *)thing->skin)->highresscale);
 
+	spr_width = spritecachedinfo[lumpoff].width;
+	spr_height = spritecachedinfo[lumpoff].height;
+	spr_offset = spritecachedinfo[lumpoff].offset;
+	spr_topoffset = spritecachedinfo[lumpoff].topoffset;
+
+#ifdef ROTSPRITE
+	if (rollangle > 0)
+	{
+		if (!sprframe->rotsprite.cached[rot])
+			R_CacheRotSprite(sprframe, rot, flip);
+		rollangle /= ROTANGDIFF;
+		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
+		if (rotsprite != NULL)
+		{
+			spr_width = rotsprite->width << FRACBITS;
+			spr_height = rotsprite->height << FRACBITS;
+			spr_offset = rotsprite->leftoffset << FRACBITS;
+			spr_topoffset = rotsprite->topoffset << FRACBITS;
+			// flip -> rotate, not rotate -> flip
+			flip = 0;
+		}
+	}
+#endif
+
 	if (papersprite)
 	{
 		rightsin = FIXED_TO_FLOAT(FINESINE((mobjangle)>>ANGLETOFINESHIFT));
@@ -5586,13 +5622,13 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	if (flip)
 	{
-		x1 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale);
-		x2 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale);
+		x1 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_scale);
+		x2 = (FIXED_TO_FLOAT(spr_offset) * this_scale);
 	}
 	else
 	{
-		x1 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].offset) * this_scale);
-		x2 = (FIXED_TO_FLOAT(spritecachedinfo[lumpoff].width - spritecachedinfo[lumpoff].offset) * this_scale);
+		x1 = (FIXED_TO_FLOAT(spr_offset) * this_scale);
+		x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_scale);
 	}
 
 	// test if too close
@@ -5614,13 +5650,13 @@ static void HWR_ProjectSprite(mobj_t *thing)
 
 	if (vflip)
 	{
-		gz = FIXED_TO_FLOAT(thing->z+thing->height) - FIXED_TO_FLOAT(spritecachedinfo[lumpoff].topoffset) * this_scale;
-		gzt = gz + FIXED_TO_FLOAT(spritecachedinfo[lumpoff].height) * this_scale;
+		gz = FIXED_TO_FLOAT(thing->z+thing->height) - FIXED_TO_FLOAT(spr_topoffset) * this_scale;
+		gzt = gz + FIXED_TO_FLOAT(spr_height) * this_scale;
 	}
 	else
 	{
-		gzt = FIXED_TO_FLOAT(thing->z) + FIXED_TO_FLOAT(spritecachedinfo[lumpoff].topoffset) * this_scale;
-		gz = gzt - FIXED_TO_FLOAT(spritecachedinfo[lumpoff].height) * this_scale;
+		gzt = FIXED_TO_FLOAT(thing->z) + FIXED_TO_FLOAT(spr_topoffset) * this_scale;
+		gz = gzt - FIXED_TO_FLOAT(spr_height) * this_scale;
 	}
 
 	if (thing->subsector->sector->cullheight)
@@ -5653,7 +5689,13 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	vis->x2 = x2;
 	vis->tz = tz; // Keep tz for the simple sprite sorting that happens
 	vis->dispoffset = thing->info->dispoffset; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
-	vis->patchlumpnum = sprframe->lumppat[rot];
+	//vis->patchlumpnum = sprframe->lumppat[rot];
+#ifdef ROTSPRITE
+	if (rotsprite)
+		vis->gpatch = (GLPatch_t *)rotsprite;
+	else
+#endif
+		vis->gpatch = (GLPatch_t *)W_CachePatchNum(sprframe->lumppat[rot], PU_CACHE);
 	vis->flip = flip;
 	vis->mobj = thing;
 	vis->z1 = z1;
@@ -5779,7 +5821,8 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing)
 	vis->z2 = z2;
 	vis->tz = tz;
 	vis->dispoffset = 0; // Monster Iestyn: 23/11/15: HARDWARE SUPPORT AT LAST
-	vis->patchlumpnum = sprframe->lumppat[rot];
+	//vis->patchlumpnum = sprframe->lumppat[rot];
+	vis->gpatch = (GLPatch_t *)W_CachePatchNum(sprframe->lumppat[rot], PU_CACHE);
 	vis->flip = flip;
 	vis->mobj = (mobj_t *)thing;
 
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index d4728315ad88f97e5f1f16ac16ed037a8f7376ce..072b17e4172d56e2d395a7af09453f0cfd6dec9a 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1401,7 +1401,7 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 		else
 		{
 			// Sprite
-			gpatch = W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
+			gpatch = spr->gpatch; //W_CachePatchNum(spr->patchlumpnum, PU_CACHE);
 			HWR_GetMappedPatch(gpatch, spr->colormap);
 		}
 
@@ -1503,7 +1503,22 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			const fixed_t anglef = AngleFixed((R_PointToAngle(spr->mobj->x, spr->mobj->y))-ANGLE_180);
 			p.angley = FIXED_TO_FLOAT(anglef);
 		}
-		p.anglex = 0.0f;
+
+		// rotsprite
+#ifdef ROTSPRITE
+		if (spr->mobj->rollangle)
+		{
+			// do i have to support ROTANGLES here??????
+			fixed_t anglef = AngleFixed(spr->mobj->rollangle);
+			p.anglex = FIXED_TO_FLOAT(anglef);
+			// pivot
+			p.centerx = FIXED_TO_FLOAT(spr->mobj->radius/2);
+			p.centery = FIXED_TO_FLOAT(spr->mobj->height/2);
+			p.roll = true;
+		}
+		else
+#endif
+			p.anglex = 0.0f;
 
 		color[0] = Surf.FlatColor.s.red;
 		color[1] = Surf.FlatColor.s.green;
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index dfee19857cca3881adbd9dc123fd56b48aef7416..e91f29cda17db9b4136bb222e243ee22c6a33dad 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -1636,7 +1636,18 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	if (flipped)
 		scaley = -scaley;
 	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
-	pglRotatef(pos->anglex, -1.0f, 0.0f, 0.0f);
+
+#ifdef ROTSPRITE
+	// rotsprite
+	if (pos->roll)
+	{
+		pglTranslatef(pos->centerx, pos->centery, 0);
+		pglRotatef(pos->anglex, 1.0f, 0.0f, 0.0f);
+		pglTranslatef(-pos->centerx, -pos->centery, 0);
+	}
+	else
+#endif
+		pglRotatef(pos->anglex, -1.0f, 0.0f, 0.0f);
 
 	val = *gl_cmd_buffer++;
 
diff --git a/src/info.c b/src/info.c
index 9aed563c7a4316ae707c4ddbb82194372977f6f8..656add08ce8d996b7f0a455a483a71c61ac4727a 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2408,7 +2408,11 @@ state_t states[NUMSTATES] =
 	{SPR_BARX, 2|FF_FULLBRIGHT, 3, {NULL}, 0, 0, S_TNTBARREL_EXPL5}, // S_TNTBARREL_EXPL4
 	{SPR_BARX, 3|FF_FULLBRIGHT, 3, {NULL}, 0, 0, S_TNTBARREL_EXPL6}, // S_TNTBARREL_EXPL5
 	{SPR_NULL, 0, 35, {NULL}, 0, 0, S_NULL}, // S_TNTBARREL_EXPL6
+#ifndef ROTSPRITE
 	{SPR_BARR, 1|FF_ANIMATE, -1, {NULL}, 7, 2, S_NULL}, // S_TNTBARREL_FLYING
+#else
+	{SPR_BARR, 1, 1, {A_RollAngle}, 14, 0, S_TNTBARREL_FLYING}, // S_TNTBARREL_FLYING
+#endif
 
 	// TNT proximity shell
 	{SPR_REMT, 0, 10, {A_Look}, 33554433, 0, S_PROXIMITY_TNT}, // S_PROXIMITY_TNT
diff --git a/src/info.h b/src/info.h
index bb27ac3e3b299681495fbd4f8bfad07324be61e9..89c96922663d13388602dbfaf2074140e25357eb 100644
--- a/src/info.h
+++ b/src/info.h
@@ -153,6 +153,11 @@ void A_SpawnObjectAbsolute();
 void A_SpawnObjectRelative();
 void A_ChangeAngleRelative();
 void A_ChangeAngleAbsolute();
+#ifdef ROTSPRITE
+void A_RollAngle();
+void A_ChangeRollAngleRelative();
+void A_ChangeRollAngleAbsolute();
+#endif
 void A_PlaySound();
 void A_FindTarget();
 void A_FindTracer();
diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c
index 063158b263b58b4f4a6b7764a6c3075a4e72867c..71fdab7292cdca299524376222de2d9be8df22fe 100644
--- a/src/lua_mobjlib.c
+++ b/src/lua_mobjlib.c
@@ -32,6 +32,9 @@ enum mobj_e {
 	mobj_snext,
 	mobj_sprev,
 	mobj_angle,
+#ifdef ROTSPRITE
+	mobj_rollangle,
+#endif
 	mobj_sprite,
 	mobj_frame,
 	mobj_sprite2,
@@ -98,6 +101,9 @@ static const char *const mobj_opt[] = {
 	"snext",
 	"sprev",
 	"angle",
+#ifdef ROTSPRITE
+	"rollangle",
+#endif
 	"sprite",
 	"frame",
 	"sprite2",
@@ -197,6 +203,11 @@ static int mobj_get(lua_State *L)
 	case mobj_angle:
 		lua_pushangle(L, mo->angle);
 		break;
+#ifdef ROTSPRITE
+	case mobj_rollangle:
+		lua_pushangle(L, mo->rollangle);
+		break;
+#endif
 	case mobj_sprite:
 		lua_pushinteger(L, mo->sprite);
 		break;
@@ -447,6 +458,11 @@ static int mobj_set(lua_State *L)
 		else if (mo->player == &players[secondarydisplayplayer])
 			localangle2 = mo->angle;
 		break;
+#ifdef ROTSPRITE
+	case mobj_rollangle:
+		mo->rollangle = luaL_checkangle(L, 3);
+		break;
+#endif
 	case mobj_sprite:
 		mo->sprite = luaL_checkinteger(L, 3);
 		break;
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 4126d071683a3b9d1faffcc62870e881d18a0d76..467c4dc261c7582f12edbcd017d6247e9b6c317b 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -179,6 +179,11 @@ void A_SpawnObjectAbsolute(mobj_t *actor);
 void A_SpawnObjectRelative(mobj_t *actor);
 void A_ChangeAngleRelative(mobj_t *actor);
 void A_ChangeAngleAbsolute(mobj_t *actor);
+#ifdef ROTSPRITE
+void A_RollAngle(mobj_t *actor);
+void A_ChangeRollAngleRelative(mobj_t *actor);
+void A_ChangeRollAngleAbsolute(mobj_t *actor);
+#endif // ROTSPRITE
 void A_PlaySound(mobj_t *actor);
 void A_FindTarget(mobj_t *actor);
 void A_FindTracer(mobj_t *actor);
@@ -8172,7 +8177,7 @@ void A_ChangeAngleRelative(mobj_t *actor)
 
 #ifdef PARANOIA
 	if (amin > amax)
-		I_Error("A_ChangeAngleRelative: var1 is greater then var2");
+		I_Error("A_ChangeAngleRelative: var1 is greater than var2");
 #endif
 /*
 	if (angle < amin)
@@ -8206,7 +8211,7 @@ void A_ChangeAngleAbsolute(mobj_t *actor)
 
 #ifdef PARANOIA
 	if (amin > amax)
-		I_Error("A_ChangeAngleAbsolute: var1 is greater then var2");
+		I_Error("A_ChangeAngleAbsolute: var1 is greater than var2");
 #endif
 /*
 	if (angle < amin)
@@ -8217,6 +8222,105 @@ void A_ChangeAngleAbsolute(mobj_t *actor)
 	actor->angle = FixedAngle(P_RandomRange(amin, amax));
 }
 
+#ifdef ROTSPRITE
+// Function: A_RollAngle
+//
+// Description: Changes the roll angle.
+//
+// var1 = angle
+// var2 = relative? (default)
+//
+void A_RollAngle(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	const angle_t angle = FixedAngle(locvar1*FRACUNIT);
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_RollAngle", actor))
+		return;
+#endif
+
+	// relative (default)
+	if (!locvar2)
+		actor->rollangle += angle;
+	// absolute
+	else
+		actor->rollangle = angle;
+}
+
+// Function: A_ChangeRollAngleRelative
+//
+// Description: Changes the roll angle to a random relative value between the min and max. Set min and max to the same value to eliminate randomness
+//
+// var1 = min
+// var2 = max
+//
+void A_ChangeRollAngleRelative(mobj_t *actor)
+{
+	// Oh god, the old code /sucked/. Changed this and the absolute version to get a random range using amin and amax instead of
+	//  getting a random angle from the _entire_ spectrum and then clipping. While we're at it, do the angle conversion to the result
+	//  rather than the ranges, so <0 and >360 work as possible values. -Red
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	//angle_t angle = (P_RandomByte()+1)<<24;
+	const fixed_t amin = locvar1*FRACUNIT;
+	const fixed_t amax = locvar2*FRACUNIT;
+	//const angle_t amin = FixedAngle(locvar1*FRACUNIT);
+	//const angle_t amax = FixedAngle(locvar2*FRACUNIT);
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_ChangeRollAngleRelative", actor))
+		return;
+#endif
+
+#ifdef PARANOIA
+	if (amin > amax)
+		I_Error("A_ChangeRollAngleRelative: var1 is greater than var2");
+#endif
+/*
+	if (angle < amin)
+		angle = amin;
+	if (angle > amax)
+		angle = amax;*/
+
+	actor->rollangle += FixedAngle(P_RandomRange(amin, amax));
+}
+
+// Function: A_ChangeRollAngleAbsolute
+//
+// Description: Changes the roll angle to a random absolute value between the min and max. Set min and max to the same value to eliminate randomness
+//
+// var1 = min
+// var2 = max
+//
+void A_ChangeRollAngleAbsolute(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	//angle_t angle = (P_RandomByte()+1)<<24;
+	const fixed_t amin = locvar1*FRACUNIT;
+	const fixed_t amax = locvar2*FRACUNIT;
+	//const angle_t amin = FixedAngle(locvar1*FRACUNIT);
+	//const angle_t amax = FixedAngle(locvar2*FRACUNIT);
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_ChangeRollAngleAbsolute", actor))
+		return;
+#endif
+
+#ifdef PARANOIA
+	if (amin > amax)
+		I_Error("A_ChangeRollAngleAbsolute: var1 is greater than var2");
+#endif
+/*
+	if (angle < amin)
+		angle = amin;
+	if (angle > amax)
+		angle = amax;*/
+
+	actor->rollangle = FixedAngle(P_RandomRange(amin, amax));
+}
+#endif // ROTSPRITE
+
 // Function: A_PlaySound
 //
 // Description: Plays a sound
diff --git a/src/p_mobj.h b/src/p_mobj.h
index a9d5244b044ad58840238d753e2e70184e2fc585..b1eda9fb6da3ded95746c806cf6734d5720f09c1 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -277,6 +277,9 @@ typedef struct mobj_s
 
 	// More drawing info: to determine current sprite.
 	angle_t angle;  // orientation
+#ifdef ROTSPRITE
+	angle_t rollangle;
+#endif
 	spritenum_t sprite; // used to find patch_t and flip value
 	UINT32 frame; // frame number, plus bits see p_pspr.h
 	UINT8 sprite2; // player sprites
@@ -397,6 +400,9 @@ typedef struct precipmobj_s
 
 	// More drawing info: to determine current sprite.
 	angle_t angle;  // orientation
+#ifdef ROTSPRITE
+	angle_t rollangle;
+#endif
 	spritenum_t sprite; // used to find patch_t and flip value
 	UINT32 frame; // frame number, plus bits see p_pspr.h
 	UINT8 sprite2; // player sprites
diff --git a/src/p_saveg.c b/src/p_saveg.c
index ea998b445e40c90135d0454ce412367373fc42d8..c557b2517058b6ae207ce21ee3f17751598ed706 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1267,6 +1267,9 @@ typedef enum
 	MD2_SLOPE        = 1<<11,
 #endif
 	MD2_COLORIZED    = 1<<12,
+#ifdef ROTSPRITE
+	MD2_ROLLANGLE    = 1<<13,
+#endif
 } mobj_diff2_t;
 
 typedef enum
@@ -1486,6 +1489,10 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 #endif
 	if (mobj->colorized)
 		diff2 |= MD2_COLORIZED;
+#ifdef ROTSPRITE
+	if (mobj->rollangle)
+		diff2 |= MD2_ROLLANGLE;
+#endif
 	if (diff2 != 0)
 		diff |= MD_MORE;
 
@@ -1650,6 +1657,10 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type)
 #endif
 	if (diff2 & MD2_COLORIZED)
 		WRITEUINT8(save_p, mobj->colorized);
+#ifdef ROTSPRITE
+	if (diff2 & MD2_ROLLANGLE)
+		WRITEANGLE(save_p, mobj->rollangle);
+#endif
 
 	WRITEUINT32(save_p, mobj->mobjnum);
 }
@@ -2726,6 +2737,12 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker)
 #endif
 	if (diff2 & MD2_COLORIZED)
 		mobj->colorized = READUINT8(save_p);
+#ifdef ROTSPRITE
+	if (diff2 & MD2_ROLLANGLE)
+		mobj->rollangle = READANGLE(save_p);
+	else
+		mobj->rollangle = 0;
+#endif
 
 	if (diff & MD_REDFLAG)
 	{
diff --git a/src/p_setup.c b/src/p_setup.c
index d0cd14b22d3011a64e26a23e44be00739420adeb..3b533ce43008da0344ea626a452faaa75f875c2d 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -540,9 +540,10 @@ levelflat_t *levelflats;
 size_t P_PrecacheLevelFlats(void)
 {
 	lumpnum_t lump;
-	size_t i, flatmemory = 0;
+	size_t i;
 
 	//SoM: 4/18/2000: New flat code to make use of levelflats.
+	flatmemory = 0;
 	for (i = 0; i < numlevelflats; i++)
 	{
 		lump = levelflats[i].lumpnum;
diff --git a/src/p_user.c b/src/p_user.c
index b74cafd2180b28951d65145c081d9670095f3f34..4fad1c08760dbb273e9bce8718a44c0f437a5b8e 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -6706,6 +6706,11 @@ static void P_NiGHTSMovement(player_t *player)
 	INT32 i;
 	statenum_t flystate;
 	UINT16 visangle;
+#ifdef ROTSPRITE
+	angle_t rollangle = 0;
+	UINT8 turningstate = 0;
+	UINT8 turndiff = 24;
+#endif
 
 	player->pflags &= ~PF_DRILLING;
 
@@ -6890,6 +6895,9 @@ static void P_NiGHTSMovement(player_t *player)
 		&& player->mo->state <= &states[S_PLAY_NIGHTS_TRANS6])
 	{
 		player->mo->momx = player->mo->momy = player->mo->momz = 0;
+#ifdef ROTSPRITE
+		player->mo->rollangle = 0;
+#endif
 		return;
 	}
 
@@ -6904,6 +6912,9 @@ static void P_NiGHTSMovement(player_t *player)
 			P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_DRILL6);
 
 		player->mo->flags |= MF_NOCLIPHEIGHT;
+#ifdef ROTSPRITE
+		player->mo->rollangle = 0;
+#endif
 
 		return;
 	}
@@ -7176,6 +7187,7 @@ static void P_NiGHTSMovement(player_t *player)
 		flystate = (P_IsObjectOnGround(player->mo)) ? S_PLAY_NIGHTS_STAND : S_PLAY_NIGHTS_FLOAT;
 	else
 	{
+#ifndef ROTSPRITE
 		visangle = ((player->anotherflyangle + 7) % 360)/15;
 		if (visangle > 18) // Over 270 degrees.
 			visangle = 30 - visangle;
@@ -7192,15 +7204,55 @@ static void P_NiGHTSMovement(player_t *player)
 				visangle += 6; // shift to S_PLAY_NIGHTS_FLY7-C
 		}
 
-		flystate = S_PLAY_NIGHTS_FLY0 + (visangle*2); // S_PLAY_FLY0-C - the *2 is to skip over drill states
+		flystate = S_PLAY_NIGHTS_FLY0 + (visangle*2); // S_PLAY_NIGHTS_FLY0-C - the *2 is to skip over drill states
+
+		if (player->pflags & PF_DRILLING)
+			flystate++; // shift to S_PLAY_NIGHTS_DRILL0-C
+#else
+		angle_t a = R_PointToAngle(player->mo->x, player->mo->y) - player->mo->angle;
+		visangle = (player->flyangle % 360);
 
+		flystate = S_PLAY_NIGHTS_FLY0;
 		if (player->pflags & PF_DRILLING)
 			flystate++; // shift to S_PLAY_NIGHTS_DRILL0-C
+		else
+		{
+			if ((visangle >= (90-turndiff) && visangle <= (90+turndiff))
+			|| (visangle >= (270-turndiff) && visangle <= (270+turndiff)))
+			{
+				turningstate = 3;
+				flystate = S_PLAY_NIGHTS_DRILL0;
+			}
+		}
+
+		if (player->flyangle >= 90 && player->flyangle <= 270)
+		{
+			if (player->flyangle == 270 && (a < ANGLE_180))
+				;
+			else if (player->flyangle == 90 && (a < ANGLE_180))
+				;
+			else
+				visangle += 180;
+		}
+
+		rollangle = FixedAngle(visangle*FRACUNIT);
+#endif // ROTSPRITE
 	}
 
 	if (player->mo->state != &states[flystate])
 		P_SetPlayerMobjState(player->mo, flystate);
 
+#ifdef ROTSPRITE
+	player->mo->rollangle = rollangle;
+	if (turningstate)
+	{
+		player->mo->frame = turningstate;
+		player->mo->tics = -1;
+	}
+	else if (player->mo->tics == -1)
+		player->mo->tics = states[flystate].tics;
+#endif // ROTSPRITE
+
 	if (player == &players[consoleplayer])
 		localangle = player->mo->angle;
 	else if (player == &players[secondarydisplayplayer])
diff --git a/src/r_data.c b/src/r_data.c
index 6889bdddebf587ce22b3526066fca43aec364ceb..bb437719e7fac9dd530ba0db18f1cb99b0eae362 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -114,7 +114,7 @@ sprcache_t *spritecachedinfo;
 lighttable_t *colormaps;
 
 // for debugging/info purposes
-static size_t flatmemory, spritememory, texturememory;
+size_t flatmemory, spritememory, texturememory;
 
 // highcolor stuff
 INT16 color8to16[256]; // remap color index to highcolor rgb value
@@ -2306,3 +2306,211 @@ void R_PrecacheLevel(void)
 			"texturememory: %s k\n"
 			"spritememory:  %s k\n", sizeu1(flatmemory>>10), sizeu2(texturememory>>10), sizeu3(spritememory>>10));
 }
+
+#ifdef ROTSPRITE
+// https://github.com/coelckers/prboom-plus/blob/master/prboom2/src/r_patch.c#L350
+boolean R_CheckIfPatch(lumpnum_t lump)
+{
+	size_t size;
+	INT16 width, height;
+	patch_t *patch;
+	boolean result;
+
+	size = W_LumpLength(lump);
+
+	// minimum length of a valid Doom patch
+	if (size < 13)
+		return false;
+
+	patch = (patch_t *)W_CacheLumpNum(lump, PU_STATIC);
+
+	width = SHORT(patch->width);
+	height = SHORT(patch->height);
+
+	result = (height > 0 && height <= 16384 && width > 0 && width <= 16384 && width < (INT16)(size / 4));
+
+	if (result)
+	{
+		// The dimensions seem like they might be valid for a patch, so
+		// check the column directory for extra security. All columns
+		// must begin after the column directory, and none of them must
+		// point past the end of the patch.
+		INT16 x;
+
+		for (x = 0; x < width; x++)
+		{
+			UINT32 ofs = LONG(patch->columnofs[x]);
+
+			// Need one byte for an empty column (but there's patches that don't know that!)
+			if (ofs < (UINT32)width * 4 + 8 || ofs >= (UINT32)size)
+			{
+				result = false;
+				break;
+			}
+		}
+	}
+
+	return result;
+}
+
+// https://github.com/Jimita/SRB2/blob/flats-png/src/r_data.c#L2157
+void R_PatchToFlat(patch_t *patch, UINT16 *raw, boolean flip)
+{
+	fixed_t col, ofs;
+	column_t *column;
+	UINT16 *desttop, *dest, *deststop;
+	UINT8 *source;
+
+	desttop = raw;
+	deststop = desttop + (SHORT(patch->width) * SHORT(patch->height));
+
+	#define DEAR_GOD_FORGIVE_ME_FOR_MY_MACROS \
+	{ \
+		INT32 topdelta, prevdelta = -1; \
+		column = (column_t *)((UINT8 *)patch + LONG(patch->columnofs[col])); \
+		while (column->topdelta != 0xff) \
+		{ \
+			topdelta = column->topdelta; \
+			if (topdelta <= prevdelta) \
+				topdelta += prevdelta; \
+			prevdelta = topdelta; \
+			dest = desttop + (topdelta * SHORT(patch->width)); \
+			source = (UINT8 *)(column) + 3; \
+			for (ofs = 0; dest < deststop && ofs < column->length; ofs++) \
+			{ \
+				*dest = source[ofs]; \
+				dest += SHORT(patch->width); \
+			} \
+			column = (column_t *)((UINT8 *)column + column->length + 4); \
+		} \
+	}
+
+	if (!flip)
+	{
+		for (col = 0; col < SHORT(patch->width); col++, desttop++)
+			DEAR_GOD_FORGIVE_ME_FOR_MY_MACROS
+	}
+	else
+	{
+		// flipped
+		for (col = SHORT(patch->width)-1; col >= 0; col--, desttop++)
+			DEAR_GOD_FORGIVE_ME_FOR_MY_MACROS
+	}
+}
+
+// https://github.com/Jimita/SRB2/blob/flats-png/src/r_data.c#L1970
+static UINT8 imgbuf[1<<26];
+patch_t *R_FlatToPatch(UINT16 *raw, UINT16 width, UINT16 height, size_t *size)
+{
+	UINT32 x, y;
+	UINT8 *img;
+	UINT8 *imgptr = imgbuf;
+	UINT8 *colpointers, *startofspan;
+
+	#define WRITE8(buf, a) ({*buf = (a); buf++;})
+	#define WRITE16(buf, a) ({*buf = (a)&255; buf++; *buf = (a)>>8; buf++;})
+	#define WRITE32(buf, a) ({WRITE16(buf, (a)&65535); WRITE16(buf, (a)>>16);})
+
+	if (!raw)
+		return NULL;
+
+	// Write image size and offset
+	WRITE16(imgptr, width);
+	WRITE16(imgptr, height);
+	// no offsets
+	WRITE16(imgptr, 0);
+	WRITE16(imgptr, 0);
+
+	// Leave placeholder to column pointers
+	colpointers = imgptr;
+	imgptr += width*4;
+
+	// Write columns
+	for (x = 0; x < width; x++)
+	{
+		int lastStartY = 0;
+		int spanSize = 0;
+		startofspan = NULL;
+
+		//printf("%d ", x);
+		// Write column pointer (@TODO may be wrong)
+		WRITE32(colpointers, imgptr - imgbuf);
+
+		// Write pixels
+		for (y = 0; y < height; y++)
+		{
+			UINT16 pixel = raw[((y * width) + x)];
+			UINT8 paletteIndex = (pixel & 0xFF);
+			UINT8 opaque = (pixel != 0xFF00); // If 1, we have a pixel
+
+			// End span if we have a transparent pixel
+			if (!opaque)
+			{
+				if (startofspan)
+					WRITE8(imgptr, 0);
+				startofspan = NULL;
+				continue;
+			}
+
+			// Start new column if we need to
+			if (!startofspan || spanSize == 255)
+			{
+				int writeY = y;
+
+				// If we reached the span size limit, finish the previous span
+				if (startofspan)
+					WRITE8(imgptr, 0);
+
+				if (y > 254)
+				{
+					// Make sure we're aligned to 254
+					if (lastStartY < 254)
+					{
+						WRITE8(imgptr, 254);
+						WRITE8(imgptr, 0);
+						imgptr += 2;
+						lastStartY = 254;
+					}
+
+					// Write stopgap empty spans if needed
+					writeY = y - lastStartY;
+
+					while (writeY > 254)
+					{
+						WRITE8(imgptr, 254);
+						WRITE8(imgptr, 0);
+						imgptr += 2;
+						writeY -= 254;
+					}
+				}
+
+				startofspan = imgptr;
+				WRITE8(imgptr, writeY);///@TODO calculate starting y pos
+				imgptr += 2;
+				spanSize = 0;
+
+				lastStartY = y;
+			}
+
+			// Write the pixel
+			WRITE8(imgptr, paletteIndex);
+			spanSize++;
+			startofspan[1] = spanSize;
+		}
+
+		if (startofspan)
+			WRITE8(imgptr, 0);
+
+		WRITE8(imgptr, 0xFF);
+	}
+
+	#undef WRITE8
+	#undef WRITE16
+	#undef WRITE32
+
+	*size = imgptr-imgbuf;
+	img = malloc(*size);
+	memcpy(img, imgbuf, *size);
+	return (patch_t *)img;
+}
+#endif
diff --git a/src/r_data.h b/src/r_data.h
index b6b0a16a15fad21fb90aa0325721d177a1c6eccc..5d4afe791fc8ac82152764aa79f8e5efae17d419 100644
--- a/src/r_data.h
+++ b/src/r_data.h
@@ -77,13 +77,20 @@ void R_CheckTextureCache(INT32 tex);
 
 // Retrieve column data for span blitting.
 UINT8 *R_GetColumn(fixed_t tex, INT32 col);
-
 UINT8 *R_GetFlat(lumpnum_t flatnum);
 
+#ifdef ROTSPRITE
+boolean R_CheckIfPatch(lumpnum_t lump);
+void R_PatchToFlat(patch_t *patch, UINT16 *raw, boolean flip);
+patch_t *R_FlatToPatch(UINT16 *raw, UINT16 width, UINT16 height, size_t *size);
+#endif
+
 // I/O, setting up the stuff.
 void R_InitData(void);
 void R_PrecacheLevel(void);
 
+extern size_t flatmemory, spritememory, texturememory;
+
 // Retrieval.
 // Floor/ceiling opaque texture tiles,
 // lookup by name. For animation?
diff --git a/src/r_defs.h b/src/r_defs.h
index def7b46f3b8aa5ecdd81c9d1d359848b87268822..a04b3128fc31b15d8f54203c47b7b154da626ba4 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -24,6 +24,10 @@
 
 #include "screen.h" // MAXVIDWIDTH, MAXVIDHEIGHT
 
+#ifdef HWRENDER
+#include "m_aatree.h"
+#endif
+
 #define POLYOBJECTS
 
 //
@@ -723,6 +727,18 @@ typedef struct
 #pragma pack()
 #endif
 
+// Rotated sprite 0-360
+#ifdef ROTSPRITE
+typedef struct
+{
+	patch_t *patch[8][ROTANGLES];
+	boolean cached[8];
+#ifdef HWRENDER
+	aatree_t *hardware_patch[8];
+#endif
+} rotsprite_t;
+#endif
+
 typedef enum
 {
 	SRF_SINGLE      = 0,   // 0-angle for all rotations
@@ -760,6 +776,10 @@ typedef struct
 
 	// Flip bits (1 = flip) to use for view angles 0-7.
 	UINT8 flip;
+
+#ifdef ROTSPRITE
+	rotsprite_t rotsprite;
+#endif
 } spriteframe_t;
 
 //
diff --git a/src/r_plane.c b/src/r_plane.c
index 2f6f97240a0418f3608d1901a8406b80716cf10f..e17116b04fa98205d7dfd6e4153af57d884a810d 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -1007,8 +1007,6 @@ void R_DrawSinglePlane(visplane_t *pl)
 		temp = P_GetZAt(pl->slope, pl->viewx, pl->viewy);
 		zeroheight = FIXED_TO_FLOAT(temp);
 
-#define ANG2RAD(angle) ((float)((angle)*M_PI)/ANGLE_180)
-
 		// p is the texture origin in view space
 		// Don't add in the offsets at this stage, because doing so can result in
 		// errors if the flat is rotated.
diff --git a/src/r_things.c b/src/r_things.c
index 92f2b946020bde6317faf9f52102cdab58f10d2d..ce11a421062292eaa0457e5a822a2c053158618b 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -22,6 +22,7 @@
 #include "m_misc.h"
 #include "info.h" // spr2names
 #include "i_video.h" // rendermode
+#include "i_system.h"
 #include "r_things.h"
 #include "r_plane.h"
 #include "r_portal.h"
@@ -35,6 +36,9 @@
 #include "fastcmp.h"
 #ifdef HWRENDER
 #include "hardware/hw_md2.h"
+#include "hardware/hw_glob.h"
+#include "hardware/hw_light.h"
+#include "hardware/hw_drv.h"
 #endif
 
 #ifdef PC_DOS
@@ -68,6 +72,11 @@ static lighttable_t **spritelights;
 INT16 negonearray[MAXVIDWIDTH];
 INT16 screenheightarray[MAXVIDWIDTH];
 
+#ifdef ROTSPRITE
+static fixed_t cosang2rad[ROTANGLES];
+static fixed_t sinang2rad[ROTANGLES];
+#endif
+
 //
 // INITIALIZATION FUNCTIONS
 //
@@ -100,7 +109,7 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 {
 	char cn = R_Frame2Char(frame); // for debugging
 
-	INT32 r;
+	INT32 r, ang;
 	lumpnum_t lumppat = wad;
 	lumppat <<= 16;
 	lumppat += lump;
@@ -111,6 +120,20 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 	if (maxframe ==(size_t)-1 || frame > maxframe)
 		maxframe = frame;
 
+	// rotsprite
+#ifdef ROTSPRITE
+	for (r = 0; r < 8; r++)
+	{
+		sprtemp[frame].rotsprite.cached[r] = false;
+		for (ang = 0; ang < ROTANGLES; ang++)
+			sprtemp[frame].rotsprite.patch[r][ang] = NULL;
+#ifdef HWRENDER
+		if (rendermode == render_opengl)
+			sprtemp[frame].rotsprite.hardware_patch[r] = M_AATreeAlloc(AATREE_ZUSER);
+#endif // HWRENDER
+	}
+#endif
+
 	if (rotation == 0)
 	{
 		// the lump should be used for all rotations
@@ -193,6 +216,157 @@ static void R_InstallSpriteLump(UINT16 wad,            // graphics patch
 		sprtemp[frame].flip &= ~(1<<rotation);
 }
 
+#ifdef ROTSPRITE
+void R_CacheRotSprite(spriteframe_t *sprframe, INT32 rot, UINT8 flip)
+{
+	UINT32 i;
+	INT32 angle;
+	patch_t *patch;
+	patch_t *newpatch;
+	UINT16 *rawsrc, *rawdst;
+	size_t size, size2;
+	const char *lumpname;
+
+	if (!sprframe->rotsprite.cached[rot])
+	{
+		INT32 sx,sy;
+		INT32 dx,dy;
+		INT32 ox,oy;
+		INT32 nox,noy;
+		INT32 width,height;
+		fixed_t ca, sa;
+		lumpnum_t lump = sprframe->lumppat[rot];
+
+		if (lump == LUMPERROR)
+			return;
+		// Because there's something wrong with SPR_DFLM, I guess
+		if (!R_CheckIfPatch(lump))
+			return;
+
+		patch = (patch_t *)W_CacheLumpNum(lump, PU_CACHE);
+		lumpname = W_CheckNameForNum(lump);
+		CONS_Debug(DBG_RENDER, "R_CacheRotSprite: %s\n", lumpname);
+		//CONS_Printf("%d\n", angle * ROTANGDIFF);
+
+		width = patch->width;
+		height = patch->height;
+		// patch origin
+		ox = (width / 2);
+		oy = (height / 2);
+
+		// Draw the sprite to a temporary buffer.
+		size = (width*height);
+		rawsrc = Z_Malloc(size * sizeof(UINT16), PU_STATIC, NULL);
+
+		// can't memset here
+		for (i = 0; i < size; i++)
+			rawsrc[i] = 0xFF00;
+
+		R_PatchToFlat(patch, rawsrc, (flip != 0x00));
+
+		// Don't cache angle = 0, that would be stoopid
+		for (angle = 1; angle < ROTANGLES; angle++)
+		{
+			INT32 minx = 32767, maxx = -32767;
+			INT32 miny = 32767, maxy = -32767;
+			INT32 newwidth, newheight;
+
+			ca = cosang2rad[angle];
+			sa = sinang2rad[angle];
+
+			// Find the dimensions of the rotated patch.
+			// This is BROKEN!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+			for (sy = 0; sy < height; sy++)
+			{
+				for (sx = 0; sx < width; sx++)
+				{
+					dx = FixedMul((sx-ox) << FRACBITS, ca) + FixedMul((sy-oy) << FRACBITS, sa) + (ox << FRACBITS);
+					dy = -FixedMul((sx-oy) << FRACBITS, sa) + FixedMul((sy-oy) << FRACBITS, ca) + (oy << FRACBITS);
+					dx >>= FRACBITS;
+					dy >>= FRACBITS;
+					if (dx < minx) minx = dx;
+					if (dx > maxx) maxx = dx;
+					if (dy < miny) miny = dy;
+					if (dy > maxy) maxy = dy;
+				}
+			}
+
+			newwidth = (maxx - minx) * 1.5f;
+			newheight = (maxy - miny) * 1.5f;
+
+			nox = (newwidth / 2);
+			noy = (newheight / 2);
+
+			size2 = (newwidth * newheight);
+			if (!size2)
+				size2 = size;
+
+			rawdst = Z_Malloc(size2 * sizeof(UINT16), PU_STATIC, NULL);
+
+			// can't memset here
+			for (i = 0; i < size2; i++)
+				rawdst[i] = 0xFF00;
+
+			// Draw the rotated sprite to a temporary buffer.
+			for (dy = 0; dy < newheight; dy++)
+			{
+				for (dx = 0; dx < newwidth; dx++)
+				{
+					sx = FixedMul((dx-nox) << FRACBITS, ca) + FixedMul((dy-noy) << FRACBITS, sa) + (ox << FRACBITS);
+					sy = -FixedMul((dx-nox) << FRACBITS, sa) + FixedMul((dy-noy) << FRACBITS, ca) + (oy << FRACBITS);
+					sx >>= FRACBITS;
+					sy >>= FRACBITS;
+					if (sx >= 0 && sy >= 0 && sx < width && sy < height)
+						rawdst[(dy*newwidth)+dx] = rawsrc[(sy*width)+sx];
+				}
+			}
+
+			// make patch
+			newpatch = R_FlatToPatch(rawdst, newwidth, newheight, &size);
+			// This is BROKEN!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+			newpatch->leftoffset = (newpatch->width / 2) - ((ox - patch->leftoffset) * ((flip != 0x00) ? -1 : 1));
+			newpatch->topoffset = (newpatch->height / 2) - (oy - patch->topoffset);
+
+			//BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer
+			if (rendermode != render_none) // not for psprite
+				newpatch->topoffset += 4;
+
+			// P_PrecacheLevel
+			if (devparm) spritememory += size;
+
+#ifdef HWRENDER
+			if (rendermode == render_opengl)
+			{
+				GLPatch_t *grPatch = HWR_GetCachedGLRotSprite(sprframe->rotsprite.hardware_patch[rot], angle, newpatch);
+				HWR_MakePatch(newpatch, grPatch, &grPatch->mipmap, false);
+				sprframe->rotsprite.patch[rot][angle] = (patch_t *)grPatch;
+			}
+			else
+#endif // HWRENDER
+				sprframe->rotsprite.patch[rot][angle] = newpatch;
+
+			// free rotated image data
+			Z_Free(rawdst);
+		}
+
+		// This rotation is cached now
+		sprframe->rotsprite.cached[rot] = true;
+
+		// free image data
+		Z_Free(rawsrc);
+
+#ifdef HWRENDER
+		if (rendermode == render_soft)
+#endif
+			W_UnlockCachedPatch(patch);
+
+		// Can remove if needed
+		I_OsPolling();
+		I_UpdateNoBlit();
+	}
+}
+#endif
+
 // Install a single sprite, given its identifying name (4 chars)
 //
 // (originally part of R_AddSpriteDefs)
@@ -214,6 +388,9 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 	lumpinfo_t *lumpinfo;
 	patch_t patch;
 	UINT8 numadded = 0;
+#ifdef ROTSPRITE
+	INT32 rot, ang;
+#endif
 
 	memset(sprtemp,0xFF, sizeof (sprtemp));
 	maxframe = (size_t)-1;
@@ -356,6 +533,16 @@ static boolean R_AddSingleSpriteDef(const char *sprname, spritedef_t *spritedef,
 	if (spritedef->numframes &&             // has been allocated
 		spritedef->numframes < maxframe)   // more frames are defined ?
 	{
+#ifdef ROTSPRITE
+		for (frame = 0; frame < spritedef->numframes; frame++)
+		{
+			spriteframe_t *sprframe = &spritedef->spriteframes[frame];
+			for (rot = 0; rot < 8; rot++)
+				if (sprframe->rotsprite.cached[rot])
+					for (ang = 0; ang < ROTANGLES; ang++)
+						Z_Free(sprframe->rotsprite.patch[rot][ang]);
+		}
+#endif // ROTSPRITE
 		Z_Free(spritedef->spriteframes);
 		spritedef->spriteframes = NULL;
 	}
@@ -449,7 +636,6 @@ UINT32 visspritecount;
 static UINT32 clippedvissprites;
 static vissprite_t *visspritechunks[MAXVISSPRITES >> VISSPRITECHUNKBITS] = {NULL};
 
-
 //
 // R_InitSprites
 // Called at program start.
@@ -457,11 +643,23 @@ static vissprite_t *visspritechunks[MAXVISSPRITES >> VISSPRITECHUNKBITS] = {NULL
 void R_InitSprites(void)
 {
 	size_t i;
+#ifdef ROTSPRITE
+	INT32 angle, realangle = 0;
+	float fa;
+#endif
 
 	for (i = 0; i < MAXVIDWIDTH; i++)
-	{
 		negonearray[i] = -1;
+
+#ifdef ROTSPRITE
+	for (angle = 0; angle < ROTANGLES; angle++)
+	{
+		fa = ANG2RAD(FixedAngle(realangle<<FRACBITS));
+		cosang2rad[angle] = FLOAT_TO_FIXED(cos(-fa));
+		sinang2rad[angle] = FLOAT_TO_FIXED(sin(-fa));
+		realangle += ROTANGDIFF;
 	}
+#endif
 
 	//
 	// count the number of sprite names, and allocate sprites table
@@ -701,7 +899,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 	INT32 texturecolumn;
 #endif
 	fixed_t frac;
-	patch_t *patch = W_CacheLumpNum(vis->patch, PU_CACHE);
+	patch_t *patch = vis->patch;
 	fixed_t this_scale = vis->mobj->scale;
 	INT32 x1, x2;
 	INT64 overflow_test;
@@ -870,7 +1068,7 @@ static void R_DrawPrecipitationVisSprite(vissprite_t *vis)
 	INT64 overflow_test;
 
 	//Fab : R_InitSprites now sets a wad lump number
-	patch = W_CacheLumpNum(vis->patch, PU_CACHE);
+	patch = vis->patch;
 	if (!patch)
 		return;
 
@@ -1050,6 +1248,15 @@ static void R_ProjectSprite(mobj_t *thing)
 	INT32 light = 0;
 	fixed_t this_scale = thing->scale;
 
+	// rotsprite
+	fixed_t spr_width, spr_height;
+	fixed_t spr_offset, spr_topoffset;
+#ifdef ROTSPRITE
+	patch_t *rotsprite = NULL;
+	angle_t arollangle = thing->rollangle;
+	UINT32 rollangle = AngleFixed(arollangle)>>FRACBITS;
+#endif
+
 	fixed_t ang_scale = FRACUNIT;
 
 	// transform the origin point
@@ -1158,11 +1365,35 @@ static void R_ProjectSprite(mobj_t *thing)
 	if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES)
 		this_scale = FixedMul(this_scale, ((skin_t *)thing->skin)->highresscale);
 
+	spr_width = spritecachedinfo[lump].width;
+	spr_height = spritecachedinfo[lump].height;
+	spr_offset = spritecachedinfo[lump].offset;
+	spr_topoffset = spritecachedinfo[lump].topoffset;
+
+#ifdef ROTSPRITE
+	if (rollangle > 0)
+	{
+		if (!sprframe->rotsprite.cached[rot])
+			R_CacheRotSprite(sprframe, rot, flip);
+		rollangle /= ROTANGDIFF;
+		rotsprite = sprframe->rotsprite.patch[rot][rollangle];
+		if (rotsprite != NULL)
+		{
+			spr_width = rotsprite->width << FRACBITS;
+			spr_height = rotsprite->height << FRACBITS;
+			spr_offset = rotsprite->leftoffset << FRACBITS;
+			spr_topoffset = rotsprite->topoffset << FRACBITS;
+			// flip -> rotate, not rotate -> flip
+			flip = 0;
+		}
+	}
+#endif
+
 	// calculate edges of the shape
 	if (flip)
-		offset = spritecachedinfo[lump].offset - spritecachedinfo[lump].width;
+		offset = spr_offset - spr_width;
 	else
-		offset = -spritecachedinfo[lump].offset;
+		offset = -spr_offset;
 	offset = FixedMul(offset, this_scale);
 	tx += FixedMul(offset, ang_scale);
 	x1 = (centerxfrac + FixedMul (tx,xscale)) >>FRACBITS;
@@ -1171,7 +1402,7 @@ static void R_ProjectSprite(mobj_t *thing)
 	if (x1 > viewwidth)
 		return;
 
-	offset2 = FixedMul(spritecachedinfo[lump].width, this_scale);
+	offset2 = FixedMul(spr_width, this_scale);
 	tx += FixedMul(offset2, ang_scale);
 	x2 = ((centerxfrac + FixedMul (tx,xscale)) >> FRACBITS) - (papersprite ? 2 : 1);
 
@@ -1273,13 +1504,13 @@ static void R_ProjectSprite(mobj_t *thing)
 		// When vertical flipped, draw sprites from the top down, at least as far as offsets are concerned.
 		// sprite height - sprite topoffset is the proper inverse of the vertical offset, of course.
 		// remember gz and gzt should be seperated by sprite height, not thing height - thing height can be shorter than the sprite itself sometimes!
-		gz = oldthing->z + oldthing->height - FixedMul(spritecachedinfo[lump].topoffset, this_scale);
-		gzt = gz + FixedMul(spritecachedinfo[lump].height, this_scale);
+		gz = oldthing->z + oldthing->height - FixedMul(spr_topoffset, this_scale);
+		gzt = gz + FixedMul(spr_height, this_scale);
 	}
 	else
 	{
-		gzt = oldthing->z + FixedMul(spritecachedinfo[lump].topoffset, this_scale);
-		gz = gzt - FixedMul(spritecachedinfo[lump].height, this_scale);
+		gzt = oldthing->z + FixedMul(spr_topoffset, this_scale);
+		gz = gzt - FixedMul(spr_height, this_scale);
 	}
 
 	if (thing->subsector->sector->cullheight)
@@ -1378,7 +1609,7 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	if (flip)
 	{
-		vis->startfrac = spritecachedinfo[lump].width-1;
+		vis->startfrac = spr_width-1;
 		vis->xiscale = -iscale;
 	}
 	else
@@ -1395,7 +1626,12 @@ static void R_ProjectSprite(mobj_t *thing)
 
 	//Fab: lumppat is the lump number of the patch to use, this is different
 	//     than lumpid for sprites-in-pwad : the graphics are patched
-	vis->patch = sprframe->lumppat[rot];
+#ifdef ROTSPRITE
+	if (rotsprite != NULL)
+		vis->patch = rotsprite;
+	else
+#endif
+		vis->patch = W_CacheLumpNum(sprframe->lumppat[rot], PU_CACHE);
 
 //
 // determine the colormap (lightlevel & special effects)
@@ -1587,7 +1823,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing)
 
 	//Fab: lumppat is the lump number of the patch to use, this is different
 	//     than lumpid for sprites-in-pwad : the graphics are patched
-	vis->patch = sprframe->lumppat[0];
+	vis->patch = W_CacheLumpNum(sprframe->lumppat[0], PU_CACHE);
 
 	// specific translucency
 	if (thing->frame & FF_TRANSMASK)
diff --git a/src/r_things.h b/src/r_things.h
index 9c3d16ab018b27abdb610e4ee5eefe7a6b5e66b3..8ceb9d07fffe7dbbd3a785f7d2a8c8521a52119d 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -17,6 +17,7 @@
 #include "sounds.h"
 #include "r_plane.h"
 #include "r_portal.h"
+#include "r_defs.h"
 
 // "Left" and "Right" character symbols for additional rotation functionality
 #define ROT_L ('L' - '0')
@@ -51,6 +52,10 @@ void R_DrawFlippedMaskedColumn(column_t *column, INT32 texheight);
 //     (only sprites from namelist are added or replaced)
 void R_AddSpriteDefs(UINT16 wadnum);
 
+#ifdef ROTSPRITE
+void R_CacheRotSprite(spriteframe_t *sprframe, INT32 rot, UINT8 flip);
+#endif
+
 //SoM: 6/5/2000: Light sprites correctly!
 void R_AddSprites(sector_t *sec, INT32 lightlevel);
 void R_InitSprites(void);
@@ -176,7 +181,7 @@ typedef struct vissprite_s
 	fixed_t xiscale; // negative if flipped
 
 	fixed_t texturemid;
-	lumpnum_t patch;
+	patch_t *patch;
 
 	lighttable_t *colormap; // for color translation and shadow draw
 	                        // maxbright frames as well