diff --git a/src/dehacked.c b/src/dehacked.c
index 729e963112cb17fb7e57d018574e0a611183cb0b..aa5d84954591ad63e9620e7327d2e7c237bb566e 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -6962,6 +6962,7 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_PULL",
 	"MT_GHOST",
 	"MT_OVERLAY",
+	"MT_ANGLEMAN",
 	"MT_POLYANCHOR",
 	"MT_POLYSPAWN",
 	"MT_POLYSPAWNCRUSH",
diff --git a/src/info.c b/src/info.c
index 3140bd7de121ed761f824d508cbf92c26eb9b642..72abcede1dad474b81a74f197dda5f3345e5836f 100644
--- a/src/info.c
+++ b/src/info.c
@@ -17814,6 +17814,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_ANGLEMAN
+		758,            // doomednum
+		S_INVISIBLE,    // 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
+		0,              // speed
+		8,              // radius
+		8,              // height
+		0,              // display offset
+		10,             // mass
+		0,              // damage
+		sfx_None,       // activesound
+		MF_NOTHINK|MF_NOBLOCKMAP|MF_NOGRAVITY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_POLYANCHOR
 		760,            // doomednum
 		S_INVISIBLE,    // spawnstate
diff --git a/src/info.h b/src/info.h
index b757deec0353319de13d62ff7fef8104ad74c1f4..c9de82541c64f16e0b76b79b1ad43121f0750c73 100644
--- a/src/info.h
+++ b/src/info.h
@@ -4305,6 +4305,7 @@ typedef enum mobj_type
 	MT_PULL,
 	MT_GHOST,
 	MT_OVERLAY,
+	MT_ANGLEMAN,
 	MT_POLYANCHOR,
 	MT_POLYSPAWN,
 	MT_POLYSPAWNCRUSH,
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 9189f50f7c46c99e6a22aaf4be76204d83c34dc5..3c1418252839601810150fc782f5763c92021c02 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -7436,6 +7436,48 @@ void P_MobjThinker(mobj_t *mobj)
 		if ((mobj->flags & MF_ENEMY) && (mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1))
 			mobj->flags2 &= ~MF2_FRET;
 
+		// Angle-to-tracer to trigger a linedef exec
+		// See Linedef Exec 457 (Track mobj angle to point)
+		if ((mobj->eflags & MFE_TRACERANGLE) && mobj->tracer && mobj->extravalue2)
+		{
+			// mobj->lastlook - Don't disable behavior after first failure
+			// mobj->extravalue1 - Angle tolerance
+			// mobj->extravalue2 - Exec tag upon failure
+			// mobj->cvval - Allowable failure delay
+			// mobj->cvmem - Failure timer
+
+			angle_t ang = mobj->angle - R_PointToAngle2(mobj->x, mobj->y, mobj->tracer->x, mobj->tracer->y);
+
+			// \todo account for distance between mobj and tracer
+			// Because closer mobjs can be facing beyond the angle tolerance
+			// yet tracer is still in the camera view
+
+			// failure state: mobj is not facing tracer
+			// Reasaonable defaults: ANGLE_67h, ANGLE_292h
+			if (ang >= (UINT32)mobj->extravalue1 && ang <= ANGLE_MAX - (UINT32)mobj->extravalue1)
+			{
+				if (mobj->cvmem)
+					mobj->cvmem--;
+				else
+				{
+					INT32 exectag = mobj->extravalue2; // remember this before we erase the values
+
+					if (mobj->lastlook)
+						mobj->cvmem = mobj->cusval; // reset timer for next failure
+					else
+					{
+						// disable after first failure
+						mobj->eflags &= ~MFE_TRACERANGLE;
+						mobj->lastlook = mobj->extravalue1 = mobj->extravalue2 = mobj->cvmem = mobj->cusval = 0;
+					}
+
+					P_LinedefExecute(exectag, mobj, NULL);
+				}
+			}
+			else
+				mobj->cvmem = mobj->cusval; // reset failure timer
+		}
+
 		switch (mobj->type)
 		{
 			case MT_WALLSPIKEBASE:
diff --git a/src/p_mobj.h b/src/p_mobj.h
index dd4da765d77dc61b298646937e31d9e55e8fac17..eac5118b84c7be261e0d095db3a317094f631c37 100644
--- a/src/p_mobj.h
+++ b/src/p_mobj.h
@@ -239,6 +239,9 @@ typedef enum
 	MFE_SPRUNG            = 1<<8,
 	// Platform movement
 	MFE_APPLYPMOMZ        = 1<<9,
+	// Compute and trigger on mobj angle relative to tracer
+	// See Linedef Exec 457 (Track mobj angle to point)
+	MFE_TRACERANGLE       = 1<<10,
 	// free: to and including 1<<15
 } mobjeflag_t;
 
diff --git a/src/p_spec.c b/src/p_spec.c
index 9f9e80eccf5507afd69871215342b1a949135324..965e0cbe062f2e6e14c2539175518faca361d4d8 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -3757,6 +3757,41 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
 				P_ResetColormapFader(&sectors[secnum]);
 			break;
 
+		case 457: // Track mobj angle to point
+			if (mo)
+			{
+				INT32 failureangle = min(max(abs(sides[line->sidenum[0]].textureoffset>>FRACBITS), 0), 360) * ANG1;
+				INT32 failuredelay = abs(sides[line->sidenum[0]].rowoffset>>FRACBITS);
+				INT32 failureexectag = line->sidenum[1] != 0xffff ?
+					(INT32)(sides[line->sidenum[1]].textureoffset>>FRACBITS) : 0;
+				boolean persist = (line->flags & ML_EFFECT2);
+				mobj_t *anchormo;
+
+				if ((secnum = P_FindSectorFromLineTag(line, -1)) < 0)
+					return;
+
+				anchormo = P_GetObjectTypeInSectorNum(MT_ANGLEMAN, secnum);
+				if (!anchormo)
+					return;
+
+				mo->eflags |= MFE_TRACERANGLE;
+				P_SetTarget(&mo->tracer, anchormo);
+				mo->lastlook = persist; // don't disable behavior after first failure
+				mo->extravalue1 = failureangle; // angle to exceed for failure state
+				mo->extravalue2 = failureexectag; // exec tag for failure state (angle is not within range)
+				mo->cusval = mo->cvmem = failuredelay; // cusval = tics to allow failure before line trigger; cvmem = decrement timer
+			}
+			break;
+
+		case 458: // Stop tracking mobj angle to point
+			if (mo && (mo->eflags & MFE_TRACERANGLE))
+			{
+				mo->eflags &= ~MFE_TRACERANGLE;
+				P_SetTarget(&mo->tracer, NULL);
+				mo->lastlook = mo->cvmem = mo->cusval = mo->extravalue1 = mo->extravalue2 = 0;
+			}
+			break;
+
 #ifdef POLYOBJECTS
 		case 480: // Polyobj_DoorSlide
 		case 481: // Polyobj_DoorSwing