diff --git a/src/d_player.h b/src/d_player.h
index eff8e54f5f1ceed71d24b6fff8190f8b345d4d19..8648b3608473db13f411fc4384147dd463b974cc 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -235,6 +235,7 @@ typedef enum
 	CR_ROPEHANG,
 	CR_MACESPIN,
 	CR_MINECART,
+	CR_ROLLOUT,
 	CR_PTERABYTE
 } carrytype_t; // pw_carry
 
diff --git a/src/dehacked.c b/src/dehacked.c
index d19f43f9c03b52b1a2d3672305404901749a9bc2..bb17856955b2a3ad83fa83f05e78ae412ad8aac2 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -2437,6 +2437,8 @@ static actionpointer_t actionpointers[] =
 	{{A_FireShrink},             "A_FIRESHRINK"},
 	{{A_SpawnPterabytes},        "A_SPAWNPTERABYTES"},
 	{{A_PterabyteHover},         "A_PTERABYTEHOVER"},
+	{{A_RolloutSpawn},           "A_ROLLOUTSPAWN"},
+	{{A_RolloutRock},            "A_ROLLOUTROCK"},
 	{{NULL},                     "NONE"},
 
 	// This NULL entry must be the last in the list
@@ -5854,6 +5856,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_LAVAFALL_LAVA3",
 	"S_LAVAFALLROCK",
 
+	// Rollout Rock
+	"S_ROLLOUTSPAWN",
+	"S_ROLLOUTROCK",
+
 	// RVZ scenery
 	"S_BIGFERNLEAF",
 	"S_BIGFERN1",
@@ -7587,6 +7593,9 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_LAVAFALL_LAVA",
 	"MT_LAVAFALLROCK",
 
+	"MT_ROLLOUTSPAWN",
+	"MT_ROLLOUTROCK",
+
 	"MT_BIGFERNLEAF",
 	"MT_BIGFERN",
 	"MT_JUNGLEPALM",
@@ -8594,8 +8603,8 @@ struct {
 	{"CR_ROPEHANG",CR_ROPEHANG},
 	{"CR_MACESPIN",CR_MACESPIN},
 	{"CR_MINECART",CR_MINECART},
+	{"CR_ROLLOUT", CR_ROLLOUT},
 	{"CR_PTERABYTE",CR_PTERABYTE},
-
 	// Ring weapons (ringweapons_t)
 	// Useful for A_GiveWeapon
 	{"RW_AUTO",RW_AUTO},
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index f3b2b4f8758f62d9d56125b6e4956e1e6b0282d7..e5e9e77a7a632cc4456aad6daf60805c8a4dbac8 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -258,6 +258,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_WSPB
 	&lspr[NOLIGHT],     // SPR_STPT
 	&lspr[NOLIGHT],     // SPR_BMNE
+	&lspr[NOLIGHT],     // SPR_PUMI
 
 	// Monitor Boxes
 	&lspr[NOLIGHT],     // SPR_MSTV
diff --git a/src/info.c b/src/info.c
index 6800ba4bea4653b9896e580f4e8238f192d6537d..076f12d403315b0cccf95423140f18a1815d76d2 100644
--- a/src/info.c
+++ b/src/info.c
@@ -147,6 +147,7 @@ char sprnames[NUMSPRITES + 1][5] =
 	"WSPB", // Wall spike base
 	"STPT", // Starpost
 	"BMNE", // Big floating mine
+	"PUMI", // Rollout Rock
 
 	// Monitor Boxes
 	"MSTV", // MiSc TV sprites
@@ -2483,6 +2484,10 @@ state_t states[NUMSTATES] =
 	{SPR_LFAL, 2|FF_FULLBRIGHT|FF_ANIMATE, 9, {NULL}, 2, 3, S_NULL}, // S_LAVAFALL_LAVA3
 	{SPR_LFAL, 11|FF_ANIMATE|FF_RANDOMANIM, 12, {NULL}, 3, 3, S_LAVAFALLROCK}, // S_LAVAFALLROCK
 
+	// Rollout Rock
+	{SPR_NULL, 0, 1, {A_RolloutSpawn}, 256*FRACUNIT, MT_ROLLOUTROCK, S_ROLLOUTSPAWN}, // S_ROLLOUTSPAWN
+	{SPR_PUMI, 0, 1, {A_RolloutRock},    63*FRACUNIT/64,  6*FRACUNIT/10,  S_ROLLOUTROCK}, // S_ROLLOUTROCK
+
 	// RVZ scenery
 	{SPR_JPLA, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BIGFERNLEAF
 	{SPR_JPLA, 1, 1, {NULL}, 0, 0, S_BIGFERN2}, // S_BIGFERN1
@@ -12818,6 +12823,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_ROLLOUTSPAWN
+		1305,           // doomednum
+		S_ROLLOUTSPAWN, // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SPAWNCEILING,  // flags
+		S_NULL          // raisestate
+	},
+
+	{           // MT_ROLLOUTROCK
+		-1,             // doomednum
+		S_ROLLOUTROCK,  // spawnstate
+		1000,           // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		8,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		32*FRACUNIT,    // speed
+		30*FRACUNIT,    // radius
+		60*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_PUSHABLE|MF_SOLID|MF_SLIDEME,  // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_BIGFERNLEAF
 		-1,             // doomednum
 		S_BIGFERNLEAF,  // spawnstate
diff --git a/src/info.h b/src/info.h
index 98a5db4ca42cb3e1b16c73e7e6a226606af692e3..8663b0576cede0ddf28aa62e05868a5f29bb75b6 100644
--- a/src/info.h
+++ b/src/info.h
@@ -272,6 +272,8 @@ void A_FallingLavaCheck();
 void A_FireShrink();
 void A_SpawnPterabytes();
 void A_PterabyteHover();
+void A_RolloutSpawn();
+void A_RolloutRock();
 
 // ratio of states to sprites to mobj types is roughly 6 : 1 : 1
 #define NUMMOBJFREESLOTS 512
@@ -399,6 +401,7 @@ typedef enum sprite
 	SPR_WSPB, // Wall spike base
 	SPR_STPT, // Starpost
 	SPR_BMNE, // Big floating mine
+	SPR_PUMI, // Rollout Rock
 
 	// Monitor Boxes
 	SPR_MSTV, // MiSc TV sprites
@@ -2608,6 +2611,10 @@ typedef enum state
 	S_LAVAFALL_LAVA3,
 	S_LAVAFALLROCK,
 
+	// Rollout Rock
+	S_ROLLOUTSPAWN,
+	S_ROLLOUTROCK,
+
 	// RVZ scenery
 	S_BIGFERNLEAF,
 	S_BIGFERN1,
@@ -4363,6 +4370,9 @@ typedef enum mobj_type
 	MT_LAVAFALL_LAVA,
 	MT_LAVAFALLROCK,
 
+	MT_ROLLOUTSPAWN,
+	MT_ROLLOUTROCK,
+
 	MT_BIGFERNLEAF,
 	MT_BIGFERN,
 	MT_JUNGLEPALM,
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 7ad65c1d0af278ee681889b3f425e0077c73bcd8..359bc7d3bd76234bb7b2180756108603eb6c591f 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -302,6 +302,8 @@ void A_FallingLavaCheck(mobj_t *actor);
 void A_FireShrink(mobj_t *actor);
 void A_SpawnPterabytes(mobj_t *actor);
 void A_PterabyteHover(mobj_t *actor);
+void A_RolloutSpawn(mobj_t *actor);
+void A_RolloutRock(mobj_t *actor);
 
 //for p_enemy.c
 
@@ -13909,4 +13911,108 @@ void A_PterabyteHover(mobj_t *actor)
 	ang = actor->extravalue1*ANG1;
 	fa = (ang >> ANGLETOFINESHIFT) & FINEMASK;
 	actor->z += FINESINE(fa);
-}
\ No newline at end of file
+}
+// Function: A_RolloutSpawn
+//
+// Description: Spawns a new Rollout Rock when the currently spawned rock is destroyed or moves far enough away.
+//
+// var1 = Distance currently spawned rock should travel before spawning a new one
+// var2 = Object type to spawn
+//
+void A_RolloutSpawn(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_RolloutSpawn", actor))
+		return;
+#endif
+
+	if (!(actor->target)
+		|| P_MobjWasRemoved(actor->target)
+		|| P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y) > locvar1)
+	{
+		actor->target = P_SpawnMobj(actor->x, actor->y, actor->z, locvar2);
+	}
+}
+
+// Function: A_RolloutRock
+//
+// Description: Thinker for Rollout Rock.
+//
+// var1 = Drag
+// var2 = Vertical bobbing speed factor
+//
+void A_RolloutRock(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_RolloutRock", actor))
+		return;
+#endif
+
+	UINT8 maxframes = actor->info->reactiontime;
+	fixed_t pi = (22*FRACUNIT/7);
+	fixed_t circumference = FixedMul(2 * pi, actor->radius);
+	fixed_t oldspeed = P_AproxDistance(actor->momx, actor->momy), newspeed, topspeed = actor->info->speed;
+	boolean inwater = actor->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER);
+
+	actor->friction = FRACUNIT;
+
+	if (inwater)
+	{
+		fixed_t height;
+		if (actor->eflags & MFE_VERTICALFLIP)
+		{
+			height = actor->waterbottom + (actor->height>>2);
+			if (actor->z + actor->height > height)
+			{
+				actor->z = height;
+				actor->momz = 0;
+			}
+		}
+		else
+		{
+			height = actor->watertop - (actor->height>>2);
+			if (actor->z < height)
+			{
+				actor->z = height;
+				actor->momz = 0;
+			}
+		}
+		actor->momz += P_MobjFlip(actor) * FixedMul(locvar2, actor->scale);
+	}
+
+	if (oldspeed > topspeed)
+	{
+		actor->momx = FixedMul(FixedDiv(actor->momx, oldspeed), topspeed);
+		actor->momy = FixedMul(FixedDiv(actor->momy, oldspeed), topspeed);
+	}
+
+	actor->momx = FixedMul(actor->momx, locvar1);
+	actor->momy = FixedMul(actor->momy, locvar1);
+
+	newspeed = P_AproxDistance(actor->momx, actor->momy);
+
+	if (newspeed < actor->scale >> 1)
+	{
+		actor->momx = 0;
+		actor->momy = 0;
+	}
+	else if (newspeed > actor->scale)
+	{
+		actor->angle = R_PointToAngle2(0, 0, actor->momx, actor->momy);
+		actor->movefactor += newspeed;
+		if (actor->movefactor > circumference / maxframes)
+		{
+			actor->reactiontime++;
+			actor->reactiontime %= maxframes;
+			actor->movefactor = 0;
+		}
+	}
+
+	actor->frame = actor->reactiontime % maxframes;
+}
diff --git a/src/p_map.c b/src/p_map.c
index 1455f3a4f056313314df95265895e755e589fe57..14b69b5758f9cb058be05f69a427252e6cb93955 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -954,6 +954,59 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			P_DamageMobj(thing, tmthing, tmthing, 1, 0);
 	}
 
+	if (thing->type == MT_ROLLOUTROCK)
+	{
+		if (tmthing->player)
+		{
+			if (tmthing->player->powers[pw_carry] == CR_ROLLOUT)
+			{
+				return true;
+			}
+			if ((thing->flags & MF_PUSHABLE) // carrying a player
+				&& ((tmthing->eflags & MFE_VERTICALFLIP) == (thing->eflags & MFE_VERTICALFLIP))
+				&& (P_AproxDistance(thing->x - tmthing->x, thing->y - tmthing->y) < (thing->radius))
+				&& (P_MobjFlip(tmthing)*tmthing->momz <= 0)
+				&& ((!(tmthing->eflags & MFE_VERTICALFLIP) && abs(thing->z + thing->height - tmthing->z) < (thing->height>>2))
+					|| (tmthing->eflags & MFE_VERTICALFLIP && abs(tmthing->z + tmthing->height - thing->z) < (thing->height>>2))))
+			{
+				thing->flags &= ~MF_PUSHABLE;
+				P_SetTarget(&thing->target, tmthing);
+				P_ResetPlayer(tmthing->player);
+				P_SetPlayerMobjState(tmthing, S_PLAY_WALK);
+				tmthing->player->powers[pw_carry] = CR_ROLLOUT;
+				P_SetTarget(&tmthing->tracer, thing);
+				return false;
+			}
+		}
+		else if (tmthing->type == thing->type)
+		{
+			if (tmthing->z > thing->z + thing->height || thing->z > tmthing->z + tmthing->height)
+				return true;
+
+			fixed_t tempmomx = thing->momx, tempmomy = thing->momy;
+			thing->momx = tmthing->momx;
+			thing->momy = tmthing->momy;
+			tmthing->momx = tempmomx;
+			tmthing->momy = tempmomy;
+		}
+	}
+	else if (tmthing->type == MT_ROLLOUTROCK)
+	{
+		if (tmthing->z > thing->z + thing->height || thing->z > tmthing->z + tmthing->height || !thing->health)
+			return true;
+
+		if (thing->flags & MF_SPRING)
+		{
+			P_DoSpring(thing, tmthing);
+			return true;
+		}
+		else if (thing->flags & MF_MONITOR && thing->flags & MF_SHOOTABLE && !(tmthing->flags & MF_PUSHABLE)) // carrying a player
+		{
+			P_KillMobj(thing, tmthing, tmthing->target, 0);
+			return true;
+		}
+	}
+
 	if (thing->type == MT_PTERABYTE && tmthing->player)
 		P_DoPterabyteCarry(tmthing->player, thing);
 
diff --git a/src/p_user.c b/src/p_user.c
index 4895095f2ee69110588e6c085d1fb31d7e1842eb..ec431a86a4d9377b97e7531424b5741f34ef6af7 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -948,6 +948,17 @@ void P_DoPlayerPain(player_t *player, mobj_t *source, mobj_t *inflictor)
 	if (player->powers[pw_carry] == CR_ROPEHANG)
 		P_SetTarget(&player->mo->tracer, NULL);
 
+	if (player->powers[pw_carry] == CR_ROLLOUT)
+	{
+		if (player->mo->tracer && !P_MobjWasRemoved(player->mo->tracer))
+		{
+			player->mo->tracer->flags |= MF_PUSHABLE;
+			P_SetTarget(&player->mo->tracer->target, NULL);
+		}
+		P_SetTarget(&player->mo->tracer, NULL);
+		player->powers[pw_carry] = CR_NONE;
+	}
+
 	{
 		angle_t ang;
 		fixed_t fallbackspeed;
@@ -4295,6 +4306,8 @@ void P_DoJump(player_t *player, boolean soundandstate)
 		{
 			player->mo->momz = 9*FRACUNIT;
 			player->powers[pw_carry] = CR_NONE;
+			player->mo->tracer->flags |= MF_PUSHABLE;
+			P_SetTarget(&player->mo->tracer->target, NULL);
 			P_SetTarget(&player->mo->tracer, NULL);
 		}
 		else if (player->powers[pw_carry] == CR_ROPEHANG)
@@ -4303,6 +4316,14 @@ void P_DoJump(player_t *player, boolean soundandstate)
 			player->powers[pw_carry] = CR_NONE;
 			P_SetTarget(&player->mo->tracer, NULL);
 		}
+		else if (player->powers[pw_carry] == CR_ROLLOUT)
+		{
+			player->mo->momz = 9*FRACUNIT + player->mo->tracer->momz;
+			player->powers[pw_carry] = CR_NONE;
+			player->mo->tracer->flags |= MF_PUSHABLE;
+			P_SetTarget(&player->mo->tracer->target, NULL);
+			P_SetTarget(&player->mo->tracer, NULL);
+		}
 		else if (player->mo->eflags & MFE_GOOWATER)
 		{
 			player->mo->momz = 7*FRACUNIT;
@@ -11124,6 +11145,8 @@ void P_PlayerThink(player_t *player)
 
 	// deez New User eXperiences.
 	{
+		angle_t diff = 0;
+		UINT8 factor;
 		// Directionchar!
 		// Camera angle stuff.
 		if (player->exiting // no control, no modification
@@ -11152,6 +11175,13 @@ void P_PlayerThink(player_t *player)
 				case CR_GENERIC:
 					player->drawangle = player->mo->tracer->angle;
 					break;
+				case CR_ROLLOUT:
+					if (cmd->forwardmove || cmd->sidemove) // only when you're pressing movement keys
+					{ // inverse direction!
+						diff = ((player->mo->angle + R_PointToAngle2(0, 0, -cmd->forwardmove<<FRACBITS, cmd->sidemove<<FRACBITS)) - player->drawangle);
+						factor = 4;
+					}
+					break;
 				/* -- in case we wanted to have the camera freely movable during zoom tubes
 				case CR_ZOOMTUBE:*/
 				case CR_ROPEHANG:
@@ -11172,9 +11202,6 @@ void P_PlayerThink(player_t *player)
 			;
 		else
 		{
-			angle_t diff;
-			UINT8 factor;
-
 			if (player->pflags & PF_SLIDING)
 			{
 #if 0 // fun hydrocity style horizontal spin
@@ -11210,15 +11237,15 @@ void P_PlayerThink(player_t *player)
 				diff = (player->mo->angle - player->drawangle);
 				factor = 8;
 			}
+		}
 
-			if (diff)
-			{
-				if (diff > ANGLE_180)
-					diff = InvAngle(InvAngle(diff)/factor);
-				else
-					diff /= factor;
-				player->drawangle += diff;
-			}
+		if (diff)
+		{
+			if (diff > ANGLE_180)
+				diff = InvAngle(InvAngle(diff)/factor);
+			else
+				diff /= factor;
+			player->drawangle += diff;
 		}
 
 		// Autobrake! check ST_drawInput if you modify this
@@ -11864,6 +11891,36 @@ void P_PlayerAfterThink(player_t *player)
 				}
 				break;
 			}
+			case CR_ROLLOUT:
+			{
+				mobj_t *mo = player->mo, *rock = player->mo->tracer;
+				UINT8 walktics = mo->state->tics - P_GetPlayerControlDirection(player);
+
+				if (!rock || P_MobjWasRemoved(rock))
+				{
+					P_SetTarget(&player->mo->tracer, NULL);
+					player->powers[pw_carry] = CR_NONE;
+					break;
+				}
+
+				if (player->cmd.forwardmove || player->cmd.sidemove)
+				{
+					rock->movedir = (player->cmd.angleturn << FRACBITS) + R_PointToAngle2(0, 0, player->cmd.forwardmove << FRACBITS, -player->cmd.sidemove << FRACBITS);
+					P_Thrust(rock, rock->movedir, rock->scale >> 1);
+				}
+
+				mo->momx = rock->momx;
+				mo->momy = rock->momy;
+				mo->momz = 0;
+
+				if (player->panim == PA_WALK && mo->tics > walktics)
+				{
+					mo->tics = walktics;
+				}
+
+				P_TeleportMove(player->mo, rock->x, rock->y, rock->z + rock->height);
+				break;
+			}
 			case CR_PTERABYTE: // being carried by a Pterabyte
 			{
 				mobj_t *ptera = player->mo->tracer;