diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index bac297f8e10a4183d30347ec4068afa5e7e37b05..623a83c473e77208e05b8f615677524d12985854 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -370,7 +370,7 @@ consvar_t cv_advancemap = {"advancemap", "Next", CV_NETVAR, advancemap_cons_t, N
 static CV_PossibleValue_t playersforexit_cons_t[] = {{0, "One"}, {1, "1/4"}, {2, "Half"}, {3, "3/4"}, {4, "All"}, {0, NULL}};
 consvar_t cv_playersforexit = {"playersforexit", "All", CV_NETVAR, playersforexit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
 
-consvar_t cv_exitmove = {"exitmove", "Off", CV_NETVAR|CV_CALL, CV_OnOff, ExitMove_OnChange, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_exitmove = {"exitmove", "On", CV_NETVAR|CV_CALL, CV_OnOff, ExitMove_OnChange, 0, NULL, NULL, 0, 0, NULL};
 
 consvar_t cv_runscripts = {"runscripts", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL};
 
@@ -3749,9 +3749,15 @@ static void ExitMove_OnChange(void)
 	if (cv_exitmove.value)
 	{
 		for (i = 0; i < MAXPLAYERS; ++i)
-			if (playeringame[i] && players[i].mo
-				&& players[i].mo->target && players[i].mo->target->type == MT_SIGN)
-				P_SetTarget(&players[i].mo->target, NULL);
+			if (playeringame[i] && players[i].mo)
+			{
+				if (players[i].mo->target && players[i].mo->target->type == MT_SIGN)
+					P_SetTarget(&players[i].mo->target, NULL);
+				
+				if (players[i].pflags & PF_FINISHED)
+					P_GiveFinishFlags(&players[i]);
+			}
+			
 		CONS_Printf(M_GetText("Players can now move after completing the level.\n"));
 	}
 	else
diff --git a/src/dehacked.c b/src/dehacked.c
index 57d5b2241114de545d773f264a74df4018fc7f9b..7ceda10a5c977738f0ce01a410f5f5b72c762b06 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -7477,6 +7477,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 
 	// Got Flag Sign
 	"S_GOTFLAG",
+	
+	// Finish flag
+	"S_FINISHFLAG",
 
 	"S_CORK",
 	"S_LHRT",
@@ -8598,6 +8601,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_LOCKONINF", // In-level Target
 	"MT_TAG", // Tag Sign
 	"MT_GOTFLAG", // Got Flag sign
+	"MT_FINISHFLAG", // Finish flag
 
 	// Ambient Sounds
 	"MT_AWATERA", // Ambient Water Sound 1
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index 8545a88b33e05e19bdb18fe23936fb2ed1c39195..c5af8d6d336a8c8b29d9ce492402c323bb964b13 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -509,6 +509,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_LCKN
 	&lspr[NOLIGHT],     // SPR_TTAG
 	&lspr[NOLIGHT],     // SPR_GFLG
+	&lspr[NOLIGHT],     // SPR_FNSF
 
 	&lspr[NOLIGHT],     // SPR_CORK
 	&lspr[NOLIGHT],     // SPR_LHRT
diff --git a/src/info.c b/src/info.c
index 20bd73498db9cf923429a64a34c44d026eafa844..cf4d7df4fb949e1233808e8424dd10aa30eb8359 100644
--- a/src/info.c
+++ b/src/info.c
@@ -407,6 +407,7 @@ char sprnames[NUMSPRITES + 1][5] =
 	"LCKN", // Target
 	"TTAG", // Tag Sign
 	"GFLG", // Got Flag sign
+	"FNSF", // Finish flag
 
 	"CORK",
 	"LHRT",
@@ -3349,7 +3350,10 @@ state_t states[NUMSTATES] =
 	{SPR_TTAG, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_TTAG
 
 	// CTF Sign
-	{SPR_GFLG,   FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTFLAG
+	{SPR_GFLG, FF_FULLBRIGHT, 2, {NULL}, 0, 0, S_NULL}, // S_GOTFLAG
+	
+	// Finish flag
+	{SPR_FNSF,    FF_TRANS30, -1, {NULL}, 0, 0, S_NULL}, // S_FINISHFLAG
 
 	{SPR_CORK,             0, -1, {NULL}, 0, 0, S_NULL}, // S_CORK
 	{SPR_LHRT, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_LHRT
@@ -17995,6 +17999,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
+	
+	{           // MT_FINISHFLAG
+		-1,             // doomednum
+		S_FINISHFLAG,   // 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
+		4*FRACUNIT,     // speed
+		8*FRACUNIT,     // radius
+		8*FRACUNIT,     // height
+		1,              // display offset
+		16,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOBLOCKMAP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
 
 	// ambient water 1a (large)
 	{           // MT_AWATERA
diff --git a/src/info.h b/src/info.h
index 59b8eb68d74b41bc62c658e1f2e0eaa04b4afde9..9aa94b46e4746a61598fad22960d735dc15c405c 100644
--- a/src/info.h
+++ b/src/info.h
@@ -670,6 +670,7 @@ typedef enum sprite
 	SPR_LCKN, // Target
 	SPR_TTAG, // Tag Sign
 	SPR_GFLG, // Got Flag sign
+	SPR_FNSF, // Finish flag
 
 	SPR_CORK,
 	SPR_LHRT,
@@ -3485,6 +3486,9 @@ typedef enum state
 
 	// Got Flag Sign
 	S_GOTFLAG,
+	
+	// Finish flag
+	S_FINISHFLAG,
 
 	S_CORK,
 	S_LHRT,
@@ -4626,6 +4630,7 @@ typedef enum mobj_type
 	MT_LOCKONINF, // In-level Target
 	MT_TAG, // Tag Sign
 	MT_GOTFLAG, // Got Flag sign
+	MT_FINISHFLAG, // Finish flag
 
 	// Ambient Sounds
 	MT_AWATERA, // Ambient Water Sound 1
diff --git a/src/p_inter.c b/src/p_inter.c
index 667245f039ede6a946bc6e4d7854c2a74fa251a4..5fb9b3fe7f3d2ceb8fa5dab6a363185d8f02bd06 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -2542,7 +2542,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 		else if (!target->player->bot && !target->player->spectator && (target->player->lives != INFLIVES)
 		 && G_GametypeUsesLives())
 		{
-			target->player->lives -= 1; // Lose a life Tails 03-11-2000
+			if (!(target->player->pflags & PF_FINISHED))
+				target->player->lives -= 1; // Lose a life Tails 03-11-2000
 
 			if (target->player->lives <= 0) // Tails 03-14-2000
 			{
diff --git a/src/p_local.h b/src/p_local.h
index 8056d137c3850b2c9071161ae5519963051c8afd..76a33c90da357fcc7fd952b62773bf50e0f2bdb3 100644
--- a/src/p_local.h
+++ b/src/p_local.h
@@ -159,6 +159,7 @@ void P_GivePlayerLives(player_t *player, INT32 numlives);
 void P_GiveCoopLives(player_t *player, INT32 numlives, boolean sound);
 UINT8 P_GetNextEmerald(void);
 void P_GiveEmerald(boolean spawnObj);
+void P_GiveFinishFlags(player_t *player);
 #if 0
 void P_ResetScore(player_t *player);
 #else
diff --git a/src/p_map.c b/src/p_map.c
index 40fee7b46f8ccc7cfe27fc6a8639d3887efbd4c3..1c2b4808bcddc6f986e8fbe8d2bc7e9d7b18e226 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -592,9 +592,6 @@ static void P_DoTailsCarry(player_t *sonic, player_t *tails)
 	if (!(tails->pflags & PF_CANCARRY))
 		return;
 
-	if (sonic->pflags & PF_FINISHED)
-		return;
-
 	if ((sonic->mo->eflags & MFE_VERTICALFLIP) != (tails->mo->eflags & MFE_VERTICALFLIP))
 		return; // Both should be in same gravity
 
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 03ca56aa81e5c4616db73c83d02451b3d12ed168..f3f0b9ab01df4db5e7b1d1d3e47ce301ce151efb 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -8005,6 +8005,37 @@ static void P_MobjSceneryThink(mobj_t *mobj)
 			mobj->frame = (mobj->frame & ~FF_TRANSMASK) | ((10 - (mobj->fuse*2)) << (FF_TRANSSHIFT));
 	}
 	break;
+	case MT_FINISHFLAG:
+	{
+		if (!mobj->target || mobj->target->player->playerstate == PST_DEAD || !cv_exitmove.value)
+		{
+			P_RemoveMobj(mobj);
+			return;
+		}
+
+		if (!camera.chase)
+			mobj->flags2 |= MF2_DONTDRAW;
+		else
+			mobj->flags2 &= ~MF2_DONTDRAW;
+
+		P_UnsetThingPosition(mobj);
+		{
+			fixed_t radius = FixedMul(10*mobj->info->speed, mobj->target->scale);
+			angle_t fa;
+
+			mobj->angle += FixedAngle(mobj->info->speed);
+
+			fa = mobj->angle >> ANGLETOFINESHIFT;
+
+			mobj->x = mobj->target->x + FixedMul(FINECOSINE(fa),radius);
+			mobj->y = mobj->target->y + FixedMul(FINESINE(fa),radius);
+			mobj->z = mobj->target->z + mobj->target->height/2;
+		}
+		P_SetThingPosition(mobj);
+
+		P_SetScale(mobj, mobj->target->scale);
+	}
+	break;
 	case MT_VWREF:
 	case MT_VWREB:
 	{
@@ -11563,6 +11594,9 @@ void P_AfterPlayerSpawn(INT32 playernum)
 
 	if (CheckForReverseGravity)
 		P_CheckGravity(mobj, false);
+	
+	if (p->pflags & PF_FINISHED)
+		P_GiveFinishFlags(p);
 }
 
 // spawn it at a playerspawn mapthing
diff --git a/src/p_user.c b/src/p_user.c
index b46659bcb516eb653970b343fdbced4bdce509ee..dfe18f287edb66af53a931794a02ebe6377df9ba 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -370,6 +370,33 @@ void P_GiveEmerald(boolean spawnObj)
 	}
 }
 
+//
+// P_GiveFinishFlags
+//
+// Give the player visual indicators
+// that they've finished the map.
+//
+void P_GiveFinishFlags(player_t *player)
+{
+	angle_t angle = FixedAngle(player->mo->angle << FRACBITS);
+	UINT8 i;
+
+	if (!player->mo)
+		return;
+
+	for (i = 0; i < 3; i++)
+	{
+		angle_t fa = (angle >> ANGLETOFINESHIFT) & FINEMASK;
+		fixed_t xoffs = FINECOSINE(fa);
+		fixed_t yoffs = FINESINE(fa);
+		mobj_t* flag = P_SpawnMobjFromMobj(player->mo, xoffs, yoffs, 0, MT_FINISHFLAG);
+		flag->angle = angle;
+		angle += FixedAngle(120*FRACUNIT);
+		
+		P_SetTarget(&flag->target, player->mo);
+	}
+}
+
 #if 0
 //
 // P_ResetScore
@@ -2171,6 +2198,7 @@ void P_DoPlayerFinish(player_t *player)
 		return;
 
 	player->pflags |= PF_FINISHED;
+	P_GiveFinishFlags(player);
 
 	if (netgame)
 		CONS_Printf(M_GetText("%s has completed the level.\n"), player_names[player-players]);
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 3a140e4ee997f07bba565657cf8806eadcc6ef95..c1b1ff6e6341d7c5a616bf7c0f08124a00da57af 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -128,6 +128,7 @@ static patch_t *minus5sec;
 static patch_t *minicaps;
 static patch_t *gotrflag;
 static patch_t *gotbflag;
+static patch_t *fnshico;
 
 static boolean facefreed[MAXPLAYERS];
 
@@ -310,6 +311,7 @@ void ST_LoadGraphics(void)
 	bmatcico = W_CachePatchName("BMATCICO", PU_HUDGFX);
 	gotrflag = W_CachePatchName("GOTRFLAG", PU_HUDGFX);
 	gotbflag = W_CachePatchName("GOTBFLAG", PU_HUDGFX);
+	fnshico = W_CachePatchName("FNSHICO", PU_HUDGFX);
 	nonicon = W_CachePatchName("NONICON", PU_HUDGFX);
 	nonicon2 = W_CachePatchName("NONICON2", PU_HUDGFX);
 
@@ -944,7 +946,7 @@ static void ST_drawLivesArea(void)
 					'\x16' | 0x80 | hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, false);
 			else
 			{
-				if (stplyr->playerstate == PST_DEAD && !(stplyr->spectator) && (livescount || stplyr->deadtimer < (TICRATE<<1)))
+				if (stplyr->playerstate == PST_DEAD && !(stplyr->spectator) && (livescount || stplyr->deadtimer < (TICRATE<<1)) && !(stplyr->pflags & PF_FINISHED))
 					livescount++;
 				if (livescount > 99)
 					livescount = 99;
@@ -1434,7 +1436,7 @@ static void ST_drawPowerupHUD(void)
 	UINT16 invulntime = 0;
 	INT32 offs = hudinfo[HUD_POWERUPS].x;
 	const UINT8 q = ((splitscreen && stplyr == &players[secondarydisplayplayer]) ? 1 : 0);
-	static INT32 flagoffs[2] = {0, 0}, shieldoffs[2] = {0, 0};
+	static INT32 flagoffs[2] = {0, 0}, shieldoffs[2] = {0, 0}, finishoffs[2] = {0, 0};
 #define ICONSEP (16+4) // matches weapon rings HUD
 
 	if (F_GetPromptHideHud(hudinfo[HUD_POWERUPS].y))
@@ -1442,6 +1444,26 @@ static void ST_drawPowerupHUD(void)
 
 	if (stplyr->spectator || stplyr->playerstate != PST_LIVE)
 		return;
+	
+// ---------
+// Finish icon
+// ---------
+
+	// Let's have a power-like icon to represent finishing the level!
+	if (stplyr->pflags & PF_FINISHED && cv_exitmove.value)
+	{
+		finishoffs[q] = ICONSEP;
+		V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, fnshico);
+	}
+	else if (finishoffs[q])
+	{
+		if (finishoffs[q] > 1)
+			finishoffs[q] = 2*finishoffs[q]/3;
+		else
+			finishoffs[q] = 0;
+	}
+
+	offs -= finishoffs[q];
 
 // -------
 // Shields