diff --git a/source/core/namedef_custom.h b/source/core/namedef_custom.h
index 6f96046e5..622c3d5ba 100644
--- a/source/core/namedef_custom.h
+++ b/source/core/namedef_custom.h
@@ -26,6 +26,7 @@ xx(DukeGlassPieces2)
 xx(DukeNaturalLightning)
 xx(RedneckBowlingPin)
 xx(DukeReactor)
+xx(DukeFootprints)
 
 xx(spawnstate)
 xx(brokenstate)
diff --git a/source/games/duke/src/animatesprites_d.cpp b/source/games/duke/src/animatesprites_d.cpp
index fbf1d818f..49fccde7d 100644
--- a/source/games/duke/src/animatesprites_d.cpp
+++ b/source/games/duke/src/animatesprites_d.cpp
@@ -58,26 +58,13 @@ void animatesprites_d(tspriteArray& tsprites, const DVector2& viewVec, DAngle vi
 		t = tsprites.get(j);
 		h = static_cast<DDukeActor*>(t->ownerActor);
 
-		if (!actorflag(h, SFLAG2_FORCESECTORSHADE))
-		switch (t->picnum)
+		if (!actorflag(h, SFLAG2_FORCESECTORSHADE) && ((t->cstat & CSTAT_SPRITE_ALIGNMENT_WALL)) || (badguypic(t->picnum) && t->extra > 0) || t->statnum == STAT_PLAYER)
 		{
-		case DEVELOPERCOMMENTARY:
-		case DEVELOPERCOMMENTARYON:
-			if (isWorldTour() && !wt_commentary)
-				t->scale = DVector2(0, 0);
-			break;
-		case FOOTPRINTS:
-		case FOOTPRINTS2:
-		case FOOTPRINTS3:
-		case FOOTPRINTS4:
-			if (t->shade == 127) continue;
-			break;
-		case BULLETHOLE:
-			t->shade = 16;
+			if (h->sector()->shadedsector == 1 && h->spr.statnum != 1)
+			{
+				t->shade = 16;
+			}
 			continue;
-		default:
-			if (((t->cstat & CSTAT_SPRITE_ALIGNMENT_WALL)) || (badguypic(t->picnum) && t->extra > 0) || t->statnum == STAT_PLAYER)
-				continue;
 		}
 
 		if (t->sectp != nullptr)
@@ -151,13 +138,6 @@ void animatesprites_d(tspriteArray& tsprites, const DVector2& viewVec, DAngle vi
 		case DUKELYINGDEAD:
 			t->pos.Z += 24;
 			break;
-		case FOOTPRINTS:
-		case FOOTPRINTS2:
-		case FOOTPRINTS3:
-		case FOOTPRINTS4:
-			if (t->pal == 6)
-				t->shade = -127;
-			break;
 		case BURNING:
 		case BURNING2:
 			if (OwnerAc && OwnerAc->spr.statnum == STAT_PLAYER)
diff --git a/source/games/duke/src/animatesprites_r.cpp b/source/games/duke/src/animatesprites_r.cpp
index 49bfdbfc0..cd1e2827c 100644
--- a/source/games/duke/src/animatesprites_r.cpp
+++ b/source/games/duke/src/animatesprites_r.cpp
@@ -53,34 +53,13 @@ void animatesprites_r(tspriteArray& tsprites, const DVector2& viewVec, DAngle vi
 		t = tsprites.get(j);
 		h = static_cast<DDukeActor*>(t->ownerActor);
 
-		switch (t->picnum)
+		if (!actorflag(h, SFLAG2_FORCESECTORSHADE) && ((t->cstat & CSTAT_SPRITE_ALIGNMENT_WALL)) || (badguypic(t->picnum) && t->extra > 0) || t->statnum == STAT_PLAYER)
 		{
-		case FOOTPRINTS:
-		case FOOTPRINTS2:
-		case FOOTPRINTS3:
-		case FOOTPRINTS4:
-			if (t->shade == 127) continue;
-			break;
-		case BULLETHOLE:
-			t->shade = 16;
-			continue;
-
-		case RRTILE1947:
-		case RRTILE2859:
-		case RRTILE3774:
-		case RRTILE8096:
-			if (isRRRA()) continue;
-
-		default:
-			if (((t->cstat & CSTAT_SPRITE_ALIGNMENT_WALL)) || (badguypic(t->picnum) && t->extra > 0) || t->statnum == STAT_PLAYER)
+			if (h->sector()->shadedsector == 1 && h->spr.statnum != 1)
 			{
-				if (h->sector()->shadedsector == 1 && h->spr.statnum != 1)
-				{
-					h->spr.shade = 16;
-					t->shade = 16;
-				}
-				continue;
+				t->shade = 16;
 			}
+			continue;
 		}
 
 		if (t->sectp != nullptr)
@@ -158,13 +137,6 @@ void animatesprites_r(tspriteArray& tsprites, const DVector2& viewVec, DAngle vi
 			if (h->spr.extra > 0)
 				t->pos.Z += 6;
 			break;
-		case FOOTPRINTS:
-		case FOOTPRINTS2:
-		case FOOTPRINTS3:
-		case FOOTPRINTS4:
-			if (t->pal == 6)
-				t->shade = -127;
-			break;
 		case POWDERKEG:
 			continue;
 		case BURNING:
diff --git a/source/games/duke/src/dispatch.cpp b/source/games/duke/src/dispatch.cpp
index c43493af3..a076ec1c7 100644
--- a/source/games/duke/src/dispatch.cpp
+++ b/source/games/duke/src/dispatch.cpp
@@ -193,10 +193,6 @@ int TILE_FIRE;
 int TILE_WATERBUBBLE;
 int TILE_SMALLSMOKE;
 int TILE_BLOODPOOL;
-int TILE_FOOTPRINTS;
-int TILE_FOOTPRINTS2;
-int TILE_FOOTPRINTS3;
-int TILE_FOOTPRINTS4;
 int TILE_CLOUDYSKIES;
 int TILE_ACCESSSWITCH;
 int TILE_ACCESSSWITCH2;
diff --git a/source/games/duke/src/flags_d.cpp b/source/games/duke/src/flags_d.cpp
index a1a2c4f8f..bcffc5d97 100644
--- a/source/games/duke/src/flags_d.cpp
+++ b/source/games/duke/src/flags_d.cpp
@@ -245,10 +245,6 @@ void initactorflags_d()
 	TILE_WATERBUBBLE = WATERBUBBLE;
 	TILE_SMALLSMOKE = SMALLSMOKE;
 	TILE_BLOODPOOL = BLOODPOOL;
-	TILE_FOOTPRINTS = FOOTPRINTS;
-	TILE_FOOTPRINTS2 = FOOTPRINTS2;
-	TILE_FOOTPRINTS3 = FOOTPRINTS3;
-	TILE_FOOTPRINTS4 = FOOTPRINTS4;
 	TILE_CLOUDYSKIES = CLOUDYSKIES;
 	TILE_ACCESSSWITCH = ACCESSSWITCH;
 	TILE_ACCESSSWITCH2 = ACCESSSWITCH2;
diff --git a/source/games/duke/src/flags_r.cpp b/source/games/duke/src/flags_r.cpp
index fefd83655..99cc2bb68 100644
--- a/source/games/duke/src/flags_r.cpp
+++ b/source/games/duke/src/flags_r.cpp
@@ -252,10 +252,6 @@ void initactorflags_r()
 	TILE_WATERBUBBLE = WATERBUBBLE;
 	TILE_SMALLSMOKE = SMALLSMOKE;
 	TILE_BLOODPOOL = BLOODPOOL;
-	TILE_FOOTPRINTS = FOOTPRINTS;
-	TILE_FOOTPRINTS2 = FOOTPRINTS2;
-	TILE_FOOTPRINTS3 = FOOTPRINTS3;
-	TILE_FOOTPRINTS4 = FOOTPRINTS4;
 	TILE_CLOUDYSKIES = CLOUDYSKIES;
 	TILE_ACCESSSWITCH = ACCESSSWITCH;
 	TILE_ACCESSSWITCH2 = ACCESSSWITCH2;
diff --git a/source/games/duke/src/funct.h b/source/games/duke/src/funct.h
index dd2c37ef9..eec0ddcc9 100644
--- a/source/games/duke/src/funct.h
+++ b/source/games/duke/src/funct.h
@@ -174,7 +174,6 @@ bool initspriteforspawn(DDukeActor* spn);
 void spawninitdefault(DDukeActor* actj, DDukeActor* act);
 void spawntransporter(DDukeActor* actj, DDukeActor* acti, bool beam);
 int spawnbloodpoolpart1(DDukeActor* acti);
-void initfootprint(DDukeActor* actj, DDukeActor* acti);
 void initshell(DDukeActor* actj, DDukeActor* acti, bool isshell);
 void spawneffector(DDukeActor* actor, TArray<DDukeActor*>* actors);
 int startrts(int lumpNum, int localPlayer);
diff --git a/source/games/duke/src/names.h b/source/games/duke/src/names.h
index 2b7d4dc73..a9c237e2e 100644
--- a/source/games/duke/src/names.h
+++ b/source/games/duke/src/names.h
@@ -18,10 +18,6 @@ extern int TILE_FIRE;
 extern int TILE_WATERBUBBLE;
 extern int TILE_SMALLSMOKE;
 extern int TILE_BLOODPOOL;
-extern int TILE_FOOTPRINTS;
-extern int TILE_FOOTPRINTS2;
-extern int TILE_FOOTPRINTS3;
-extern int TILE_FOOTPRINTS4;
 extern int TILE_CLOUDYSKIES;
 extern int TILE_ACCESSSWITCH;
 extern int TILE_ACCESSSWITCH2;
diff --git a/source/games/duke/src/player.cpp b/source/games/duke/src/player.cpp
index 4d2dd066b..999c44138 100644
--- a/source/games/duke/src/player.cpp
+++ b/source/games/duke/src/player.cpp
@@ -508,7 +508,7 @@ void footprints(int snum)
 			DukeSectIterator it(actor->sector());
 			while (auto act = it.Next())
 			{
-				if (act->spr.picnum == TILE_FOOTPRINTS || act->spr.picnum == TILE_FOOTPRINTS2 || act->spr.picnum == TILE_FOOTPRINTS3 || act->spr.picnum == TILE_FOOTPRINTS4)
+				if (act->IsKindOf(NAME_DukeFootprints))
 					if (abs(act->spr.pos.X - p->GetActor()->spr.pos.X) < 24)
 						if (abs(act->spr.pos.Y - p->GetActor()->spr.pos.Y) < 24)
 						{
@@ -521,16 +521,10 @@ void footprints(int snum)
 				p->footprintcount--;
 				if (p->cursector->lotag == 0 && p->cursector->hitag == 0)
 				{
-					DDukeActor* fprint;
-					switch (krand() & 3)
-					{
-					case 0:	 fprint = spawn(actor, TILE_FOOTPRINTS); break;
-					case 1:	 fprint = spawn(actor, TILE_FOOTPRINTS2); break;
-					case 2:	 fprint = spawn(actor, TILE_FOOTPRINTS3); break;
-					default: fprint = spawn(actor, TILE_FOOTPRINTS4); break;
-					}
+					DDukeActor* fprint = spawn(actor, PClass::FindActor(NAME_DukeFootprints));
 					if (fprint)
 					{
+						fprint->spr.Angles.Yaw = p->actor->spr.Angles.Yaw;
 						fprint->spr.pal = p->footprintpal;
 						fprint->spr.shade = (int8_t)p->footprintshade;
 					}
diff --git a/source/games/duke/src/sectors_d.cpp b/source/games/duke/src/sectors_d.cpp
index e68481e57..1722ddd27 100644
--- a/source/games/duke/src/sectors_d.cpp
+++ b/source/games/duke/src/sectors_d.cpp
@@ -198,22 +198,6 @@ bool checkhitswitch_d(int snum, walltype* wwal, DDukeActor *act)
 	case ALIENSWITCH:
 	case ALIENSWITCHON:
 		break;
-	case DEVELOPERCOMMENTARY + 1: //Twentieth Anniversary World Tour
-		if (act)
-		{
-			StopCommentary();
-			act->spr.picnum = DEVELOPERCOMMENTARY;
-			return true;
-		}
-		return false;
-	case DEVELOPERCOMMENTARY: //Twentieth Anniversary World Tour
-		if (act)
-		{
-			if (StartCommentary(lotag, act))
-				act->spr.picnum = DEVELOPERCOMMENTARY+1;
-			return true;
-		}
-		return false;
 	case ACCESSSWITCH:
 	case ACCESSSWITCH2:
 		if (ps[snum].access_incs == 0)
@@ -1220,20 +1204,8 @@ void checksectors_d(int snum)
 		{
 			if (fi.checkhitswitch(snum, nullptr, neartagsprite)) return;
 
-			if (neartagsprite->GetClass() != RUNTIME_CLASS(DDukeActor))
-			{
-				if (CallOnUse(neartagsprite, p))
-					return;
-			}
-			else
-				switch (neartagsprite->spr.picnum)
-				{
-				case PLUG:
-					S_PlayActorSound(SHORT_CIRCUIT, pact);
-					p->GetActor()->spr.extra -= 2 + (krand() & 3);
-					SetPlayerPal(p, PalEntry(32, 48, 48, 64));
-					break;
-				}
+			if (CallOnUse(neartagsprite, p))
+				return;
 		}
 
 		if (!PlayerInput(snum, SB_OPEN)) return;
diff --git a/source/games/duke/src/sectors_r.cpp b/source/games/duke/src/sectors_r.cpp
index 751c2041c..50d894038 100644
--- a/source/games/duke/src/sectors_r.cpp
+++ b/source/games/duke/src/sectors_r.cpp
@@ -1631,11 +1631,6 @@ void checksectors_r(int snum)
 				if (!isRRRA()) return;
 				OnBoat(p, neartagsprite);
 				return;
-
-			case PLUG:
-				S_PlayActorSound(SHORT_CIRCUIT, pact);
-				p->GetActor()->spr.extra -= 2 + (krand() & 3);
-				SetPlayerPal(p, PalEntry(32, 48, 48, 64));
 				break;
 			}
 		}
diff --git a/source/games/duke/src/sounds.cpp b/source/games/duke/src/sounds.cpp
index c65224a01..7c75973eb 100644
--- a/source/games/duke/src/sounds.cpp
+++ b/source/games/duke/src/sounds.cpp
@@ -80,6 +80,8 @@ void MuteSounds()
 		});
 }
 
+void setSpritesetImage(DDukeActor* self, unsigned int index);
+
 class DukeSoundEngine : public RazeSoundEngine
 {
 	// client specific parts of the sound engine go in this class.
@@ -113,7 +115,7 @@ public:
 		{
 			UnloadSound(schan->SoundID.index());
 			currentCommentarySound = NO_SOUND;
-			if (currentCommentarySprite) currentCommentarySprite->spr.picnum = DEVELOPERCOMMENTARY;
+			if (currentCommentarySprite) setSpritesetImage(currentCommentarySprite, 0);
 			I_SetRelativeVolume(1.0f);
 			UnmuteSounds();
 		}
@@ -851,7 +853,7 @@ void StopCommentary()
 	}
 }
 
-bool StartCommentary(int tag, DDukeActor* actor)
+int StartCommentary(int tag, DDukeActor* actor)
 {
 	if (wt_commentary && Commentaries.Size() > (unsigned)tag && Commentaries[tag].IsNotEmpty())
 	{
diff --git a/source/games/duke/src/sounds.h b/source/games/duke/src/sounds.h
index 97221920c..33c52d5cf 100644
--- a/source/games/duke/src/sounds.h
+++ b/source/games/duke/src/sounds.h
@@ -109,7 +109,7 @@ void S_ContinueLevelMusic(void);
 void S_ParseDeveloperCommentary();
 
 void StopCommentary();
-bool StartCommentary(int tag, DDukeActor* sprnum);
+int StartCommentary(int tag, DDukeActor* sprnum);
 
 
 END_DUKE_NS
diff --git a/source/games/duke/src/spawn.cpp b/source/games/duke/src/spawn.cpp
index 4a2796870..a3d447a21 100644
--- a/source/games/duke/src/spawn.cpp
+++ b/source/games/duke/src/spawn.cpp
@@ -403,37 +403,6 @@ int spawnbloodpoolpart1(DDukeActor* act)
 //
 //---------------------------------------------------------------------------
 
-void initfootprint(DDukeActor* actj, DDukeActor* act)
-{
-	auto sect = act->sector();
-	if (actj)
-	{
-		bool away = isAwayFromWall(act, 5.25);
-		if (!away)
-		{
-			act->spr.scale = DVector2(0, 0);
-			return;
-		}
-
-		act->spr.cstat = CSTAT_SPRITE_ALIGNMENT_FLOOR;
-		if ((ps[actj->PlayerIndex()].footprintcount & 1)) act->spr.cstat |= CSTAT_SPRITE_XFLIP;
-		act->spr.Angles.Yaw = actj->spr.Angles.Yaw;
-	}
-
-	act->spr.pos.Z = sect->floorz;
-	if (sect->lotag != 1 && sect->lotag != 2)
-		act->spr.scale = DVector2(0.5, 0.5);
-
-	insertspriteq(act);
-	ChangeActorStat(act, STAT_MISC);
-}
-
-//---------------------------------------------------------------------------
-//
-// 
-//
-//---------------------------------------------------------------------------
-
 void spawneffector(DDukeActor* actor, TArray<DDukeActor*>* actors)
 {
 	auto sectp = actor->sector();
diff --git a/source/games/duke/src/spawn_d.cpp b/source/games/duke/src/spawn_d.cpp
index 2129dac95..33c7ffb80 100644
--- a/source/games/duke/src/spawn_d.cpp
+++ b/source/games/duke/src/spawn_d.cpp
@@ -179,49 +179,6 @@ DDukeActor* spawninit_d(DDukeActor* actj, DDukeActor* act, TArray<DDukeActor*>*
 		ChangeActorStat(act, STAT_MISC);
 		break;
 
-	case FEMMAG1: // ok
-	case FEMMAG2: // ok
-		act->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
-		ChangeActorStat(act, 0);
-		break;
-	case DUKETAG: // ok
-	case SIGN1:		// ok
-	case SIGN2:		// ok
-		if (ud.multimode < 2 && act->spr.pal)
-		{
-			act->spr.scale = DVector2(0, 0);
-			ChangeActorStat(act, STAT_MISC);
-		}
-		else act->spr.pal = 0;
-		break;
-	case MASKWALL1:	// all ok
-	case MASKWALL2:
-	case MASKWALL3:
-	case MASKWALL4:
-	case MASKWALL5:
-	case MASKWALL6:
-	case MASKWALL7:
-	case MASKWALL8:
-	case MASKWALL9:
-	case MASKWALL10:
-	case MASKWALL11:
-	case MASKWALL12:
-	case MASKWALL13:
-	case MASKWALL14:
-	case MASKWALL15:
-	{
-		auto j = act->spr.cstat & (CSTAT_SPRITE_ALIGNMENT_MASK | CSTAT_SPRITE_XFLIP | CSTAT_SPRITE_YFLIP);
-		act->spr.cstat = j | CSTAT_SPRITE_BLOCK;
-		ChangeActorStat(act, 0);
-		break;
-	}
-	case FOOTPRINTS:	// ok
-	case FOOTPRINTS2:
-	case FOOTPRINTS3:
-	case FOOTPRINTS4:
-		initfootprint(actj, act);
-		break;
-
 	case FEM1:
 	case FEM2:
 	case FEM3:
@@ -263,20 +220,8 @@ DDukeActor* spawninit_d(DDukeActor* actj, DDukeActor* act, TArray<DDukeActor*>*
 	case MIKE:
 		if (act->spr.picnum == MIKE)
 			act->spr.yint = act->spr.hitag;
-		[[fallthrough]];
-	case WEATHERWARN: // ok
-		ChangeActorStat(act, STAT_ACTOR);
+		ChangeActorStat(act, 1);
 		break;
-
-	case SPOTLITE: // ok
-		break;
-	case BULLETHOLE: // ok
-		act->spr.scale = DVector2(0.046875, 0.046875);
-		act->spr.cstat = CSTAT_SPRITE_ALIGNMENT_WALL | randomFlip();
-		insertspriteq(act);
-		ChangeActorStat(act, STAT_MISC);
-		break;
-
 	case ONFIRE:
 		// Twentieth Anniversary World Tour
 		if (!isWorldTour())
@@ -376,10 +321,6 @@ DDukeActor* spawninit_d(DDukeActor* actj, DDukeActor* act, TArray<DDukeActor*>*
 		ChangeActorStat(act, STAT_STANDABLE);
 		break;
 
-	case PLUG:
-		act->spr.lotag = 9999;
-		ChangeActorStat(act, STAT_STANDABLE);
-		break;
 	case WATERBUBBLEMAKER:
 		if (act->spr.hitag && act->spr.picnum == WATERBUBBLEMAKER)
 		{	// JBF 20030913: Pisses off move(), eg. in bobsp2
@@ -594,23 +535,6 @@ DDukeActor* spawninit_d(DDukeActor* actj, DDukeActor* act, TArray<DDukeActor*>*
 		ChangeActorStat(act, STAT_STANDABLE);
 		break;
 
-	case CAMERAPOLE: // ok
-		act->spr.extra = 1;
-
-		if (gs.camerashitable) act->spr.cstat = CSTAT_SPRITE_BLOCK_ALL;
-		else act->spr.cstat = 0;
-		[[fallthrough]];
-
-	case GENERICPOLE: // ok
-
-		if (ud.multimode < 2 && act->spr.pal != 0)
-		{
-			act->spr.scale = DVector2(0, 0);
-			ChangeActorStat(act, STAT_MISC);
-			break;
-		}
-		else act->spr.pal = 0;
-		break;
 	case STEAM:
 		if (actj)
 		{
diff --git a/source/games/duke/src/spawn_r.cpp b/source/games/duke/src/spawn_r.cpp
index becf95569..289f87185 100644
--- a/source/games/duke/src/spawn_r.cpp
+++ b/source/games/duke/src/spawn_r.cpp
@@ -97,25 +97,6 @@ DDukeActor* spawninit_r(DDukeActor* actj, DDukeActor* act, TArray<DDukeActor*>*
 		act->spr.cstat |= CSTAT_SPRITE_BLOCK_ALL;
 		ChangeActorStat(act, 0);
 		break;
-	case FEMMAG1:
-	case FEMMAG2:
-		act->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
-		ChangeActorStat(act, 0);
-		break;
-
-	case MASKWALL7:
-	{
-		auto j = act->spr.cstat & (CSTAT_SPRITE_ALIGNMENT_MASK | CSTAT_SPRITE_XFLIP | CSTAT_SPRITE_YFLIP);
-		act->spr.cstat = j | CSTAT_SPRITE_BLOCK;
-		ChangeActorStat(act, 0);
-		break;
-	}
-	case FOOTPRINTS:
-	case FOOTPRINTS2:
-	case FOOTPRINTS3:
-	case FOOTPRINTS4:
-		initfootprint(actj, act);
-		break;
 	case FEM10:
 	case NAKED1:
 	case STATUE:
@@ -144,15 +125,6 @@ DDukeActor* spawninit_r(DDukeActor* actj, DDukeActor* act, TArray<DDukeActor*>*
 		ChangeActorStat(act, STAT_ACTOR);
 		break;
 
-	case SPOTLITE:
-		break;
-	case BULLETHOLE:
-		act->spr.scale = DVector2(0.046875, 0.046875);
-		act->spr.cstat = CSTAT_SPRITE_ALIGNMENT_WALL | randomFlip();
-		insertspriteq(act);
-		ChangeActorStat(act, STAT_MISC);
-		break;
-
 	case EXPLOSION2:
 	case EXPLOSION3:
 	case BURNING:
@@ -234,10 +206,6 @@ DDukeActor* spawninit_r(DDukeActor* actj, DDukeActor* act, TArray<DDukeActor*>*
 		ChangeActorStat(act, STAT_MISC);
 		break;
 
-	case PLUG:
-		act->spr.lotag = 9999;
-		ChangeActorStat(act, STAT_STANDABLE);
-		break;
 	case WATERBUBBLEMAKER:
 		act->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
 		ChangeActorStat(act, STAT_STANDABLE);
@@ -686,20 +654,6 @@ DDukeActor* spawninit_r(DDukeActor* actj, DDukeActor* act, TArray<DDukeActor*>*
 		}
 		act->spr.shade = act->sector()->floorshade;
 		break;
-	case CAMERAPOLE:
-		act->spr.extra = 1;
-
-		if (gs.camerashitable) act->spr.cstat = CSTAT_SPRITE_BLOCK_ALL;
-		else act->spr.cstat = 0;
-
-		if (ud.multimode < 2 && act->spr.pal != 0)
-		{
-			act->spr.scale = DVector2(0, 0);
-			ChangeActorStat(act, STAT_MISC);
-			break;
-		}
-		else act->spr.pal = 0;
-		break;
 	case STEAM:
 		if (actj)
 		{
diff --git a/source/games/duke/src/vmexports.cpp b/source/games/duke/src/vmexports.cpp
index a39cda4eb..907674b23 100644
--- a/source/games/duke/src/vmexports.cpp
+++ b/source/games/duke/src/vmexports.cpp
@@ -190,6 +190,22 @@ DEFINE_ACTION_FUNCTION_NATIVE(_Duke, updatepindisplay, updatepindisplay)
 	return 0;
 }
 
+DEFINE_ACTION_FUNCTION_NATIVE(_Duke, StartCommentary, StartCommentary)
+{
+	PARAM_PROLOGUE;
+	PARAM_INT(tag);
+	PARAM_POINTER(act, DDukeActor);
+	ACTION_RETURN_BOOL(StartCommentary(tag, act));
+	return 0;
+}
+
+DEFINE_ACTION_FUNCTION_NATIVE(_Duke, StopCommentary, StopCommentary)
+{
+	PARAM_PROLOGUE;
+	StopCommentary();
+	return 0;
+}
+
 DEFINE_GLOBAL_UNSIZED(dlevel)
 DEFINE_GLOBAL(camsprite)
 
@@ -226,7 +242,7 @@ DEFINE_FIELD(DDukeActor, temp_pos)
 DEFINE_FIELD(DDukeActor, temp_pos2)
 DEFINE_FIELD(DDukeActor, temp_angle)
 
-static void setSpritesetImage(DDukeActor* self, unsigned int index)
+void setSpritesetImage(DDukeActor* self, unsigned int index)
 {
 	auto& spriteset = static_cast<PClassActor*>(self->GetClass())->ActorInfo()->SpriteSet;
 
diff --git a/wadsrc/static/filter/duke.worldtour/engine/engine.def b/wadsrc/static/filter/duke.worldtour/engine/engine.def
index 61b051317..d0cdb2a38 100644
--- a/wadsrc/static/filter/duke.worldtour/engine/engine.def
+++ b/wadsrc/static/filter/duke.worldtour/engine/engine.def
@@ -4,4 +4,5 @@ spawnclasses
 	5163 = DukeFireball
 	5736 = DukeGenericDestructible, "WTGLASS1", "", "GLASS_BREAKING", spawnglass
 	5737 = DukeGenericDestructible, "WTGLASS2", "", "GLASS_BREAKING", spawnglass
+	5294 = DeveloperCommentary
 }
diff --git a/wadsrc/static/filter/dukelike/engine/engine.def b/wadsrc/static/filter/dukelike/engine/engine.def
index b6d60643a..dec3db636 100644
--- a/wadsrc/static/filter/dukelike/engine/engine.def
+++ b/wadsrc/static/filter/dukelike/engine/engine.def
@@ -122,6 +122,34 @@ spawnclasses
 	680 = DukeChair3
 	569 = DukeToilet
 	571 = DukeStall
+	1069 = DukePlug
+	568 = DukeFemMag1
+	577 = DukeFemMag2
+	4900 = DukeTag
+	4909 = DukeSign1
+	4912 = DukeSign2
+	285	= DukeMaskWall1
+	913	= DukeMaskWall2
+	914	= DukeMaskWall3
+	915	= DukeMaskWall4
+	514	= DukeMaskWall5
+	1059 = DukeMaskWall6
+	1174 = DukeMaskWall7
+	1124 = DukeMaskWall8
+	255	= DukeMaskWall9
+	387	= DukeMaskWall10
+	391	= DukeMaskWall11
+	609	= DukeMaskWall12
+	830	= DukeMaskWall13
+	988	= DukeMaskWall14
+	1024 = DukeMaskWall15
+	550 = DukeFootprints
+	672 = DukeFootprints
+	673 = DukeFootprints
+	674 = DukeFootprints
+	952 = DukeBulletHole
+	554 = DukeCameraPole
+	977 = DukeGenericPole
 
 	1272 = DukeTrash
 	634 = DukeBolt1
diff --git a/wadsrc/static/filter/redneck/engine/engine.def b/wadsrc/static/filter/redneck/engine/engine.def
index 29969ba53..5deb51a0d 100644
--- a/wadsrc/static/filter/redneck/engine/engine.def
+++ b/wadsrc/static/filter/redneck/engine/engine.def
@@ -106,6 +106,15 @@ spawnclasses
 	1100 = DukeStall
 	2121 = RedneckToiletSeat
 	2122 = RedneckToilet2
+	1097 = DukeFemMag1
+	1106 = DukeFemMag2
+	2264 = DukeMaskWall7
+	1079 = DukeFootprints
+	1144 = DukeFootprints
+	1145 = DukeFootprints
+	1146 = DukeFootprints
+	1212 = DukeBulletHole
+	1083 = DukeCameraPole
 
 	26 = RedneckDynamite
 	1416 = RedneckMortar
diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt
index cb6f28625..6ee64ecbd 100644
--- a/wadsrc/static/zscript.txt
+++ b/wadsrc/static/zscript.txt
@@ -61,6 +61,7 @@ version "4.10"
 #include "zscript/games/duke/actors/frameeffect.zs"
 #include "zscript/games/duke/actors/naturallightning.zs"
 
+#include "zscript/games/duke/actors/dukemisc.zs"
 #include "zscript/games/duke/actors/projectiles.zs"
 #include "zscript/games/duke/actors/rat.zs"
 #include "zscript/games/duke/actors/jibs.zs"
diff --git a/wadsrc/static/zscript/games/duke/actors/dukemisc.zs b/wadsrc/static/zscript/games/duke/actors/dukemisc.zs
new file mode 100644
index 000000000..e241c0fd9
--- /dev/null
+++ b/wadsrc/static/zscript/games/duke/actors/dukemisc.zs
@@ -0,0 +1,269 @@
+
+class DukePlug : DukeActor
+{
+	default
+	{
+		pic "PLUG";
+		statnum STAT_STANDABLE;
+		lotag 9999;
+	}
+	
+	override bool OnUse(DukePlayer p)
+	{
+		p.actor.PlayActorSound("SHORT_CIRCUIT");
+		p.actor.extra -= random(2, 5);
+		p.pals = Color(32, 48, 48, 64);
+		return true;
+	}
+}
+
+class DukeFemMag1 : DukeActor
+{
+	default 
+	{
+		pic "FEMMAG1";
+		statnum STAT_DEFAULT;
+	}
+	
+	override void Initialize()
+	{
+		self.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
+	}
+}
+
+class DukeFemMag2 : DukeFemMag1
+{
+	default { pic "FEMMAG1"; }
+}
+
+class DukeTag : DukeActor
+{
+	default 
+	{
+		pic "DUKETAG";
+	}
+	
+	override void Initialize()
+	{
+		if (ud.multimode < 2 && self.pal)
+		{
+			self.scale = (0, 0);
+			self.ChangeStat(STAT_MISC);
+		}
+		else self.pal = 0;
+	}
+}
+
+class DukeSign1 : DukeTag
+{
+	default { pic "SIGN1"; }
+}
+
+class DukeSign2 : DukeTag
+{
+	default { pic "SIGN2"; }
+}
+
+class DukeMaskWall1 : DukeActor
+{
+	default
+	{
+		pic "MASKWALL1";
+		statnum STAT_DEFAULT;
+	}
+	
+	override void Initialize()
+	{
+		let j = self.cstat & (CSTAT_SPRITE_ALIGNMENT_MASK | CSTAT_SPRITE_XFLIP | CSTAT_SPRITE_YFLIP);
+		self.cstat = j | CSTAT_SPRITE_BLOCK;
+	}
+}
+
+class DukeMaskWall2 : DukeMaskWall1
+{
+	default { pic "MASKWALL2"; }
+}
+
+class DukeMaskWall3 : DukeMaskWall1
+{
+	default { pic "MASKWALL3"; }
+}
+
+class DukeMaskWall4 : DukeMaskWall1
+{
+	default { pic "MASKWALL4"; }
+}
+
+class DukeMaskWall5 : DukeMaskWall1
+{
+	default { pic "MASKWALL5"; }
+}
+
+class DukeMaskWall6 : DukeMaskWall1
+{
+	default { pic "MASKWALL6"; }
+}
+
+class DukeMaskWall7 : DukeMaskWall1
+{
+	default { pic "MASKWALL7"; }
+}
+
+class DukeMaskWall8 : DukeMaskWall1
+{
+	default { pic "MASKWALL9"; }
+}
+
+class DukeMaskWall10 : DukeMaskWall1
+{
+	default { pic "MASKWALL10"; }
+}
+
+class DukeMaskWall11 : DukeMaskWall1
+{
+	default { pic "MASKWALL11"; }
+}
+
+class DukeMaskWall12 : DukeMaskWall1
+{
+	default { pic "MASKWALL12"; }
+}
+
+class DukeMaskWall13 : DukeMaskWall1
+{
+	default { pic "MASKWALL13"; }
+}
+
+class DukeMaskWall14 : DukeMaskWall1
+{
+	default { pic "MASKWALL14"; }
+}
+
+class DukeMaskWall15 : DukeMaskWall1
+{
+	default { pic "MASKWALL15"; }
+}
+
+class DukeFootprints : DukeActor
+{
+	default
+	{
+		statnum STAT_MISC;
+		spriteset "FOOTPRINTS", "FOOTPRINTS2", "FOOTPRINTS3", "FOOTPRINTS4";
+	}
+	
+	override void Initialize()
+	{
+		if (self != self.ownerActor)
+		{
+			bool away = self.isAwayFromWall(5.25);
+			if (!away)
+			{
+				self.scale = (0, 0);
+				return;
+			}
+			self.cstat = CSTAT_SPRITE_ALIGNMENT_FLOOR;
+			self.insertspriteq();
+		}
+		let sect = self.sector;
+		self.pos.Z = sect.floorz;
+		if (sect.lotag != ST_1_ABOVE_WATER && sect.lotag != ST_2_UNDERWATER)
+			self.scale = (0.5, 0.5);
+		self.setSpriteSetImage(random(0, 3));
+	}
+	
+	override bool animate(tspritetype t)
+	{
+		if (self.shade == 127) t.shade = 127;
+		if (t.pal == 6) t.shade = -127;
+		return true;
+	}
+}
+
+class DukeBulletHole : DukeActor
+{
+	default
+	{
+		statnum STAT_MISC;
+		pic "BULLETHOLE";
+		scaleX 0.046875;
+		scaleY 0.046875;
+	}
+	
+	override void Initialize()
+	{
+		self.cstat = CSTAT_SPRITE_ALIGNMENT_WALL | randomFlip();
+		self.insertspriteq();
+	}
+	
+	override bool animate(tspritetype t)
+	{
+		t.shade = 16;
+		return true;
+	}	
+}
+
+class DukeGenericPole : DukeActor
+{
+	default
+	{
+		pic "GENERICPOLE";
+	}
+	
+	override void Initialize()
+	{
+		if (ud.multimode < 2 && self.pal != 0)
+		{
+			self.scale = (0, 0);
+			self.ChangeStat(STAT_MISC);
+		}
+		else self.pal = 0;
+	}
+}
+
+class DukeCameraPole : DukeGenericPole
+{
+	default
+	{
+		pic "CAMERAPOLE";
+		extra 1;
+	}
+	
+	override void Initialize()
+	{
+		if (gs.camerashitable) self.cstat = CSTAT_SPRITE_BLOCK_ALL;
+		else self.cstat = 0;
+		super.Initialize();
+	}
+}
+
+class DeveloperCommentary : DukeActor
+{
+	default
+	{
+		spriteset "DEVELOPERCOMMENTARY", "DEVELOPERCOMMENTARYON";
+	}
+	
+	override bool OnUse(DukePlayer p)
+	{
+		if (!wt_commentary) return false;
+		if (self.spriteSetIndex == 0)
+		{
+			if (Duke.StartCommentary(self.lotag, self))
+				self.setSpriteSetImage(1);
+		}
+		else
+		{
+			Duke.StopCommentary();
+			self.setSpriteSetImage(0);
+		}
+		return true;
+	}
+
+	override bool animate(tspritetype t)
+	{
+		if (!wt_commentary) t.scale = (0, 0);
+		else t.shade = self.shade;
+		return true;
+	}
+}
diff --git a/wadsrc/static/zscript/games/duke/dukegame.zs b/wadsrc/static/zscript/games/duke/dukegame.zs
index 137351fe1..c2f8e8a22 100644
--- a/wadsrc/static/zscript/games/duke/dukegame.zs
+++ b/wadsrc/static/zscript/games/duke/dukegame.zs
@@ -154,6 +154,8 @@ struct Duke native
 	native static int GetSoundFlags(Sound snd);
 	native static int badguyID(int id);
 	native static void updatepindisplay(int tag, int pinmask);
+	native static bool StartCommentary(int tag, DukeActor act);
+	native static void StopCommentary();
 	static int rnd(int val)
 	{
 		return (random(0, 255) >= (255 - (val)));