diff --git a/src/dehacked.c b/src/dehacked.c
index 6b38ceb40fadc4f95922370b29039160b0bdce6a..31d4129dfd7d6f2f67b81d149259b1f0b14709ec 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -1651,7 +1651,6 @@ static actionpointer_t actionpointers[] =
 	{{A_GiveShield},             "A_GIVESHIELD"},
 	{{A_GravityBox},             "A_GRAVITYBOX"},
 	{{A_ScoreRise},              "A_SCORERISE"},
-	{{A_ParticleSpawn},          "A_PARTICLESPAWN"},
 	{{A_AttractChase},           "A_ATTRACTCHASE"},
 	{{A_DropMine},               "A_DROPMINE"},
 	{{A_FishJump},               "A_FISHJUMP"},
@@ -5702,7 +5701,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_SEED",
 
 	"S_PARTICLE",
-	"S_PARTICLEGEN",
 
 	// Score Logos
 	"S_SCRA", // 100
diff --git a/src/info.c b/src/info.c
index d2bd1dab540b20a302ef3264f6dc935417e117ca..734d462310ab33fc738830383985524574fc3eee 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2786,7 +2786,7 @@ state_t states[NUMSTATES] =
 	{SPR_STEM, 4, 2, {NULL}, 0, 0, S_STEAM6},              // S_STEAM5
 	{SPR_STEM, 5, 2, {NULL}, 0, 0, S_STEAM7},              // S_STEAM6
 	{SPR_STEM, 6, 2, {NULL}, 0, 0, S_STEAM8},              // S_STEAM7
-	{SPR_STEM, 7, 18, {NULL}, 0, 0, S_STEAM1},             // S_STEAM8
+	{SPR_NULL, 0, 18, {NULL}, 0, 0, S_STEAM1},             // S_STEAM8
 
 	// Bumpers
 	{SPR_BUMP, FF_ANIMATE|FF_GLOBALANIM, -1, {NULL},   3, 4, S_NULL},   // S_BUMPER
@@ -2963,7 +2963,6 @@ state_t states[NUMSTATES] =
 
 	// Particle sprite
 	{SPR_PRTL, 0, 2*TICRATE, {NULL}, 0, 0, S_NULL}, // S_PARTICLE
-	{SPR_NULL, 0, 3, {A_ParticleSpawn}, 0, 0, S_PARTICLEGEN}, // S_PARTICLEGEN
 
 	{SPR_SCOR, 0, 32, {A_ScoreRise}, 0, 0, S_NULL}, // S_SCRA  - 100
 	{SPR_SCOR, 1, 32, {A_ScoreRise}, 0, 0, S_NULL}, // S_SCRB  - 200
@@ -14499,7 +14498,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_PARTICLEGEN
 		757,            // doomednum
-		S_PARTICLEGEN,  // spawnstate
+		S_INVISIBLE,    // spawnstate
 		1000,           // spawnhealth
 		S_NULL,         // seestate
 		sfx_None,       // seesound
@@ -14520,7 +14519,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT, // flags
+		MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -16619,7 +16618,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_PENGUINATOR_WADDLE1, // seestate
 		sfx_None,       // seesound
 		0,              // reactiontime
-		sfx_s3k8a,      // attacksound
+		sfx_ngjump,     // attacksound
 		S_NULL,         // painstate
 		200,            // painchance
 		sfx_None,       // painsound
diff --git a/src/info.h b/src/info.h
index 5b1169e22a67a27d6ec6b2b8a10701d200fbdd83..c9f7299d80790b60b8564e075ecb3f523ae8d332 100644
--- a/src/info.h
+++ b/src/info.h
@@ -57,7 +57,6 @@ void A_ExtraLife(); // Extra Life
 void A_GiveShield(); // Obtained Shield
 void A_GravityBox();
 void A_ScoreRise(); // Rise the score logo
-void A_ParticleSpawn();
 void A_AttractChase(); // Ring Chase
 void A_DropMine(); // Drop Mine from Skim or Jetty-Syn Bomber
 void A_FishJump(); // Fish Jump
@@ -3066,7 +3065,6 @@ typedef enum state
 	S_SEED,
 
 	S_PARTICLE,
-	S_PARTICLEGEN,
 
 	// Score Logos
 	S_SCRA, // 100
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 367eec60f8479db564b22446808d44adb743fd46..7ad33ef2deb64dc5db3f4b7f7fe84299779839ed 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -112,7 +112,6 @@ void A_ExtraLife(mobj_t *actor);
 void A_GiveShield(mobj_t *actor);
 void A_GravityBox(mobj_t *actor);
 void A_ScoreRise(mobj_t *actor);
-void A_ParticleSpawn(mobj_t *actor);
 void A_BunnyHop(mobj_t *actor);
 void A_BubbleSpawn(mobj_t *actor);
 void A_FanBubbleSpawn(mobj_t *actor);
@@ -3958,53 +3957,6 @@ void A_ScoreRise(mobj_t *actor)
 	P_SetObjectMomZ(actor, actor->info->speed, false);
 }
 
-// Function: A_ParticleSpawn
-//
-// Description: Hyper-specialised function for spawning a particle for MT_PARTICLEGEN.
-//
-// var1 = unused
-// var2 = unused
-//
-void A_ParticleSpawn(mobj_t *actor)
-{
-	INT32 i = 0;
-	mobj_t *spawn;
-
-#ifdef HAVE_BLUA
-	if (LUA_CallAction("A_ParticleSpawn", actor))
-		return;
-#endif
-	if (!actor->health)
-		return;
-
-	if (!actor->lastlook)
-		return;
-
-	if (!actor->threshold)
-		return;
-
-	for (i = 0; i < actor->lastlook; i++)
-	{
-		spawn = P_SpawnMobj(
-			actor->x + FixedMul(FixedMul(actor->friction, actor->scale), FINECOSINE(actor->angle>>ANGLETOFINESHIFT)),
-			actor->y + FixedMul(FixedMul(actor->friction, actor->scale), FINESINE(actor->angle>>ANGLETOFINESHIFT)),
-			actor->z,
-			(mobjtype_t)actor->threshold);
-		P_SetScale(spawn, actor->scale);
-		spawn->momz = FixedMul(actor->movefactor, spawn->scale);
-		spawn->destscale = spawn->scale/100;
-		spawn->scalespeed = spawn->scale/actor->health;
-		spawn->tics = (tic_t)actor->health;
-		spawn->flags2 |= (actor->flags2 & MF2_OBJECTFLIP);
-		spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
-
-		actor->angle += actor->movedir;
-	}
-
-	actor->angle += (angle_t)actor->movecount;
-	actor->tics = (tic_t)actor->reactiontime;
-}
-
 // Function: A_BunnyHop
 //
 // Description: Makes object hop like a bunny.
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 5a8729df2d86b938f8541e0f24bcdb16b27ba124..301536ef47615164805ff2ddea011c688a48b961 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -7116,6 +7116,59 @@ void P_MobjThinker(mobj_t *mobj)
 					return;
 				}
 				break;
+			case MT_PARTICLEGEN:
+				if (!mobj->lastlook)
+					return;
+
+				if (!mobj->threshold)
+					return;
+
+				if (--mobj->fuse <= 0)
+				{
+					INT32 i = 0;
+					mobj_t *spawn;
+					fixed_t bottomheight, topheight;
+					INT32 type = mobj->threshold, line = mobj->cvmem;
+
+					mobj->fuse = (tic_t)mobj->reactiontime;
+
+					bottomheight = lines[line].frontsector->floorheight;
+					topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
+
+					if (mobj->waterbottom != bottomheight || mobj->watertop != topheight)
+					{
+						if (mobj->movefactor && (topheight > bottomheight))
+							mobj->health = (tic_t)(FixedDiv((topheight - bottomheight), abs(mobj->movefactor)) >> FRACBITS);
+						else
+							mobj->health = 0;
+
+						mobj->z = ((mobj->flags2 & MF2_OBJECTFLIP) ? topheight : bottomheight);
+					}
+
+					if (!mobj->health)
+						return;
+
+					for (i = 0; i < mobj->lastlook; i++)
+					{
+						spawn = P_SpawnMobj(
+							mobj->x + FixedMul(FixedMul(mobj->friction, mobj->scale), FINECOSINE(mobj->angle>>ANGLETOFINESHIFT)),
+							mobj->y + FixedMul(FixedMul(mobj->friction, mobj->scale), FINESINE(mobj->angle>>ANGLETOFINESHIFT)),
+							mobj->z,
+							(mobjtype_t)mobj->threshold);
+						P_SetScale(spawn, mobj->scale);
+						spawn->momz = FixedMul(mobj->movefactor, spawn->scale);
+						spawn->destscale = spawn->scale/100;
+						spawn->scalespeed = spawn->scale/mobj->health;
+						spawn->tics = (tic_t)mobj->health;
+						spawn->flags2 |= (mobj->flags2 & MF2_OBJECTFLIP);
+						spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
+
+						mobj->angle += mobj->movedir;
+					}
+
+					mobj->angle += (angle_t)mobj->movecount;
+				}
+				break;
 			default:
 				if (mobj->fuse)
 				{ // Scenery object fuse! Very basic!
@@ -10321,8 +10374,8 @@ ML_EFFECT4 : Don't clip inside the ground
 	}
 	case MT_PARTICLEGEN:
 	{
-		fixed_t radius, speed, bottomheight, topheight;
-		INT32 type, numdivisions, time, anglespeed, ticcount;
+		fixed_t radius, speed;
+		INT32 type, numdivisions, anglespeed, ticcount;
 		angle_t angledivision;
 		INT32 line;
 		const size_t mthingi = (size_t)(mthing - mapthings);
@@ -10341,13 +10394,9 @@ ML_EFFECT4 : Don't clip inside the ground
 		else
 			type = (INT32)MT_PARTICLE;
 
-		speed = abs(sides[lines[line].sidenum[0]].textureoffset);
-		bottomheight = lines[line].frontsector->floorheight;
-		topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
-
 		if (!lines[line].backsector
 		|| (ticcount = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS)) < 1)
-			ticcount = states[S_PARTICLEGEN].tics;
+			ticcount = 3;
 
 		numdivisions = (mthing->options >> ZSHIFT);
 
@@ -10365,18 +10414,9 @@ ML_EFFECT4 : Don't clip inside the ground
 			angledivision = 0;
 		}
 
-		if ((speed) && (topheight > bottomheight))
-			time = (INT32)(FixedDiv((topheight - bottomheight), speed) >> FRACBITS);
-		else
-			time = 1; // There's no reasonable way to set it, so just show the object for one tic and move on.
-
+		speed = abs(sides[lines[line].sidenum[0]].textureoffset);
 		if (mthing->options & MTF_OBJECTFLIP)
-		{
-			mobj->z = topheight;
 			speed *= -1;
-		}
-		else
-			mobj->z = bottomheight;
 
 		CONS_Debug(DBG_GAMELOGIC, "Particle Generator (mapthing #%s):\n"
 				"Radius is %d\n"
@@ -10384,20 +10424,20 @@ ML_EFFECT4 : Don't clip inside the ground
 				"Anglespeed is %d\n"
 				"Numdivisions is %d\n"
 				"Angledivision is %d\n"
-				"Time is %d\n"
 				"Type is %d\n"
 				"Tic seperation is %d\n",
-				sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, time, type, ticcount);
+				sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, type, ticcount);
 
 		mobj->angle = 0;
 		mobj->movefactor = speed;
 		mobj->lastlook = numdivisions;
 		mobj->movedir = angledivision*ANG1;
 		mobj->movecount = anglespeed*ANG1;
-		mobj->health = time;
 		mobj->friction = radius;
 		mobj->threshold = type;
 		mobj->reactiontime = ticcount;
+		mobj->cvmem = line;
+		mobj->watertop = mobj->waterbottom = 0;
 
 		break;
 	}